Skip to content

Commit

Permalink
Add support for Hilt AssistedInject (#271)
Browse files Browse the repository at this point in the history
* Add support for Hilt AssistedInject

* Review changes

Co-authored-by: Natan Vieira <natanvnascimento@gmail.com>

* Fix lint erors

* Annotate function with`@ExperimentalVoyagerApi`

---------

Co-authored-by: Natan Vieira <natanvnascimento@gmail.com>
  • Loading branch information
ghostbear and DevNatan committed Dec 24, 2023
1 parent 044ea20 commit fdc4b99
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 15 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HiltDetailsViewModel, HiltDetailsViewModel.Factory> { 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <T : ViewModel> create(modelClass: Class<T>): T = HiltDetailsViewModel(index) as T
}

@AssistedFactory
interface Factory {
fun create(index: Int): HiltDetailsViewModel
}
}
50 changes: 50 additions & 0 deletions voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,3 +52,51 @@ public inline fun <reified T : ViewModel> 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 <reified VM : ViewModel, F> 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]
}
}

0 comments on commit fdc4b99

Please sign in to comment.