Android prevent DialogFragment opening twice - android

I am trying to prevent my DialogFragment opening twice. Here is what I do:
I try to keep only one instance of my fragment. I create and add my fragment like this:
//MyFragment.java
public static MyFragment mInstance;
public static void instantiateFragment() {
MyFragment myFragment = MyFragment.getInstance();
if(!myFragment.isAdded()) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(myFragment, TAG);
ft.commit();
}
}
private static MyFragment getInstance() {
if(mInstance == null) {
mInstance = new MyFragment();
}
return mInstance;
}
And when a button is clicked, I intentionally try to add fragment twice like this:
MyFragment.instantiateFragment();
MyFragment.instantiateFragment();
But I get IllegalStateException: Fragment already added. Any ideas about that?
Thanks.

Indeed it's a problem with asynchronous commit of transactions, so as #Android jack stated you can use executePendingTransactions() like in this answer,
or even better use commitNow(),
or try something like this:
public static void instantiateFragment() {
Fragment myFragment = getSupportFragmentManager().findFragmentByTag(TAG);
if (myFragment == null) {
myFragment = MyFragment.getInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(myFragment, TAG);
ft.commit();
}
}

I think this has to do with the asynchronous behaviour of fragment transactions.Fragment Transactions are committed asynchronously. So at first call, your fragment is added but it is committed asynchronously.Again in your next call your fragment is not added as it is not committed yet so !myFragment.isAdded() returns false.Then while adding the fragment the previous transaction is committed due to which it raises exception.
Try to use this
getFragmentManager().executePendingTransactions();
before your (!myFragment.isAdded()) code.

Related

FragmentManager.findFragmentByTag() returns null

I have found a specific case when FragmentManager.findFragmentByTag("tag") returns null.
I have a gut feeling it has to do with timing?
I have a networking library with the following callbacks:
onStart()
{
Utils.ShowLoadingDialog("loading");
}
onFinnish()
{
Utils.DismissLoadingDialog("loading");
}
Then in my Utils class I have the following code:
public void showLoadingDialog(String title, String message, String tag) {
DialogFragment loadingDialogFragment = new LoadingDialogFragment();
Bundle args = new Bundle();
args.putString(CommonBundleAttributes.CONNECTING_ACTIVITY_DIALOG_TITLE, title);
args.putString(CommonBundleAttributes.CONNECTING_ACTIVITY_DIALOG_MESSAGE, message);
loadingDialogFragment.setArguments(args);
FragmentTransaction transaction = fragManager.beginTransaction();
loadingDialogFragment.show(transaction, tag);
}
public void dismissLoadingDialog(String tag) {
DialogFragment dg = (DialogFragment) fragManager.findFragmentByTag(tag);
if (dg != null) {
// this reference isn't null so the dialog is available
dg.dismissAllowingStateLoss();
}
}
Now this generally works fine. However in cases when the network layer detects there is no internet. It will throw an error and then immediately call onFinnish(). In this case the Utils.DismissDialog(tag) does nto find the fragment and therefore does not dismiss it?
You can use executePendingTransactions() to wait for the fragment transaction to come through.
public void dismissLoadingDialog(String tag) {
fragManager.executePendingTransactions();
DialogFragment dg = (DialogFragment) fragManager.findFragmentByTag(tag);
if (dg != null) {
// this reference isn't null so the dialog is available
dg.dismissAllowingStateLoss();
}
}
Use TRY-CATCH or even IF statement to check current internet-connection.
This can be case with the committing the transaction.
Just check if in your show method if you have commit the transaction for Dialog fragment or not.
transaction.commit();
Until that your fragment manager does not have fragment to be added in that.
Also you need to make sure you are finding fragment from same Activity's fragment manager to which you committed fragment.

Android - support.v4.Fragment

I read Android documentation on how to add a Fragment to an activty. It says that to add a Fragment to an Activty I should write this code inside the Activity class:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
but this is for android.app.Fragment class.
For android.support.v4.Fragment, documentation say that instead of getFragmentManager() I should call getSupportFragmentManager() and that Activity must extends FragmentActivty.
So I did this change, and now this is my activity code:
public class ExampleActivity extends AppCompactActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
AppCompactActivity is a subclass of FragmentActivity so I respected the constraints.
The problem is that if I run my application I get this error:
java.lang.RuntimeException:
com.myapplication.ExampleActivity#13388c6 must implement OnFragmentInteractionListener
I typed OnFragmentInteractionListener on Android doc web search and this is what I get:
Immediately below there is the link
https://developer.android.com/training/basics/fragments/communicating.html
that shows me a guide to communication between fragments. It speaks about ListFragment and I don't care it.
I'm very very confuse because every time I read documentation there are always things that force me to make internet search to find workaround or fix to problems.
Is there a persone that can explain me first how to fix this problem.
From the tutorial, you can read the following:
In order to receive event callbacks from the fragment, the activity
that hosts it must implement the interface defined in the fragment
class.
So, implement it in your Activity with something like this in your fragment:
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
Why did you need it? Because you've forced the host activity to implement it with something like this:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}

Pass object data between fragments and activity in android

I want to pass a complex object between activity and fragments as well as fragments and fragments. Currently, the main activity create a fragment input object and set that as a member of the fragment needed to be open. Similarly, when another fragment wants to load another fragment it creates fragment input and notifies the main activity. See Main and child fragment code below. My question, is this correct implementation. Sometimes I encountered input being null in child activity, if the activity pauses and restarts.
Please tell me what I have done wrong, whats the best way to pass data.
public class FragmentInput {
public String url = "";
public String title = "";
public String time = "";
... other memebers
}
Main Activity
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
BaseFragment fragment = new LandingFragment();
**FragmentInput input = new FragmentInput();
input.stringinput = stringinput;
fragment.input = input;
fragmentTransaction.replace(R.id.fragment, fragment);
fragmentTransaction.commit();**
public void replaceFragment(BaseFragment fragment) {
if (fragment == null)
return;
if (fragment instanceof firstFragment) {
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(0, 0);
fragmentManager.popBackStackImmediate(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
} else {
String ttag = fragment
.getClass().toString();
Fragment tempF = fragmentManager.findFragmentByTag(ttag);
if (tempF != null)
return;
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.fragment_enter,
R.anim.fragment_exit, R.anim.fragment_leftenter,
R.anim.fragment_leftexit);
fragmentTransaction.replace(R.id.fragment, fragment, ttag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
ChildFragment
#Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
try {
activity = getActivity();
resource = activity.getResources();
view = getView();
**if (input != null) {
String url= input.url;**
button.onclick(){
FragmentInput input = new FragmentInput();
input.url = path;
input.title = resource.getString(R.string.txt_from_gallery);
**BaseFragment fr = new otherFragment();
FragmentChangeListener fc = (FragmentChangeListener) getActivity();
fr.setInput(input);
fc.replaceFragment(fr);**
}
}
If your fragments attach to same activity, you can store your objects in activity and access to objects like below:
((YourActivity)getActivity()).getYourObjects();
If you are storing your objects in bundle in you activity i recommand to call the code sample i gave above in onActivityCreated() method of your fragments to avoid null pointer exception.
If you want to pass your objects between activities or fragments in bundle. You should implement Parcelable to your objects and pass them.
What's Parcelable?
http://developer.android.com/reference/android/os/Parcelable.html
Parcelable is more efficient but you can check Serializable:
http://developer.android.com/reference/java/io/Serializable.html
It's not a good design to pass large objects in bundle.
Also you can pass your objects with interfaces or you can pass them with bus events. You can check Guava or OttoBus.
http://square.github.io/otto/
In this case you can use static holder (it's a bit similar to a singleton pattern).
Create a new class
public class Holder
{
private static FragmentInput input = null;
public static void setInput(FragmentInput value) { this.input = value; }
public static FragmentInput getInput() { return input; }
}
In your main activity, after you create your new FragmentInput object
hold it on the Holder
Holder.setInput(input);
And you can access it anywhere, simply call
FragmentInput myInput = Holder.getInput();

commitAllowingStateLoss on DialogFragment

I have an IllegalStateException on showing a DialogFragment :
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
i know why its happening but i want to using commitAllowingStateLoss on showing dialog by overriding DialogFragment show function :
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit(); //replace it by commitAllowingStateLoss
}
but i don't access to mDismissed and mShownByMe variables , how can i access those variables to modify them as it's parent did.
I think to prevent throwing IllegalStateException on DialogFragment might be better to use :
YourDialogFragment dialogFragment = new YourDialogFragment();
fragmentManager.beginTransaction().add(dialogFragment, YourDialogFragment.TAG_FRAGMENT).commitAllowingStateLoss();
instead of using show() on DialogFragment.
The solution about commitAllowingStateLoss works if your DialogFragment has no state to save, otherwise they will be lost like the function name told. But I think in most cases we have state to save, that is the major benefit of DialogFragment: Android recreate it and maintains its state automatically.
A better solution would be to check if the recreate process done, if not then return to caller, which is either an Activity or a FragmentActivity, it should call mark it and call the show function again later in its onPostResume() or onResumeFragments() callback, which we can make sure all fragments are recreated.
Here is an overridden show() from a subclass of DialogFragment:
public boolean show(FragmentManager fragmentManager) {
if (fragmentManager.isStateSaved()) return false;
show(fragmentManager, tagName);
return true;
}
Origin dialog fragment
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit(); //replace it by commitAllowingStateLoss
}
I don't know mDismissed, mShownByMe variables used for, so should be better if override show(FragmentManager, String) method of DialogFragment and it works fine with me
override fun show(manager: FragmentManager?, tag: String?) {
if (manager?.isDestroyed == false && !manager.isStateSaved) {
super.show(manager, tag)
}
}
isStateSaved available from appcompat >= 26.0.0 or androidx

java.lang.IllegalStateException: Activity has been destroyed

Working with Robolectric , I'm very new to android. I made a first test class using Activity. It worked nicely.
Now I want make a test for fragment.
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment(fragment);
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment(LoginFragment fragment) {
FragmentManager fragmentManager = new FragmentActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText)fragment.getActivity().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is my first test class for Fragment class. It throws
"java.lang.IllegalStateException: Activity has been destroyed" in startFragment#fragmentTransaction.commit().
Anyone knows how to fix this ?
You can find whole source from https://github.com/msbaek/frame-test
Thanks in advance !!
In my case, specifically, my problem was when creating the activity.
I was using
activity = Robolectric.buildActivity(MyActivity.class).get();
And it should be
activity = Robolectric.buildActivity(MyActivity.class).create().get();
Hope it helps someone :D
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment();
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment() {
FragmentActivity activity = new FragmentActivity();
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText) fragment.getView().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is working version. Following 3 lines are important(it's from robolectric source - DialogFragmentTest).
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
The fragments are supposed to be displayed from an Activity. The flow should be:
allocate a new fragment object in a FragmentActivity class
get the fragment manager to add the newly allocated fragment
In your case you do not have a connection to a real activity. You allocate a FragmentActivity with new FragmentActivity() and try to get the support manager. While this compiles there is no "real" activity able to manage your fragment. Fragments can be added on activities already displayed and here it's not the case.
I recommend reading this page as it explains these things very well: http://developer.android.com/guide/components/fragments.html
That happened for me when I used fragmentTransaction.commitAllowingStateLoss(); from sub Fragment whose parent fragment had setRetainInstance(true); I had activity as property what lead to leaking activity on rotation.

Categories

Resources