Hi! I’m running into the following exception when executing `hiltNavGraphViewModel()` in a NavHost:...
w
Hi! I’m running into the following exception when executing
hiltNavGraphViewModel()
in a NavHost:
Expected an activity context for creating a HiltViewModelFactory for a NavBackStackEntry but instead found: android.app.ContextImpl@78d1695
The culprit is overriding the locale in the main activity to be able to switch languages on the fly. My main activity’s setContent looks like this:
Copy code
setContent {
    OverrideLocale(language.value, this) {
        Navigation()
    }
}
With
OverrideLocale
defined as following:
Copy code
@Composable
private fun OverrideLocale(language: String, mainActivity: AppCompatActivity, content: @Composable () -> Unit) {
    val configuration = LocalConfiguration.current
    configuration.setLocale(Locale(language))
    val context = LocalContext.current.createConfigurationContext(configuration)
    CompositionLocalProvider(
        LocalOnBackPressedDispatcherOwner provides mainActivity,
        LocalConfiguration provides configuration,
        LocalContext provides context
    ) {
        content()
    }
}
Looking at the exception’s stack trace the
HiltViewModelFactory
never seems to reach a point where it deems the context to be an activity:
Copy code
@JvmName("create")
public fun HiltViewModelFactory(
    context: Context,
    navBackStackEntry: NavBackStackEntry
): ViewModelProvider.Factory {
    val activity = context.let {
        var ctx = it
        while (ctx is ContextWrapper) {
            if (ctx is Activity) {
                return@let ctx
            }
            ctx = ctx.baseContext
        }
        throw IllegalStateException(
            "Expected an activity context for creating a HiltViewModelFactory for a " +
                "NavBackStackEntry but instead found: $ctx"
        )
    }
[...]
I.e.,
return@let ctx
above is never called.
Any thoughts on how the locale can be overridden correctly? Removing
OverrideLocale
makes the code run just fine.
f
Did you add
@AndroidEntryPoint
to the activity hosting the composables?
a
w
@FunkyMuse Yes, I’ve added
@AndroidEntryPoint
@Albert Chang From what I can tell my implementation is pretty much identical to the one in the linked thread. That’s where I got my solution from, after all :)
Alright, looks like I found a solution. Instead of providing the context created by
createConfigurationContext
directly, I use ContextWrapper to update the
getResources
call to use the next context but use the MainActivity for all other calls:
Copy code
class ContextWithUpdatedResources(
    private val resources: Context,
    base: Context
) : ContextWrapper(base) {
    override fun getResources(): Resources {
        return resources.resources
    }
}
My
OverrideLocale
is defined as following:
Copy code
@Composable
private fun OverrideLocale(language: String, mainActivity: MainActivity, content: @Composable () -> Unit) {
    val configuration = LocalConfiguration.current
    configuration.setLocale(Locale(language))
    val resources = LocalContext.current.createConfigurationContext(configuration)
    CompositionLocalProvider(
        LocalOnBackPressedDispatcherOwner provides mainActivity,
        LocalConfiguration provides configuration,
        LocalContext provides ContextWithUpdatedResources(resources, mainActivity)
    ) {
        content()
    }
}
i
Note that
LocalOnBackPressedDispatcher
also does the same context unwrapping, so overriding it isn't necessary
1
w
Thank you for the pointers! @Ian Lake @Albert Chang @FunkyMuse
201 Views