diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5924cab8..7c708b61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ kotlin = "1.9.21" kodein = "7.20.2" koin = "3.4.3" koin-compose = "1.0.4" -hilt = "2.47" +hilt = "2.49" leakCanary = "2.9.1" appCompat = "1.6.1" lifecycle = "2.6.1" diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt index b5c2cf01..83f2559c 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt @@ -20,9 +20,7 @@ data class HiltDetailsScreen( // Uncomment version below if you want keep using ViewModel instead of to convert it to ScreenModel // ViewModelProvider.Factory is not required. Until now Hilt has no support to Assisted Injection by default - /*val viewModel: HiltDetailsViewModel = getViewModel( - viewModelProviderFactory = HiltDetailsViewModel.provideFactory(index) - )*/ + // val viewModel: HiltDetailsViewModel = getViewModel { factory -> factory.create(index) } // This version include more boilerplate because we are simulating support // to Assisted Injection using ScreenModel. See [HiltListScreen] for a simple version diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt index c7d08f8a..8546b01e 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt @@ -1,18 +1,18 @@ package cafe.adriel.voyager.sample.hiltIntegration import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel -// Until now, there is no support to assisted injection. -// Follow the thread here: https://github.com/google/dagger/issues/2287 -class HiltDetailsViewModel( - val index: Int +@HiltViewModel(assistedFactory = HiltDetailsViewModel.Factory::class) +class HiltDetailsViewModel @AssistedInject constructor( + @Assisted val index: Int ) : ViewModel() { - companion object { - fun provideFactory( - index: Int - ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T = HiltDetailsViewModel(index) as T - } + + @AssistedFactory + interface Factory { + fun create(index: Int): HiltDetailsViewModel } } diff --git a/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ViewModel.kt b/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ViewModel.kt index 90385fe7..77c2c704 100644 --- a/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ViewModel.kt +++ b/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ViewModel.kt @@ -9,8 +9,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.hilt.internal.componentActivity +import dagger.hilt.android.lifecycle.withCreationCallback /** * A function to provide a [dagger.hilt.android.lifecycle.HiltViewModel] managed by voyager ViewModelLifecycleOwner @@ -50,3 +52,51 @@ public inline fun Screen.getViewModel( provider[T::class.java] } } + +/** + * A function to provide a [dagger.hilt.android.lifecycle.HiltViewModel] managed by Voyager ViewModelLifecycleOwner + * instead of using Activity ViewModelLifecycleOwner. + * There is compatibility with Activity ViewModelLifecycleOwner too but it must be avoided because your ViewModels + * will be cleared when activity is totally destroyed only. + * + * @param viewModelProviderFactory A custom factory commonly used with Assisted Injection + * @param viewModelFactory A custom factory to assist with creation of ViewModels + * @return A new instance of [ViewModel] or the existent instance in the [ViewModelStore] + */ +@Composable +@ExperimentalVoyagerApi +public inline fun Screen.getViewModel( + viewModelProviderFactory: ViewModelProvider.Factory? = null, + noinline viewModelFactory: (F) -> VM +): VM { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + } + return remember(key1 = VM::class) { + val hasDefaultViewModelProviderFactory = requireNotNull(lifecycleOwner as? HasDefaultViewModelProviderFactory) { + "$lifecycleOwner is not a androidx.lifecycle.HasDefaultViewModelProviderFactory" + } + val viewModelStore = requireNotNull(viewModelStoreOwner?.viewModelStore) { + "$viewModelStoreOwner is null or have a null viewModelStore" + } + + val creationExtras = hasDefaultViewModelProviderFactory.defaultViewModelCreationExtras + .withCreationCallback(viewModelFactory) + + val factory = VoyagerHiltViewModelFactories.getVoyagerFactory( + activity = context.componentActivity, + delegateFactory = viewModelProviderFactory + ?: hasDefaultViewModelProviderFactory.defaultViewModelProviderFactory + ) + + val provider = ViewModelProvider( + store = viewModelStore, + factory = factory, + defaultCreationExtras = creationExtras + ) + + provider[VM::class.java] + } +}