KaustubhPatange / navigator

A small navigation library for Android to ease the use of fragment transactions & handling backstack (also available for Jetpack Compose).
Apache License 2.0
99 stars 5 forks source link

[Add] Support for Scoped ViewModels #20

Closed KaustubhPatange closed 2 years ago

KaustubhPatange commented 2 years ago

Proposed

Targeting: (#15)

Currently, if you create a ViewModel using viewModel() composable function, the ViewModel is tied to the activity's ViewModelStoreOwner. This is bad as those ViewModels will only be cleared when the activity's lifecycle is destroyed.

We want this behavior to replicate how fragments do it i.e if you consider a composable screen like a fragment then the ViewModel scoped to composable function should be cleared when the composable's composition is abanded/removed i.e when the fragment's lifecycle is destroyed. This way we won't keep any unused ViewModel instances in the memory.

To make this change, each destination Route will be associated with a LifecycleController class which will manage ViewModel instances scoped to that destination. This also saves state in a bundle from SavedStateHandle when activity's onSaveStateInstance(...) is called & restored when the destination is recreated from the restored backstack after process death.

Usage

class TestRoute : Route {
   @Parcelize data class First(...) : TestRoute()
   @Parcelize data class Second(...) : TestRoute()
}

class TestViewModel : ViewModel()

@Composable
fun Main() {
   ...
   navigator.Setup(...) { dest ->
      when(dest) {
         is TestRoute.First -> { 
            val viewModel = viewModel<TestViewModel>()  // <-- Scoped to TestRoute.First
            ...
         }
         is TestRoute.Second -> {
            val viewModel = viewModel<TestViewModel>()  // <-- Scoped to TestRoute.Second
            ...
         }
      }
   }
}

Support for dagger-hilt

The PR provides an additional artifact to support the creation of ViewModel which is annotated using @HiltViewModel. Through this one can create an instance of ViewModel using hiltViewModel() composable function.

@HiltViewModel
class TestViewModel @Inject constructor(savedStateHandle: SavedStateHandle) : ViewModel()

@Composable
fun Main() {
   ...
   val viewModel = hiltViewModel<TestViewModel>()
}

Additional Changes