loadState를 사용해 데이터의 새로 고침 시점과 추가 데이터가 List에 추가되는 동안 화면에 Loading Composable을 쉽게 추가할 수 있다
HomeScreen에서 ViewModel에서 가져온 데이터로 PlantList를 호출할 수 있다
HomeScreen의 CollectionsCarousel Composable은 다른 화면에서도 재사용될 수 있는 Composable이다
HomeScreen과 PlantDetailScreen에서 CollectionsCarousel의 동작 차이는 항목 클릭 시 확장되는 형태라는 점이다
Carousel 구현 시 확장/축소 로직 및 내부 상태, 특정 화면 이동 등의 로직은 재상용 가능한 UI Component로는 불가능하다
CollectionsCarousel Composable을 재사용하게 하려면 상태를 전달하고 이벤트를 외부로 노출해야 한다
CollectionsCarousel의 상태는 CollectionsCarouselState라는 클래스로 생성해서 다룰 수 있다
Carousel이 선택한 Collection의 확장 가능 여부 및 표시할 식물에 대한 정보를 표시한다
Collection 클릭 시에 상태를 업데이트하는 로직도 캡슐화할 수 있다
CollectionsCarouselState은 매개 변수로 CollectionsCarousel Composable에 전달하며 CollectionsCarousel을 호출하는 곳은 이 상태를 작성해서 관리할 필요가 있다
이벤트는 식물을 클릭할 때마다 호출되는 Lambda를 추가 매개변수로 정의할 수 있다
Composable이 Root에 가까울 때 상태를 호스팅하고 로직을 처리하며 이벤트를 처리하는 ViewModel을 생성한다
재사용 가능한 Composable 경우에는 ViewModel을 사용하면 안 된다. 대신 상태를 가지는 클래스를 작성해서 관리하고 호출하는 곳에 이벤트를 공개해야 한다. 그 이유는 ViewModel이 Activity/Fragment/Navigation Graph의 목적지로 Scope가 지정되기 때문이다. 그리고 해당 Scope에서는 하나의 인스턴스만 사용 가능하다
화면 레벨의 Component에서 ViewModel의 이점은 다음과 같다
Configuration 변경에도 그대로 존재한다
SavedStateHandle을 통해 프로세스가 종료되어도 데이터가 남아있다
Lifecycle이 화면과 일치하는 상태에 적합하다
HomeScreen의 상태는 HomeViewModel이 관리하며 carouselState는 ViewModel이 표시하는 UI 상태에 포함된다
ViewModel과 상태 수집에 대한 연결 없이 HomeScreen 테스트 작성 및 Preview 사용이 되어야 한다
Root Composable의 HomeScreen 변경은 쉽다. 필요한 정보를 매개변수로 추가하기만 하면 된다
viewModel과 collectAsState의 호출을 1레벨 위로 올리는 것은 큰 변화는 없지만, HomeScreen이 어디에 적용되는 것을 고려하면 차이가 보인다
위 예시는 Activity에 하나의 화면만 보여주는 경우에는 유효하지만, 실제로는 훨씬 복잡하다
Compose를 도입할 경우 기존 앱의 자연스러운 마이그레이션 흐름은 화면을 한 번에 하나씩 대응하는 것이다
예를 들어 HomScreen은 실제로 HomeFragment의 유일한 콘텐츠로 onCreateView에서 ComposeView를 만든다
앱의 모든 화면이 재사용 및 테스트 가능한 Composable을 둘러싼 Wrapper라면 Navigation Compose와 모든 화면을 하나로 묶는 것이다
Jetpack Navigation Component는 처음부터 일반 런타임으로 만들어져있어 목적지에 대해서는 아는 것이 없다
해당 런타임이 공유된다는 것은 각 구현이 Kotlin DSL을 통해 지원된다는 의미이다
Navigation Compose에는 NavController와 NavHost 이 2가지가 필요하다
NavController
NavController를 입력으로 사용하는 NavHost와는 별도의 객체로 Stateful NavController를 생성할 수 있다
NavHost 외부의 다른 Component가 NavController를 상태에 대한 단일 진실 공급원으로 사용하여 목적지 변경에 반응할 수 있다
NavHost
Composable 목적지를 Compose 계층 구조에 추가하는 역할을 한다
HomeScreen의 경우 Home 목적지를 만들며 고유 경로 선언(예 : “home”)이 가능하다
NavHost startDestination에 Navigation 시작 목적지 설정을 할 수 있다
기존 viewModel 대신 hiltNavGraphViewModel 메소드를 사용해야 한다
목적지가 @AndroidEntryPoint Activity/Fragment가 아니므로 Default Factory는 Hilt Factory가 아니다.
viewModel의 편리한 wrapper이고 항상 적절한 Factory가 설정되도록 한다
2개 화면의 경우
Navigation Compose에 추가하기 전 PlantDetailScreen을 빌드하는 것이 첫 번째 단계이다
Preview 및 테스트를 위해서 HomeScreen처럼 Stateless Component로 빌드해야 한다
그 후 생성한 PlantDetailScreen을 Navigation Graph에 추가한다
arguments에 PlantDetailScreen에 필요한 식물 ID 값을 추가한다. 해당 케이스에서 ID는 int 타입이므로 NavType.IntType을 사용한다
전달된 식물 ID는 ViewModel의 SavedStateHandle을 통해서 사용할 수 있다
HomeScreen에 노출된 식물 클릭 시 Lambda를 사용해 PlatDetailScreen의 경로로 목적지를 navigate를 호출할 수 있다
이후로는 NavController가 나머지 부분을 처리하며 HomeScreen의 상태를 저장하고 PlatDetailScreen으로 교체한다