시배's Android

Compose | Modular Navigation with Jetpack Compose 본문

Android/Compose

Compose | Modular Navigation with Jetpack Compose

si8ae 2023. 9. 21. 16:59
 

Modular Navigation with Jetpack Compose

A large amount of mobile apps will need some form of Navigation, allowing users to move between different parts of an application. When…

medium.com

대부분의 모바일 앱에는 사용자가 애플리케이션의 여러 부분 사이를 이동할 수 있도록 하는 일종의 내비게이션이 필요합니다. Android 앱 내에서 이러한 요구 사항을 구현할 때 애플리케이션은 자체 솔루션을 구현하거나 기존의 인텐트 또는 조각 관리자에 의존하거나 최근 몇 년 동안 탐색 컴포넌트 옵션을 탐색해 왔습니다. Jetpack Compose의 알파 버전과 개발자 프리뷰 버전이 출시되는 동안 "내비게이션은 어때요?", "컴포저블 사이를 탐색할 수 있나요?"라는 질문을 자주 받았습니다. Jetpack Compose를 사용하면 컴포저블로 자유롭게 작업할 수 있다는 아이디어는 fragment가 없고 (대부분) Activity가 없는 앱이라는 개념을 도입하여 UI를 표시할 때 컴포저블에만 의존할 수 있게 해줍니다.

 

내비게이션 컴포넌트의 내비게이션 컴포즈 지원 덕분에 이제 이것이 완전히 가능해졌습니다. 애플리케이션은 앱에서 단일 액티비티를 실행할 수 있으며, 컴포저블을 사용하여 애플리케이션을 구성하는 UI 컴포넌트를 나타낼 수 있습니다. 저는 이 컴포저블을 몇 개의 프로젝트에 사용해 왔으며, 안드로이드 앱을 빌드하는 새로운 방식이 정말 마음에 들었습니다. 그러나 기존 가이드와 블로그 게시물을 통해 프로젝트에 처음 사용한 후 프로젝트에서 마찰과 문제점을 추가할 수 있는 몇 가지 사항을 발견하기 시작했습니다:

 

  • 컴포저블 함수에서 NavHostController 참조 필요 - 많은 경우 컴포저블이 다른 컴포저블에 대한 내비게이션을 수행해야 할 것입니다. 많은 경우 기본 접근 방식이 컴포저블 함수를 통해 이 NavHostController 레퍼런스를 전달하는 것을 발견했습니다. 즉, 내비게이션을 수행하려면 항상 현재 NavHostController에 대한 참조가 있어야 합니다. 이는 확장성이 좋지 않으며 내비게이션을 수행하기 위해 이 참조에 의존할 수밖에 없습니다.
  • 내비게이션 로직을 테스트하기 어려움 - 컴포저블이 이 NavHostController를 직접 사용하여 내비게이션을 트리거하는 경우 내비게이션 로직을 테스트하기가 어렵습니다. 컴포저블 함수는 현재 instrumentation 테스트를 사용하여 테스트합니다. 다른 클래스(예: 뷰 모델)가 내비게이션 로직을 처리하도록 하면 프로젝트의 단위 테스트 내에서 내비게이션 이벤트를 테스트할 수 있습니다.
  • 컴포즈 마이그레이션에 friction 추가 - 컴포즈를 사용하는 프로젝트 중 상당수는 크기가 다양한 기존 프로젝트일 것입니다. 이러한 경우 프로젝트를 완전히 다시 작성하지 않고 다양한 방식으로 Compose로 마이그레이션할 가능성이 매우 높습니다. 새로운 기능은 Compose에서 작성하고 기존 컴포넌트는 천천히 다시 작성할 수 있습니다. 이러한 경우 이러한 컴포저블에 이 NavHostController를 제공하는 것이 어려울 수 있습니다. 예를 들어, 컴포저블로 재작성되는 기존 컴포넌트가 해당 함수에 NavHostController를 제공하기 어려운 방식으로 격리되어 있을 수 있습니다.
  • 내비게이션 종속성에 결합 - 내비게이션을 수행하기 위해 NavHostController 참조가 필요하다는 것은 이를 사용하는 모든 모듈에 Compose Navigation 종속성에 대한 참조가 필요하다는 것을 의미합니다. 마찬가지로, 다른 모듈에서 뷰 모델을 제공하기 위해 Hilt Navigation Compose 종속성을 사용하려는 경우 해당 종속성 역시 마찬가지입니다. 이는 예상된 요구 사항처럼 느껴지지만, 이러한 것들에 대한 중앙 집중식 종속성은 위의 문제를 해결할 때 좋은 부수적인 효과입니다.

이는 제가 생각해본 몇 가지 사항일 뿐이지만, Compose Navigation을 통해 기존 애플리케이션에 어떻게 적용할지, 또는 새 프로젝트에서 어떻게 구성할지 생각해 보셨을 수도 있습니다.

 

몇 년 전에 읽은 모듈형 내비게이션에 관한 기사에서 영감을 받아 모듈화된 안드로이드 앱에 Compose Navigation을 추가할 때 제가 수행한 몇 가지 탐색 작업을 공유하고자 합니다. 

 

  • NavHostController 참조에 의존하지 않고 여러 feature 모듈에서 컴포저블로 이동하는 방법
  • 이러한 모듈을 내비게이션 작성에서 분리하고 뷰 모델을 통해 중앙화된 위치에서 내비게이션을 처리하는 방법
  • 각 피처 모듈이 해당 종속성에 의존하지 않고 Hilt Navigation Compose를 사용하여 이러한 컴포저블에 뷰 모델을 제공하는 방법
  • 내비게이션 로직을 최적화하여 테스트에 대한 접근 방식을 단순화하는 방법

Modularized Applications

 

GitHub - hitherejoe/minimise: Minimise app built using kotlin multiplatform

Minimise app built using kotlin multiplatform. Contribute to hitherejoe/minimise development by creating an account on GitHub.

github.com

  • App 모듈 - 애플리케이션의 기본 모듈입니다. 여기에는 애플리케이션의 기능 모듈에 포함된 다양한 컴포저블에 대한 내비게이션을 제공하는 컴포저블 내비게이션 그래프가 포함되어 있습니다.
  • Navigation 모듈 - 프로젝트 전체에서 내비게이션을 오케스트레이션하는 모듈입니다. 이는 각 모듈이 내비게이션을 트리거하는 방법과 이러한 내비게이션 이벤트를 관찰하는 방법을 제공합니다.
  • Feature 모듈 - 지정된 기능에 대한 컴포저블을 포함하는 모듈입니다. 참고: 샘플 코드에서는 두 개 이상의 기능 모듈을 사용하지만 다이어그램을 간결하게 유지하기 위해 여기에는 하나만 포함했습니다.

이 다이어그램을 통해 서로 다른 모듈 간의 관계와 이러한 모듈이 함께 작동하여 원하는 Navigation 목표를 달성하는 방법을 확인할 수 있습니다.

  • 앱 모듈은 NavHostController를 사용하여 내비게이션 그래프를 빌드하고, feature 모듈 내에서 컴포저블을 컴포저블하고 탐색할 수 있는 방법을 제공합니다.
  • 내비게이션 모듈은 그래프에서 탐색할 수 있는 가능한 목적지를 정의합니다. 이는 필요에 따라 기능 모듈 내에서 트리거되는 명령으로 구조화됩니다.
  • 앱 모듈은 내비게이션 매니저를 통해 트리거될 때 내비게이션 모듈을 사용하여 이러한 내비게이션 명령을 관찰합니다. 이벤트가 발생하면 NavHostController가 컴포저블을 탐색하는 데 사용됩니다.
  • feature 모듈은 내비게이션 모듈을 사용하여 뷰 모델에서 이러한 내비게이션 이벤트를 트리거하고, 이러한 이벤트를 관찰하는 모든 것에 의존하여 실제 내비게이션을 처리합니다.
  • 뷰 모델은 앱 모듈 내에서 Hilt Navigation Compose를 사용하여 컴포저블 feature에 제공되며, 뷰 모델의 범위는 해당 NavHostController 내의 현재 백스택 항목으로 지정됩니다.

Composable Destinations

컴포저블로 이동하는 것에 대해 생각하기 전에 가지 컴포저블을 만들어야 합니다. 미니멈 프로젝트에는 인증 화면과 대시보드 화면이라는 가지 기능이 있습니다. 사용자는 인증 화면에서 시작하여 앱에서 인증에 성공하면 대시보드로 이동합니다.

여기서는 Authentication이라는 새로운 컴포저블 함수를 정의하는 것으로 시작하겠습니다. 여기에는 이 화면의 상태를 보관하는 AuthenticationState kotlin 클래스에 대한 참조가 사용됩니다. 이 글에서는 코드가 중요하지 않으므로 이러한 컴포저블의 내부를 자세히 살펴보지는 않고, 컴포저블이 무엇으로 구성되어 있는지 개략적으로 살펴보기로 하겠습니다.

@Composable
private fun Authentication(
    viewState: AuthenticationState
)

이 상태는 @HiltViewModel 어노테이션으로 어노테이션된 뷰모델에서 오는데, 이는 jetpack Hilt Integration을 사용하여 수행됩니다.

@HiltViewModel
class AuthenticationViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    ...
) : ViewModel() {
    val state: LiveData<AuthenticationState> ...
}

이전 컴포저블 함수가 private로 표시된 것을 보셨을 것입니다. feature 모듈에 접근하는 모든 사람이 인증 UI를 구성할 수 있도록 공개적으로 접근 가능한 컴포저블 함수를 추가하겠습니다. 이 함수에 뷰모델을 인자로 추가하여 뷰모델 제공 방식을 분리하고 컴포저블 테스트 접근 방식을 개선할 수 있는 공간을 추가하겠습니다.

@Composable
fun Authentication(
    viewModel: AuthenticationViewModel
) {
    val state by viewModel.uiState.observeAsState()
    Authentication(state)
}

이제 애플리케이션의 인증 기능을 위한 컴포저블 함수가 준비되었습니다. 애플리케이션의 두 부분 사이를 탐색할 수 있도록 두 번째 기능인 애플리케이션의 대시보드에 대한 또 다른 컴포저블 함수를 만들어 보겠습니다. 여기서는 위와 동일하게 대시보드 UI를 구성하는 데 사용할 컴포저블 함수와 상태를 오케스트레이션하는 데 사용할 뷰모델을 만들겠습니다.

@Composable
fun Dashboard(
    viewModel: DashboardViewModel
) {
    val state by viewModel.uiState.observeAsState()
    DashboardContent(state)
}

@HiltViewModel
class DashboardViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    ...
) : ViewModel()

이제 컴포저블 함수로 구성된 두 개의 기능이 각각 고유한 뷰모델을 갖게 되었습니다. 하지만 현재 앱에서는 아무 것도 하지 않으므로 다음에는 앱의 두 부분 사이를 이동하도록 구성하는 방법에 대해 자세히 알아보겠습니다.

Setting up the Navigation Routes

애플리케이션의 내비게이션을 제어하는 중앙 집중식 내비게이션 모듈을 사용할 것이므로 앱에서 지원되는 내비게이션에 대한 일종의 컨트랙트를 만들어야 합니다. 내비게이션 컨트롤러를 보유한 클래스에서 관찰하여 트리거할 수 있는 다양한 내비게이션 이벤트를 정의할 수 있는 NavigationCommand의 형태로 만들겠습니다.

 

프로젝트 내에 Compose Navigation 아직 없는 경우 다음 종속성을 추가해야 합니다.

androidx.navigation:navigation-compose:1.0.0-alpha07

여기서는 인터페이스인 NavigationCommand를 정의하는 것으로 시작하겠습니다. 이것은 내비게이션 이벤트에 대한 요구 사항을 정의할 것입니다. 현재는 목적지와 제공될 인수를 지원하기 위해서만 필요합니다. 이 클래스는 다른 요구 사항의 요구 사항을 충족해야 하는 경우 발전할 수 있는 공간이 있습니다.

interface NavigationCommand {

    val arguments: List<NamedNavArgument>

    val destination: String
}

컨트랙트가 준비되었으므로 이제 애플리케이션의 특정 기능 사이를 탐색하는 사용할 있는 가지 탐색 명령을 정의할 있으며, 인증 대시보드 기능 모두에 사용할 것입니다. 여기서는 위에서 NavigationCommand 인터페이스를 구현하는 새로운 함수를 각각 정의하겠습니다. 지금은 명령의 목적지 속성을 만족하도록 내비게이션 목적지를 하드코딩하겠습니다. 그러면 목적지는 내비게이션 컨트롤러가 내비게이션할 컴포저블을 계산할 사용됩니다.

object NavigationDirections {

    val authentication  = object : NavigationCommand {

        override val arguments = emptyList<NamedNavArgument>()

        override val destination = "authentication"

    }

    val dashboard = object : NavigationCommand {

        override val arguments = emptyList<NamedNavArgument>()

        override val destination = "dashboard"
    }
}

이러한 목적지는 현재 내비게이션이 수행될 내비게이션 인수를 사용하지 않지만 앱의 다른 부분에서도 필요할 있으므로 유연하게 사용할 있도록 하고 싶었습니다. 필요한 경우 내비게이션 모듈 내에서 내비게이션 작성에 사용되는 인수를 중앙 집중화할 있습니다. 대시보드 목적지에 대한 함수를 사용하여 원하는 인수를 제공한 다음, 이를 사용하여 인수 목록을 작성할 있습니다. 이렇게 하면 내비게이션 계약에 대한 접근 방식을 그대로 유지하면서 모듈화된 내비게이션으로 유연성을 확보할 있습니다.

object DashboardNavigation {

  private val KEY_USER_ID = "userId"
  val route = "dashboard/{$KEY_USER_ID}"
  val arguments = listOf(
    navArgument(KEY_USER_ID) { type = NavType.StringType }
  )

  fun dashboard(
    userId: String? = null
  ) = object : NavigationCommand {
    
    override val arguments = arguments

    override val destination = "dashboard/$userId"
  }
}

위와 같이 개체의 경로와 인수를 사용하여 내비게이션을 구성한 다음, dashboard() 함수를 사용하여 이벤트를 트리거하여 실제 내비게이션을 수행할 있습니다. 내비게이션 인수를 사용하는 것은 현재 요구 사항의 범위를 벗어난 것이지만, 위의 내용을 통해 대략적인 예시를 있기를 바랍니다.

Setting up the Navigation Graph

이제 내비게이션 명령을 정의했으므로 앱의 내비게이션 그래프를 구성할 수 있습니다. 이 그래프는 목적지와 목적지가 가리키는 컴포저블을 정의하는 데 사용됩니다. 여기서는 기억NavController 컴포저블 함수를 사용하여 새로운 NavHostController 참조를 정의하는 것으로 시작하겠습니다. 이 함수는 그래프의 탐색을 처리하는 데 사용됩니다.

val navController = rememberNavController()

이제 내비게이션 그래프를 구성하는 컴포저블을 포함하는 사용되는 NavHost 정의할 있으며, 빌더 인수를 사용하여 제공될 것입니다. 지금은 그래프가 시작해야 목적지와 함께 이전에 정의한 NavHostController 참조를 제공하겠습니다. 이를 위해 인증 화면을 사용하여 이전에 정의한 NavigationDirections 참조에서 목적지 문자열에 액세스합니다.

NavHost(
    navController,
    startDestination = NavigationDirections.Authentication.destination
) {

}

이 startDestination이 정의되어 있으면 NavHost가 이를 사용하여 탐색 그래프의 초기 상태를 구성하고 인증 대상 문자열과 일치하는 컴포저블에서 사용자를 시작한다는 의미입니다.

Setting up the Navigation Destinations

내비게이션 그래프의 초기 목적지는 정의했지만 그래프를 구성하는 컴포저블 목적지는 아직 정의하지 않았으므로 현재 상태로는 제대로 작동하지 않습니다! 따라서 여기서는 NavGraphBuilder.composable 함수를 사용하여 목적지를 추가하겠습니다. 먼저 NavigationDirections 정의에 정의된 문자열을 사용하여 컴포저블의 경로를 제공하면, 경로를 탐색할 때마다 컴포저블 목적지의 본문이 UI 구성될 경로가 됩니다. 여기서는 해당 본문에 대해 이전에 정의한 Authentication 컴포저블을 제공합니다.

composable(NavigationDirections.Authentication.destination) {
    Authentication()
}

그런 다음 대시보드 대상에 대해 동일한 작업을 다시 수행하여 이전에 본문에 대해 정의한 대시보드 컴포저블을 사용하여 이 컴포저블을 탐색하도록 트리거하는 경로를 정의합니다.

composable(NavigationDirections.Dashboard.destination) {
    Dashboard()
}
위와 같이 이제 탐색 그래프 내에 두 개의 컴포저블 목적지가 정의되었으며, 그래프에서 인증 컴포저블이 시작 목적지로 사용됩니다.
 

Providing ViewModels to Composables

인증 대시보드에 대해 이전에 정의한 컴포저블로 돌아가면 위의 선언이 컴파일되지 않는 것을 있습니다. 이는 해당 컴포저블 함수에 필요한 뷰모델 인수가 누락되었기 때문입니다. 이러한 인수를 제공하기 위해 NavController 레퍼런스와 함께 hiltNavGraphViewModel 확장 함수를 활용하겠습니다. 함수에 액세스하려면 애플리케이션에 다음 의존성을 추가해야 합니다.

androidx.hilt:hilt-navigation-compose:1.0.0-alpha01

이 확장 함수를 사용하면 NavController의 제공된 경로로 범위가 지정된 뷰모델 참조가 제공됩니다.
Authentication(
    navController.hiltNavGraphViewModel(route = NavigationDirections.Authentication.destination)
)

컴포저블 자체 내에서 이 작업을 수행할 수도 있지만, 이를 인수로 전달하면 기능 모듈 외부에 Compose Navigation + hilt navigation 컴포저블 종속성을 모두 유지할 수 있습니다. 컴포저블 함수를 통해 뷰모델 자체를 제공할 수 있으면 컴포저블 테스트와 모의 레퍼런스를 제공할 때 유용할 수 있습니다.

 

내비게이션 그래프가 항상 존재한다는 것을 보장할 있다면(뷰모델을 제공할 자체 경로를 제공할 필요가 없다면) NavController 확장 함수가 아닌 hiltNavGraphViewModel() 함수를 사용할 있습니다. 함수는 현재 백스택 항목에서 경로를 추론하므로 경로를 제공할 필요가 없습니다.

Authentication(
    hiltNavGraphViewModel()
)

composable(NavigationDirections.Dashboard.destination) {
    Dashboard(
        hiltNavGraphViewModel()
    )
}

현재는 hiltNavGraphViewModel()이 아니라 -> hiltViewModels()로 대체 되었습니다.

 

 

Hilt  |  Android 개발자  |  Android Developers

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Hilt Dagger Hilt의 기능을 확장하여 androidx 라이브러리에서 특정 클래스의 종속 항목 삽입을 사용 설정합니다.

developer.android.com

Handling Navigation Events

뷰모델이 준비되었으므로 이제 내비게이션 그래프가 처리할 내비게이션 이벤트를 트리거할 수 있는 단계에 가까워졌습니다. 이 부분의 핵심은 이벤트를 트리거하고 관찰할 중앙 집중식 위치이며, 이 경우 NavigationManager라는 싱글톤 클래스가 될 것입니다. 이 클래스는 두 가지를 정의해야 합니다:

  • 외부 클래스가 이러한 이벤트를 관찰할 수 있도록 이전에 정의된 NavigationCommand 이벤트를 출력하는 데 사용되는 컴포넌트입니다.
  • 위 컴포넌트의 옵저버가 이러한 이벤트를 처리할 수 있도록 이러한 NavigationCommand 이벤트를 트리거하는 데 사용할 수 있는 함수.

이를 염두에 두고 다음과 같이 보이는 NavigationManager 클래스가 있습니다:

class NavigationManager {

    var commands = MutableStateFlow(Default)

    fun navigate(
        directions: NavigationCommand
    ) {
        commands.value = directions
    }

}

여기서 명령 참조는 외부 클래스에서 트리거된 NavigationCommands를 관찰하는 데 사용할 수 있으며, 탐색 함수는 제공된 NavigationCommand를 기반으로 내비게이션을 트리거하는 데 사용할 수 있습니다.

 

클래스는 싱글톤 인스턴스여야 한다는 점에 유의해야 합니다. 이렇게 하면 NavigationManager 통신하는 모든 클래스가 동일한 인스턴스를 참조하도록 있습니다. 애플리케이션의 경우, hilt 싱글톤 컴포넌트 내에 클래스가 정의되어 있습니다.

@Module
@InstallIn(SingletonComponent::class)
class AppModule {

    @Singleton
    @Provides
    fun providesNavigationManager() = NavigationManager()
}

이렇게 하면 내비게이션 그래프에서 처리할 내비게이션 이벤트를 관찰할 있습니다. 앞서 내비게이션 그래프를 정의하는 사용되는 NavHost 참조를 정의했는데, 여기에는 NavHostController 참조도 제공했습니다. NavHostController 다른 목적지 간의 내비게이션을 트리거하는 데에도 사용할 있으며, 이는 관찰된 NavigationCommand 이벤트가 발생할 때마다 수행할 있는 작업입니다. 여기서 하고 싶은 것은 NavigationManager 대한 참조를 삽입한 다음 포함된 명령을 사용하여 내비게이션 이벤트를 관찰하는 것입니다. 이러한 명령은 StateFlow 사용하기 때문에 Compose Runtime collectAsState() 확장 함수를 사용하여 Compose 상태의 형태로 발생하는 상태 흐름 이벤트를 수집할 있습니다. 그런 다음 상태의 값을 탐색할 방향에 사용하여 NavHostController 사용하여 트리거할 있습니다.

Triggering Navigation Events

이제 내비게이션 명령에 대한 관찰이 준비되었으므로 이를 트리거하겠습니다. 뷰모델에서 이 작업을 수행하여 컴포저블에서 내비게이션에 대한 책임을 제거할 것입니다.
여기서 할 일은 뷰모델에 내비게이션 매니저를 추가하고 생성자를 통해 이를 제공하는 것입니다. 이 참조는 싱글톤이므로 내비게이션 그래프가 호스팅되는 곳에서 활용되는 것과 동일한 인스턴스가 될 것입니다.
@HiltViewModel
class AuthenticationViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val authenticate: Authenticate,
    private val sharedPrefs: Preferences,
    private val navigationManager: NavigationManager
)

이제 뷰모델에서 직접 내비게이션 이벤트를 트리거할 수 있습니다. 컴포저블이 뷰모델을 수동으로 호출하여 내비게이션을 트리거하고 싶을 수도 있고, 어떤 연산 결과에 따라 내비게이션을 트리거하고 싶을 수도 있습니다. 어떤 경우든 내비게이션 함수를 트리거하고 내비게이션 명령에 사용할 NavigationDirections를 전달하면 됩니다.

navigationManager.navigate(NavigationDirections.Dashboard)

이제 이 내비게이션 로직이 뷰 모델에 있으므로 필요한 함수가 호출되었는지 확인하여 내비게이션 매니저의 모의 인스턴스를 쉽게 테스트할 수 있습니다.
verify(mockNavigationManager).navigate(NavigationDirections.Dashboard)

Wrapping Up

위의 내용을 적용하여 모듈식 앱용 Jetpack Compose에 대한 탐색을 구현할 수 있었습니다. 이러한 변경을 통해 탐색 로직을 중앙 집중화할 수 있었으며, 이 과정에서 여러 가지 이점을 확인할 수 있었습니다:

 

  • 컴포저블에 NavHostController를 전달할 필요가 없어졌으며, 이 참조는 내비게이션을 수행하는 데 사용됩니다. 이를 컴포저블 외부에 두면 기능 모듈이 내비게이션 작성 종속성에 종속될 필요가 없어지고 테스트 시 생성자도 간소화됩니다.
  • 뷰모델 지원을 컴포저블에 추가하여 내비게이션 컨트롤러를 통해 제공하고, 각 피처 모듈에 힐트 컴포즈 내비게이션 관련 종속성을 추가할 필요 없이 컴포저블 함수를 통해 뷰모델을 제공했습니다. 이를 통해 앞서 언급한 이점을 얻을 수 있을 뿐만 아니라 컴포저블의 테스트가 간소화되어 테스트 시 뷰모델과 그 중첩 클래스의 모의 인스턴스를 쉽게 제공할 수 있습니다.
  • 탐색 로직을 중앙 집중화하고 트리거할 수 있는 항목에 대한 컨트랙트를 만들었습니다. 위에서 언급한 이점 외에도 앱의 탐색을 더 쉽게 이해하고 디버그하는 데 도움이 됩니다. 앱을 처음 사용하는 사람이라면 누구나 내비게이션이나 앱이 지원하는 기능과 이러한 기능이 트리거되는 위치를 이해하는 데 있어 마찰이 줄어들 수 있습니다.
  • 위의 사항과 더불어, Compose 도입과 함께 마찰을 줄이는 데 도움이 되는 방식으로 내비게이션 작업을 할 수 있었습니다. 기존 앱에 컴포즈를 도입하면 개발자는 앱에 컴포즈 가능한 섹션을 추가할 가능성이 높습니다. 뷰를 대체하는 단일 컴포즈 가능 섹션이나 컴포즈 가능한 UI를 나타내는 전체 화면이 될 수 있습니다. 접근 방식에 관계없이 모듈식 탐색 접근 방식을 사용하면 작업을 단순하게 유지하고 책임을 최소화하는 데 도움이 될 수 있습니다.