My project structure has a BaseActivity which is extended by multiple child activities, so the structure is like
BaseActivity
^
|
-------------------------------
| | |
ChildActivityA ChildActivityB ChildActivityC
I am using DataBinding with LiveData, and hence every time I need to set up the lifecycleOwner for the respective binding class i.e (in ChildActivityA)
val binding = DataBindingUtil.setContentView(R.layout.somelayout)
binding.lifecycleOwner = this#ChildActivityA
Now I need to repeat this boilerplate in each Activity, so instead I created a helper extension function to replace the above two lines i.e
fun <T : ViewDataBinding> BaseActivity.setDataBindingView(layoutId: Int): T {
val binding = DataBindingUtil.setContentView(this, layoutId)
binding.lifecycleOwner = this#BaseActivity
}
and then call in my ChildActivityA as
val binding = setDataBindingView(R.layout.someLayout)
As you can see the binding.lifecycleOwner is set to BaseActivity instead of the actual ChildActivityA, now will this cause any trouble? Will the binding.lifecycleOwner be still following the lifecycle of ChildActivityA?
this#ChildActivityA and this#BaseActivity refer to the same actual object in memory - there's only one activity object that exists. Therefore they're entirely equivalent.
Related
I am trying to understand how can I use my same viewmodel across different fragments which even belongs to different activities.
So let's say I have Activity1 with Fragment A, Fragment B and Activity2 with Fragment C. How do I create a single instance of viewmodel that I can use across all these fragments.
I tried understanding shared viewmodel but seems like it is to be used if sharing data between fragments of a single activity and not multiple activities.
So basically I want to create a single instance of viewmodel across all the fragments? How can I achieve this functionality also keeping in mind the MVVM approach.
Any help would be appreciated. Thanks!
This is not supported. Google's recommendation is to put all your screens in a single Activity.
But, you can make an intermediate singleton class that each instance of the ViewModel uses.
Or maybe you could use a factory that treats it like a temporary singleton and does reference counting so it doesn't get cleared too early or hang onto the reference for too long. Untested example of what I mean:
private var viewModelInstance: MyViewModel? = null
private var refCount = 0
class MyViewModel: ViewModel() {
override fun onCleared() {
if (--refCount > 0) {
return
}
viewModelInstance = null
// Do typical onCleared cleanup here
}
}
class MyViewModelFactory: ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
require(modelClass.isAssignableFrom(MyViewModel::class.java)) { "Factory only supports MyViewModel" }
refCount++
viewModelInstance = viewModelInstance ?: MyViewModel()
return viewModelInstance as T
}
}
Shared viewmodel between multiple activities is not supported.
One way to achieve this using AndroidViewModel. You can create a ViewModel extending AndroidViewModel . This requires application instance. This viewmodel will be binded to application lifecycle and same instance will be available through out the lifecycle of the application. In one activity you can add data, and in other activity you can get updated data.
This will be acting something like singleton instance(But not exactly).
Addition to this, you can also use live data in AndroidViewModel if you use observer with activity/fragment lifecycle owner. So the observer will be live only till life cycle of fragment or activity.
ViewModel:
class AppViewModel constructor(private val mApplication: Application) :
AndroidViewModel(mApplication) {
//ViewModel Logic
}
Initializing Viewmodel:
You can initialize viewmodel like this in any fragment or activity.
val appViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(MyApp.getInstance())
.create(AppViewModel::class.java)
Application Class:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
mInstance = this
}
companion object {
lateinit var mInstance: CooperApp
#Synchronized
fun getInstance(): MyApp {
return mInstance
}
}
}
Also one more thing we can use is like initializing viewmodel in application class and create similar function to getInstance() which will return viewmodel instance and use it all over the app
Observer not calling in Second Fragment. Any help would be greatly appreciated.
Fragment1
viewModel.productData(model)
findNavController().navigate(R.id.actionProductListToDetails)
ViewModel class
val productData = MutableLiveData<ModelProductSubItem>()
fun productData(data: ModelProductSubItem) {
productData.value = data
}
Fragment2
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(ProductSubListItemViewModel::class.java)
viewModel.productData.observe(viewLifecycleOwner, Observer{ it ->
println("Product_Name"+it.product_name) // Not invoking
})
Tried with
viewModel = activity?.run {
ViewModelProvider(this).get(ProductSubListItemViewModel::class.java)
} ?: throw Exception("Invalid Activity")
})
FYI
I have same ViewModelProvider
I think the problem is there may be two ViewModelProvider(this), each using "this" on a different fragment. If you have two providers, then there are two separate models that do not share their triggers.
That's why, when you call ViewModelProvider you call the activity which holds the fragments as the single source:
viewmodel = activity?.run {
ViewModelProviders.of(this).get(SomeViewModel::class.java)
} ?: throw Exception("Invalid Activity")
Although with the newer version of ViewModel and fragment-ktx artifact this is not neccesary, I would recommend using this latest version.
Most likely what's happening is that, despite your FYI comment, you are actually instantiating different ViewModels for each context. If you want to share the same instance of an Activity ViewModel among multiple Fragments, then each fragment should get the shared ViewModel like this:
new ViewModelProvider(requireActivity()).get(SomeViewModel.class)
Instead of
new ViewModelProvider(this)).get(SomeViewModel.class)
If you are using Kotlin extensions (KTX) I believe you should do this:
val viewModel by activityViewModels<SomeViewModel>()
Instead of
val viewModel by viewModels<SomeViewModel>()
I am trying to get a value from the SharedViewModel class but the ViewModelProvider() is giving a parameter error when i am passing requireActivity() although the same initilization and assignment works in my fragments.
It is requiring "ViewModelStoreOwner" to be passed.
class CourseRepository(val app: Application) {
private var viewModel: SharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
val courseData = MutableLiveData<List<Course>>()
init {
CoroutineScope(Dispatchers.IO).launch {
callWebService()
}
}
#WorkerThread
suspend fun callWebService() {
if (Utility.networkAvailable(app)) {
val retrofit = Retrofit.Builder().baseUrl(WEB_SERVICE_URL).addConverterFactory(MoshiConverterFactory.create()).build()
val service = retrofit.create(CourseService::class.java)
val serviceData = service.getCourseData(viewModel.pathName).body() ?: emptyList()
courseData.postValue(serviceData)
}
}
}
The purpose of the ViewModel here is because i am storing the Id of the selected RecyclerView item in order to send it to a server
ViewModel instances are scoped to Fragments or Activities (or anything with a similar lifecycle), which is why you need to pass in a ViewModelStoreOwner to the provider to get a ViewModel from it. The point of ViewModels is that they will exist until the store they belong to is destroyed.
The requireActivity method doesn't work here, because you're not inside a Fragment.
Some things to consider here:
Do you really need ViewModel in this use case? Could you perhaps use just a regular class that you can create by calling its constructor?
Could you call this Repository from your ViewModel, and pass in any parameters you need from there?
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.
In my current project I use the next line:
mViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
For instance a ViewModel but in https://developer.android.com/reference/android/arch/lifecycle/ViewModelProviders.html#ViewModelProviders() recommend use ViewModelProvider.AndroidViewModelFactory because ViewModelProviders() was deprecated in API level 1.1.0.
any idea for this purpose?
If you had a simple ViewModel extending AndroidViewModel without any additional constructor parameters, its as follows
Extend AndroidViewModel without any additional constructor
parameters
class FooViewModel(application: Application) : AndroidViewModel(application) {}
Create View Model in Activity
val viewModel = ViewModelProvider(this).get(FooViewModel::class.java)
But if you had a ViewModel extending AndroidViewModel with any additional constructor parameters, its as follows
Extend AndroidViewModel with any additional constructor
parameters
class FooViewModel(application: Application, foo: Foo) : AndroidViewModel(application) {}
Create a new view model factory extending ViewModelProvider.AndroidViewModelFactory
class FooViewModelFactory(val application: Application, val foo: Foo): ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FooViewModel(
application, foo
) as T
}
}
Create View Model in Activity
val viewModel = ViewModelProvider(this, FooViewModelFactory(application, foo)).get(FooViewModel::class.java)
EDIT: The original question is now irrelevant, as you should no longer use the ViewModelProviders utility class. Instead, you should create a ViewModelProvider instance like so:
val viewModel = ViewModelProvider(thisFragment).get(MyViewModel::class.java)
Original answer below.
ViewModelProviders is just a utility class with static methods, there's no need to instantiate it (there are no instance methods in it anyway), so the constructor being deprecated shouldn't be a concern.
The way you use it is by calling its appropriate of method for your use case, passing in a Fragment or Activity, and then calling get on the ViewModelProvider it returns:
val viewModel = ViewModelProviders.of(thisFragment).get(MyViewModel::class.java)
If you don't provide your own factory in the second parameter of the of method, AndroidViewModelFactory will be used by default. This implementation can either create ViewModel subclasses that have no constructor parameters, or ones that extend AndroidViewModel, like such:
class MyViewModel(application: Application) : AndroidViewModel(application) {
// use application
}
you can try this code
ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(UserViewModel::class.java)
You can use AndroidViewModelFactory like this:
mViewModel = ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory(application))
.get(MainViewModel::class.java)
So, it is quite simple:
// This is the deprecated way to create a viewModel.
viewModel = ViewModelProviders.of(owner).get(MyViewModel::class.java)
//This is the deprecated way to create a viewModel with a factory.
viewModel = ViewModelProviders.of(owner, mViewModelFactory).get(MyViewModel::class.java)
// This is the new way to create a viewModel.
viewModel = ViewModelProvider(owner).get(MyViewModel::class.java)
//This is the new way to create a viewModel with a factory.
viewModel = ViewModelProvider(owner, mViewModelFactory).get(MyViewModel::class.java)
Open build.gradle(Module:~.app)
Edit appcompat version to 1.3.0-alpha02
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'