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

Compose Camp 22KR Pathway3

Compose Camp 22KR Pathway3

2022년도 한국 Compose Camp에서의 Pathway3 슬라이드입니다.
https://developersonair.withgoogle.com/events/composecamp_22kr

유튜브 영상입니다.
https://www.youtube.com/watch?v=XhKRISZRyHE

Sungyong An

October 25, 2022
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. This work is licensed under the Apache 2.0 License Sungyong

    An, Android GDE, NAVER WEBTOON Compose Camp
  2. This work is licensed under the Apache 2.0 License Pathway

    3. Architecture and state https://speakerdeck.com/fornewid/compose-camp-22kr-pathway3
  3. Sungyong An NAVER WEBTOON, Android GDE @fornewid on Github 캠핑지기

    Link: https://github.com/fornewid
  4. This work is licensed under the Apache 2.0 License Pathway

    3. Architecture and state Architecting your Compose UI Compose에서 UDF 패턴을 구현하는 방법, 이벤트 및 상태 홀더를 구현하는 방법, Compose에서 ViewModel을 사용하는 방법에 중점을 둡니다. A Compose state of mind Compose의 상태 모델과 컴포지션, 상태 호이스팅/상태 홀더/AAC ViewModel을 언제 사용하는지, 컴포지션 외부에서 상태를 변경하는 법을 알아봅니다. Advanced state and side effects Jetpack Compose의 상태 및 side effects API와 관련된 고급 개념을 알아봅니다. Compose Navigation Compose에서 Navigation 라이브러리를 사용하여 앱 내 탐색과 딥 링크를 지원하고, 탐색을 테스트하는 방법을 알아봅니다. Link: https://developer.android.com/courses/pathways/jetpack-compose-for-android-developers-3 Article Video Codelab Codelab
  5. This work is licensed under the Apache 2.0 License https://github.com/gdgand/ComposeCamp2022

    pathway3 - AdvancedStateAndSideEffectsCodelab - NavigationCodelab Codelab 안내 Link: https://github.com/gdgand/ComposeCamp2022
  6. This work is licensed under the Apache 2.0 License Codelab

    안내 Link: https://github.com/gdgand/ComposeCamp2022/tree/main/pathway3
  7. This work is licensed under the Apache 2.0 License Keyword

    NavController, NavHost. Navigate to a composable. Navigate with arguments. Deep links. Testing. LaunchedEffect, DisposableEffect, SideEffect. rememberCoroutineScope. rememberUpdatedState. produceState, derivedStateOf, snapshotFlow. Unidirectional data flow. State, Event, State hoisting. State holder, AAC ViewModel. Stateful vs Stateless. Composition, Recomposition. remember, rememberSaveable. State Side-effect Navigation
  8. This work is licensed under the Apache 2.0 License Jetpack

    Compose에서 데이터 스트림을 관찰하는 방법, state holder를 만드는 방법, side effect API를 사용하는 방법, Composable에서 suspend 함수를 호출하는 방법을 알아봅니다. Advanced State and Side Effects Codelab Link: https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects#0
  9. This work is licensed under the Apache 2.0 License var

    name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) Unidirectional Data Flow Link: https://developer.android.com/jetpack/compose/architecture
  10. This work is licensed under the Apache 2.0 License val

    state = rememberTextFieldState("") OutlinedTextField( value = state.name, onValueChange = { state.name = it }, label = { Text("Name") } ) State Holder as source of truth class TextFieldState(initialName: String) { var name by mutableStateOf(initialName) } @Composable fun rememberTextFieldState( name: String ): TextFieldState = remember(name) { TextFieldState(name) } Link: https://developer.android.com/jetpack/compose/state#state-holder-source-of-truth Composable에 여러 UI 요소의 상태가 관련되어 있는 복잡한 UI 로직이 있다면, UI 로직과 UI 요소의 상태를 State Holder로 분리하는 것을 권장한다.
  11. This work is licensed under the Apache 2.0 License val

    name by viewModel.name OutlinedTextField( value = name, onValueChange = { viewModel.onNameChange(it) }, label = { Text("Name") } ) ViewModel as source of truth class MyViewModel : ViewModel() { var name = mutableStateOf("") private set fun onNameChange(name: String) { this.name.value = name } } Link: https://developer.android.com/jetpack/compose/state#viewmodels-source-of-truth 화면 단위의 UI 상태를 관리하고, 비즈니스 로직에 접근이 필요한 경우에 사용하는 특별한 유형의 State Holder. Composition에 연관된 상태를 참조해서는 안된다. (Memory Leak 발생 가능)
  12. This work is licensed under the Apache 2.0 License Defining

    source of truth Link: https://developer.android.com/jetpack/compose/state#managing-state
  13. This work is licensed under the Apache 2.0 License class

    MyViewModel : ViewModel() { private val _name = MutableStateFlow("") val name: StateFlow<String> get() = _name } val name by viewModel.name.collectAsState() Consuming LiveData or Flow class MyViewModel : ViewModel() { private val _name = MutableLiveData("") val name: LiveData<String> get() = _name } val name by viewModel.name.observeAsState("") LiveData Flow Link: https://developer.android.com/jetpack/compose/state#use-other-types-of-state-in-jetpack-compose
  14. This work is licensed under the Apache 2.0 License value

    changed -> Side-effect 내부에서 발생하면 Recomposition을 예측하기 어려워서, Composable 내부에서는 side-effect가 없어야 한다. 화면 이동과 같은 일회성 이벤트를 처리할 때, side-effect가 필요한 경우에는 Effect API를 사용한다. Link: https://developer.android.com/jetpack/compose/side-effects produceState snapshotFlow LaunchedEffect rememberCoroutineScope DisposableEffect SideEffect rememberUpdatedState derivedStateOf CoroutineScope NOT CoroutineScope -> awaitDispose{} remember in Composable outside Composable -> onDispose{} value changed -> every recomposition ->
  15. This work is licensed under the Apache 2.0 License LaunchedEffect

    if (state.hasError) { LaunchedEffect(scaffoldState.snackbarHostState) { scaffoldState.snackbarHostState.showSnackbar( message = "Error message", actionLabel = "Retry message" ) } } 코루틴을 호출할 수 있는 Composable 함수. 키가 변경되면 기존 코루틴이 취소되고 새 코루틴에서 suspend 함수가 실행된다. composition을 종료하면 코루틴이 취소된다. Link: https://developer.android.com/jetpack/compose/side-effects#launchedeffect
  16. This work is licensed under the Apache 2.0 License rememberCoroutineScope

    val scope = rememberCoroutineScope() Button( onClick = { scope.launch { scaffoldState.snackbarHostState.showSnackbar("Something happened!") } } ) { Text("Press me") } Link: https://developer.android.com/jetpack/compose/side-effects#remembercoroutinescope 호출되는 composition에 바인딩된 CoroutineScope를 반환하는 Composable 함수. composition을 종료하면 자동으로 취소된다.
  17. This work is licensed under the Apache 2.0 License rememberUpdatedState

    @Composable fun LandingScreen(onTimeout: () -> Unit) { val currentOnTimeout by rememberUpdatedState(onTimeout) LaunchedEffect(Unit) { delay(SplashWaitTimeMillis) currentOnTimeout() } /* content */ } Link: https://developer.android.com/jetpack/compose/side-effects#rememberupdatedstate recomposition마다 값을 업데이트해주는 Composable 함수. 재생성/재시작 비용이 많이 들거나 금지된, 오래걸리는 작업에 사용하기 적합하다.
  18. This work is licensed under the Apache 2.0 License DisposableEffect

    DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } Link: https://developer.android.com/jetpack/compose/side-effects#disposableeffect 키가 변경되거나 composition을 종료할 때, 정리가 필요하면 사용하는 Composable 함수.
  19. This work is licensed under the Apache 2.0 License SideEffect

    @Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { /* ... */ } SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics } Link: https://developer.android.com/jetpack/compose/side-effects#sideeffect-publish composition될 때마다 호출되는 Composable 함수. Compose에서 관리하지 않는 객체에 Compose 상태를 공유할 때 주로 사용한다.
  20. This work is licensed under the Apache 2.0 License produceState

    val imageState by produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { val image = imageRepository.load(url) value = if (image == null) Result.Error else Result.Success(image) awaitDispose { /* ... */ } } Link: https://developer.android.com/jetpack/compose/side-effects#producestate 비동기로 생성된 값을 observable State로 반환하는 Composable 함수. Flow, LiveData, RxJava와 같은 구독 기반 상태를 Compose State로 변환할 수 있다. composition을 떠날 때 코루틴이 취소되고, 값이 달라질 때만 recomposition된다. 구독을 제거할 때는 awaitDispose 함수를 사용한다.
  21. This work is licensed under the Apache 2.0 License derivedStateOf

    val showButton = listState.firstVisibleItemIndex > 0 // X val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } Link: https://developer.android.com/jetpack/compose/side-effects#derivedstateof 계산 결과를 State로 반환하는 Composable 함수. 계산에 사용된 상태 중 하나가 변경될 때마다 다시 계산하고, 계산 결과는 캐싱된다.
  22. This work is licensed under the Apache 2.0 License snapshotFlow

    val editableUserInputState = rememberEditableUserInputState(hint = "Choose Destination") val currentOnDestinationChanged by rememberUpdatedState(onToDestinationChanged) LaunchedEffect(editableUserInputState) { snapshotFlow { editableUserInputState.text } .filter { !editableUserInputState.isHint } .collect { currentOnDestinationChanged(editableUserInputState.text) } } Link: https://developer.android.com/jetpack/compose/side-effects#snapshotFlow Compose State를 Flow로 변환해주는 Composable 함수. snapshotFlow 블록 내에서 읽은 State 객체 하나의 값이 변경되면, Flow가 새로운 값을 방출한다.
  23. This work is licensed under the Apache 2.0 License Jetpack

    Compose에서 Navigation 라이브러리를 사용하는 방법을 알아봅니다. Navigation Codelab Link: https://developer.android.com/codelabs/jetpack-compose-navigation#0
  24. This work is licensed under the Apache 2.0 License NavBackStackEntry

    Structure NavHost NavGraph NavGraph NavDestination NavDestination NavController visibleEntries Composable Composable destination
  25. This work is licensed under the Apache 2.0 License NavController

    val navController = rememberNavController() 앱 화면을 구성하는 Composable의 Back Stack과 각 화면의 상태를 추적한다. Stateful. Link: https://developer.android.com/jetpack/compose/navigation#getting-started
  26. This work is licensed under the Apache 2.0 License NavController를

    이용하여 탐색하는 컨테이너. Composable 간에 이동하면 NavHost의 컨텐츠가 재구성(recomposed). Kotlin DSL 구문으로 NavGraph를 정의한다. NavHost NavHost(navController = navController, startDestination = "profile") { composable("profile") { Profile(/*...*/) } composable("friendslist") { FriendsList(/*...*/) } /*...*/ navigation(...) { composable(...) { /*...*/ } } } = Nested NavGraph = NavDestination Link: https://developer.android.com/jetpack/compose/navigation#create-navhost
  27. This work is licensed under the Apache 2.0 License Navigate

    to a composable navController.navigate("friendslist") // "android-app://androidx.navigation/$route" NavGraph에서 Composable Destination으로 이동하려면, route와 함께 navigate 함수를 호출한다. 실제로는 딥링크로 이동한 것처럼 다뤄진다. Link: https://developer.android.com/jetpack/compose/navigation#nav-to-composable
  28. This work is licensed under the Apache 2.0 License Navigate

    with arguments NavHost(...) { composable( "profile/{userId}", arguments = listOf(navArgument("userId") { type = NavType.StringType }) ) { backStackEntry -> Profile(navController, backStackEntry.arguments?.getString("userId")) } } navController.navigate("profile/user1234") 이동할 때 argument를 전달하려면 딥링크처럼 route에 placeholder를 추가해야 한다. 실수로 $를 붙이지 않도록 주의할 것. Link: https://developer.android.com/jetpack/compose/navigation#nav-with-args
  29. This work is licensed under the Apache 2.0 License Thank

    You! Link: https://developer.android.com/courses/pathways/jetpack-compose-for-android-developers-3