Clearing sharedViewModel - android

I am using Koin for injecting viewModel into fragment. My app is single activity. I need that sharedViewModel only in servisFragment and partFragment.
I would like to clear that viewModel from Activity after navigation marked with red.
How can I do that?
Code for injecting viewModel
private val servisViewModel by sharedViewModel<ServisViewModel>()
Koin sharedViewModel
inline fun <reified T : ViewModel> Fragment.sharedViewModel(
name: String? = null,
noinline from: ViewModelStoreOwnerDefinition = { activity as
ViewModelStoreOwner },
noinline parameters: ParametersDefinition? = null
) = lazy { getSharedViewModel<T>(name, from, parameters) }
Thank you for any help.

if you need to clear all viewModels from that Fragment try this in your Fragment
viewModelStore.clear()
if you need to clear concrete ViewModel try this
getViewModelStore(ViewModelParameters(...)).clear()

If you are using koin to inject, in the onDestoy of the fragment you should use
requireActivity().viewModelStore.clear()
because viewModelStore directly from fragment will return none to clear
But the problem with this is that it will clear ALL the view model scoped within this ViewModelStore. So you won't have control of which ViewModel to clear.

Related

How to pass arguments to ViewModel from Fragment and inject dependencies at the same time?

I need to open a Compose component with its own ViewModel and pass arguments to it, but at the same time I inject dependencies to this ViewModel. How can I achieve this? Can I combine ViewModel factory and Dependency Injection (Hilt)?
Yes. you can..
Have your component be like this:
#Composable
fun MyScreen(
viewModel: MyViewModel = hiltViewModel()
) {
...
}
and in your viewModel:
#HiltViewModel
class MyViewModel #Inject constructor(
private val repository: MyRepository,
... //If you have any other dependencies, add them here
): ViewModel() {
...
}
When you pass arguments to the ViewModel, make sure that Hilt knows where to get that dependency. If you follow the MVVM architecture, then the ViewModel should handle all the data and the composable all the ui related components. So usually, you only need the ViewModel injection into the composable and all the other data injected dependencies into the ViewModel.
The composable should only care about the data that it gets from the ViewModel. Where the ViewModel gets that data and the operations it does on that data, it does not care.
Lemme know if this is what you meant..
Check out the official website for more:
Hilt-Android
Yes, you can. This is called "Assisted Inject" and it has it's own solutions in Hilt, Dagger(since version 2.31) and other libraries like AutoFactory or square/AssistedInject.
In this article, you can find an example of providing AssistedInject in ViewModel for Composable with Hilt Entry points.
Here is some code from article in case if article would be deleted:
In the main Activity, we’ll need to declare EntryPoint interface which will provide Factory for creating ViewModel:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#EntryPoint
#InstallIn(ActivityComponent::class)
interface ViewModelFactoryProvider {
fun noteDetailViewModelFactory(): NoteDetailViewModel.Factory
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NotyTheme {
NotyNavigation()
}
}
}
}
We get Factory from Activity and instantiating our ViewModel with that Factory and assisted some field:
#Composable
fun noteDetailViewModel(noteId: String): NoteDetailViewModel {
val factory = EntryPointAccessors.fromActivity(
LocalContext.current as Activity,
MainActivity.ViewModelFactoryProvider::class.java
).noteDetailViewModelFactory()
return viewModel(factory = NoteDetailViewModel.provideFactory(factory, noteId))
}
Now just go to your navigation components and use this method to provide ViewModel to your Composable screen as following:
NavHost(navController, startDestination = Screen.Notes.route, route = NOTY_NAV_HOST_ROUTE) {
composable(
Screen.NotesDetail.route,
arguments = listOf(navArgument(Screen.NotesDetail.ARG_NOTE_ID) { type = NavType.StringType })
) {
val noteId = it.arguments?.getString(Screen.NotesDetail.ARG_NOTE_ID)!!
NoteDetailsScreen(navController, noteDetailViewModel(noteId))
}
}

Dagger-Android: ViewModel not destroyed when fragment destroyed

in my project I am using Dagger2 to inject ViewModels into fragments.
override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})
To briefly explain my situation, I have a fragment that uses fragment state adapter which contains two fragments. For convinience, I'll call the parent fragment fragment A and child fragments in fragment state adapter fragment B and fragment C.
Typically, when testing the app user spends time in fragment B that contains a recyclerview. When user taps one of the items, it leads to a different fragment with some detailed information. When user enters that detail fragment, fragment B holding that item goes through onPause() and onStop(). At the same time, onStop() is called in fragment C.
The point is, if user spends enough time in fragment B (contained by fragment A), fragment C is destroyed and this is not by surprise because I know that is intended by fragment state adapter. It is supposed to get rid of some fragments when not visible.
My problem is that when fragment C gets destroyed, viewmodel associated with it does not get destroyed. This is bad because now when user goes to fragment C, which still has reference to old viewmodel, app doesn't supply any data to the fragment because when onDestroy() is called, viewmodel of fragment C is cleared and thus viewmodelscope.launch is not working.
I also thought of not using viewmodelscope (use coroutinescope instead) but that is not the issue. What I am curious and eager to know is why viewmodel of fragment C, scoped to lifecycle of fragment C is not destroyed.(I want to get rid of old viewmodel at the demise of fragment C and get new viewmodel instance)
Please understand my clumsy wording and my lack of knowledge that might give out some confusion. I am new to dagger. Please see my code below for better understanding.
AppComponent.kt
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
RepositoryModule::class,
DataSourceModule::class,
ServiceModule::class,
DaoModule::class,
ViewModelModule::class,
]
)
ViewModelModule.kt
#MapKey
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class ViewModelKey(val value: KClass<out ViewModel>)
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(AllStockListTabViewModel::class)
abstract fun bindAllStockListTabViewModel(allStockListTabViewModel: AllStockListTabViewModel): ViewModel
}
ViewModelFactory
#Singleton
class ViewModelFactory #Inject constructor(
private val viewModelMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return viewModelMap[modelClass]?.get() as T
}
}
Fragment
class AllStockListTabFragment #Inject constructor() :
ViewModelFragment<FragmentAllStockListBinding>(R.layout.fragment_all_stock_list) {
#Inject
lateinit var viewModelFactory: ViewModelFactory
override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})
}
Adapter
tradingTabAdapter = TradingTabAdapter(
this.childFragmentManager,
this.lifecycle,
tradingStateTabFragment,
allStockListTabFragment
)
class TradingTabAdapter #Inject constructor(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
private val tradingStateTabFragment: TradingStateTabFragment,
private val allStockListTabFragment: AllStockListTabFragment
) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun createFragment(position: Int): Fragment =
when (position) {
0 -> tradingStateTabFragment
else -> allStockListTabFragment
}
override fun getItemCount(): Int = 2
}
SubComponent
#FragmentScope
#Subcomponent(
modules = [
TradingTabBindingModule::class,
TradingTabModule::class,
EventModule::class,
UseCaseModule::class
]
)
AdapterModule
#Module
class TradingTabModule {
#Provides
fun provideTradingTabAdapter(
fragment: TradingTabFragment,
allStockListTabFragment: AllStockListTabFragment,
tradingStateTabFragment: TradingStateTabFragment
) = TradingTabAdapter(
fragment.childFragmentManager,
fragment.lifecycle,
tradingStateTabFragment,
allStockListTabFragment
)
I found that create method of ViewModelFactory is not called when fragment C is destroyed and created again. I think this is because I am using lazy initialization of viewmodel and that is how ViewModelLazy works. It caches viewmodel and invokes factory's create method only when cache is null. I guess what's happening is old viewmodel of fragment C is still referencing the dead viewmodel(which survived viewModelStore.onclear). I put a log statement in the init block of viewmodel of fragment C and I can see that it is called only for the very frist time fragment C is created and never called again even when fragment C is destroyed and created again.
Thank you so much for your patience reading all this haha. So I need help from any expereienced Android gurus who might be able to give some insight.
My goal: make viewmodel destroyed and recreated with the lifecycle of fragment. I want to avoid memory leak due to unused zombie viewmodels.
Current situation: viewmodel never gets destroyed and reborn fragment still references old viewmodel and thus lazy initialisation keeps the cache of old viewmodel, not triggering create method of ViewModelFactory.
--Edit--
version of dagger im using
"com.google.dagger:dagger-android:2.37"
Since your ViewModel is tied to your Activity, it is not getting destroyed when Fragment is destroyed.
#ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): ViewModel
You can check this answer which explains How to use ViewModel with Fragment.
How to use ViewModel in a fragment?

Why do we need ViewModelProvider.Factory to pass view model to a screen?

I am new to Android development. Currently, I am using Jetpack Compose to build Android apps. I am also learning with MVVM architecture.
One thing I don't understand with this architecture is why we need to use ViewModelProvider.Factory to pass view model to a screen.
For example,
Instead of this,
#Composable
fun HomeScreen() {
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val repository = InMemoryPlantService()
#Suppress("UNCHECKED_CAST")
return HomeViewModel(
plantRepository = repository
) as T
}
}
val homeViewModel: HomeViewModel = viewModel(null, factory)
val currentState: State<HomeViewState> = homeViewModel.viewState.collectAsState()
HomeScreenScaffold(currentState.value)
}
Can't we do this,
#Composable
fun HomeScreen() {
val repository = InMemoryPlantService()
val homeViewModel: HomeViewModel = HomeViewModel(
plantRepository = repository
)
val currentState: State<HomeViewState> = homeViewModel.viewState.collectAsState()
HomeScreenScaffold(currentState.value)
}
Please help.
Full source code can be found here: https://github.com/adammc331/bloom
HomeScreen can be found here: https://github.com/AdamMc331/Bloom/blob/development/app/src/main/java/com/adammcneilly/bloom/HomeScreen.kt
When you call:
val homeViewModel: HomeViewModel = viewModel(null, factory)
The function viewModel(...) will create a new HomeViewModel if it's the first time you request the ViewModel, or it will return the previous instance of HomeViewModel if it already exists. That's one of the advantages of using ViewModels, because on configuration change (or on recomposition) your ViewModel should be reused, not created again. And the way it works is by using a ViewModelProvider.Factory to create the ViewModel when it's necessary. Your ViewModel has a parameter on its constructor, there's no way the default Android classes would know how to create your ViewModel and pass that parameter (i.e. the repository) without you providing a custom ViewModelProvider.Factory. If your ViewModel doesn't have any parameters, the default ViewModelProvider.Factory uses reflection to create your class by using the no-argument constructor.
If you do this:
val homeViewModel: HomeViewModel = HomeViewModel(
plantRepository = repository
)
Your ViewModel will be created many times and won't be reused across configuration changes or recompositions because you're always creating it there - instead of asking for it to be created or reusing it if it already exists, which is what the viewModel(...) function does.
As per a codelab in Room,
By using viewModels and ViewModelProvider.Factory,the framework will take care of the lifecycle of the ViewModel. It will survive configuration changes and even if the Activity is recreated, you'll always get the right instance of the WordViewModel class.
You do not have to use ViewModelProvider.Factory to instantiate your ViewModel.
Lets assume you have an Entity:
#Entity(tableName = "user")
data class User(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "user_id") val userId: Long)
And a DAO for that entity:
#Dao
interface UserDao {//some methods}
Without using a repository you can instantiate your ViewModel with the help of android.app.Application like so:
class UserViewModel(
application: Application
) : AndroidViewModel(application) {
val dao = AppDatabase.getDatabase(application, viewModelScope).userDao()
}
And then later in a Fragment create your ViewModel which you can later pass into your composable:
private val userViewModel: userViewModel by viewModels()

Use ViewModelFactory inside Fragment

I'm trying to share a ViewModel between my activity and my fragment. My ViewModel contains a report, which is a complex object I cannot serialize.
protected val viewModel: ReportViewModel by lazy {
val report = ...
ViewModelProviders.of(this, ReportViewModelFactory(report)).get(ReportViewModel::class.java)
}
Now I'm trying to access the viewmodel in a fragment, but I don't want to pass all the factory parameters again.
As stated by the ViewModelProvider.get documentation:
Returns an existing ViewModel or creates a new one in the scope
I want to access the ViewModel instance defined in the activity, so I tried the following but it logically crashes as the model doesn't have an empty constructor:
protected val viewModel: ReportViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(ReportViewModel::class.java)
}
How one should access its "factorysed" ViewModels in a fragment? Should we pass the factory to the fragment?
Thanks!
A little late but I had this question myself. What I found is you can do the following:
In your activity override getDefaultViewModelProviderFactory() like so:
override fun getDefaultViewModelProviderFactory(): ReportViewModelFactory {
return ReportViewModelFactory(report)
}
now in your fragments you can do
requireActivity().getDefaultViewModelProviderFactory()
to get the factory.
Or simply instantiate your viewModel like:
private val viewModel: ReportViewModel by activityViewModels()

How to share same instance of ViewModel between Activities using Koin DI?

I am using Koin library in Kotlin for DI
Koin providing by viewmodel() for get instance of ViewModel by sharedViewModel() to get same instance in fragments.
How can I get same instance of the ViewModel in activities ? I didn't find any way to achieve this.
you must use single{} instead of viewModel{} in module declaration.
single { SharedViewModel() }
And, you can use viewModel() in your views.
View1
private val viewModel: SharedViewModel by viewModel()
View2
private val viewModel: SharedViewModel by viewModel()
But you must load modules when view start by
loadKoinModules(module1)
The important point is that you must unload module in when destroy view.
unloadKoinModules(mainModule)
So, when unload modules your singleton ViewModel will be destroyed.
#EDIT
Now, you can use sharedViewModel declaration.
After some research or discussion on architecture level and also report and issue github Koin,i found solution for this
In this scenario,We should save that state/data into Repository which we need to share between multiple activities not in the viewModel and two or more different ViewModels can access same state/data that are saved in single instance of repository
you need to read more about ViewModel to understand it better.
https://developer.android.com/topic/libraries/architecture/viewmodel
ViewModel is connected to your Activity
so you can share your Activities ViewModel only between his Fragments ,
that is what mean sharedViewModel in koin
sharedViewModel is the same if you use ViewModel Factory with same context .
sharing any data between Activities can be done via Intent , there is no another way in Android,
or you can keep some static / global data and share it between Activities
I would suggest making the app a ViewModelStoreOwner and injecting the viewModels using as owner the app.
The code required would look like this
class App : Application(), ViewModelStoreOwner {
private val mViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore {
return mViewModelStore
}
}
You can define some extensions to easily inject the viewModels
val Context.app: App
get() = applicationContext as App
inline fun <reified T : ViewModel> Context.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(app, null) }, T::class, parameters)
}
}
inline fun <reified T : ViewModel> Fragment.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(requireContext().app, null) }, T::class, parameters)
}
}
You can then inject your viewModel like this
class MainActivity : AppCompatActivity() {
private val mAppViewModel: AppViewModel by appViewModel()
}
The advantage of this solution is that you don't need to recreate the view model and if you decide to save the state between app restarts, you can easily make the app an SavedStateRegistryOwner as well and using the SavedStateHandle save/restore your state from inside the viewModel, being now bound to the process lifecycle.
I know this is very very late but you can try this:
if you are extending a baseviewmodel, you need to declare the baseViewmodel as a single then in your respective activity inject the BaseViewModel.
Practical example:
val dataModule = module {
single { BaseViewModel(get(), get()) }
}
in your ViewModel
class LoginViewModel(private val param: Repository,
param1: Pref,
param2: Engine) : BaseViewModel(param1, param2)
Then in your activity class
val baseViewModel: BaseViewModel by inject()
Hope this help someone.

Categories

Resources