Code for question on Github
I have a app which is using fragments + viewmodels. In one fragment I need to present a list of options to the user when they hit a button. I'm doing this using an AlertDialog builder and it works to solve that problem. But if I open the application, hit the button to show the alertdialog, dismiss the dialog, and then rotate the phone to trigger a teardown/buildup the alertdialog get's reshown.
I'm using Android's databinding to bind ui stuff to the ViewModel(not sure if it matters). So the basic flow is:
App starts
Fragment subscribes to 1 observable in the ViewModel
User clicks button
Due to databinding the click is handled in the ViewModel by the method buttonClicked()
Inside vm.buttonClicked() function I get data and update the observable the fragment is observing
Fragment see's new data in observable
Fragment creates AlertDialog in code and shows dialog to user
User either makes a selection or dismisses the dialog
User rotates phone, causes lifecycle change
When the ui get's rebuilt, it shows the AlertDialog again
I have created a simple demo on github.
If you clone that repo then start the app but DONT hit the button, orientation changes go as expected. If you click the button and dismiss the dialog then rotate the phone, you'll see that the AlertDialog get's reshown.
ViewModel's lifecycle is different than of the fragment. When the orientation changes the Fragment gets recreated but the ViewModel stays.
Now what's happening is, when you update the value of the MutableLiveData it broadcasts an update to the Observer; when the Fragment is recreated on rotation change, it subscribes to the LiveData all over again and since there is an update on the value, the MutableLiveData broadcasts the update to the newly subscribed observer.
So you should, for example, save your fragment state in onSaveInstanceState, use the savedInstanceState to get the last update to the MutableLiveData value and check if a change had happened in the observer before showing the dialog.
Or you can move the dialog logic to the on click handler. Showing a dialog in an Observer is not a good approach in my opinion.
If you are working with LiveData and events, this can help you to tackle some scenarios. In summary, you should work with SingleLiveEvents
Related
I have two Fragments let it be Fragment A and Fragment B. In Fragment A I use viewModel.items.collect {} to get data from database. Inside the collect {} the user sees a DialogFragment. The problem is that when I go to Fragment A, I see this dialog, then move to Fragment B, then go back to Fragment A and see this dialog again, although this is not a good. Is it possible to somehow make it so that when returning back from Fragment B to Fragment A, this dialog is not shown?
viewLifecycleOwner.lifecycleScope.launch {
viewModel.items.collect {
MyDialog().show(childFragmentManager, MyDialog.TAG)
}
}
I suspect that property items of your viewmodel is type of StateFlow. If it's true you should change to SharedFlow. StateFlow remembers the last emitted value and emits it for all new subscribers, so when you start collecting you get an event that triggers your dialog but when you back to that Fragment collect is called once again and dialog is triggered again. By default SharedFlow does not repeat the last value and is the best option for one-time events.
I have a global empty state fragment that is shown whenever a network request fails to reach API. The fragment shows a simple error message and a "Retry" button.
I can navigate to the fragment from any other fragments of my app so the "parent" fragment is never the same.
I want to find a way to pass a callback to the empty state fragment so that whenever the user clicks on the "retry" button, I can retry the network call that triggered the empty state and check if the call worked in order to navigate back to the previous fragment.
I think that the recommended way should be using viewModel but since the empty state fragment could be call from any other fragment, the view model containing the network call would also, never be the same.
I wanted to know what would be the best approach here.
Thank you.
I've been struggling with this problem for like a week, it's basically a Backstack problem I didn't found an optimal solution yet. I am working on a project which's flow is like this:
Activity (Fragment_A -> Fragment_B -> Fragment_C -> Fragment_A)
I am setting on Fragment_A one DatePicker and a TimePicker and I have 2 TextViews that redirect the user to Fragment_B. From Fragment_B I send some data to Fragment_C that converts the data and sends it back to Fragment_A.
The problem is that as far as I've understood is that when going back to Fragment_A it goes with a new instance, not the old, so my data there is lost. I resolved for now with sharedPreferences but it seems too complicated for a solution. I've tried with custom beginTransaction() and Navigation component but it didn't turn out well.
I was close, and when going from Fragment_C to Fragment_A there was the new instance on top of the old and at onBackPressed it popped the new instance, and showed me the old but how can I resolve so I won't need to press the back button? I tried to send a Boolean so if it's a new instance pop it, but it popped the old one I guess, because when I pressed back, it just quit the Activity instead of popping the new one.
Any help is appreciated!
An Activity-scoped ViewModel will make sharing data between child Fragments easier. Your Fragments observe the data in the ViewModel, so you don't even have to worry about passing data around. And with Navigation Component, you can use popUpTo and popUpToInclusive to pop Fragments B and C off the back stack, leaving just Fragment A.
If you're not familiar with these concepts, this free course covers them all wonderfully:
https://www.udacity.com/course/developing-android-apps-with-kotlin--ud9012
I have a widget that launches a dialog with two options. One Button to make emergency call, and another to call customer service. Once the dialog is launched from a widget, and I tap on one of the two option, the button doesn't respond. But, if I background the app and bring it to foreground, then that previous selection of the button that I made gets called. I'm using the correct flag when launching the activity from the Widget.
The order of the lifecycle of the Fragment that takes place when things are working normally with the Dialog is below. The similar lifecycle takes place when foreground and backgrounding the app. Not exactly sure why the callback for the buttons on the Dialog doesn't respond when launched from the Widget. Thank you!
OnCreateView()
OnViewCreated()
OnStart()
OnResume()
I was able to fix this problem by simply using navController.navigate(). This allows the NavController to handle the Fragment lifecycle properly and in the correct order.
Since Fragments were introduced I keep my eyes open for a good solution to update a Fragment to which I'm going back to.
This is my scenario:
I have a One-Activity App. When the App starts it shows a FirstFragment. With a button click the user can open a SecondFragment. Both Fragments are full screen and will be opened via androids NavController.
Note: Both fragments get their data from a rx-based repository. The repository and its underlying classes handle all CRUD operations.
What I want to do:
Updating the FirstFragment as soon as the user clicks back on the SecondFragment OR in other words: I want to update the FirstFragment as soon as it comes on top from the background.
The Problem
The FirstFragment gets no callback or livecycle event if it comes into view again.
In the past I solved this problem with a ListenerInterface that was triggered in the activities onBackPressed or with a broadcast event. But is there no better solution? I question if there is a possibility via LiveData or via NavController or anything else. Or does anybody know how google solves this problem?
What I NOT want to do
As my data layer is based on rx I could of course have a BehaviourSubject that I subscribe on in the FirstFragment. Then when I switch to SecondFragment I could keep the subscription undisposed so the FirstFragment will be updated as soon as i call onNext on the BehaviourSubject. I've seen apps doing this and it seems wrong to me. In my opinion a subscription should be disposed as soon as another ViewModel steps into place.
Thanks in advance
Android, notify backstack fragments on some change