I've looked at several of the questions pertaining to handling an orientation change in a DialogFragment, and the common theme seems to be that the askers aren't getting any answers, at none that I understand...
So, I'd like to take a different approach... Is there a way I can programmatically dismiss the dialog... Looking at my LogCat, I can see that if the dialogfragment is active, and the screen orientation is changed, then my Activity restarts.
Along the way, the DialogFragment is also restarted, but this happens after onCreate and before onResume (as close as I can tell). It's fairly simple for me to detect this in my dialog, and what I'd like to do is abort the dialog view creation in this case. What I've tried so far is like this:
if (normal_conditions_detected)
{
View v = inflater.inflate (R.layout.dialog_layout, container, false);
final Dialog d = getDialog();
d.setTitle (R.string.sensor_config);
... more stuff
return v
}
else
{
getDialog().cancel();
return null;
}
This does avoid the null pointer nonsense I was getting, but now I get what I can only describe as an empty dialog, even with the cancel() command in there. Is there a way I can get my dialogFragment code to refuse to create the view?
When you rotate your device, activity executes
onSaveInstanceState
method and save the current state which means it saves your fragment state as well, after recreation it executes
onCreate(Bundle savedInstanceState)
method again, savedInstanceState holds your old data, in here you can make something like
if (savedInstanceState == null){
YourDialogFragment f = new YourDialogFragment()
f.show // etc
}
Only once your fragment will be created, instead of putting a control inside fragment, you can control it by the activity
Related
When I open an dialog and navigate the screen from A Fragment to B Fragment,
The dialog is still shown above B Fragment.
I do dismiss & null to the dialog fragment property. But still, sometimes, it exists.
So, What I do is, calling this in B Fragment. (In this case, Bottom Sheet)
(requireActivity().supportFragmentManager).findFragmentByTag(MyBottomSheet.TAG)?.let {
if(it is MyBottomSheet) {
it.dismiss()
}
}
This is better, but it doesn't work sometimes too. So, I thought this is because screen animation. So, I gave 200L before calling dismiss(). Then, It seems to work better.
However, I don't think this is the true and complete solution.
How can I restrict a dialog to a fragment so that I don't need to consider those dismiss thing here and there.
When I create DialogFragment or BottomSheet. I set attachToParent = true like this.
_binding = DataBindingUtil.inflate(inflater, R.layout.modal_bottom_sheet_my, container, true)
And also when I navigate between fragments, I launch it singleTop.
But none of them worked. How can I solve this questioin? Is there something like single instance?
Here's the issue I'm having. I've got an activity A that has a fragment F, which is contained in FragmentPagerAdapter FPA, which is in view V. (A->V->FPA->F)
When A gets destroyed (or in this case, swapped out), F is attached, and is in FPA, which is in V. However, when A gets recreated (someone hits the back button back into the activity, for instance), V and FPA don't exist, so F is recreated (in the attached state!), but to something that doesn't exist, so it's not in the view hierarchy at all. Then, when FPA tries to instantiateState on this fragment, it'll try to attach it, which does nothing because it's already attached to thin air.
There are obviously a few ways to fix this (have V and FPA exist in onCreate of the activity, so that the fragment has somewhere to go when it gets created, for instance), but I'd like to continue to lazily create FPA and V only when needed.
Thusly, it seems like updating the state of F to detached in onDestroy() would be desirable. However, state is saved in onPause(), which means I'm kinda out of luck here.
Is there a way to update the saved state of F in A's onDestroy()? Is there a way to say "don't rehydrate this fragment if the activity gets destroyed"? Is there some other obvious way of thinking about this that I'm not considering? It feels like I'm going about things the wrong way here.
I've had my trouble with FragmentPagers. What I do is passing a null bundle in the Activity onCreate() and then create everything from scratch every time it is created. Like so:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
// do my stuff
}
This way the fragment wont be passed on when the activity is recreated.
You wrote:
Is there some other obvious way of thinking about this that I'm not considering?
This doesn't directly answer your title question but provides a convenient solution to how to preserve Fragment state in a ViewPager.
You can save the Fragment states when the Activity is destroyed by tagging the Fragment in the Activity that initializes the Fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photos_pager_activity);
MyImageFragment fragment;
if (savedInstanceState != null) {
fragment = (MyImageFragment) getFragmentManager().findFragmentByTag("my_image_fragment_tag");
} else {
fragment = new MyImageFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "my_image_fragment_tag").commit();
}
See also:
Uses of fragment tags
ViewPager and fragments — what's the right way to store fragment's state?
I have an application that has two fragments as actionbar tabs. The fragments are attached/detached when switching between the tabs. Any time I switch a tab, change the orientation, or press back to exit the application, the view is destroyed. I need it to be restored to its previous state when it is reopened. I know, at least on the orientation change, to use onSaveInstanceState and save the data there so I can restore it when the view is recreated. However, for some reason even though the data gets saved properly to the outState bundle and is read properly from the savedInstanceState bundle, the view doesn't update to what it should update to. For example, I start a service and while that service is running I need to hide two buttons and show two other buttons in their place. I use a boolean to check if the service is running, then put it in the outState so I can see which buttons to show or hide. My code for that is:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("isRunning", isRunning);
}
In onCreateView:
if (savedInstanceState != null) {
isRunning = savedInstanceState.getBoolean("isRunning", false);
if (isRunning) {
showStopButton();
}
}
And the showStopButton code is:
private void showStopButton() {
btnStart.setVisibility(View.GONE);
btnReset.setVisibility(View.GONE);
btnStop.setVisibility(View.VISIBLE);
btnLoop.setVisibility(View.VISIBLE);
}
So all this works, the boolean is found as true while the service is running, and showStopButton() is called. However, it doesn't appear to actually do anything. The view state just resets itself to as if the first two buttons (which I want to be hidden) are shown instead of the ones I actually want to be shown. Any idea why this is happening/how to fix it?
I also have a listview that I need to stay populated with the same values as before that I can't get to work either.
Also, onSaveInstanceState isn't called when switching tabs (and I think not when pressing the back button either?). How should I go about retaining the view state in these cases?
I started using DialogFragment, because they are working nicely through orientation changes, and stuff. But there is nasty problem I encountered.
I have AsyncTask that shows progress DialogFragment and dismisses it onPostExecute. Everything works fine, except when onPostExecute happens while application is in background (after pressing Home button, for example). Then I got this error on DialogFragment dismissing - "Can not perform this action after onSaveInstanceState". Doh. Regular dialogs works just fine. But not FragmentDialog.
So I wonder, what is the proper way of dismissing DialogFragment while application is in background? I haven't really worked with Fragments a lot, so I think that I'm just missing something.
DialogFragment has a method called dismissAllowingStateLoss()
This is what I did (df == dialogFragment):
Make sure that you call the dialog this way:
df.show(getFragmentManager(), "DialogFragment_FLAG");
When you want to dismis the dialog make this check:
if (df.isResumed()){
df.dismiss();
}
return;
Make sure that you have the following in the onResume() method of your fragment (not df)
#Override
public void onResume(){
Fragment f = getFragmentManager().findFragmentByTag("DialogFragment_FLAG");
if (f != null) {
DialogFragment df = (DialogFragment) f;
df.dismiss();
}
super.onResume();
}
This way, the dialog will be dismissed if it's visible.. if not visible the dialog is going to be dismisded next the fragment becomes visible (onResume)...
This is what I had to do to achieve what you want:
I have a Fragment activity on which i was showing a dialog fragment named fragment_RedemptionPayment which is globally declared at the top. The following code dismisses the DialogFragment if it was showing before the activity goes in background and comes back in foreground.
#Override
public void onResume() {
super.onResume();
if(fragment_RedemptionPayment.isVisible()){
fragment_RedemptionPayment.dismiss();
}
}
Another new way of checking the state before calling dismiss is this:
if(!dialog.isStateSaved){
dialog.dismiss()
} else {
//Change the UI to suit your functionality
}
In this way its is checked that state is saved or not, basically on pause and onSaveInstanceState has been called.
For Java you can use isStateSaved()
A solution that might work is setting Fragment.setRetainInstance(true) in your dialogfragment, but that's not the prettiest of fixes.
Sometimes I have noticed that I have to queue up my dialog actions to let the framework restore the state first. If you can get hold of the current Looper (Activity.getMainLooper()) and wrap that in a Handler you could try passing your dismissal to the back of the queue by posting a runnable on that queue.
I often end up using a separate fragment that it retaininstance(true) that has a ResultReceiver. So i pass on that result receiver to my jobs and handle callbacks in its onReceive (often as a router for other receivers). But that might be a bit more work than it is worth if you are using async tasks.
1) I launch a background task (via AsyncTask)
new FindJourneyTask().execute(); // FindJourneyTask extends AsyncTask
2) Still in the main thread (just before new thread is launched) I create a dialog with showDialog(dialogId)
// this method is in FindJourneyTask
protected void onPreExecute() {
showDialog(DIALOG_FINDING_JOURNEY);
}
3) Screen orientation changes and the Activity is recreated
4) How can I now dismiss the dialog from the FindJourneyTask? Calling dismissDialog(dialogId) does nothing.
// this method is in FindJourneyTask
protected void onPostExecute(FindJourneyResult result) {
dismissDialog(DIALOG_FINDING_JOURNEY); // does nothing
}
This is a common problem, and there are no real good solutions. The issue is that on screen orientation change, the entire Activity is destroyed and recreated. At the same time, the Dialog you previously had is re-created in the new Activity, but the old background task still refers to the old Activity when it tries to dismiss the dialog. The result is that it dismisses a dialog which was long ago destroyed, rather than dismissing the dialog the new orientation created.
There are three basic solutions:
Override the default orientation-handling code so that your Activity is not destroyed upon rotation. This is probably the least satisfactory answer, as it blocks a lot of code that is automatically run upon orientation changes.
Create a static member variable of your Activity that references the Activity itself, so you can call STATIC_ACTIVITY_VARIABLE.dismissDialog().
Code a solution in which the background task keeps track of the current Activity and updates itself as necessary.
These three solutions are discussed at length here: http://groups.google.com/group/android-developers/browse_thread/thread/bf046b95cf38832d/
There is a better solution to this problem now which involves using fragments.
If you create a dialog using DialogFragment, then this fragment will be responsible for maintaining your dialog's lifecycle. When you show a dialog, you supply a tag for your fragment (DialogFragment.show()). When you need to access your dialog, you just look for the necessary DialogFragment using FragmentManager.findFragmentByTag instead of having a reference to the dialog itself.
This way if device changes orientation, you will get a new fragment instead of the old one, and everything will work.
Here's some code based also in #peresisUser answer:
public void onSaveInstanceState(Bundle outState) {
AppCompatActivity activity = (AppCompatActivity) context;
FragmentManager fragmentManager = activity.getSupportFragmentManager();
DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag("your_dialog_tag");
if(dialogFragment!=null) {
Dialog dialog = dialogFragment.getDialog();
if(dialog!=null && dialog.isShowing()) {
dialogFragment.dismiss();
}
}
}
This is long after the question was asked and answered, but i stumbled upon this problem also and wanted to share my solution...
I check in onSavedInstance() which runs on orientation change, whether the dialog is showing or not with dialog.isShowing(), and pass it into outState variable. Then in your onCreate(), you check this var if it's true. If it is, you simply dismiss your dialog with dialog.dismiss()
Hope this helps others :()
I tried adding setRetainInstance(true); on OnCreate function of DialogFragment. This will cause dialog to dismiss on rotation.
Just add this line to specific activity in your Manifest to solve this problem android:configChanges="orientation|screenSize|smallestScreenSize"
like this,
<activity
android:name=".PDFTools"
android:exported="false"
android:configChanges="orientation|screenSize|smallestScreenSize"
android:theme="#style/Theme.DocScanner.NoActionBar" />