How to Share data between a parent and child fragment android Kotlin - android

I have fragment which contain two dialog fragments i.e (Fragment A -> (Navigate) -> (Dialog A, DialogB) I want to share data between these fragments I tried this way mentioned in developer android Share data between a parent and child fragment
class ListFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel
private val viewModel: ListViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
// Update the list UI
}
}
}
class ChildFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel using the parent fragment's scope
private val viewModel: ListViewModel by viewModels({requireParentFragment()})
...
}
When I use it in my case is not working and the views in the parent fragment are not changing when I modify data in the child fragment Also when I logged the view models I found that each view model have its own Id as shown below. Any way to deal with this problem or clear explanation to this.

Related

Fragment to fragment communication with viewmodel without host activity

There is a straight forward implementation for fragment to fragment communucation using viewmodel and a host activity (ViewModel by activityViewModels()). However I have a case where there is no intermediate activity for communication.
I am working on a modularized application with modules including app and wallet. app depends on wallet and the wallet module has a fragment: WalletFragment that can be accessed by the app's module MainActivity.
Now if I create a viewModel instance for app's module MainActivity, I won't be able to access it from wallet module because wallet module has no dependency on the app module. How can I achieve this?
To the main question: I have another scenerio where the WalletFragment calls a new BottomSheetFragment in the same wallet module. Is there a way I can create a ViewModel instance for this WalletFragment that can be accessed by the BottomSheetFragment without having to take the route of ViewModel by activityViewModels(). In other words is there such thing as ViewModel by fragmentViewModels()?
I think you can use something called SharedViewModel, for example:
This is the viewModel that will be shared.
class ShareableViewModel(): ViewModel(){
val message = MutableLiveData<String>()
fun sendMessage(text: String) {
message.value = text
}
}
And now the FirstFragment is the fragment that will send something, in this case, just a string.
class FirstFragment: Fragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val shareableViewModel = ViewModelProvider(requireActivity()).get(ShareableViewModel::class.java)
shareableViewModel.sendMessage("Message from first Fragment")
}
}
Finally, SecondFragment is the fragment that is receiving the message fragment Fragment1
class SecondFragment: Fragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val shareableViewModel = ViewModelProvider(requireActivity()).get(ShareableViewModel::class.java)
shareableViewModel.message.observe(viewLifecycleOwner, Observer {
//Receiving the message
})
}
}
I hope it can help. For more details, you can see:
https://blog.mindorks.com/shared-viewmodel-in-android-shared-between-fragments

Fragment Never Receives LiveData updates from Shared ViewModel

My app uses MVVM architecture. I have a ViewModel shared by both an Activity and one of its child fragments. The ViewModel contains a simple string that I want to update from the Activity and observe in the fragment.
My issue is simple: the observe callback is never reached in my fragment after the LiveData updates. For testing, I tried observing the data in MainActivity, but that works fine. Additionally, observing LiveData variables in my fragment declared in other ViewModels works fine too. Only this ViewModel's LiveData seems to pose a problem for my fragment, strangely.
I'm declaring the ViewModel and injecting it into my Activity and Fragment via Koin. What am I doing incorrectly to never get updates in my fragment for this ViewModel's data?
ViewModel
class RFIDTagViewModel: ViewModel() {
private val _rfidTagUUID = MutableLiveData<String>()
val rfidTagUUID: LiveData<String> = _rfidTagUUID
fun tagUUIDScanned(tagUUID: String) {
_rfidTagUUID.postValue(tagUUID)
}
}
Activity
class MainActivity : AppCompatActivity(), Readers.RFIDReaderEventHandler,
RFIDSledEventHandler.TagScanInterface {
private val rfidViewModel: RFIDTagViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
rfidViewModel.rfidTagUUID.observe(this, {
Timber.d("I'm ALWAYS reached")
})
}
override fun onResume() {
rfidViewModel.tagUUIDScanned(uuid) //TODO: data passed in here, never makes it to Fragment observer, only observed by Activity successfully
}
}
Fragment
class PickingItemFragment : Fragment() {
private val rfidViewModel: RFIDTagViewModel by viewModel()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
rfidViewModel.rfidTagUUID.observe(viewLifecycleOwner, { tagUUID ->
Timber.d("I'm NEVER reached")
})
}}
Koin DI Config
val appModule = module {
viewModel { RFIDTagViewModel() }
}
In your Fragment I see you are using viewModels(). viewModels() here will be attached to the Fragment, not to the Activity.
If you want to shareViewModel between Fragment and Activity, then in Fragment you use activityViewModels(). Now, in the Fragment, your shareViewModel will be attached to the Activity containing your Fragment.
Edit as follows:
PickingItemFragment.kt
class PickingItemFragment : Fragment() {
private val rfidViewModel: RFIDTagViewModel by activityViewModels()
}
More information: Communicating with fragments
You need to use the same viewmodel, aka, sharedViewModel, the way you are doing you are using two different instances of the same viewmodel.
To fix it.
On both activity and fragment:
private val rfidViewModel: RFIDTagViewModel by activityViewModels()
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=pt-br

Does an instance of a SharedViewmodel never dies?

I have an app that has a main activity and fragments depend on it, so this is normal.
Now, two of my 10 fragments need to communicate, which I use the example given here
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ? Because now I dont need anymore that viewmodel instance, but as per documentation says, this instance will be retained from the Activity that contains these fragments
This is not what I'm looking for to communicate between fragments since now a new instance of that viewmodel will be the same as the past one I have created, I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ?
Correct. While it so happens that only two of your fragments use it, that ViewModel is scoped to the activity.
I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Then perhaps you should not be using activityViewModels(). For example, you could isolate these two fragments into a nested navigation graph and set up a viewmodel scoped to that graph.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
The ViewModel system does not know about what is or is not "depending on it". It is all based on the ViewModelStore and the ViewModelStoreOwner that supplies it. activityViewModels() uses the activity as the ViewModelStoreOwner, so viewmodels in that ViewModelStore are tied to the activity.

Pass data from fragment with recyclerview to the other fragment using ViewModel in android

I have a viewPager inside my application which contains 3 Fragments.
First Fragment has a recyclerview on click of the item of recyclerview I pass some data to another fragment which is not the part of viewpager fragements. I am using the latest viewmodel logic to achieve this functionality and as per the steps mentioned by many websites I am doing the following this.
Creating the separate class which extends viewmodel
class MovieObservable : ViewModel() {
private val selectedMovieID = MutableLiveData<Int>()
fun setSelectedMovieID(id : Int){
selectedMovieID.value = id
}
fun getSelectedMovieID(): MutableLiveData<Int> {
return selectedMovieID
}
Initialize the viewmodel inside oncreateView Fragment
private lateinit var mViewModel : MovieObservable
mViewModel = ViewModelProvider(this).get(MovieObservable::class.java)
Now from my RecyclerView I have implemented the click listener and implemented that in my fragment and override the onclick event with the position and value
override fun onClickNowPlaying(position: Int, view: View, result: Result) {
System.out.println("now playing ID" + result.id)
mViewModel.setSelectedMovieID(result.id)
val newFragment = MovieDetailsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.mainLayout, newFragment, "NewFragment")
transaction?.addToBackStack(null)
transaction?.commit()
}
I add the logic of observing the value in the details fragment which is below
private lateinit var mViewModel : MovieObservable
mViewModel = ViewModelProvider(this).get(MovieObservable::class.java)
mViewModel.getSelectedMovieID().observe(viewLifecycleOwner, Observer {getmydata->
System.out.println("in the next fragment " + getmydata)
})
So All logic is finished to pass the data from one fragment to another but the problem is I am not getting the data on the details fragment.
What is it which I am missing here.
With latest library of ViewModel, it is even easier to create and share ViewModel between Fragments of Activity:
First add the activity-ktx and fragment-ktx dependencies in your app module's build.gradle:
dependencies {
// For Activity
def activity_version = "1.2.0-alpha04"
implementation "androidx.activity:activity-ktx:$activity_version"
// For Fragment
def fragment_version = "1.3.0-alpha04"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
}
Suppose your ViewModel be as:
class MyViewModel : ViewModel() { }
Then, in your activity:
class MyActivity : AppCompatActivity() {
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val model: MyViewModel by viewModels()
}
And then, your fragments would have:
class MyFirstFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: MyViewModel by activityViewModels()
}
class MySecondFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: MyViewModel by activityViewModels()
}

Sharing one view model with multiple fragments

I am having some common logic which I currently have it in a Util class. Now, I want to move this logic to ViewModel class. As this util method is used in different fragments, is it a good practice to create a common view model (feature based view model) for multiple fragments. I know Google recommended to use 1 view model for 1 view. Please suggest.
If you've got common code, you could have several viewModels that inherit from a baseViewModel, which contains the shared code.
The advantage of this over a Util class is that the shared code is only visible to ViewModels that derive from the base, and can't get confused with anything else.
It's better to create a viewmodel per each fragment, but it is possible to create a single viewmodel for several fragments. According to the official documents:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}

Categories

Resources