I'm using Android Navigation Component, in a Single-Activity App, there is a ViewModel that I want to be re-created and it's shared between some DialogFragments that compose this specific flow. I want to know if there is a way to have a sharedViewModel tied to this FragmentScope
I would like to use a Scoped sharedViewModel, I think that this will improve the library capacities
As an alternative, I would like to know if there is a way to clear a viewModel instance when the fragment is destroyed
koin-android
Yes, you can set the scope like this:
sharedViewModel<ViewModel>(from = {//your scope})
In my case I have ViewPager, and i use the same viewModel for internal Fragments like:
val viewModel by sharedViewModel<MyViewModel>(from = { parentFragment!! })
I'm trying to pass a scope that I've created in the sharedViewModel(from = {}) but it expects a ViewModelStoreOwner.
The way that you uses it, doesn't work for me, once the parentFragment of my DialogFragments is the NavHostFragment from the Navigation
So what I need is to have a way to get a sharedViewModel for a specific scope.
Perhaps more check around a dedicated scope, not something tied to a Fragment nor an activity. You can create a scope instance by hand, and retrieve it elsewhere by its id.
About that, I'm trying to define the scope in my module, like this:
scope(named("fragment_scope")) {
scoped { factory { MyScopedClass() } }
viewModel { MyScopedViewModel(get<MyScopedClass>(), get<NotScopedClass>()) }
}
And retrieve it like this:
private val fragmentScope = getKoin().getOrCreateScope("fragment_scope_id", named("fragment_scope"))
private val viewModel: MyScopedViewModel by fragmentScope.viewModel(parentFragment!!)
But it's not working.
More details?
I'm getting an exception when I try to retrieve the viewModel instance from this scope:
private val viewModel: MyScopedViewModel by fragmentScope.viewModel(parentFragment!!)
With this I'm getting this exception:
Unable to instantiate fragment ScopedFragment: calling Fragment constructor caused an exception
The parentFragment is the NavHostFragment for Android Navigation
I'll edit the previous message to add this line.
[EDIT] I Figured out the exception. Looks like I can't pass the parentFragment to the fragmentScope.viewModel() function. Passing this solve the problem. Now I want to have access to the same instance of this created viewModel in my DialogFragment.
Before changing to Android Navigation, it was an Activity that instantiates this viewModel and all the other Fragments that belongs to this flow uses a shared instance of this viewModel, now that I've changed to Android Navigation and my only activity is the Main activity, I want to start a fresh instance of the viewModel for every time that I launches a specific Fragment.
After some digging I found that using a Scope will help me with this problem, and in fact helped, I'm, able to create a new instance of the viewModel every time I launch this Fragment, now I want to know how to retrieve this instance in the other Fragments.
@arnaudgiuliani Any idea?
Can you pass again your complete code, your case is a bit complex :)
Send even a gists
Store a weak reference to the fragment in the activity in the onCreated function.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as? MainActivity)?.myFragment= WeakReference(this)
}
use it to create the sharedViewModel in other fragments
val myFrag= (activity as? MainActivity)?.myFragment?.get() ?: return
viewModel = sharedViewModel<MyViewModel>(from = {myFrag}) { parametersOf(abc) }.value
As long as the fragment is in the back stack I believe its pretty much guaranteed to be alive.
The alternative is to have a regular unscoped (Activity) sharedViewModel then call a reset() and init(my constructor params) function on the model before navigating to the desired fragment
--- Edit
Now that I'm thinking about it, I don't even see what's stopping you from just putting a weak reference of the sharedViewModel on the activity instead.
In the end, sharing data between fragments is just a huge pain on android. And ultimately, dumb.
Can you pass again your complete code, your case is a bit complex :)
Send even a gists
We found out a way to overcome this issue, with NavigationComponent we have access to the ViewModelStoreOwner for a graph, so we created a new graph for this flow in order to get the right ViewModel with:
by sharedViewModel(from = { findNavController().getViewModelStoreOwner(R.id.new_graph)})
I think I'm facing a kinda similar problem as @arctouch-carlosottoboni
I have Fragment A and B. And Fragment A is using ViewModel A and Fragment B is using ViewModel B. (Note: I'll be navigating from Fragment A to B)
So in my 'di' it structured kinda like this.
val module = module {
scope(named("some_name")) {
viewModel { A() }
viewModel { B() }
}
}
I get the viewModel using:
getKoin().createScope("some_unique_id", named("some_name"))
This is so far so good. But here comes the problem.
The moment I want to access the ViewModel A on my Fragment B, I write this code on my Fragment B.
val scope = getKoin().getScope("some_unique_id") // no problem, I get the same scope I get on Fragment A
val expectingSameViewModelA = scope.viewModel(this)
// problem: here I get a new instance of ViewModel A
Note: I'm inside the same activity and navigating Fragment A -> Fragment B
I did fix the problem with a trick, but I think this could be better. I changed my 'di' like this:
Previously:
val module = module {
scope(named("some_name")) {
viewModel { A() }
viewModel { B() }
}
}
Now:
val module = module {
scope(named("some_name")) {
scoped { A() }
viewModel { B() }
}
}
I've replaced the 'viewModel' with 'scoped'. And I can get the ViewModel with the same code previously mentioned.
But now I'm missing out things like: viewModelScope.launch() is not working when I switch tabs on my application (If I replace scope with viewModel I know I won't be facing this issue).
It would be great if @arnaudgiuliani can help me tackle this issue.
Yes, you can set the scope like this:
sharedViewModel<ViewModel>(from = {//your scope})In my case I have ViewPager, and i use the same viewModel for internal Fragments like:
val viewModel by sharedViewModel<MyViewModel>(from = { parentFragment!! })
Does this still work? I have the exact same usecase.
I use Android Navigation Components (so single activity, I don't want to use the Activity as scope becuase the activity is never destroyed)
I have a fragment with a viewpager with fragments. I want the viewpager fragmetns to use the same viewmodel isntance as the parent fragment.
Struggling to get this right.. Any help would be appreciated.
Having a custom scope for this seems overcomplicated? I would just like to use the parent fragment as the scope if possible.
Hi, currently I'm using: Koin 2.1.0-beta-1
and this version of library have more elegant way to do it.
So, try to do like this :
private val viewModel by lazy {
requireParentFragment().getViewModel<MyViewModel>()
}
To more deep understanding how it works, please check the package org.koin.androidx.viewmodel.ext.android of Koin library
@krasavello13 Thank you for the reply.
Unfortunately I get the following error when trying that:
java.lang.ClassCastException: androidx.fragment.app.FragmentViewLifecycleOwner cannot be cast to android.content.ComponentCallbacks
Any ideas what could cause this?
EDIT: Nevermind, I took the code from your original comment from the email notification I got. Your updated code works! Thank you!!
Oh, excellent
Have a nice coding
Will it works with Navigation Library?
I guess it depends on your own use case, but I am using it with the
Navigation library without issue
On Tue, Feb 25, 2020, 1:47 PM igorka48 notifications@github.com wrote:
Will it works with Navigation Library?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/InsertKoinIO/koin/issues/593?email_source=notifications&email_token=AGEYEHHRJZNQ6LXI2AIRQW3REUAOHA5CNFSM4I5IWM6KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEM3VDEQ#issuecomment-590827922,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AGEYEHEWF5K7Y3H6QQ27H3LREUAOHANCNFSM4I5IWM6A
.
Will it works with Navigation Library?
No, its will work only if you have parent and child fragments
For correct work with navigation features (as nested navigation graph)use this extension:
inline fun <reified VM : ViewModel> Fragment.sharedGraphViewModel(
@IdRes navGraphId: Int,
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
) = lazy {
val store = findNavController().getViewModelStoreOwner(navGraphId).viewModelStore
getKoin().getViewModel(ViewModelParameter(VM::class, qualifier, parameters, null, store, null))
}
thnx
thnx
No problem, if you will have some question, please let me know
Have a nice coding
Store a weak reference to the fragment in the activity in the onCreated function.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (activity as? MainActivity)?.myFragment= WeakReference(this) }use it to create the sharedViewModel in other fragments
val myFrag= (activity as? MainActivity)?.myFragment?.get() ?: return viewModel = sharedViewModel<MyViewModel>(from = {myFrag}) { parametersOf(abc) }.valueAs long as the fragment is in the back stack I believe its pretty much guaranteed to be alive.
The alternative is to have a regular unscoped (Activity) sharedViewModel then call a reset() and init(my constructor params) function on the model before navigating to the desired fragment
--- Edit
Now that I'm thinking about it, I don't even see what's stopping you from just putting a weak reference of the sharedViewModel on the activity instead.In the end, sharing data between fragments is just a huge pain on android. And ultimately, dumb.
looks awful
The feature request https://github.com/InsertKoinIO/koin/issues/442 would achieve the desired outcome
I have 3 fragments in an activity in the order A -> B -> C and I need to use a particular viewmodel shared between two fragments B and C. If I use shared view model in the activity even when I go back to the first fragment (A) the viewmodel instance is retained. How do I tackle this? Can I use scope for this and if so, how? All the fragments are created from the activity, so requireParentFragment won't work.
@aldrinjoemathew use findFragmentByTag(FragmentATag)
@Wokrey findFragmentByTag on what? parentFragmentManager?
@Wokrey findFragmentByTag on what? parentFragmentManager?
@NahroTo First time I'm hearing about parentFragmentManager). I guess just fragmentManager is enough
Aren't these solutions too complicated? Can't we just use
getKoin().getScope(scopeId).getSource<ParentFragment>().getViewModel<ParentFragmentViewModel>() ?
The only problem is to share the scopeId of the ParentFragment or specify custom scopeId when creating a scope (I prefer this one). Maybe we should move in this direction? In this way, we don't even need navGraphViewModels() anymore, because instead of graphs we have scopes.
UPD:
I've just created the required functions.
How it looks:
In ParentFragment:
private val sharedViewModel: ParentFragmentViewModel by lifecycleScope(SCOPE_ID).viewModel(this)
In ChildFragment:
private val sharedViewMode: ParentFragmentViewModel by sharedViewModel(SCOPE_ID)
How it works:
inline fun <reified T : ViewModel> Fragment.sharedViewModel(scopeId: String): Lazy<T> =
getKoin().getScope(scopeId).getSource<Fragment>().viewModel()
fun LifecycleOwner.lifecycleScope(scopeId: String): Scope = getOrCreateAndroidScope(scopeId)
private fun LifecycleOwner.getOrCreateAndroidScope(scopeId: String): Scope {
return getKoin().getScopeOrNull(scopeId) ?: createAndBindAndroidScope(scopeId, getScopeName())
}
Most helpful comment
Hi, currently I'm using: Koin 2.1.0-beta-1
and this version of library have more elegant way to do it.
So, try to do like this :
To more deep understanding how it works, please check the package org.koin.androidx.viewmodel.ext.android of Koin library