{"id":46365,"date":"2022-12-29T21:33:42","date_gmt":"2022-12-29T12:33:42","guid":{"rendered":"https:\/\/www.charlezz.com\/?p=46365"},"modified":"2023-05-02T12:25:53","modified_gmt":"2023-05-02T03:25:53","slug":"android-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%ec%97%90-mvi-%eb%8f%84%ec%9e%85%ed%95%98%ea%b8%b0","status":"publish","type":"post","link":"https:\/\/charlezz.com\/?p=46365","title":{"rendered":"Android \ud504\ub85c\uc81d\ud2b8\uc5d0 MVI \ub3c4\uc785\ud558\uae30"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">MVI \ub3c4\uc785\ubc30\uacbd<\/h2>\n\n\n\n<p>\ud504\ub85c\uc81d\ud2b8\uc5d0 Jetpack Compose\ub97c \ub3c4\uc785\ud558\uace0 1\ub144\uc815\ub3c4 \uc801\uadf9 \uc4f0\uba74\uc11c &#8216;\uc0c1\ud0dc&#8217; \uad00\ub9ac\uc758 \uc911\uc694\uc131\uc744 \uba38\ub9ac\uac00 \uc544\ub2cc \ubab8\uc73c\ub85c \ub290\uaef4\ubc84\ub838\ub2e4. \uc0c1\ud0dc \uad00\ub9ac\ub97c \uc5b4\ub5bb\uac8c \ud558\uba74 \uc88b\uc744\uae4c \uace0\ubbfc\ud558\ub358 \uc911 \ub3d9\ub8cc \uac1c\ubc1c\uc790\uac00 \uc774\uc804\uc5d0 \ub098\uc5d0\uac8c \ub9d0\ud574\uc92c\ub358 MVI\uac00 \ub5a0\uc62c\ub790\ub2e4.&nbsp;<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>&#8220;MVI \ub294 \uc0c1\ud0dc\ub97c \uc27d\uac8c \uad00\ub9ac\ud574\uc900\ub2e4\uad6c blah blah&#8230;&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>Compose \ub3c4\uc785 \uc774\uc804\uc5d0\ub294 \uadf8\ub550 \uc0c1\ud0dc \uad00\ub9ac\ub97c \ud06c\uac8c \uc911\uc694\ud558\uac8c \uc0dd\uac01\ud558\uc9c0 \uc54a\uc558\ub2e4. \uc544\ub2c8 \uc5b4\uca4c\uba74 \uad00\ub9ac\uac00 \uc5c9\ub9dd\uc778\ub370 \uc798 \uad00\ub9ac\ub418\uace0 \uc788\ub2e4\uace0 \ubbff\uace0 \uc788\uc5c8\ub098\ubcf4\ub2e4.<\/p>\n\n\n\n<p>Compose\ub97c \uc4f0\uba74\uc11c UI\uac00 \uac31\uc2e0\ub418\uc9c0 \uc54a\uac70\ub098 Recomposition\uc774 \ube48\ubc88\ud558\uac8c \ubc1c\uc0dd\ud558\ub294 \uac83\uc744 \ud655\uc778\ud588\ub2e4. \uadf8\ub7ec\uace0 \ub098\uc11c\uc57c \uc0c1\ud0dc\uac00 \uad00\ub9ac\ub418\uc9c0 \uc54a\uc73c\uba74 \uc571\uc758 \ud488\uc9c8\uc774 \ub5a8\uc5b4\uc9c0\uace0 \ud37c\ud3ec\uba3c\uc2a4\uac00 \uc800\ud558 \ub420 \uc218 \uc788\ub2e4\ub294 \uac83\uc744 \uae68\ub2ec\uc558\ub2e4.<\/p>\n\n\n\n<p>\uc571\uc758 \uc0c1\ud0dc\ub294 \uc2dc\uac04\uc758 \ud750\ub984\uc5d0 \ub530\ub77c \ub2e4\uc74c\uacfc \uac19\uc774 \ub2e4\uc591\ud558\uac8c \ubcc0\uacbd\ub420 \uc218 \uc788\ub2e4.&nbsp;<\/p>\n\n\n\n<ul>\n<li>\ub124\ud2b8\uc6cc\ud06c \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc84c\uc744 \ub54c \ud45c\ud604\ub418\ub294 \uc2a4\ub0b5\ubc14<\/li>\n\n\n\n<li>\uc0ac\uc6a9\uc790\uc758 \uc785\ub825\uc5d0 \uc758\ud574 \uc791\uc131\ub418\ub294 \ud14d\uc2a4\ud2b8<\/li>\n\n\n\n<li>\ud2b9\uc815 \uc2dc\uac04\uc5d0 \uc6b8\ub9ac\ub294 \uc54c\ub78c\uc18c\ub9ac<\/li>\n<\/ul>\n\n\n\n<p>\uc77c\ubc18\uc801\uc73c\ub85c \uc774 \ubd84\uc57c\uc5d0\uc11c \uc0c1\ud0dc(state)\ub77c \ud568\uc740 \ud074\ub798\uc2a4 \uc624\ube0c\uc81d\ud2b8\uc758 \uc5b4\ub5a0\ud55c \uac12 \ub610\ub294 \ub370\uc774\ud130\ub2e4. \uc608) ViewModel \uac1d\uccb4\uc5d0 \uc120\uc5b8\ub418\uc5b4\uc788\ub294 State<br>Compose\uc758 \uc5c5\ub370\uc774\ud2b8\ub294 \uc0c1\ud0dc\uc758 \ubcc0\uacbd\uc5d0 \uc758\ud574\uc11c \uc774\ub8e8\uc5b4\uc9c0\uba70 \uc774\ub97c \uc7ac\uad6c\uc131(Recomposition)\uc774\ub77c\uace0 \ud55c\ub2e4.<br>Compose\ub97c \ub3c4\uc785\ud55c \uc774\ud6c4, \uac1c\ubc1c\uc790\ub294 \ub354 \uc774\uc0c1 View\uc5d0 \uc811\uadfc\ud574\uc11c \uc5c5\ub370\uc774\ud2b8 \ud558\ub294 \uc218\uace0\ub97c \ub35c\uc5c8\uc73c\uba70 \uadf8\uc800 &#8216;\uc0c1\ud0dc \uad00\ub9ac&#8217;\uc5d0\ub9cc \uc9d1\uc911\ud558\uba74 \ub41c\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MVI\ub780 \ubb34\uc5c7\uc778\uac00?<\/h2>\n\n\n\n<p>MVC, MVP \ub610\ub294 MVVM \ucc98\ub7fc MVI\ub3c4 \uad00\uc2ec\uc0ac\ub97c \ub098\ub204\uace0 \ud574\ub2f9 \uad00\uc2ec\uc0ac\uc758 \uc55e\uae00\uc790\ub97c \ub530\uc11c \ub9cc\ub4e0 \ud328\ud134\uc774\ub2e4.<\/p>\n\n\n\n<p><strong>Model<\/strong>: UI\uc5d0 \ubc18\uc601\ub420&nbsp;<strong>\uc0c1\ud0dc<\/strong>\ub97c \uc758\ubbf8\ud55c\ub2e4. \uadf8\ub7ec\ubbc0\ub85c MVP \ub610\ub294 MVVM \ubaa8\ub378\uc758 \uc815\uc758\uc640\ub294 \ub2e4\ub974\ub2e4.<\/p>\n\n\n\n<p><strong>View<\/strong>: UI \uadf8 \uc790\uccb4\ub2e4. View, Activity, Fragment, Compose \ub4f1\uc774 \ub420 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<p><strong>Intent<\/strong>: \uc0ac\uc6a9\uc790 \uc561\uc158 \ubc0f \uc2dc\uc2a4\ud15c \uc774\ubca4\ud2b8\uc5d0 \ub530\ub978 \uacb0\uacfc<\/p>\n\n\n\n<p>\uc77c\ubc18\uc801\uc73c\ub85c \uc774 Model, View, Intent\uc758 \uc0c1\uad00\uad00\uacc4\ub294 \ub2e4\uc74c\uacfc \uac19\uc774 \uc21c\uc218\ud568\uc218 \ud615\uc2dd\uc73c\ub85c \ud45c\ud604\ud55c\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"546\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.03.23-1024x546.png\" alt=\"\" class=\"wp-image-46366\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.03.23-1024x546.png 1024w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.03.23-300x160.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.03.23-768x409.png 768w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.03.23.png 1524w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>\uc608\ub97c \ub4e4\uc5b4\ubcf4\uc790\uba74, \uc0ac\uc6a9\uc790\uac00 View\ub97c \ud074\ub9ad\ud574\uc11c \ud654\uba74 \ubaa9\ub85d\uc744 \uac31\uc2e0\ud558\uace0\uc790 \ud55c\ub2e4. \ubaa9\ub85d\uc744 \uac31\uc2e0\ud558\uace0\uc790 \ud558\ub294 \uc758\ub3c4(Intent)\uac00 \uacb0\uad6d \uc0c8\ub85c\uc6b4 \ubaa8\ub378, \uc989 \uc0c1\ud0dc\ub97c \uc5c5\ub370\uc774\ud2b8 \ud558\uac8c \ub418\uace0 \uc774\uac83\uc774 View(\uc77c\ubc18\uc801\uc73c\ub85c Compose)\uc5d0 \ubc18\uc601\ub41c\ub2e4.<\/p>\n\n\n\n<p>MVI\ub294 \ub2e8\ubc29\ud5a5 \ud750\ub984(Uni-directional flow) \uad6c\uc870\ub2e4. \uadf8\ub807\uae30 \ub54c\ubb38\uc5d0 \ub2e4\uc74c \uadf8\ub9bc\ucc98\ub7fc \ud45c\ud604\ud558\uae30\ub3c4 \ud55c\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"1018\" height=\"1024\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28-1018x1024.png\" alt=\"\" class=\"wp-image-46367\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28-1018x1024.png 1018w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28-298x300.png 298w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28-150x150.png 150w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28-768x772.png 768w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.07.28.png 1088w\" sizes=\"(max-width: 1018px) 100vw, 1018px\" \/><\/figure>\n\n\n\n<p>\uc0ac\uc6a9\uc790\uc758 \uc561\uc158\uc774 \uc0c8\ub85c\uc6b4 Intent\ub85c \ubcc0\uacbd\ub418\uace0, \ud574\ub2f9 Intent\ub85c \ubd80\ud130 \uc0c8\ub85c\uc6b4 Model\uc744 \ub9cc\ub4e4\uc5b4 View\ub97c \uac31\uc2e0\ud558\ub294 \ud750\ub984\uc744 \ubcf4\uc5ec\uc900\ub2e4.<\/p>\n\n\n\n<p>MVI\uc5d0\uc11c Model\uc740 \uc0c1\ud0dc\ub97c \ud45c\ud604\ud558\ub294&nbsp;<strong>\ubcc0\uacbd \ubd88\uac00\ud55c<\/strong>&nbsp;\ub370\uc774\ud130\ub2e4. \uc571\uc758 \uc0c1\ud0dc\ub294 \ub2e8\ubc29\ud5a5 \ud750\ub984\uc5d0\uc11c Intent\ub85c\ubd80\ud130 Model\uc744 \uc0dd\uc131\ud560 \ub54c\ub9cc \uc0c8\ub85c\uc6b4 Model \uac1d\uccb4\ub97c \uc0dd\uc131\ud55c\ub2e4. \uc774\ub7ec\ud55c \uad6c\uc870\ub85c \uc778\ud574 \uc6b0\ub9ac\ub294 \uc608\uce21\uac00\ub2a5\ud558\ub3c4\ub85d \uc0c1\ud0dc\ub97c \uc124\uc815\ud560 \uc218 \uc788\uace0 \uc774\ub85c \uc778\ud574 \ub514\ubc84\uae45\uc774 \uc26c\uc6cc\uc9c4\ub2e4.<\/p>\n\n\n\n<p>\ub9cc\uc57d \ub2f9\uc2e0\uc774 MVVM\uc744 \uc0ac\uc6a9\ud574\uc654\ub2e4\uba74, MVI\ub294 \uc804\ud600 \uc0c8\ub85c\uc6b4 \uac83\uc774 \uc544\ub2c8\uba70 \uc774\ub97c \uc704\ud574 \uc804\uccb4\ub97c \ubcc0\uacbd\ud560 \ud544\uc694\ub294 \uc5c6\ub2e4. VM(View Model)\uc740 \uc0c1\ud0dc\uac00 \uad00\ub9ac\ub418\uae30 \uc704\ud574 \uc88b\uc740 \uc7a5\uc18c\uc774\uba70 \ub2e4\uc774\uc5b4\uadf8\ub7a8\uc73c\ub85c \ub098\ud0c0\ub0b4\uc790\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"578\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-1024x578.png\" alt=\"\" class=\"wp-image-46368\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-1024x578.png 1024w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-300x169.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-768x433.png 768w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-1536x867.png 1536w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-28--11.14.22-2048x1155.png 2048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>\uc704 \uadf8\ub9bc\uc740 MVVM \uacc4\uce35 \uad00\uc810\uc5d0\uc11c MVI\uac00 \uc5b4\ub290 \uacc4\uce35\uc5d0 \uc18d\ud558\ub294\uc9c0 \ubcf4\uc5ec\uc900\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MVI \uc608\uc81c\ucf54\ub4dc1<\/h2>\n\n\n\n<p>\ub2e4\uc74c \uc608\uc81c\ucf54\ub4dc\ub294 Event \ubc1c\uc0dd -&gt; State \ubcc0\uacbd -&gt; View \ubc18\uc601 \uc21c\uc11c\ub85c MVI \ud328\ud134\uc744 \uad6c\ud604\ud558\uace0 \uc788\ub2e4.<\/p>\n\n\n\n<p>Event\ub294 Increment\uc640 Decrement\ub85c \ub098\ub258\uba70 \uc774\ub294 \uac01\uac01 State.count \uac12\uc744 1\uc529 \uc99d\uac00 \ub610\ub294 \uac10\uc18c \uc2dc\ud0a8\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class ViewModel(){\n\n    private val _state = <em>MutableStateFlow<\/em>(State())\n    val state:StateFlow&lt;State&gt; = _state\n\n    override suspend fun onEvent(<em>event<\/em>: Event) {\n        when (<em>event<\/em>) {\n            is Event.Increment -&gt; {\n                _state.value = _state.value.copy(count = _state.value.count + 1)\n            }<strong>\n            <\/strong>is Event.Decrement -&gt; {\n                _state.value = _state.value.copy(count = _state.value.count - 1)\n            }<strong>\n        <\/strong>}\n    }\n}<\/pre>\n\n\n\n<p>\ud558\uc9c0\ub9cc \uc774 \ucf54\ub4dc\ub294 <strong>\uc2a4\ub808\ub4dc\uc5d0 \uc548\uc804\ud558\uc9c0 \uc54a\ub2e4<\/strong>. onEvent\uac00 \uc11c\ub85c \ub2e4\ub978 \uc2a4\ub808\ub4dc\uc5d0\uc11c \ud638\ucd9c\ub420 \uacbd\uc6b0 \ub3d9\uc2dc\uc131 \uc774\uc288\uac00 \ubc1c\uc0dd\ud558\uae30 \ub54c\ubb38\uc774\ub2e4. <\/p>\n\n\n\n<p>\uc2e4\uc81c \uc11c\ub85c \ub2e4\ub978 \uc2a4\ub808\ub4dc\uc5d0\uc11c onEvent(Increment)\uc640&nbsp;onEvent(Decrement)\ub97c 10\ub9cc\ubc88\uc529 \ud638\ucd9c\ud574\ubcf4\uba74 0\uc774 \uc544\ub2cc \uac12\uc774 \ub098\uc62c \ud655\ub960\uc774 \ub192\ub2e4. (<a href=\"https:\/\/github.com\/Charlezz\/MVIExample\/blob\/master\/app\/src\/test\/java\/com\/charlezz\/mviexample\/ViewModelTest.kt\">\uc7ac\ud604 \uac00\ub2a5\ud55c \ud14c\uc2a4\ud2b8 \ucf54\ub4dc<\/a>)<\/p>\n\n\n\n<p>\ub4e4\uc5b4\uc624\ub294 \uc774\ubca4\ud2b8\ub85c\ubd80\ud130 _state.update{&#8230;} \ub97c \ud1b5\ud574 \uc0c1\ud0dc\ub97c \ubcc0\uacbd\b\ud558\uba74 \ub3d9\uc2dc\uc131 \uc624\ub958\ub294 \ud574\uacb0\ud560 \uc218 \uc788\uc9c0\ub9cc, MVI\ud328\ud134\uc744 \uad6c\ud604\ud558\uae34 \uc704\ud55c \uadfc\ubcf8\uc801\uc778 \ud574\uacb0\ubc29\ubc95\uc740 \uc544\ub2c8\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MVI \uc608\uc81c\ucf54\ub4dc2<\/h2>\n\n\n\n<p>\ub3d9\uc2dc\uc131 \uc624\ub958\ub97c \ud68c\ud53c\ud558\uae30 \uc704\ud574 \ub2e4\uc74c\uacfc \uac19\uc774 \ucf54\ub4dc\ub97c \uc791\uc131\ud560 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class ViewModel {\n\n    private val events = <em>Channel<\/em>&lt;Event&gt;()\n\n    private val _state = <em>MutableStateFlow<\/em>(State())\n\n    val state:StateFlow&lt;State&gt; = <em>MutableStateFlow<\/em>(State())\n\n    init {\n        events.<em>receiveAsFlow<\/em>()\n            .<em>onEach<\/em>(::updateState)\n            .<em>launchIn<\/em>(viewModelScope)\n    }\n\n    override suspend fun onEvent(<em>event<\/em>: Event) {\n        events.send(<em>event<\/em>)\n    }\n\n    private fun updateState(<em>event<\/em>: Event) {\n        when (<em>event<\/em>) {\n<meta charset=\"utf-8\">            is Event.Increment -&gt; {\n                _state.value = _state.value.copy(count = _state.value.count + 1)\n            }<strong>\n            <\/strong>is Event.Decrement -&gt; {\n                _state.value = _state.value.copy(count = _state.value.count - 1)\n            }<strong>\n        <\/strong>}\n    }\n}<\/pre>\n\n\n\n<p>Channel\uc744 \ub3c4\uc785\ud558\uc5ec \uc774\ubca4\ud2b8\ub97c \uc21c\ucc28\uc801\uc73c\ub85c \ucc98\ub9ac\ud558\uac8c \ub054 \ubcc0\uacbd\ud588\ub2e4. \uc774\ub85c \uc778\ud574 \uc2a4\ub808\ub4dc \uc548\uc815\uc131\uc774 \ubcf4\uc7a5\ub41c\ub2e4.<\/p>\n\n\n\n<p>\ud558\uc9c0\ub9cc \uc774 \uc608\uc81c\ucf54\ub4dc\uc5d0\ub3c4 \ud55c\uac00\uc9c0 \uc544\uc26c\uc6b4 \uc810\uc774 \uc874\uc7ac\ud558\ub294\ub370, updateState \ud568\uc218 \ubc16\uc5d0\uc11c\ub3c4 _state\ub97c \ubcc0\uacbd\ud560 \uc218 \uc788\ub2e4\ub294 \uc810\uc774\ub2e4.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MVI \uc608\uc81c\ucf54\ub4dc3 (State Reducer)<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p><strong>Reducer = (State, Event) -&gt; State<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>Reducer\ub780 \ud604\u001d\uc7ac\uc758 \uc0c1\ud0dc\uc640 \uc804\ub2ec \ubc1b\uc740 \uc774\ubca4\ud2b8\ub97c \ucc38\uace0\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc0c1\ud0dc\ub97c \ub9cc\ub4dc\ub294 \uac83\uc744 \ub9d0\ud55c\ub2e4. Reducer\ub97c \ud1b5\ud574&nbsp;<em>\uc608\uc81c\ucf54\ub4dc2<\/em>\uac00 \uac00\uc9c0\uace0 \uc788\ub358 \ubb38\uc81c\uc810\uc744 \ud574\uacb0\ud574\ubcf4\uc790.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class ViewModel {\n\n    private val events = <em>Channel<\/em>&lt;Event&gt;()\n\n    <strong>\/\/ State Reducer<\/strong>\n    val state = events.<em>receiveAsFlow<\/em>()\n        .<em>runningFold<\/em>(State(), <strong>::reduceState<\/strong>)\n        .<em>stateIn<\/em>(viewModelScope, SharingStarted.Eagerly, State())\n\n    override suspend fun onEvent(<em>event<\/em>: Event) {\n        events.send(<em>event<\/em>)\n    }\n\n    private fun reduceState(<em>current<\/em>: State, <em>event<\/em>: Event): State {\n        return when (<em>event<\/em>) {\n            is Event.Increment -&gt; <em>current<\/em>.copy(count = <em>current<\/em>.count + 1)\n            is Event.Decrement -&gt; <em>current<\/em>.copy(count = <em>current<\/em>.count - 1)\n        }\n    }\n}<\/pre>\n\n\n\n<p>\uc774\ubca4\ud2b8 \ucc44\ub110\ub85c\ubd80\ud130 \uc0c1\ud0dc\ub97c \ubcc0\uacbd\ud558\uae30 \ub54c\ubb38\uc5d0 \uc774\uc81c \ub354 \uc774\uc0c1 \uc678\ubd80\uc5d0\uc11c \uc0c1\ud0dc\ub97c \ubcc0\uacbd\ud560 \uc218 \uc788\ub294 \uc694\uc778\uc740 \uc5c6\ub2e4. \uc0c1\ud0dc \uad00\ub9ac\ub97c \ud55c\uacf3\uc5d0\uc11c \ud560 \uc218 \uc788\uac8c \ub418\uba74\uc11c Race Condition\uc744 \ubc30\uc81c \uc2dc\ud0a4\uace0, \uc0c1\ud0dc\ub97c \uc608\uce21\ud558\uae30 \uc27d\uace0, \ub514\ubc84\uae45\uc744 \uc218\uc6d4\ud558\uac8c \ud560 \uc218 \uc788\uac8c \ub418\uc5c8\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MVI \uc7a5\ub2e8\uc810 \uc815\ub9ac<\/h2>\n\n\n\n<p>\uc9c0\uae08\uae4c\uc9c0 \uc124\uba85\ud55c \ub0b4\uc6a9\uc73c\ub85c \uc7a5\ub2e8\uc810\uc744 \uc815\ub9ac\ud574\ubcf4\uc790\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\uc7a5\uc810<\/h3>\n\n\n\n<ul>\n<li>\uc0c1\ud0dc \uad00\ub9ac\uac00 \uc27d\ub2e4<\/li>\n\n\n\n<li>\ub370\uc774\ud130\uac00 \ub2e8\ubc29\ud5a5\uc73c\ub85c \ud750\ub978\ub2e4.<\/li>\n\n\n\n<li>\uc2a4\ub808\ub4dc \uc548\uc815\uc131\uc744 \ubcf4\uc7a5\ud55c\ub2e4.<\/li>\n\n\n\n<li>\ub514\ubc84\uae45 \ubc0f \ud14c\uc2a4\ud2b8\uac00 \uc27d\ub2e4<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ub2e8\uc810<\/h3>\n\n\n\n<ul>\n<li>\ub7ec\ub2dd\ucee4\ube0c\uac00 \uac00\ud30c\ub974\ub2e4<\/li>\n\n\n\n<li>\ubcf4\uc77c\ub7ec\ud50c\ub808\uc774\ud2b8 \ucf54\ub4dc\uac00 \uc591\uc0b0\ub41c\ub2e4.<\/li>\n\n\n\n<li>\uc791\uc740 \ubcc0\uacbd\ub3c4 Intent\ub85c \ucc98\ub9ac \ud574\uc57c\ud55c\ub2e4.<\/li>\n\n\n\n<li>Intent, State, Side Effect \ub4f1 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud55c \uac1d\uccb4\ub97c \uc0dd\uc131\ud574\uc57c \ud558\ubbc0\ub85c&nbsp;<strong>\ud30c\uc77c \ubc0f \uba54\ubaa8\ub9ac \uad00\ub9ac<\/strong>\uc5d0 \uc720\uc758\ud574\uc57c \ud55c\ub2e4.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\uc2e4\uc804 \uc608\uc81c &#8211;&nbsp;<a href=\"https:\/\/github.com\/charlezz\/mviexample\">\uc0ac\uc6a9\uc790 \ubaa9\ub85d \ubd88\ub7ec\uc624\uae30<\/a><\/h2>\n\n\n\n<p><video controls=\"\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi-screencapture-1672239994373.mp4\"><\/video><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sealed interface MainEvent{\n\n    object Loading:MainEvent <strong>\/\/ \ud504\ub85c\uadf8\ub808\uc2a4 \ubc14 \ud45c\uc2dc<\/strong>\n\n    class Loaded(val users:List&lt;User&gt;): MainEvent <strong>\/\/ \uc720\uc800 \ubaa9\ub85d \ubd88\ub7ec\uc628 \uacb0\uacfc<\/strong>\n\n}\n\ndata class MainState(\n    val users: List&lt;User&gt; = emptyList(),\n    val loading:Boolean = false,\n    val error: String? = null\n)\n\n@HiltViewModel\nclass MainViewModel @Inject constructor(\n    private val repository: MainRepository\n) : ViewModel() {\n\n    private val events = <em>Channel<\/em>&lt;MainEvent&gt;()\n\n    val state: StateFlow&lt;MainState&gt; = events.<em>receiveAsFlow<\/em>()\n        .<em>runningFold<\/em>(MainState(), ::reduceState)\n        .<em>stateIn<\/em>(<em>viewModelScope<\/em>, SharingStarted.Eagerly, MainState())\n\n\n    private fun reduceState(<em>current<\/em>: MainState,<em>event<\/em>:MainEvent):MainState{\n        return when(<em>event<\/em>){ <strong>\/\/ \ud604\uc7ac \uc0c1\ud0dc\uc640 \ub4e4\uc5b4\uc628 \uc774\ubca4\ud2b8\ub97c \ucc38\uc870\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc0c1\ud0dc\ub97c \ub9cc\ub4e4\uc5b4 \ub0b8\ub2e4.<\/strong>\n            MainEvent.Loading -&gt; {\n                <em>current<\/em>.copy(loading = true)\n            }\n            is MainEvent.Loaded -&gt; {\n                <em>current<\/em>.copy(loading = false, users = <em>event<\/em>.users)\n            }\n        }\n    }\n\n    fun fetchUser() { <strong>\/\/ fetch \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uba74 \uc21c\ucc28\uc801\uc73c\ub85c \uc774\ubca4\ud2b8\ub97c \ubc1c\uc0dd\uc2dc\ud0a8\ub2e4.<\/strong>\n        <em>viewModelScope<\/em>.<em>launch <\/em><strong>{\n            <\/strong>events.send(MainEvent.Loading)\n            val <em>users <\/em>= repository.getUsers()\n            events.send(MainEvent.Loaded(users = <em>users<\/em>))\n        <strong>}\n    <\/strong>}\n\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Side Effects<\/h2>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"1002\" height=\"872\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-29--12.14.55.png\" alt=\"\" class=\"wp-image-46370\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-29--12.14.55.png 1002w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-29--12.14.55-300x261.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2022\/12\/www.charlezz.com-android-mvi--2022-12-29--12.14.55-768x668.png 768w\" sizes=\"(max-width: 1002px) 100vw, 1002px\" \/><\/figure>\n\n\n\n<p>\uc2e4\uc138\uacc4\uc5d0\uc11c View(Model(Intent())) \uc21c\uc218\ud568\uc218 \uad6c\uc870\ub85c\ub9cc \uc798 \uc21c\ud658\ud558\uae38 \uae30\ub300\ud558\uc9c0\ub9cc \ud604\uc2e4\uc740 \uadf8\ub807\uc9c0 \ubabb\ud558\ub2e4. \uac04\ud639 \uc0c1\ud0dc\ub97c \ubcc0\uacbd\ud560 \ud544\uc694\uac00 \uc5c6\ub294 \uc774\ubca4\ud2b8\uac00 \ud544\uc694\ud560 \uc218\ub3c4 \uc788\uae30 \ub54c\ubb38\uc774\ub2e4. \uc608\ub97c \ub4e4\uba74 Activity\/Fragment \uc774\ub3d9, Logging, Analytics, \ud1a0\uc2a4\ud2b8 \ub178\ucd9c \ub4f1\uc774 \uadf8\uc5d0 \ud574\ub2f9\ud55c\ub2e4. \uadf8\ub807\uae30 \ub54c\ubb38\uc5d0 MVI\ub97c \uc5b8\uae09\ud560 \ub54c \uc77c\ubc18\uc801\uc73c\ub85c Side Effects(\ubd80\uc218\ud6a8\uacfc)\ub77c\ub294 \uac1c\ub150\uc744 \uc368\uc11c \uc774\ub97c \ucc98\ub9ac \ud55c\ub2e4. \uc704&nbsp;<em>\uc2e4\uc804 \uc608\uc81c<\/em>&nbsp;\ucf54\ub4dc\ub97c \uc608\uc2dc\ub85c \uc0ac\uc6a9\uc790 \uc218(users.size)\ub97c \ud1a0\uc2a4\ud2b8\ub85c \ub178\ucd9c \ud558\ub294 Side Effect\ub97c \ub9cc\ub4e4\uc5b4 \ubcf4\uc790.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">@HiltViewModel\nclass MainViewModel @Inject constructor(\n    private val repository: MainRepository\n) : ViewModel() {\n\n    private val events = <em>Channel<\/em>&lt;MainEvent&gt;()\n\n    val state: StateFlow&lt;MainState&gt; = events.<em>receiveAsFlow<\/em>()\n        .<em>runningFold<\/em>(MainState(), ::reduceState)\n        .<em>stateIn<\/em>(<em>viewModelScope<\/em>, SharingStarted.Eagerly, MainState())\n\n    <strong>private val _sideEffects = <em>Channel<\/em>&lt;String&gt;() \/\/ \uc0ac\uc774\ub4dc \uc774\ud399\ud2b8 \ucc98\ub9ac\uc6a9 \ucc44\ub110\n\n    val sideEffects = _sideEffects.<em>receiveAsFlow<\/em>()<\/strong>\n\n    private fun reduceState(<em>current<\/em>: MainState,<em>event<\/em>:MainEvent):MainState{\n        return when(<em>event<\/em>){\n            MainEvent.Loading -&gt; {\n                <em>current<\/em>.copy(loading = true)\n            }\n            is MainEvent.Loaded -&gt; {\n                <em>current<\/em>.copy(loading = false, users = <em>event<\/em>.users)\n            }\n        }\n    }\n\n    fun fetchUser() {\n        <em>viewModelScope<\/em>.<em>launch <\/em><strong>{\n            <\/strong>events.send(MainEvent.Loading)\n            val <em>users <\/em>= repository.getUsers()\n            events.send(MainEvent.Loaded(users = <em>users<\/em>))\n            <strong>_sideEffects.send(\"${<em>users<\/em>.size} user(s) loaded\")<\/strong> <strong>\/\/ \uc0ac\uc774\ub4dc \uc774\ud399\ud2b8 \ubc1c\uc0dd<\/strong>\n        <strong>}\n    <\/strong>}\n\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>MVI \ub3c4\uc785\ubc30\uacbd \ud504\ub85c\uc81d\ud2b8\uc5d0 Jetpack Compose\ub97c \ub3c4\uc785\ud558\uace0 1\ub144\uc815\ub3c4 \uc801\uadf9 \uc4f0\uba74\uc11c &#8216;\uc0c1\ud0dc&#8217; \uad00\ub9ac\uc758 \uc911\uc694\uc131\uc744 \uba38\ub9ac\uac00 \uc544\ub2cc \ubab8\uc73c\ub85c \ub290\uaef4\ubc84\ub838\ub2e4. \uc0c1\ud0dc \uad00\ub9ac\ub97c \uc5b4\ub5bb\uac8c \ud558\uba74 \uc88b\uc744\uae4c \uace0\ubbfc\ud558\ub358 \uc911 \ub3d9\ub8cc \uac1c\ubc1c\uc790\uac00 \uc774\uc804\uc5d0 \ub098\uc5d0\uac8c \ub9d0\ud574\uc92c\ub358 MVI\uac00 \ub5a0\uc62c\ub790\ub2e4.&nbsp; &#8220;MVI \ub294 \uc0c1\ud0dc\ub97c \uc27d\uac8c \uad00\ub9ac\ud574\uc900\ub2e4\uad6c blah blah&#8230;&#8221; Compose \ub3c4\uc785 \uc774\uc804\uc5d0\ub294 \uadf8\ub550 \uc0c1\ud0dc \uad00\ub9ac\ub97c \ud06c\uac8c \uc911\uc694\ud558\uac8c \uc0dd\uac01\ud558\uc9c0 \uc54a\uc558\ub2e4. \uc544\ub2c8 \uc5b4\uca4c\uba74 \uad00\ub9ac\uac00 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[38,3],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46365"}],"collection":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=46365"}],"version-history":[{"count":8,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46365\/revisions"}],"predecessor-version":[{"id":46560,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/46365\/revisions\/46560"}],"wp:attachment":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=46365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=46365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=46365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}