InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
9.08k stars 718 forks source link

Get scoped viewmodel from child fragment #665

Closed TooLazyy closed 3 years ago

TooLazyy commented 4 years ago

Let's say I have a module for my fragment.

    scope(named<MyFragment>()) {
        viewModel {
            MyFragmentVM(
                get(),
                get(),
                get(),
                get()
            )
        }
    }

From MyFragmentVM I open a dialog fragment, where I pass MyFragment's scopeId. From my dialog on some action I would like to call my MyFragmentVM's method. Is there any way to get the MyFragmentVM? I can see some methods, but they require ViewModelParameters and so on. I need smth that does not require anything else but scope/scopeId.

HarryMMR commented 4 years ago

The recommendation is your host activity will have a view model. Then fragments can use the sharedViewModel. Koin already does that for you. Don't create your own scope. https://doc.insert-koin.io/#/koin-android/viewmodel?id=shared-viewmodel

`class WeatherActivity : AppCompatActivity() {

/*
 * Declare WeatherViewModel with Koin and allow constructor dependency injection
 */
private val weatherViewModel by viewModel<WeatherViewModel>()

}

class WeatherHeaderFragment : Fragment() {

/*
 * Declare shared WeatherViewModel with WeatherActivity
 */
private val weatherViewModel by sharedViewModel<WeatherViewModel>()

}

class WeatherListFragment : Fragment() {

/*
 * Declare shared WeatherViewModel with WeatherActivity
 */
private val weatherViewModel by sharedViewModel<WeatherViewModel>()

}

`

TooLazyy commented 4 years ago

Yeah, I know. But a shared viewmodel is tied to an activity's lifecycle, so it won't be destroyed with fragment. Plus in my case I have some fragment dependent logic inside its viewmodel, that wouldn't be nice to separate it and put just 1 call to a shared view model. In Dagger2 I can get a parent Component and inject fragment viewmodel inside my dialog, so now I try Koin and wanna find the way to do the same.

jeziellago commented 4 years ago

You need only declare sharedViewModel inside fragments.

Don't create your own scope.

class MyFragment : Fragment() {

    private val myViewModel by sharedViewModel<MyViewModel>()
}

The extension sharedViewModel call getSharedViewModel that uses requireActivity() internally and already "bind" the scope to activity.


inline fun <reified T : ViewModel> Fragment.sharedViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> =
    lazy { getSharedViewModel<T>(qualifier, parameters) }

fun <T : ViewModel> Fragment.getSharedViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return requireActivity().getViewModel( // Here
        clazz,
        qualifier,
        parameters
    )
}

Therefore, you don't need to declare the viewModel within the activity if you do not use it .

TooLazyy commented 4 years ago

You need only declare sharedViewModel inside fragments.

Don't create your own scope.

class MyFragment : Fragment() {

    private val myViewModel by sharedViewModel<MyViewModel>()
}

The extension sharedViewModel call getSharedViewModel that uses requireActivity() internally and already "bind" the scope to activity.

inline fun <reified T : ViewModel> Fragment.sharedViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> =
    lazy { getSharedViewModel<T>(qualifier, parameters) }

fun <T : ViewModel> Fragment.getSharedViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return requireActivity().getViewModel( // Here
        clazz,
        qualifier,
        parameters
    )
}

Therefore, you don't need to declare the viewModel within the activity if you do not use it .

will that viewmodel be destroyed with fragment? I mean if I leave the fragment with declared sharedviewmodel will it still be alive?

jeziellago commented 4 years ago

You need only declare sharedViewModel inside fragments.

Don't create your own scope.

class MyFragment : Fragment() {

    private val myViewModel by sharedViewModel<MyViewModel>()
}

The extension sharedViewModel call getSharedViewModel that uses requireActivity() internally and already "bind" the scope to activity.

inline fun <reified T : ViewModel> Fragment.sharedViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> =
    lazy { getSharedViewModel<T>(qualifier, parameters) }

fun <T : ViewModel> Fragment.getSharedViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return requireActivity().getViewModel( // Here
        clazz,
        qualifier,
        parameters
    )
}

Therefore, you don't need to declare the viewModel within the activity if you do not use it .

will that viewmodel be destroyed with fragment? I mean if I leave the fragment with declared sharedviewmodel will it still be alive?

You need only declare sharedViewModel inside fragments.

Don't create your own scope.

class MyFragment : Fragment() {

    private val myViewModel by sharedViewModel<MyViewModel>()
}

The extension sharedViewModel call getSharedViewModel that uses requireActivity() internally and already "bind" the scope to activity.

inline fun <reified T : ViewModel> Fragment.sharedViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> =
    lazy { getSharedViewModel<T>(qualifier, parameters) }

fun <T : ViewModel> Fragment.getSharedViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return requireActivity().getViewModel( // Here
        clazz,
        qualifier,
        parameters
    )
}

Therefore, you don't need to declare the viewModel within the activity if you do not use it .

will that viewmodel be destroyed with fragment? I mean if I leave the fragment with declared sharedviewmodel will it still be alive?

The lifecycleOwner in this case is the Activity. The viewModel will be destroyed with the Activity and not with fragment.

TooLazyy commented 4 years ago

So here we're back again. As I told before - I don't need a sharedviewmodel for the case described above. I need to get my fragment's NOT shared view model from a dialog. In case of shared vm it will outlive fragment, what's not good in my case. So as I can see there's no way to get NOT shared vm from a child fragment event if you have the scopeId.

jeziellago commented 4 years ago

So here we're back again. As I told before - I don't need a sharedviewmodel for the case described above. I need to get my fragment's NOT shared view model from a dialog. In case of shared vm it will outlive fragment, what's not good in my case. So as I can see there's no way to get NOT shared vm from a child fragment event if you have the scopeId.

For your case, I think you could use [getParentFragment](https://developer.android.com/reference/androidx/fragment/app/Fragment.html#getParentFragment()) from DialogFragment as a LifecycleOwner and get viewModel from that. Maybe this...

// on dialog
val myViewModel: MyViewModel = getParentFragment().getViewModel(...) // using named here

What do you think?

TooLazyy commented 4 years ago

yeah, guess that's the only way

krasavello13 commented 4 years ago

You can try to use this solution:

private val viewModel by lazy { 
     requireParentFragment().getViewModel<MyViewModel>()
} 

Or if you are using Navigation component from Google:

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))
}

For correct work you have to put your fragments to the nested navigation

Kharlos commented 2 years ago

Yo can use this too

scope(named()) { viewModel { YourFragmentClassViewModel(get()) } scoped { UseCaseExample(get()) } }

and in your YourFragmentClass just add extended with AndroidScopeComponent and create

override val scope : Scope by fragmentScope()

and that's all

YoussefSeddik commented 1 year ago

Can you share please the code of how to get the viewmodel in the base and child class and why you are using named() here