Upgrade to Pro — share decks privately, control downloads, hide ads and more …

선언형 UI에서의 상태관리

HyunWoo Lee
December 21, 2024

선언형 UI에서의 상태관리

GDG Devfest Songdo 2024에서 진행한 선언형 UI에서의 상태관리의 Speaker Deck입니다.

HyunWoo Lee

December 21, 2024
Tweet

More Decks by HyunWoo Lee

Other Decks in Programming

Transcript

  1. Incheon ࢶ঱ഋ UIীࢲ੄ ࢚కҙܻ - Circuitҗ Rinਸ ઺बਵ۽ ੉അ਋ Android/React

    Native Developer, Viva Republica(Toss) Organizer, Kotlin User Groups Seoul/GDG Incheon
  2. Before Compose class SampleActivity: AppCompatActivity() { private var name =

    "" override fun onCreate(bundle: SavedInstanceState?) { super.onCreate(bundle) // ࢲߡాन APIService.getMyName().enqueue(object: Callback { override fun onResponse(ޤदӽ੉) { name = response.body().name updateUi() // binding.tv.text = name } }) } }
  3. Before Compose • ѐߊ੗о ૒੽ ߸ࣻܳ ߸҃ೞҊ UI੄ ࢚కܳ ߸҃ೞب۾

    ௏٘ܳ ੘ ࢿ೧ঠೣ • ߸ࣻо ചݶ੄ ߸҃ਸ दఃחؘ ઺ਃبо ڄয૗ • ചݶਸ ߸҃ೞח Ѫ਷ ߸ࣻ ч੉ ইצ ѐߊ੗
  4. Before Compose - DataBinding class SampleActivity: AppCompatActivity() { private val

    viewModel by viewModels() private lateinit var binding: ActivitySampleBinding override fun onCreate(bundle: SavedInstanceState?) { super.onCreate(bundle) binding = DataBindingUtil.setContentView(this, R.layout.ui) binding.viewModel = viewModel } } <layout> <data> <variable name=“vm” type=“com.sample.ViewModel” /> </data> <TextView <!— ࢤۚ —> android:text=“@{vm.user.nam e}” /> </layout>
  5. Data Binding ߸ࣻܳ ߸҃݅ ೧ب ചݶ੄ ࢚కо ߸҃ؽ ߸ࣻо ചݶ੄

    “࢚క”о غب۾ ࢸ҅ ߑೱ੉ ߸҃ ૊, ചݶ੄ ࢚క ߸҃ ઱୓о ѐߊ੗ীࢲ दझమਵ۽ ੹بؽ ঑ب੸ੋ ࠽٘ఋ੐ ࢚थҗ ೧ةೞӝ য۰਍ ী۞ झఖ ౟ۨ੉झ Data Binding -> View Bindingਵ۽ ֈযоח ୶ࣁ Ӓۢীب ࠛҳೞҊ ѐߊ੗о ചݶ੄ ߸ࣻо “࢚క”۽ ੋधೞѱ ػ ୐ ࣁ؀
  6. Compose بੑ ੉റ @Composable fun SampleText() { var text by

    remember { mutableStateOf("") } LaunchedEffect(Unit) { text = APIService.getName().await() } TextField(value = text, onValueChange = { text = it }) }
  7. ࢤпࠁ׮ ҙܻ೧ঠೡ ࢚క੄ ઙܨח ׮নೞ׮ ഥਗоੑ ੑ۱ Form • ੉ܴ/੉ݫੌ

    ઱ࣗ/࠺޻ߣഐ ١ ੑ۱ UI • ੉ݫੌ઱ࣗ ઺ࠂ ৈࠗ • ࠺޻ߣഐ Ѩૐ • ࠺޻ߣഐ ഛੋҗ ࠺޻ߣഐ ੌ஖ ৈࠗ • ডҙ ز੄
  8. ੹৉࢚క৬ ૑৉࢚క ࢚కо ־ҳীѱ ҕਬغҊ ੓חо ೞա੄ ஹನք౟ && ੗ध

    ஹನք౟ٜীѱ ҕਬ - ૑৉࢚క ੹୓ ஹನք౟ٜ੉ ࢎਊೡ ࣻ ੓ѱ ҕਬ - ੹৉࢚క
  9. ੹৉ ࢚క(Global State) @Composable fun TodoTheme( darkTheme: Boolean = false,

    content: @Composable () -> Unit ) { val colors = todoColors() val typography = TodoTypography() ProvideTodoColorAndTypography(colors, typography){ MaterialTheme(content = content) } }
  10. Configuration Change CompositionLocal۽ ੹׳غח ч੉ ੹৉ী ੹౵غפө Config Change۽ ч੉

    ࠛউ੿೧૑ݶ ੹୓ ஹನք౟ী ৔ೱ -> জ উ੿ࢿ ੷ೞ ೐۽ࣁझ ࢤݺ઱ӝী উ੿੸ਵ۽ ؘ੉ఠ ੷੢द ਊ۝ ޙઁ rememberSavableਸ ഝਊೞݶ 1MB ੉ղ۽ ؘ੉ఠܳ ੷੢೧ঠೞחؘ ਗೞח ݅ఀ੄ ؘ੉ఠܳ ੷੢ਸ ೞ׮о ৘࢚஖ ޅೠ Exception੉ ఠ૕ ࣻ ੓਺ উ٘۽੉٘ীࢲ ੹৉࢚కҙܻ
  11. ੿݈ ViewModelী ֍חѱ ୭ࢶ? Configuration Changeী ٮۄࢲ ч੉ ߸҃೧ঠೡ ࣻ

    ੓਺ ѓ۟द Fold৬ э਷ ҃਋ ੽ਵݶ Phone UI, ಟ஖ݶ Tablet UI ࠁৈ઻ঠೞח ா੉ झ ߊࢤ, ੉۠ ؘ੉ఠܳ Config Change উ੿੸੉ѱ فݶ য়൤۰ ী۞ ߊࢤ ׮ܲ ஹನք౟ٜী ࠛ೙ਃೠ ੿ࠁܳ ֈѹ п ё୓ח ޻੽ೞѱ ҙ҅ػ ׮ܲ ё୓ٜী ؀೧ࢲ ઁೠػ ૑ध݅ਸ оઉঠ ೠ׮. (Law of Demeter)
  12. StateHolder Class @Stable class NiaAppState( val navController: NavHostController, coroutineScope: CoroutineScope,

    networkMonitor: NetworkMonitor, userNewsResourceRepository: UserNewsResourceRepository, timeZoneMonitor: TimeZoneMonitor, ) { private val previousDestination = mutableStateOf<NavDestination?>(null) ……
  13. StateHolder Class @Composable fun rememberNiaAppState( … ): NiaAppState { return

    remember { NiaAppState( navController = navController, coroutineScope = coroutineScope, networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, timeZoneMonitor = timeZoneMonitor, )
  14. Circuit? Circuit is a simple, lightweight, and extensible framework for

    building Kotlin applications that’s Compose from the ground up.
  15. CircuitUiState, CircuitUiEvent sealed inte rf ace State : CircuitUiState {

    data object Loading : State data object NoFavorites : State data class Results( val list: List<Favorite>, val eventSink: (Event) -> Unit ) : State } sealed inte rf ace Event : CircuitUiEvent { data class ClickFavorite(id: Long): Event }
  16. @Composable fun CounterPresenter(): CounterState { var count by rememberSaveable {

    mutableStateOf(0) } return CounterState(count) { event -> when (event) { CounterEvent.Increment -> count++ CounterEvent.Decrement -> count-- } } } Presenter
  17. @Composable fun CounterPresenter(): CounterState { var count by rememberRetained {

    mutableStateOf(0) } return CounterState(count) { event -> when (event) { CounterEvent.Increment -> count++ CounterEvent.Decrement -> count-- } } } Presenter
  18. // CircuitUiState੄ ఋੑ expo rt inte rf ace CounterState {

    value: number } // CircuitUiState ୡӝ ё୓ const initialState: CounterState = { value: 0, } Redux Toolkitҗ Presenter
  19. // Circuit੄ presenter expo rt const counterSlice = createSlice({ name:

    'counter', // Updateغח state initialState, reducers: { // event ੿੄ ࠗ࠙ increment: (state) => { state.value += 1 }, decrement: (state) => { state.value -= 1 } } ) Redux Toolkitҗ Presenter
  20. @CircuitInject(FavoriteScreen::class, ActivityRetainedComponent::class) @Composable fun FavoritesList(state: FavoritesScreen.State) { when (state) {

    Loading -> Text(text = stringResource(R.string.loading_favorites)) NoFavorites -> Text( modi fi er = Modi fi er.testTag("no favorites"), text = stringResource(R.string.no_favorites) ) is Results -> { LazyColumn { items(state.list) { Favorite(it, state.eventSink) } } } } } Screen
  21. Circuitী ؀ೠ рۚೠ ࣗх • ؀ഋ জҗ э਷ ҃਋, ఋੑ/ҳഅী

    ؀ೠ ӏઁٜ੉ ౱੉ ࢤ࢑ೞח ௏ ٘੄ ాੌࢿҗ оةࢿਸ ֫ৈ઴ ࣻ ੓਺ • Unit Testب рױೞѱ ੘ࢿೡ ࣻ ੓਺ • Compose runtimeਸ ഝਊ೧ࢲ ߈਽ഋ ࢚కܳ ઁ੘ೡ ࣻ ੓׮ח Ѫী ௾ ੄੄
  22. Circuitী ؀ೠ рۚೠ ࣗх • ੉Ѧ ੸ਊೡ ࣻ ੓ח ഥࢎо

    ݻ੉ա ੓ਸө? • ׮নೠ بҳٜ(Flow, Rx, Legacy View ١)җ interopਸ ೡ ࣻ ੓૑݅, ੉ܳ interopೞӝ ਤ೧ ٘ח ݆ࣻ਷ ҕٜࣻਸ ࢤп೧ঠؽ • ъઁ۽ ҳഅ/ࢎਊ೧ঠೞח Ѫٜ(Presenter, Navigator ١)੉ ݆ই पઁ۽ ೐۽؋࣌ী ੸ਊؼ݅ೠ૑ח ੄ޙ
  23. @Composable fun ScreenA() { var isB by rememberRetained { mutableStateOf(true)

    } rememberRetained{ "A" } if(isB) { rememberRetained{ "B" } } else { rememberRetained{ "C" } } } takahirom/Rin