New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dagger/Hilt ViewModel Injection (with compose and navigation-compose) #2166
Comments
Run into the same issue, a quick workaround is to pass a UUID to |
What version of the AndroidX ViewModel extension are you using? The issue that caused the |
I'm using 1.0.0-alpha02. @AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
val viewModel = viewModel<HomeViewModel>(factory = defaultViewModelProviderFactory)
Button(onClick = {
navController.navigate("home2")
}) {
Text(text = "Click me!")
}
}
composable("home2") {
val viewModel = viewModel<HomeViewModel>(factory = defaultViewModelProviderFactory)
Text(text = "home2")
}
}
}
}
}
class HomeViewModel @ViewModelInject constructor(
val sharedPreferences: SharedPreferences
) : ViewModel() When you click the button and navigate to "home2", the app will crash |
I am facing the same issue but for me the app crashes immediatelly when navigating to the second screen. |
Thanks for the sample code! It looks like you are hitting the same issue I've described here: #2152 (comment) In essence using the activity or fragment as the Sadly there is no workaround for now since |
Here is a complete workaround using reflection @Composable
inline fun <reified VM : ViewModel> navViewModel(
key: String? = null,
factory: ViewModelProvider.Factory? = AmbientViewModelProviderFactory.current,
): VM {
val navController = AmbientNavController.current
val backStackEntry = navController.currentBackStackEntryAsState().value
return if (backStackEntry != null) {
// Hack for navigation viewModel
val application = AmbientApplication.current
val viewModelFactories = AmbientViewModelFactoriesMap.current
val delegate = SavedStateViewModelFactory(application, backStackEntry, null)
val hiltViewModelFactory = HiltViewModelFactory::class.java.declaredConstructors.first()
.newInstance(backStackEntry, null, delegate, viewModelFactories) as HiltViewModelFactory
viewModel(key, hiltViewModelFactory)
} else {
viewModel(key, factory)
}
}
@Composable
fun ProvideNavigationViewModelFactoryMap(factory: HiltViewModelFactory, content: @Composable () -> Unit) {
// Hack for navigation viewModel
val factories =
HiltViewModelFactory::class.java.getDeclaredField("mViewModelFactories").also { it.isAccessible = true }
.get(factory).let {
it as Map<String, ViewModelAssistedFactory<out ViewModel>>
}
Providers(
AmbientViewModelFactoriesMap provides factories
) {
content.invoke()
}
} usage: val AmbientApplication = staticAmbientOf<Application>()
@AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
Providers(
AmbientApplication provides application
) {
ProvideNavigationViewModelFactoryMap(factory = defaultViewModelProviderFactory as HiltViewModelFactory) {
NavHost(navController = navController, startDestination = "home") {
composable("home") {
val viewModel = navViewModel<HomeViewModel>()
Button(onClick = {
navController.navigate("home2")
}) {
Text(text = "Click me!")
}
}
composable("home2") {
val viewModel = navViewModel<HomeViewModel>()
Text(text = "home2")
}
}
}
}
}
}
}
class HomeViewModel @ViewModelInject constructor(
val sharedPreferences: SharedPreferences
) : ViewModel() |
@Tlaster - In your workaround, what are |
Add androidx.hilt:hilt-navigation and androidx.hilt:hilt-navigation-fragment containing APIs for using @HiltViewModel and Hilt's ViewModelFactory with AndroidX Navigation. The APIs will allow users to retrieve a HiltViewModelFactory whose view model owner and saved state owner is the NavBackStackEntry. Additionally it provide a kotlin extension, `hiltNavGraphViewModels` which mimics `navGraphViewModels`. These artifacts essentially tackle: * google/dagger#2152 * google/dagger#2166 Test: HiltNavGraphViewModelLazyTest Relnote: Provide APIs for using @HiltViewModel with Navigation. Change-Id: I00e675363f5af3922205a30f4670a4c33877a7b3
@danysantiago just saw you referenced the issue in a commit inside AndroidX for Hilt package. Do you have any ETA on this being released ? The last update for Also, for those using only Dagger2 and not Hilt. If we want to go full Compose (no Fragments and navigation-compose) do we have to switch to Hilt to make DI works ? |
@Guimareshh, we have a scheduled release of the You don't have to switch to Hilt to make DI work with Compose, but Hilt still makes some things easier, such as App, Activity and Service injections along with ViewModel injection. Note that the functions in |
Thanks for the detailed reply @danysantiago 👍 About your answer on Dagger/Hilt: I asked you this because I'm not comfortable with the design decisions of Hilt not being able to insert components in the middle of the hierarchy (explained here). I imagine that if I want to go with Dagger2 without Hilt, with a full Compose app (with |
@danysantiago Indeed as soon as a custom component is needed to inject our objects from, Hilt does not avoid the dagger-style boilerplate since we need entry points to inject the dependencies if I'm not mistaken |
@danysantiago Did you release said component? |
Hey - For Compose you can use
We didn't release a I'll close this for now since |
@mitchtabian, these screenshots are amazing at explaining the issue, thanks! However, this is not a Hilt + ViewModel issue and more of the way Navigation currently works. If the |
@mitchtabian See below for your boolean: val isOnBackstack = try {
navController.getBackStackEntry(destinationID)
true
} catch(e: IllegalArgumentException) {
false
} |
@danysantiago Thank you for your reply! @05nelsonm Yes of course I can get a boolean that tells me if it's already in the stack. What I meant was if we as developers had a simple boolean similar to |
Created a feature request in case anyone wanted to star/follow. https://issuetracker.google.com/issues/178796184 |
This is now fixed na? You told us in a video and we starred the issue haha |
Hello,
I am currently trying to build an app with only Compose (meaning no Fragments and navigation-compose, along with architecture components such as Hilt and ViewModel).
I tried using the viewModel function with the
defaultViewModelProviderFactory
of the Activity.I had to move this code inside a
NavHost
Composable. I reported this on the KotlinLang Slack and was told this issue relates to #2152 . It uses the incorrect Scope for a Navigation Composable.In the case of the related issue, the scope is too small and in my case, it is the exact opposite Problem.
Although the issue should be fixed by a more correct approach to scoping, it is still worth to file a bug for the inverse problem with saved state.
The text was updated successfully, but these errors were encountered: