getSupportFragmentManager().popBackStackImmediate() works but getSupportFragmentManager().getBackStackEntryAt(index) doesn't - android

So I've got a main activity that hosts all of the fragments in my app. Let me just say beforehand that every time I open a new fragment, I do it like this:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(vg.getId(), new MyFragment()); //obviously MyFragment varies from usage to usage but nothing else
ft.addToBackStack("My Fragment's Name");
ft.commit();
MyFragment in this case extends androidx.fragment.app.Fragment, which only has a method getFragmentManager(). It does NOT have getSupportFragmentManager().
If I want to go back to a previous fragment, from the currently shown fragment I would do getFragmentManager().popBackStackImmediate(). However, if I wanted to pop the backstack from the activity, I have to use getSupportFragmentManager().popBackStackImmediate() or else nothing happens. This led me to assume that calling getFragmentManager() from a fragment returned the same reference as calling getSupportFragmentManager() from the activity. However, if I try to run getSupportFragmentManager().getBackStackEntryAt(/* an int */) from the main activity I get a NullPointerException. If I run getSupportFragmentManager().getBackStackEntryCount() from the main activity it always returns zero.
So why is it then that getSupportFragmentManager() in the main activity simultaneously works and doesn't work? Why can I use it to pop the backstack, yet I can't access the backstack itself from the main activity? I'm totally clueless. Help would be appreciated.
EDIT: This is my onBackPressed():
#Override
public void onBackPressed() {
getSupportFragmentManager().popBackStackImmediate();
getWindow().setSoftInputMode(defaultSoftInputMode);
try {
String fragmentName = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
Toast.makeText(getApplicationContext(), fragmentName, Toast.LENGTH_SHORT).show(); // Debug to see if the correct name is being shown
} catch (NullPointerException npe) {
// Print to console
// It always catches an error here and I don't know why
}
}
The specific method which throws the exception is getSupportFragmentManager().getBackStackEntryAt(int index). Further inspection shows that this method queries an ArrayList<BackStackRecord> for a value, but the exception occurs because this ArrayList is null.

Related

Fragment getting detached from Activity without obvious reason

My problem: When i close my app via long-pressing home button (Samsung S4) in the app manager, everything works fine on the next startup.
But, when i close the app in onBackPressed when its time to close it, i get a crash on the next start of the app, because "getActivty" returns null.
I checked multiple times which lifecycle methods get called during these two scenarios, and wasn't able to find any difference.
Also, i use the piece of code producing the crash in another fragment, but there it works fine.
The question is: Why returns getActivity null, when the Activity is definitely alive? Better said, why is my fragment not attached to the Activity anymore?
Relevant code (i hope)
init of main fragment:
//Declared globally in Activity
FirstFragment fr;
//In activities onCreate
fr = new FirstFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, fr, "first")
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
Switching to the second fragment is done with this method (Where the parameter "Object fragment" is "new SecondFragment")
public void changeFragment(Object fragment, String name, boolean stayMain) {
if (!stayMain) {
findViewById(R.id.title_bar_left_button).setBackgroundResource(R.drawable.ic_back);
findViewById(R.id.title_bar_left_button).setOnClickListener(backToMainListener);
}
((TextView) findViewById(R.id.navTitle)).setText(name);
findViewById(R.id.title_bar_right_button).setVisibility(View.GONE);
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, (Fragment)fragment, name)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
}
Code causing the crash: (since getActivity returns null in described scenario
Context c = this.getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(c);
Any help is appreciated

IllegalStateException when adding & replacing Fragments; commit vs commitAllowingStateLoss

It is rare that this happens, but occasionally my app will crash due to an IllegalStateException when adding and replacing fragments. Here is how I am doing it, I do so with an animation.
private void addFragmentReplace(int containerId, Fragment fragment) {
// check if the fragment has been added already
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getTag());
if (!Utils.checkIfNull(temp) && temp.isAdded()) {
return;
}
// replace fragment and transition with animation
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commit();
}
I have researched into changing "commit()" to "commitAllowingStateLoss()" but is that really a solution? It will prevent the crashes, however, won't it cause other conflicts such as the fragment not displaying at times or other? Is the following an odd improvement to my above code snippet?
private void addFragmentReplace(int containerId, Fragment fragment) {
// check if the fragment has been added already
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getTag());
if (!Utils.checkIfNull(temp) && temp.isAdded()) {
return;
}
// replace fragment and transition with animation
try {
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commit();
} catch (IllegalStateException e) {
e.printStackTrace();
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commitAllowingStateLoss();
}
}
Thanks in advance. My concerns come from the documentation for commitAllowingStateLoss() which reads
Like {#link #commit} but allows the commit to be executed after an
activity's state is saved. This is dangerous because the commit can
be lost if the activity needs to later be restored from its state, so
this should only be used for cases where it is okay for the UI state
to change unexpectedly on the user.
Some tips or advice on this would be appreciated. Thanks!

Fragment getActivity() returns null in Activity JUnit test

I wrote Android JUnit test for Activity that instantiates fragments (actually tabs). During the test, when I try to do anything with these tabs, they crash because getActivity() method in them returns null. The actual application (not a test) never shows this behavior and fragment getActivity() always returns the right parent activity there. My test case looks like:
public class SetupPanelTest extends ActivityUnitTestCase<MyAct> {
FSetup s;
public SetupPanelTest() {
super(MyAct.class);
}
protected void setUp() throws Exception {
super.setUp();
startActivity(new Intent(), null, null);
final MyAct act = getActivity();
AllTabs tabs = act.getTabs();
String tabname = act.getResources().getString(R.string.configuration);
// This method instantiates the activity as said below
s = (FSetup) tabs.showTab(tabname);
FragmentManager m = act.getFragmentManager();
// m.beginTransaction().attach(s).commit();
// ... and even this does not help when commented out
assertTrue(s instanceof FSetup); // Ok
assertEquals(act, s.getActivity()); // Failure
}
public void testOnPause() {
// this crashes because s.getActivity == null;
s.onPause();
}
}
The AllTabs creates a fragment, then required, in this way:
FragmentManager manager = getFragmentManager();
Fragment fragment = manager.findFragmentByTag(tabname);
if (fragment == null || fragment.getActivity() == null) {
Log.v(TAG, "Instantiating ");
fragment = new MyFragment();
manager.beginTransaction().replace(R.id.setup_tab, fragment, tabname).commit();
....
Here, all fragments are initially placeholders that are later replaced by the actual fragments:
<FrameLayout
android:id="#+id/setup_tab"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
The logcat shows that the new fragment has been instantiated. In the same layout, there is also the previously mentioned AllTabs fragment that seems not having this problem (where and how it gets FragmentManager otherwise):
<TabWidget
android:id="#android:id/alltabs"
...
Most impressively, when I call attach directly on the fragment manager obtained on the right activity, this still has no effect. I tried to put five seconds delay (I have read that transaction may be delayed), I tried to call the rest of the test through runOnUiThread - nothing helps.
The question is that is need to do so to attach my fragments to the activity also during the test. I have fragment and I have activity, I cannot attach one to another.
Even if you call .commit() on transaction, it is still not done, fragments are attached only lazily.
FragmentManager m = activity.getFragmentManager();
m.executePendingTransactions();
This finally attaches all fragments to the activity. Seems redundant when running the application itself but required in JUnit test case.

Switch fragment at the moment location is found

I have one mainactivity in which the location is determined. In this mainactivity I first add a splashscreen fragment. At the moment I find the first location/GPS is working correctly and found a location, I want to replace this splashscreen with my main menu.
Relevant code in the mainactivity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame);
//Check that the activity is using the layout version
// the framelayout
if(findViewById(R.id.fragment_container)!=null){
//To avoid overlapping fragments check if we are restored from a state, then we don't have to do anything
if(savedInstanceState != null){
return;
}
Splash splashscr = new Splash();
Main main = new Main();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, splashscr).commit();
// I **think something should be added here
//to check if a location has been found yet.**
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, main);
transaction.commit();
}
}
I tried adding a while(foundlocation!=true) in that location. The foundlocation would then be set to true in either onLocationChanged() or in the end of getLocation(). However, it didn't solve my problem. Can someone help me out? If I didn't make my problem clear enough please say so.
EDIT: After considering the comment of Gabe I tried this (locationfound is a public boolean initiated as false in the activity class). But if I now use the DDMS to send a location the splashscreen won't go away. If I'm correct, the first time the location is changed, the boolean is still false and thus it should switch fragment.
I tried it without the if statement and then it was working. What do I forget about here?
public void onLocationChanged(final Location location) {
if(locationfound=false)
{
//Some irrelevant code about saving the new location
Main main = new Main();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, main);
transaction.commit();
locationfound=true;
}
//changing value of sometextboxes in the Main fragment
}
Don't try to wait in onCreate- that holds up the UI thread leading to unresponsive UIs. Instead, you should switch the fragments in the onLocationChanged function the first time its called (by using a boolean flag variable to tell if its the first time).

Got exception: fragment already active

I have a fragment;
MyFragment myFrag = new MyFragment();
I put bundle data to this fragment:
Bundle bundle = new Bundle();
bundle.putString("TEST", "test");
myFrag.setArguments(bundle);
Then, I replace old fragment with this one and put on backstack:
//replace old fragment
fragmentTransaction.replace(R.id.fragment_placeholder, myFrag, "MyTag");
//put on backstack
fragmentTransaction.addToBackStack(null);
//commit & get transaction ID
int transId = fragmentTransaction.commit();
Later, I pop backstack with the above transaction ID(transId):
//pop the transaction from backstack
fragmentManager.popBackStack(transId,FragmentManager.POP_BACK_STACK_INCLUSIVE);
Later, I set bundle data as argument again to my fragment(myFrag):
//Got Java.lang.IllegalStateException: fragment already active
myFrag.setArguments(bundle);
As you see, my above code got exception Java.lang.IllegalStateException: fragment already active . I don't understand why myFrag is still active though I have popped the transaction of it from backstack., anyhow, since I got the exception I thought I have no choice but de-active the fragment, So, I did:
Fragment activeFragment = fragMgr.findFragmentByTag("MyTag");
fragmentTransaction.remove(activeFragment);
I am not sure if my above code really can de-active the fragment, since I didn't find how to de-active an fragment. :(
After that, when I try to set bundle data to my fragment myFrag again, I still got the same error:
Java.lang.IllegalStateException: fragment already active
Seems even I removed the fragment, it is still active...Why? How to de-active a fragment?
Reading the setArguments(Bundle args) source will help you understand:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
You cannot use setArguments(Bundle args) again in your code on the same Fragment. What you want to do I guess is either create a new Fragment and set the arguments again. Or you can use getArguments() and then use the put method of the bundle to change its values.
Try removing the previous fragment before adding the new one: https://stackoverflow.com/a/6266144/969325
remove() change fragment status to de-actiive. In your case, you just didn't call commit() after remove(..).
fragmentTransaction.remove(activeFragment);
You would do commit() after remove(), too.
fragmentTransaction.remove(activeFragment).commit();
Had the same issue. I was adding the fragment to backstack. And the error was because I didn't call popbackstack(). Using popbackstack helped me
I'm running into the same issue on Xamarin.android. Here's what the documentation says.
This can only be called before the fragment has been attached to its activity
Just call public method from fragment
if(userFragment==null){
userFragment = new UserFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.EXTRA_CUSTOMER, result);
userFragment.setArguments(bundle);
}else{
try {
Customer customer = new Customer();
customer.parseCustomer(new JSONObject(result));
userFragment.updateVeiw(customer);
} catch (JSONException e) {
e.printStackTrace();
}
}
First I start with describing why this happens and then I'll come up with the solution I found working... .
This issue happens when Android is removing the fragment from the stack but is not yet finished with removing. In order to check this, you can use the isRemoving() method of the fragment. If false, i.e. the fragment is not active, you can go on with setting the arguments using setArguments(bundle). Otherwise, you can't set arguments to an already active fragment and can only override it by addressing the same arguments using getArguments().putAll(bundle).
To summarize,
if (myFrag.isRemoving()) {
myFrag.getArguments().putAll(bundle);
} else {
myFrag.setArguments(bundle);
}
If you want to avoid this, i.e. removing the fragment at once so there is no active fragment, you might want to use onBackPressed() in onBackStackChangedListener(), which will set the isRemoving() to false.
Check whether your layout current one or old one for example
setContentView(R.layout.activity_main);
Delete old .gradle file in your project file and rebuild gradle file for project.

Categories

Resources