Closed glush closed 3 years ago
Got the exact same issue with:
implementation ("io.insert-koin:koin-android-ext:3.1.2")
implementation ("io.insert-koin:koin-androidx-compose:3.1.2")
implementation ("androidx.navigation:navigation-compose:2.4.0-alpha04")
Is there any update on this issue?
I've worked around this by following suit on how Hilt handles scoping ViewModels to a nav graph. They essentially us a NavBackstackEntry
as their ViewModelStoreOwner
.
So I created the following function:
@Composable
inline fun <reified VM : ViewModel> getNavGraphViewModel(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
backStackEntry: NavBackStackEntry
): VM {
return remember(qualifier,parameters) {
backStackEntry.getViewModel(qualifier, parameters)
}
}
And you would pass something like navController.getBackStackEntry("your-root-route-like-the-route-of-your-nav-host")
@Composable
fun HomeScreen(
navController: NavController,
vehiclesViewModel: VehiclesViewModel = getNavGraphViewModel(
backStackEntry = navController.getBackStackEntry("root")
)
) {
}
where this HomeScreen is a composable in your NavHost, and root
is the route of my NavHost
I run in the same issue!
These are My dependecies:
const val core = "io.insert-koin:koin-core:3.1.1"
const val android = "io.insert-koin:koin-android:3.1.1"
const val compose = "io.insert-koin:koin-androidx-compose:3.1.1"
const val navigation = "androidx.navigation:navigation-compose:2.4.0-alpha04"
I've released a library - cokoin, which supports Compose Navigation . It is an alternative to org.koin:koin-androidx-compose
.
implementation {
implementation "dev.burnoo:cokoin-jetpack-navigation:0.1.8"
}
NavHost
with KoinNavHost
:
KoinNavHost(navController, startDestination = "1") {
composable("1") {
Screen1()
}
}
getNavController
and getNavViewModel
inside composables.
@Composable
fun Screen1() {
val navController = getNavController()
Button(onClick = { navController.navigate("2") }) {
val navViewModel = getNavViewModel<MainViewModel>()
//...
}
}
Update solution from https://github.com/InsertKoinIO/koin/issues/1079#issuecomment-902215765
At koin 3.1.3
and above, there is no backStackEntry.getViewModel(qualifier, parameters)
, so the solution above doesn't work.
After looking into the source code of koin, I found that the getViewModel()
method from koin 3.1.3
use LocalViewModelStoreOwner
.
Therefore, we could use the CompositionLocal
to provide the NavBackStackEntry
as ViewModelStoreOwner
composable(Home) { navBackStackEntry ->
CompositionLocalProvider(LocalViewModelStoreOwner provides navBackStackEntry) {
HomeScreen(navController = navController, homeViewModel = getViewModel())
}
}
Last update bring owner
param to the getViewModel function:
inline fun <reified T : ViewModel> getViewModel(
qualifier: Qualifier? = null,
owner: ViewModelOwner = getComposeViewModelOwner(),
scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
noinline parameters: ParametersDefinition? = null,
)
Where ViewModelOwner can be used to pass StoreOwner:
@Composable
fun getComposeViewModelOwner(): ViewModelOwner {
return ViewModelOwner.from(
LocalViewModelStoreOwner.current!!,
LocalSavedStateRegistryOwner.current
)
}
Documentation need to be updated also.
Same problem with 3.2.0-beta-1
there some fixes about VM API that are coming in 3.1.6. It should realign ViewModel API on Google ones
it will be ported to 3.2.x
I'm a bit confused since there are multiple solutions posted here. With Koin 3.2.0 what is the current solution to get a ViewModel
instance that is bound to the current NavBackStackEntry
when using Navigation Compose? Does it work out of the box?
By the way, the documentation here is outdated for Koin 3.2.0 as it still mentions ViewModelOwner
in getViewModel()
. This has been replaced with ViewModelStoreOwner
.
It seems this works out of the box with Koin 3.2.0 since LocalViewModelStoreOwner
inside the composable function of Navigation Compose provides the current NavBackStackEntry
.
I still have this problem and don't know how to solve it.
what version have you @danieldaeschle ?
Latest koin-androidx-compose 3.4.2
Latest koin-androidx-compose 3.4.2
I'm also experiencing this issue when trying to pass a parameter to a ViewModel.
do you use koinViewModel
function?
Add "io.insert-koin:koin-androidx-compose-navigation:3.4.5" and use koinNavViewModel() function. It works!
Ran into same problem, using koin-androidx-compose:3.4.6 and compose-destinations:core:1.9.42-beta for navigation, koinNavViewModel() doesn't change anything, everything just returns new instances of my ViewModel
@kepper104 can you provide a new sample project?
~I'm seeing the same behaviour a ViewModel is created each time. Even after switching to use koinNavViewModel
from koin-androidx-compose-navigation v3.4.6~
Update, turns out my issue was probably not a Koin issue. When navigating with the NavHostController I wasn't setting the NavOptions with launchSingleTop = true
so new instances of the Screen and ViewModel were being created which makes sense. This is noted in the official docs but was missed when the functionality was added to project. Once I added the below the ViewModel was not recreated each time I navigated to the screen.
navHostController.navigate(route = navigationState.route.name) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navHostController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
I want to use share same viewmodel instance in koin in jetpack compose navigation. I know there is a function in koinViewModel() to get instance of viewModel. I recently saw a Koin Documentation have separate koin-androidx-compose-navigation which gives a koinNavViewModel() function.
build.gradle.kts
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("io.insert-koin:koin-android:3.4.0")
implementation("io.insert-koin:koin-androidx-workmanager:3.4.0")
implementation("io.insert-koin:koin-androidx-compose:3.4.6")
implementation("io.insert-koin:koin-androidx-compose-navigation:3.4.6")
implementation(platform("androidx.compose:compose-bom:2023.06.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.foundation:foundation-layout")
implementation("androidx.compose.material:material")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.runtime:runtime")
implementation("androidx.compose.runtime:runtime-livedata")
implementation("androidx.compose.ui:ui-tooling")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:$2.6.2")
implementation("androidx.navigation:navigation-compose:$2.6.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
} Now I am trying to make a use of One viewmodel to different screens. When I get some data in viewmodel, I stored in SharedFlow and navigate to another screen with same viewmodel instance it gives me variable null.
MainActivity.kt
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { SimpleComposeNavigationTheme { SimpleNavigation() } } } } SimpleNavigation
@Composable fun SimpleNavigation(navController: NavHostController = rememberNavController()) {
NavHost(
navController = navController,
startDestination = navController.currentBackStackEntry?.destination?.route ?: "first_screen"
) {
composable("first_screen") {
val viewModel: FirstViewModel = koinNavViewModel()
Surface {
Column(Modifier.fillMaxSize()) {
Button(onClick = { viewModel.updateName("Hello world") }) {
Text(text = "Add Name")
}
Button(onClick = {
navController.navigate("second_screen") {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}) {
Text(text = "Next Screen")
}
}
}
}
composable("second_screen") {
val viewModel: FirstViewModel = koinNavViewModel()
val firstName by viewModel.firstName.collectAsState()
LaunchedEffect(firstName){
println(">> $firstName")
}
Surface {
Column(Modifier.fillMaxSize()) {
firstName?.let { name -> Text(text = name) }
}
}
}
}
} FirstViewModel.kt
class FirstViewModel : ViewModel() { private val _firstName = MutableStateFlow<String?>(null) val firstName: StateFlow<String?> = _firstName.asStateFlow()
fun updateName(name: String) {
_firstName.value = name
}
}
@vivek-modi Did you found a solution to this?
I'd try to use Koin 3.0.1 with new Jetpack Compose Navigation component. I'd pass ViewModel instance to Composable function using "composabe way" -
@Composable fun ComposableFun(viewmodel: MyViewModel = getViewModel()) {...}
If I'm not using Jetpack Compose Navigation Koin works as expected - getViewModel() returns existing ViewModel instance.
When app navigation use Jetpack Compose Navigation component getViewModel() creates new ViewModel instance every time at composable function enter. Same behavior if
val vm : MainViewModel by viewModel()
used inside composable fun.
To Reproduce Here is repo. https://gitlab.com/gLush/koinwithnavigation Initial commit - working sample w/o Navigation component. Last commit - Navigation Component implementation with problem described. You can see how random viewModel stamp changes on screen change. Logcat also shows new VM instance creation.
Expected behavior viewModel: ViewModelType = getViewModel() should return existing ViewModel instance.
Koin project used and used version (please complete the following information): implementation ("io.insert-koin:koin-android-ext:3.0.1") implementation ("io.insert-koin:koin-androidx-compose:3.0.1")
Additional moduleDefinition Add any other moduleDefinition about the problem here. implementation ("androidx.navigation:navigation-compose:1.0.0-alpha10")