DialogFragment's Dialog loses set properties after a conf change - android

I created a progressdialog following the new "fragment way" with this code:
public class DialogUpdateTrackRecords extends DialogFragment {
public static DialogUpdateTrackRecords newInstance() {
DialogUpdateTrackRecords frag = new DialogUpdateTrackRecords();
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public ProgressDialog onCreateDialog(Bundle savedInstanceState) {
this.setCancelable(false);
ProgressDialog dialog= new ProgressDialog(getActivity());
dialog.setTitle("Caricamento tragitti");
dialog.setIndeterminate(true);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("Sending something");
return dialog;
}
}
and I show it from an activity like this way:
FragmentManager fm= getSupportFragmentManager();
uploadDialogFrament= (DialogFragment) getSupportFragmentManager().findFragmentByTag("sendDialog");
if(uploadDialogFrament!=null)
uploadDialogFrament.dismiss();
FragmentTransaction ft= fm.beginTransaction();
uploadDialogFrament= DialogUpdateTrackRecords.newInstance();
uploadDialogFrament.show(ft,"sendDialog");
fm.executePendingTransactions();
((ProgressDialog)uploadDialogFrament.getDialog()).setMax(trackRecordSize);
if( trackRecordSize > 1 )
((ProgressDialog)uploadDialogFrament.getDialog()).setIndeterminate(false);
As you can see, I get a reference to the dialog and I set it (depending on my needs). Everything works like a charm but.. if I rotate the device, the dialog goes back to its pristine state instead of retaining (in the example: the bar is set back to an indefinite state) my new settings. I checked if I was creating and displaying a new dialog erroneously, but this is not the case. So.. how could I keep my changes over the recreation of the activity?

Have you tried
setRetainInstance(true)
Since the DialogFragment extends the base Fragment, im pretty sure that will work.

OK.. this is simply a workaround and I DO NOT SUGGEST IT. Notwithstanding the displaimer: I really don't have the necessary time to dig into the logic of this bug and I used this simple workaround. To make it short: each time I resume the dialog fragment, I update it with all the settings it should automatically have. Here's an example:
public class DialogUpdateTrackRecords extends DialogFragment {
private boolean indeterminate= true;
public static DialogUpdateTrackRecords newInstance() {
DialogUpdateTrackRecords frag = new DialogUpdateTrackRecords();
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onResume() {
super.onResume();
((ProgressDialog)getDialog()).setIndeterminate(indeterminate);
}
#Override
public ProgressDialog onCreateDialog(Bundle savedInstanceState) {
this.setCancelable(false);
ProgressDialog dialog= new ProgressDialog(getActivity());
dialog.setTitle("Caricamento tragitti");
dialog.setIndeterminate(true);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("Invio delle statistiche completate a ISF Modena in corso");
return dialog;
}
public void setIndeterminate(){
((ProgressDialog)getDialog()).setIndeterminate(false);
indeterminate= false;
}
/*
[italian soh]
http://stackoverflow.com/questions/12433397/android-dialogfragment-disappears-after-orientation-change
thanks, google. Thanks.
[/italian soh]
*/
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
}
Anyway: if anybody finds the reason of that weird behavior I would be more than happy to fix my code properly.

Related

Show dialog above view pager that has nested fragments

I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:
public class MainActivity extends AppCompatActivity {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
// ...
tabLayout.setupWithViewPager(viewPager);
}
#Override
protected void onResume() {
super.onResume();
MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);
if (dialog == null) {
new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
}
}
// ...
}
When the activity is resumed a dialog fragment is shown inside the main activity.
The single fragment inside the view pager is defined like this:
public class RootFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.root_fragment, container, false);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
}
return root;
}
}
This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:
public class FirstLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.first_level_fragment, container, false);
root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
if (f == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
}
}
});
return root;
}
public static class SecondLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.second_level_fragment, container, false);
}
}
}
This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:
E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity
ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No
view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md
Without the dialog appearing everything works great. You can test it by downloading the test project.
It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?
It is important that the second fragment is retained.
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment
When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.
You have to make two changes to fix this issue.
Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager
getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
Add the SecondLevelFragment using FragmentManager
getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.
If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.
Override the back press on activity
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
if(fragment instanceof RootFragment){
boolean handled = ((RootFragment)fragment).onBackPressed();
if(handled){
return;
}
}
super.onBackPressed();
}
And handle the back press on RootFragment
public boolean onBackPressed() {
int count = getChildFragmentManager().getBackStackEntryCount();
if(count > 0){
getChildFragmentManager().popBackStackImmediate();
return true;
}
return false;
}
I created a Pull request to your repository . please check
https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1
Let me know if any questions.
If you override onDismiss so resolved crash. enjoy it.
#Override
protected void onResume() {
super.onResume();
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if(dialog == null){
CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);
}
}
public static class CustomDialog extends DialogFragment {
public static CustomDialog newInstance() {
CustomDialog d = new CustomDialog();
return d;
}
#Override
public void onDismiss(DialogInterface dialog) {
// super.onDismiss(dialog);
Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
}
#Override
public void onCancel(DialogInterface dialog) {
// super.onCancel(dialog);
Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Dialog");
builder.setMessage("This is a message!");
builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
return builder.show();
}
}
If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.
From the docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.
If you use this you can also remove the setRetainInstance(true) calls.
Well, I had downloaded your Test app and it seems that I have fixed the problem.
In your FirstLevelFragment class, comment the following line
//if (nestedNestedFragment == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
//}
And
Comment setRetainInstance(true); in SecondLevelFragment
I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.
Hope this Helps,
Thanks.

Fragment setTitle and set Menu

I want to set activity tilte and menu from fragment,
(Below code is working well, but i looking for a better implementation)
in 1st step, i created a interface
public interface IGetDetails {
public String getTitle();
public int getMenu();
}
every fragment implements this Interface
for example
public class TestFragment extends Fragment implements IGetDetails {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.tes, null);
}
#Override
public String getTitle() {
return "test Title";
}
#Override
public int getMenu() {
return R.menu.test;
}
}
In Activity
I used a stack to record frgments
Stack<Fragment> fragmentStack=new Stack<>();
when user navigate from one fragment to another,i added new fragment to stack.example
currentFragment=new TestFragment();
fragmentStack.push(currentFragment);
fragmentTransaction.add(R.id.bodyFragment, currentFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
invalidateOptionsMenu();
setTitle();
in onBackpressed
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>0){
FragmentManager manager =getSupportFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove( fragmentStack.pop());
trans.commit();
manager.popBackStack();
currentFragment=fragmentStack.lastElement();
setTitle();
invalidateOptionsMenu();
}else{
super.onBackPressed();
}
}
setTitle Method in Activity
public void setTitle() {
IGetDetails fragment = (IGetDetails) currentFragment;
if (fragment != null) {
String title = fragment.getTitle();
if (title == null || title.equals("")) {
setTitle(getString(R.string.app_name));
} else {
setTitle(title);
}
}
}
Just override onAttach and onDetach() in your Fragment Class.
for ex-
#Override
public void onDetach() {
// TODO Auto-generated method stub
super.onDetach();
getActionBar().setTitle("My Account");
}
For Menus your Fragment has to call
setHasOptionsMenu(true)
in it's onCreate method.
Then just override onCreateOptionsMenu and onOptionsItemSelected to populate your menu and listen for clicks.
To set the title from the fragment use getActivity().setTitle(), for example in the onActivityCreated method :
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle("My Fragment Title");
}
Note: this does only work for API>=11
For setting title of Current fragment.
currentFragment=new TestFragment();
...
setTitle(currentFragment.getTitle());
You just need to modify in setTitle() body to better implement it:
public void setTitle(String title) {
getActivity().setTitle(title);
}
For Menu options change, you are calling invalidateOptionsMenu() which will call onPrepareOptionsMenu() and your R.menu.menu will be set.
No modification is required in this case
For titles, you need to set the title in onStart():
#Override
public void onStart() {
super.onStart();
((FragmentActivity)getActivity()).setTitle();
}
}
Since onStart() is called every time the fragment will be loaded, you will always be displaying the title of the currently displayed fragment.
To load options menu of your Fragment:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//if using fragments from support library
((FragmentActivity)getActivity()).setTitle();
//otherwise
getActivity().setTitle();
}
}
Now override onCreateOptionsMenu() and load the menu you want to display there. There's no need for a workaround when Android provides you with a proper solution.

How to SaveInstanceState on dialog class?

In my application i have a 45 "editText" . now, when the user rotate the device the dialog closed and all the data are gone. so i created a "public static boolean isShow = false" variable in the Dialogclass and it true is the dialog are "show" and on my MainActivity i saved to the bundle and it's work my dialog not closed beacuse i create a new dialog when "isShow" = false. but all the data cleared . My question is do i need to save all of the 45 editText one by one? if i need , there is a way to cancel the rotation only for this dialog?
public class DialogSetting extends Dialog {
public static boolean isShow = false;
public DialogSetting(Context context) {
super(context);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.dialog_setting);
this.context = context;
this.show();
isShow = true;
this.setOnCancelListener(new DialogInterface.OnCancelListener()
{
#Override
public void onCancel(DialogInterface dialog)
{
isShow = false;
}
});
}
public void close(View v){
isShow = false;
this.dismiss();
}
MainActivity:
#Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putBoolean("dialogSetting", DialogSetting.isShow);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
DialogSetting.isShow = savedInstanceState.getBoolean("dialogSetting");
if(DialogSetting.isShow){
new DialogSetting(this);
}
First of all: You should inherit your dialog from DialogFragment. If you're doing this the FragmentManager will take care of recreating the Fragment after rotating the device.
So here's how your Dialog class should look like:
public class MyDialog extends DialogFragment {
public static MyDialog newInstance(){
MyDialog f = new MyDialog();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_setting, container, false);
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
// Everything else that needs to be done to setup the dialog (findViewById, Listener etc.)
return view;
}
}
Here's how you need to call the Dialog (slightly more complex):
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
MyDialog dialogFragment = MyDialog.newInstance();
dialogFragment.show(ft, "dialog");
To prevent any loss of data because of the rotation, you need to save the data in a bundle inside the dialog (basically the same as you did in your Activity).
Here's a basic example of saving the entered text of an EditText:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
text = savedInstanceState.getString("et_text");
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("et_text", mEditText.getText().toString());
}
You can read more about DialogFragments in the docs(click).

Prevent "Dialog" Activity from interacting with background activity when clicking outside bounds

I'm creating an activity that looks like a dialog.
Here is the style:
<style name="TablesDialogActivity" parent="#android:style/Theme.Holo.Dialog">
<item name="android:windowBackground">#drawable/frame_background_left</item>
</style>
Here is the activity onCreate():
protected void onCreate(Bundle savedInstanceState) {
getWindow().setFlags(LayoutParams.FLAG_NOT_TOUCH_MODAL, LayoutParams.FLAG_NOT_TOUCH_MODAL);
getWindow().setFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
super.onCreate(savedInstanceState);
}
And also inside the activity touch interceptor:
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
finish();
return true;
}
return false;
}
It pretty much works, activity finishes on touch outside dialog bounds, but it also interacts with buttons on the background activity which is bad. Documentation on the onTouchEvent says that you should return true if you consumed the touch event. I return true, but it doesn't seem that way.
hm..interesting)
in my app i use fragments, so i use DialogFragment instead of Dialog.
i created safe show dialog method
private static void showDialog(FragmentManager fragmentManager, String dialogTag, BeamDialogData data) {
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.commit();
fragmentManager.executePendingTransactions();
Fragment prev = fragmentManager.findFragmentByTag(dialogTag);
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
BeamDialog beamDialog = new BeamDialog();
beamDialog.setData(data);
beamDialog.show(fragmentManager, dialogTag);
}
public static void showDialogSafe(final FragmentManager fragmentManager, final String dialogTag,
final BeamDialogData data, Handler handler) {
handler.post(new Runnable() {
#Override
public void run() {
showDialog(fragmentManager, dialogTag, data);
}
});
}
BeamDialog is my custom DialogFragment
so there are not backgrounds clicks)
i hope, that this will useful for you)
Note, that you will need the whole code from the question too.
In addition to my solution inside background activity (or just base activity of your application) I added:
private FrameLayout touchInterceptor;
#Override
protected void onPause() {
if (touchInterceptor.getParent() == null) {
((ViewGroup) findViewById(android.R.id.content)).addView(touchInterceptor);
}
super.onPause();
}
#Override
protected void onResume() {
((ViewGroup) findViewById(android.R.id.content)).removeView(touchInterceptor);
super.onResume();
}
And in the onCreate():
// For intercepting clicks from dialog like activities
touchInterceptor = new FrameLayout(this);
touchInterceptor.setClickable(true);
Now works like a charm! :)

Showing a progressbar from within a fragemnet

I have a fully working app which contains a fragment declared as follows:
public class SearchableListFragment extends Fragment implements TabListener
At one point during the work of the fragment, we execute a rather slow loop. Imagine something like:
for(int i = 0;i < large_number;i++)
{
// do complex maths
}
but this loop takes rather a long time and I'd like to have a progress bar appear during the loop. I have seen a variety of examples of adding dialogs, but they all seem to fail because one part or another appears not to apply to fragments.... or they are not applicable because they assume one thing or another that simply does not apply in my case. How can I wrap my loop in some code that will display a progress bar (either a linear bar or a swirling circle - whatever's easiest).
EDIT: The fragment is deployed within a SherlockFragmentActivity. Also the fragments are being implemented via android.support.v4.app.*
You might want to take a look at this question: addView not visible in custom FrameLayout but clickable
Especially its "SOLUTION" ( http://pastebin.com/2xjnCDLS ), which is a viewgroup which switches between the progressbar and the contentview(s), and is easily usable in your layout-xml, was very helpful for me in various occasions. Just call showProgressbar() before and showNext() after you've loaded your content/made your computations.
I have this ModalProgress class that I use every where to show modal progress, I guess you can use it as it is:
public class ModalProgress extends DialogFragment {
public static void show(Activity activity, String title,
String message) {
if (activity == null) {
return;
}
FragmentManager fm = activity.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
hideInternal(ft, fm);
// Create and show the dialog.
DialogFragment newFragment = new ModalProgress();
Bundle args = new Bundle();
args.putString("title", title);
args.putString("message", message);
newFragment.setArguments(args);
newFragment.show(ft, "dialog");
}
private static void hideInternal(FragmentTransaction ft, FragmentManager fm) {
Fragment prev = fm.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
}
public static void hide(FragmentActivity activity) {
if (activity == null) {
return;
}
FragmentManager fm = activity.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
hideInternal(ft, fm);
ft.commit();
}
public ModalProgress() {
setCancelable(false);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
ProgressDialog dialog = new ProgressDialog(getActivity());
String title = args.getString("title");
String msg = args.getString("message");
if (!TextUtils.isEmpty(title)) {
dialog.setTitle(title);
}
if (!TextUtils.isEmpty(msg)) {
dialog.setMessage(msg);
}
return dialog;
}
}
Now move your loop code inside AsyncTask:
private class LongTask extends AsyncTask<Void, Void, Void> {
protected void onPreExecute (){
ModalProgress.show(getActivity(), "Working", "Please wait...");
}
protected Void doInBackground(Void... voids) {
for (int i = 0; i < count; i++) {
// your loop here
}
return null;
}
protected void onPostExecute(Void result) {
ModalProgress.hide(getActivity());
}
}
and then just execute AsyncTask.

Categories

Resources