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>()
Related
I'm having a hard time understand what scopes to use for view models and live data when using fragments. Here is my ViewModel:
class MyViewModel: ViewModel() {
var myLiveData = MutableLiveData<WrappedResult<DataResponse>>()
private val repository = MyRespository()
private var job: Job? = null
fun getData(symbol: String) {
job = viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getData(symbol)
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Success(response)
}
} catch(e: Exception) {
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Failure(e)
}
}
}
}
}
I can create the view model in the fragment using (where "this" is the fragment):
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
However, I can observe the LiveData with two options:
viewModel.getMyLiveData.observe(this...
or
viewModel.getMyLiveData.observe(getViewLifecycleOwner()...
It would appear that the job I create in the view model is going to be scoped to the fragment's lifecycle (through viewModelScope) and not the fragment's view lifecycle, but I have a choice between these two for the live data.
I could use some guidance and what the best practice is here. Also, does any of this matter whether the fragment has retained instance or not? Currently the fragment has setRetainInstance(true). Finally, from everything I've read I shouldn't need to clear the observer in the fragment or override onCleared when things are setup this way. Is that correct?
refer the doc of view model
https://developer.android.com/topic/libraries/architecture/viewmodel?gclid=Cj0KCQjwtZH7BRDzARIsAGjbK2blIS5rGzBxBdX6HpB5PMKgpUQHvdKXbwrt-ukTnWkpax1otMk4sm4aAuzPEALw_wcB&gclsrc=aw.ds#lifecycle
Viewmodel will only gets destoyed once the activity is finished.As the fragments are on the top of acitivity, the lifecycle of fragment will not affect the Viewmodel.The data will be persisted there on the viewmodel. So you can write a method to reset the data in viewmodel while you are entering in to oncreate of fragment.
In Fragment, OnCreate :
getViewModel.init()
on ViewModel
fun init() {
// clear all varialbes/datas/ etc here
}
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'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()
In my app I have some data that will be used across all app in the different fragments. According to the Official Android Guides we should use LiveData and SharedViewModel
That documentations shows just how to use data from SharedViewModel in fragment. But ...
How to use that data in the FragmentViewModel?
Use case #1: using the SharedInfo from SharedViewModel I need to make some request to the server and to do smth with response from server in the FragmentViewModel
Use case #2: I have some screen (fragment) that shows info both from FragmentVM and SharedVM
Use case #3: When user click on SomeButton I need to pass some data from SharedViewModel to the ViewModel
I have found two possibles ways how to do it (maybe their are very similar), but I seems that it can be done more clearly
1) Subscribe to LiveData from SharedViewModel in the fragment and call some method in the ViewModel
2) Use the "CombineLatest" approach like in the RX ( thanks for https://github.com/adibfara/Lives )
Some example to reproduce:
class SharedViewModel(app: Application) : ViewModel(app) {
val sharedInfo = MutableLiveData<InfoModel>()
}
class MyFragmentViewModel(app: Application) : ViewModel(app) {
val otherInfo = MutableLiveData<OtherModel>()
}
class StartFragment : Fragment(){
lateinit var viewModel: MyFragmentViewModel
lateinit var sharedViewModel: SharedViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Create Shared ViewModel in the Activity Scope
activity?.let {
sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
}
// Create simple ViewModel forFragment
viewModel = ViewModelProviders.of(this).get(MyFragmentViewModel::class.java)
// Way #1
sharedViewModel.sharedInfo.observe(this, Observer{
viewModel.toDoSmth(it)
})
viewModel.otherInfo.observe(this, Observer{
sharedViewModel.toDoSmth(it)
})
// Way #2
combineLatest(sharedViewModel.sharedInfo, viewModel.otherInfo){s,o -> Pair(s,o)}.observe(this, Observe{
viewModel.doSmth(it)
// or for example
sharedViewModel.refreshInfo(it)
})
}
}
I expect to found some clear way to access to LiveData from SharedVM from FragmentVm and vise versa. Or maybe I think wrong and this is a bad approach to do that and I shouldn't do it
How I can use shared viewModel with fragments without activity?
Like in code but in place of requireActivity() use ParentFragment. In this case when ParentFragment will destroyed, SharedViewModel is cleared, but when I provide SharedViewModel from activity, it not cleared when ParentFragment destroyed.
And I use Navigation Components, which mean that I can`t set tag for fragment and then use findFragmentByTag()
class ParentFragment:Fragment{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
class ChildFragment:Fragmnet{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
You can try scoped-vm - it allows you to request ViewModel for scope identified by a String key. Scope lives till the last fragment that requested ViewModel gets destroyed, then ViewModel gets cleared.
You can use this code to obtain SharedViewModel both in ParentFragment and ChildFragment.
ScopedViewModelProviders
.forScope(this, "scope")
.of(requireActivity())
.get(SharedViewModel::class.java)
See you can initialize viewModel in all fragments which you want to share viewmodel, and use Rx with viewModel, your all process in these fragments will keep running until you want to cancel it,you can call viewModel.oncleard() from Activity or Fragment.
public override fun onCleared() {
super.onCleared()
compositeDisposable.dispose()
// or cancel any process
}
If you do not know ViewModel Scope, please check this image