I have 1 activity which contains parent fragment. Inside the parent fragment is 2 child fragments. I want to only create one viewmodel instance and use it in both child fragment.
This is my code in parent fragment:
val factory = ViewModelFactory.getInstance(requireContext())
viewModel = ViewModelProvider(this, factory)[FavoriteViewModel::class.java]
And this one in child fragment:
viewModel = (requireParentFragment() as FavoriteParentFragment).viewModel // force close
It force close without error in logcat.
When i try to move the parent fragment's code to the activity (without changing the code) and change the child fragment's code to:
viewModel = (activity as MainActivity).viewModel // works
It works.
So is it possible to do the first method (that is force closed)? If so, how to do that properly and why i did not get any logcat error.
Instead of trying to access the viewmodel from the parent directly, use the scope of the parent to get a shared viewmodel.
Inside your child fragments, use this code
val factory = ViewModelFactory.getInstance(requireContext())
val viewModel = ViewModelProvider(requireParentFragment(), factory).get(FavoriteViewModel::class.java)
Using the above code, you no longer need to store an instance of the viewmodel in the parent fragment.
Related
I have parent Fragment that contains child fragment. Inside child fragment I have ViewPager with fragments. My question is how can I share ViewModel between parent child and fragments in viewpager and makeing Viewmodel visible only on ParentFragment scope?
what do you mean when you say "visible only on ParentFragment scope"?
According Google's document, there is one way that you can share ViewModel.
Check this document: https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
Shortly, your parent fragment and child fragment will use the same ViewModel. Your parent fragment will call the function of ViewModel to change the data, your child fragment just observer the LiveData of ViewModel.
You can place your sharedViewModel inside your Activity. Then you can access it from every fragment which in attached to this activity with this code:
(requireActivity() as MainActivity).viewModel;
With this approach, you can set data from one fragment and observe data from another fragment. So, you enable communication between two fragments.
Setting data:
viewModel.liveDataObject.value = value
Observing data:
viewModel.liveDataObject.observe(viewLifecycleOwner) {}
When trying to inflate and set my presenter to my databinding component in this way my presenter methods are not called.
val fragmentBinding = FragmentListEditBinding.inflate(layoutInflater)
fragmentBinding.presenter = ListEditorPresenter(this, requireContext())
but when using this
val fragmentBinding = DataBindingUtil.setContentView<FragmentListEditBinding(requireActivity(), R.layout.fragment_list_edit)
fragmentBinding.presenter = ListEditorPresenter(this, requireContext())
It works fine, but then the layout is covering the full screen.
Any ideas how to fix this issue?
Please tell me if more context is needed.
The second method is for activity, not for the fragment, For fragment, you have to do it in the first method.
Before DataBinding and ViewBinding, In and activity we call setContentView(R.layout.activity_main) to set the view for the activity, but for fragment, we override the onCreateView method and inflate a view and return it.
So the way of setting view for activity and fragment is different from the beginning.
So the DataBindingUtil.setContentView is made for activity, while the FragmentListEditBinding.inflate custom/manual inflation is made for fragment. As i have already mentioned it above.
I am trying to load initial data onto a fragment in my application. In the following lines of code (located at the very end of onCreate, I attempt retrieve the fragment and load data onto it:
// Display the current list of matches to the user
var eventsFragment: EventsFragment =
supportFragmentManager.findFragmentByTag("eventsFragment") as EventsFragment
eventsFragment.displaySchedule(currList)
However, I receive the following error:
Caused by: kotlin.TypeCastException: null cannot be cast to non-null
type com.example.alarmfornbamatches.ui.main.EventsFragment
My guess is that the fragment hasn't loaded at the end of onCreate. So how do I execute the displaySchedule function once the fragment is fully loaded and available to be referenced for UI updates?
You can solve the TypeCastException by using this:
// Display the current list of matches to the user
var eventsFragment: EventsFragment? = supportFragmentManager.findFragmentByTag("eventsFragment") as EventsFragment
eventsFragment?.displaySchedule(currList)
You are looking for a fragment that you haven't created, in a place you haven't put it yet. You must first create the fragment and add it to the supportFragmentManager using transactions. If you want do pass data to the fragment you should do so by add this to your fragment (if your list is not string, then it must be serializable and use putSerializable instead.
companion object{
fun newInstance(list: ArrayList<String>) = EventsFragment().apply{
arguments = Bundle().apply {
putStringArrayList(KEY, list)
}
}
}
and then pull the data out of the arguments bundle in the onCreate method in your fragment.
Now in the onCreate in your activity you will create the fragment and add it like so
eventsFragment = EventsFragment.newInstance(list)
supportFragmentManager.beginTransaction().add(R.id.frameLayoutId, eventsFragment, fragmentTag).commit()
only now, if necessary can you use the code you have to get the fragment again, and you should always do it expecting ti to possibly come back as null:
eventsFragment = supportFragmentManager.findFragmentByTag(fragmentTag) as? EventsFragment ?: EventsFragment.newInstance(list)
The problem with your code is you are trying to find a fragment which is not there that's why its null. Not sure where you made the Fragment transaction.
To fix this There can be two cases:
If you already have the data in your Activity its better you pull it inside Fragment by using getActivity() in #onViewCreated() or by a Shared ViewModel.
If you are loading data from a source(Network/IO) then you can use a Shared ViewModel or Get fragment from back stack and call its method or you can use conventional callback stuff.
I can add some sample code but i am not sure which option is best fit for you since its not clear from question that u are using MVVM or not.
While Navigation component of JetPack looks pretty promising I got to a place where I could not find a way to implement something I wanted.
Let's take a look at a sample app screen:
The app has one main activity, a top toolbar, a bottom toolbar with fab attached.
There are 2 challenges that I am facing and I want to make them the right way.
1. I need to implement fragment transactions in order to allow replacing the fragment on the screen, based on the user interaction.
There are three ways I can think of and have this implemented:
the callbacks way. Having a interface onFragmentAction callback in fragment and have activity implement it. So basically when user presses a button in FragmentA I can call onFragmentAction with params so the activity will trigger and start for example transaction to replace it with FragmentB
implement Navigation component from JetPack. While I've tried it and seems pretty straightforward, I had a problem by not being able to retrieve the current fragment.
Use a shared ViewModel between fragment and activity, update it from the fragment and observe it in the activity. This would be a "replacement" of the callbacks
2. Since the FAB is in the parent activity, when pressed, I need to be able to interact with the current visible fragment and do an action. For instance, add a new item in a recyclerview inside the fragment. So basically a way to communicate between the activity and fragment
There are two ways I can think of how to make this
If not using Navigation then I can use findFragmentById and retrieve the current fragment and run a public method to trigger the action.
Using a shared 'ViewMode' between fragment and activity, update it from activity and observe it in the fragment.
So, as you can see, the recommended way to do navigation would be to use the new 'Navigation' architecture component, however, at the moment it lacks a way to retrieve the current fragment instance so I don't know how to communicate between the activity and fragment.
This could be achieved with shared ViewModel but here I have a missing piece: I understand that fragment to fragment communication can be made with a shared ViewModel. I think that this makes sense when the fragments have something in common for this, like a Master/Detail scenarion and sharing the same viewmodel is very useful.
But, then talking between activity and ALL fragments, how could a shared ViewModel be used? Each fragment needs its own complex ViewModel. Could it be a GeneralViewModel which gets instantiated in the activity and in all fragments, together with the regular fragment viewmodel, so have 2 viewmodels in each fragment.
Being able to talk between fragments and activity with a viewmodel will make the finding of active fragment unneeded as the viewmodel will provide the needed mechanism and also would allow to use Navigation component.
Any information is gladly received.
Later edit. Here is some sample code based on the comment bellow. Is this a solution for my question? Can this handle both changes between fragments and parent activity and it's on the recommended side.
private class GlobalViewModel ():ViewModel(){
var eventFromActivity:MutableLiveData<Event>
var eventFromFragment:MutableLiveData<Event>
fun setEventFromActivity(event:Event){
eventFromActivity.value = event
}
fun setEventFromFragment(event:Event){
eventFromFragment.value = event
}
}
Then in my activity
class HomeActivity: AppCompatActivity(){
onCreate{
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromFragment.observe(){
//based on the Event values, could update toolbar title, could start
// new fragment, could show a dialog or snackbar
....
}
//when need to update the fragment do
viewModel.setEventFromActivity(event)
}
}
Then in all fragments have something like this
class FragmentA:Fragment(){
onViewCreated(){
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromActivity.observe(){
// based on Event value, trigger a fun from the fragment
....
}
viewModelFragment = ViewModelProviders.of(this, factory)
.get(FragmentAViewModel::class.java)
viewModelFragment.some.observe(){
....
}
//when need to update the activity do
viewModel.setEventFromFragment(event)
}
}
Hello i create class that implement BottomSheetDialogFragment with dynamic content. The content is a Fragment. So when initialize the BottomSheet i passing fragment object, and attach it to specific Container ID inside this BottomSheetDialogFragment. Looks like this :
private fun attachContentFragment() {
val transaction = childFragmentManager.beginTransaction()
transaction.apply {
replace(R.id.flContent, state.layoutContent)
commit()
}
}
state.layoutContent is my attached Fragment
I need to dismiss the BottomSheet if every action called in that fragment.
As far as i know, i need to get the object of BottomSheet that hold me(Fragment) and dismiss it.
But how i can get that BottomSheet object?
Thanks
So, technically it is a fragment inside fragment situation. I think there is several solutions here:
Call Activity from your child fragment. BottomSheetDialogFragment will subscribe to Activity for such events and react on them.
Get the instance of a BottomSheetDialogFragment by calling proper FragmentManager (which possible is an Activity one). You can get an instance of a fragment byTag for example.
Or you can call getParentFragment from a child Fragment.