We have lots of dialog need to show in ActivityA, ActivityA has ViewModelA, which has the live data to control when need to show dialog. Can I create dialog in ViewModelA and pass the dialog as a parameter in the LiveData?
In ViewModelA have
MutableLiveData<SingleLiveEvent<DialogEvent> dialogEvent;
For DialogEvent:
class DialogEvent(DialogFragment dialog)
When need to display dialog, update dialogEvent.
SomeDialogFragment fragment = new SomeDialogFragment.newInstance();
dialogEvent.postValue(new SingleLiveEvent(fragment));
In ActivityA observe the dialogEvent
viewModelA.getDialogEvent().observe {event -> event.dialog.show()}
ViewModel shouldn't hold reference to Views. According to me, you can have your custom model for your dialog as LiveData and observe the data in your fragment/activity and take actions say show/hide/text on that basis from fragment/activity itself. Feel free to ask in doubt.
Related
I need to do an API call from a Dialog. Do I need to go back to the fragment for doing so, or is there any way to refer to the fragment view model?
Yes, it is possible and I was able to do that because class DialogFragment extends Fragment. So I added a view model just like any other fragment.
like below where BaseDialog class extends DialogFragment
You can try with this:
Use interface, implement it in fragment so you have callback funtion.
Pass high ordered function, declare it like this in dialog:
var click: (() -> Unit)? = null;
Then you can set it from fragment when you instantiate your dialog.
Use shared view model, for example make view model in your activity and then you can access it from every fragment or dialog like this:
(requireActivity() as MainActivity).viewModel
Like this you can set value in view model variable (liveData often) inside your dialog and observe changes in fragment
I think you can pass a high ordered function to dialog and handle it in fragment using viewModel inside.
I am trying to pass data from dialog destination to fragment. But it is not getting called If I try the same consecutively. For single try it works fine.
Below is my code that is in my dialog fragment:
val navBackStackEntry = findNavController().getBackStackEntry(R.id.fragment_name)
onClick of button I am doing this
navBackStackEntry.savedStateHandle.set(ConstantUtils.SAVED_DATE, "")
dismiss()
In fragment's onResume I am observing the data like below:
val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle
savedStateHandle?.getLiveData<String>(ConstantUtils.SAVED_DATE)?.observe(viewLifecycleOwner,
Observer { date ->
//Code goes here
savedStateHandle.remove<String>(ConstantUtils.SAVED_DATE)
})
When I click the button from dialog it comes to onResume of fragment, If I again go to the dialog and click on button it does not return to onResume of fragment.
Pleas let me know what mistake am I making here.
If you want to pass argument to fragment and receive one, you can use setFragmentResult() function can pass and receive bundle between fragments.
This is source link for more study
https://developer.android.com/guide/fragments/communicate#fragment-result
I am using Android Navigation Component in my project. What I am not satisfied is the fragment making the decision to do fragment transition to next fragment transition i.e
In my LoginFragment I have this -
viewModel.onLoginPressed(email, password)
.observe(viewLifecycleOwner, Observer {
if (it.userLoggedIn) {
activity?.findNavController(R.id.nav_host_fragment)
?.navigate(R.id.action_loginFragment_to_productsFragment)
}
})
According to me, the view must be dummy and must not do any such decisions of what to do on loginSuccess for example. The viewModel must be responsible for this.
How do I use this navigation component inside viewModel?
The ViewModel doesn't need to know about navigation, but it knows about events and state.
The communication should be more like:
NavHostActivity -> Fragment -> ViewModel
Your Fragment has Views, and click listeners, and state. The user types a user/password and presses a button. This onClick listener will tell the view model -> (pseudo-code) onUserPressedTheLoginButtonWith(username, password)
The ViewModel in turn will receive this, do what it needs (like check if you're already logged or whatever, perhaps the final decision is to navigate to another fragment).
What the ViewModel will do is expose a LiveData like
val navigationEvent = LiveData<...>
So the viewModel will navigationEvent.postValue(...)
The Fragment should observe this viewModel.navigationEvent.observe(...) { }
And in THERE in the fragment it can either navigate directly or -if you have an Interface- use it like:
yourNavigator.navigateTo(...) //either your VM knows the destination or the yourNavitagor has a navigateToLogin() concrete method, this is all "it depends what you like/prefer/etc.".
In summary
Activity Host contains Nav code, irrelevant.
Fragment(s) can communicate to a NavDelegate created by you (and likely injected) or Fragments simply know the details and do it by themselves.
Fragment(s) observe the navigation state/event from the viewModel.
Fragment(s) push events from the UI (clicks, actions, etc.) to this viewModel
The ViewModel decides what to do and updates the "liveData"(s) (there can be more than one type of thing you want to observe, not only navigation).
The Fragment(s) react to this observation and act accordingly, either doing it themselves or delegating (step 2 ^).
That's how I'd do it.
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)
}
}
The doubt I have is conceptual.
Activity A holds Fragment B. Fragment B has a list view which is filled by a custom adapter. Each item from the adapter has a checkbox which onChecked true should display a AlertDialog to allow users to choose a item. My question is, should this interaction with the dialog ( and its different listeners (onClick, onCancel, onKey, etc) ) be handled by the holder class of each item, by the adapter class, the fragment or the activity?
I'd wrap your AlertDialog with DialogFragment instead of using pure AlertDialog, so it will handle dialog recreation on configuration change for you. Now, the interactions from this dialog should be passed back to activity I assume, for this I'd create an interface and make your activity to implement it. And lastly in the DialogFragment#onAttach cast your activity to this interface and hold this reference in some field, once user done any interactions, use this reference to safely pass data back to activity, or any other client, implementing this interface. Hope this makes sense.