I am new to Andoid development and I have created a snippet for replacing fragments programetically.
I followed the guide on android developers.
I have create a method named selectFrag and fired it on button click:
public void selectFrag(View view)
{
Fragment fr;
if(view == findViewById(R.id.showsecond)) {
fr = new secondfragment();
} else {
fr = new firstfragment();
}
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_place,fr);
ft.addToBackStack(null);
ft.commit();
}
Code works perfectly and I understand everything except addToBackStack(null).
I experimented and understood that this method is for adding the fragment to the back button's stack, so that if we click back button, it do not leave the screen and show the previous work.
But, I do not understand what null shows here. I searched on web but I only knew that it is a TAG and we could use something like this.
So, my question is very simple :What is meant by null here? or What does null do?
(sorry for my bad English.)
From documentation, it is pretty clear:
public abstract FragmentTransaction addToBackStack (String name)
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
Parameters
name // An optional name for this back stack state, or null.
So your parameter is optional and represents the name of the fragment.
If you just want to add this transaction to the back stack and don't need to access it later then you can put null as the name.
In this context, null in plain English means "I don't need a name for this fragment". That is why it says the name is optional. If you do put a name you can use that name later. If you put a null that just means "add this fragment to the back stack and I don't need it anymore".
The use of the name is to identify that specific fragment. This can be useful for example if you want to obtain that fragment from the FragmentManager:
addToBackStack (FRAGMENT_NAME);
getFragmentMangager().findFragmentByTag(FRAGMENT_NAME);
Just want to clarify, the 'name' used in addToBackStack(name) can't be used for retrieving the fragment by calling fragmentManager.findFragmentByTag(tag). The 'tag' is different from the 'name'.
The 'tag' used in add/replace(id, fragment, tag) is used to retrieve the fragment by calling fragmentManager.findFragmentByTag(tag).
However, the 'name' used in addToBackStatck(name) is used to control to which fragment you want to pop the fragment back stack by calling popBackStatck/Immediate(name, flags). So if I have a fragment stack with named fragments: A, B, C and D with A at the bottom. When you call popBackStack(B, XXX_EXCLUSIVE), then your fragment back stack will be like: A and B after the call. Without the name, you can't do that.
I think you should not use .addToBackStack(null);
if you are trying to popthe Backstack chances are that it might throw
java.lang.IllegalStateException as it wont be having valid reference to Popout the last item which is added. This needs to be checked... i am not 100% sure.
Related
First of all, I'm pretty new to Android development.
I'm building an simple Android app with 1 Activity which contains 3 fragments.
Lets name the fragments A, B and C. Each fragment contains some input fields and a "next" button. The last fragment contains a "finish" button which sends the data (collected in the fragments) to an API and navigates to fragment A again to start over again.
In fragment A and B, i navigate to the next fragment by calling this function:
private void replaceFragment(Fragment someFragment) {
FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
transaction.replace(R.id.nav_host_fragment_content_main, someFragment);
transaction.addToBackStack(null);
transaction.commit();
}
This works fine so far. In fragment C i post the collected data to the API and call this code afterwards:
FragmentManager fm = getParentFragmentManager();
fm.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
This works perfect but only once. After i'm "redirected" from fragment C to fragment A and go again via B to C and click Finish in fragment C, the mentioned code is triggered but I'm not redirected to A anymore.
When i kill the app and restart the app, it works once again. What I'm doing wrong?
Update
During debugging i can see that the BackStack is a list with BackStackEntries. This list always contains 2 entries when I'm in frament C and i thought fm.popBackStackImmediate(0,... "redirects" to entry 0 of the BackStack. Now i notice that each BackStackEntry has a property mIndex which is increasing all the time. Probably is the id parameter in the popBackStackImmediate not the position in the BackStack but refers to the BackStackItem its mIndex.
Is this true and if yes, how can i get the ID of the correct back stack item in this case?
It seems that the id in fm.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); is not the position of the BackStackEntry in the BackStack but the id of the BackStackEntry. This id is increasing never reused so its continuously increasing.
The BackStackEntry of fragment A is always on position 0 in my case so i can simply get its id like:
Integer $homeFragmentBackStackId = fm.getBackStackEntryAt(0).getId();
And than do:
fm.popBackStackImmediate($homeFragmentBacStackId,FragmentManager.POP_BACK_STACK_INCLUSIVE);
In my activity page, I have a Button to call a Fragment. FragmentManager tries to call that fragment's newInstance function.
It used to work well but from some point in the project, I needed to change its name to something else because its name was testObjectBlaBla and I needed to release it to Play Store so I changed its package name. But now it gives
java.lang.IllegalArgumentException: No view found for id 0x7f0900d0
(com.myblabla.example:id/fragment_container) for fragment
MarkerSpecification_Vehi{18103143 #0 id=0x7f0900d0 BLANK_FRAGMENT}
I tried to change the package name back to the old one but still but I can't change the name every time I want to go live.
MarkerSpecification_Acco fragment =MarkerSpecification_Acco.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.enter_from_right,R.anim.exit_to_right,R.anim.enter_from_right,R.anim.exit_to_right);
transaction.addToBackStack(null);
transaction.add(R.id.fragment_container,fragment,"BLANK_FRAGMENT").commit();
Not sure if this is the true piece because exception not giving any error in my own code but in fragmentManager.java class instead. Probably bacause trying to search the old package name com.myblabla.example instead of com.project.project. But I think it is the only place where the code tries to find a fragment.
How about you change the calling of the fragment to this:
Fragment frag = new MarkerSpecification_Acco();
Also check in your XML File if your container reference has changed to your new Fragment name.
I'm trying to replace the fragment in a container.
For the visual stuff I can see a change and the new fragment get initialized.
But if I check, which object in the container is, it still gives me the old fragment i replace before, the leads to the problem, that i can't call a method of the new fragment because it didn't got replaced to 100%.
Here my code:
fragmentTransaction = getFragmentManager().beginTransaction();
Volumefragment volumefragment = new Volumefragment();
System.out.println("Change Fragment");
fragmentTransaction.replace(R.id.framecontainer,volumefragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
if(getFragmentManager().findFragmentById(R.id.framecontainer) instanceof Dummyfragmenet){
System.out.println("Wrong object");
}
}
When you do a commit with the Fragment transaction, what you are really doing is scheduling the replacement of that fragment. It won't happen immediately, and it will be replaced when the UI thread gets ready to do it. So your printing is being called when the fragment transaction is not fully completed (Since it is happening asynchronously). If you want to make sure that the fragment has been replaced, right after the commit and before the printing, add the following:
getFragmentManager().executePendingTransactions()
To clarify, and taken from the documentation "After a FragmentTransaction is committed with FragmentTransaction.commit(), it is scheduled to be executed asynchronously on the process's main thread."
Visit https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()
Background:
I am writing an android application with an activity that can be populated by one of a number of fragments. Relatively often, I need to pass a bundle of data to the active fragment so that it can update the UI accordingly.
To get the current fragment, I call getfragmentManager().findFragmentByTag() and cast the returned fragment to my custom fragment class.
The problem:
The above method usually works fine. However, findFragmentByTag() occasionally returns null. Upon further investigation, I have concluded that this will only occur when I run or debug from Android studio (and even then it doesn't happen every time).
Relevant Code:
protected void onCreate(Bundle savedInstanceState){
//Do lots of stuff
currentFragmentTag = "";
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
currentFragmentTag = getFragmentManager().getBackStackEntryAt(getFragmentManager().getBackStackEntryCount() - 1).getName();
}
}
});
init();
//Do some more stuff
//this should always be the case
if (currentFragmentTag.equals(LOGIN_FRAGMENT_TAG)) {
((LoginFragment) getFragmentManager().findFragmentByTag(currentFragmentTag)).beginLogin();
}
}
private void init(){
//changed to reflect George Mulligan's advice
Fragment currentFrag = getFragmentManager().findFragmentByTag(LOGIN_FRAGMENT_TAG);
if(currentFrag == null) {
currentFragmentTag = LOGIN_FRAGMENT_TAG;
getFragmentManager().beginTransaction()
.add(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
.addToBackStack(LOGIN_FRAGMENT_TAG)
.commit();
getFragmentManager().executePendingTransactions();
}
}
public void updateFragment(){
Bundle dataBundle = new Bundle();
//put stuff in dataBundle
if (currentFragmentTag.equals(LOGIN_FRAGMENT_TAG)) {
LoginFragment currentFrag = (LoginFragment) getFragmentManager().findFragmentByTag(currentFragmentTag);
if (currentFrag != null) {
currentFrag.passBundleToFragment(dataBundle);
Log.d(TAG, "Fragment returned is valid.");
} else {
Log.d(TAG, "Fragment returned is null.");
}
}
//else if a different fragment is active then update it in the same way
}
//Manually open the loginFragment. This can be called from other fragments. My problem always occurs before this is called however.
#Override
public void openLoginScreen() {
if(/*some conditions*/) {
LoginFragment loginFrag = LoginFragment.newInstance();
getFragmentManager().beginTransaction()
.replace(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
.addToBackStack(LOGIN_FRAGMENT_TAG)
.commit();
getFragmentManager().executePendingTransactions();
currentFragmentTag = LOGIN_FRAGMENT_TAG;
updateFragment();
}
}
Normally, my logcat looks something like this:
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
...etc.
But every now and then, and only when I start the app from Android Studio, I get something like:
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is valid
Fragment returned is valid
What on Earth is going on here?
UPDATE:
I have been able to reproduce this error while disconnected from Android Studio by clicking the app switch button, closing my app and immediately restarting it. Provided I do this quickly enough, it never fails to behave as I described above.
After some more logging and chasing down other issues, I discovered that in these particular cases, onCreate() is being called twice.
My app is designed to run only in landscape mode, in part to avoid issues that come with recreating the activity. It would seem, however, that when the app is closed and restarted quickly, Android never finishes the necessary rotation to portrait mode for the home screen before my app is launched again. My assumption is that this causes the OS to rotate back to landscape after the app is running and thereby restart it.
All of this is fine and dandy, except for the fact that it doesn't explain why findFragmentByTag() sometimes returns null.
Every object in my Activity class should be recreated, right? So shouldn't the FragmentManager be re-initialized as well? Or is getFragmentManager() a static reference to something outside of the Activity itself?
SECOND UPDATE:
I tried George's idea of checking if the fragment had already been added before I call beginTransaction() and, although it didn't solve the problem, I noticed something strange when debugging:
I set a breakpoint at Log.d(TAG, "Fragment returned is null.");. Closing the app and quickly restarting it guarantees that this code will be reached as I mentioned above. Then, if I view the Fragment Manager by calling getFragmentManager() in the Evaluate Expression window, I notice that a `Login Fragment' has already been added, but it doesn't have a tag associated with it.
Setting a breakpoint at Log.d(TAG, "Fragment returned is valid.");in the same app session, however, reveals the LoginFragment is added with a tag as would be expected.
There is no point in my code where I ever add a fragment without setting a tag. Could this have something to do with the activity being recreated and the Fragment manager losing tags even though it holds onto the fragments themselves?
A few things to note on this.
In updateFragment you should really compare your Strings using .equals instead of reference comparison == so LOGIN_FRAGMENT_TAG.equals(currentFragmentTag)as a best practice and to avoid confusion.
Also, the FragmentManager will remember which fragments you have added to it across orientation changes. So in the one example you mentioned where you get into onCreate twice you will then likely have two instances of your LoginFrag on the back stack.
You can avoid this by doing the same lookup you are doing in your updateFragment method and only adding the fragment if it isn't found.
LoginFragment currentFrag = (LoginFragment) getFragmentManager()
.findFragmentByTag(LOGIN_FRAGMENT_TAG);
if(currentFrag == null) {
currentFrag = getFragmentManager().beginTransaction()
.replace(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
.addToBackStack(LOGIN_FRAGMENT_TAG)
.commit();
//executePendingTransactions() usually isn't necessary...
getFragmentManager().executePendingTransactions();
}
I'm guessing multiple fragments are used in this activity since you bother having the String currentFragmentTag but if not then it would be better for you to just keep a reference to your LoginFragment as a field on the class so you can avoid the additional lookup of it in the updateFragment method.
Other than that I tried reproducing your error and I cannot so the error might be elsewhere in code you are not showing.
This isn't a direct answer to your question, but a suggestion for avoiding the problem in the first place.
Because the interaction between the Fragment and Activity Lifecycles is so complex and difficult to reason about, I've found that it's much easier if you completely decouple the two.
Instead of obtaining a reference to the Fragment and invoking a method on it directly, I set up a message bus (e.g. Otto or GreenRobot) and in your case, would have the Activity post a message to the bus, and have the Fragment subscribe to that message. Much cleaner, and if the Fragment happens to not be ready (hasn't registered with the bus yet), there's no error condition.
Ok, this should be a comment but, code formatting is ugly as a comment. Perhaps your fragment gets paused.
try this
#Override
public void onResume() {
super.onResume();
updateFragment();
}
In the main activity of my app there is a container that hosts fragments.
When a user clicks a button in the "default" fragment (the first fragment that is displayed), the fragment changes, and so do the actionbar buttons.
One of the buttons in the actionbar of this new fragment open another activity.
In that activity, when a user clicks the back button, the activity closes, and the fragment that was shown in the MainActivity (the fragment that opened the new activity) is still there (which is fine).
However, if a user clicks the back button again, it does not return to the previous fragment. While it does return when the activity does not open.
It turns out that opening the activity clears the backstack (verified by Logging the count from the FragmentManager class), while I'm not quite sure whether this is supposed to behave like this or not, it kinda makes sense. Unfortunately, it is not the behavior I desire.
MainActivity: Fragment A (default) ---> Fragment B ---> Acivity B
Therefore, my question is how can I keep the backstack after the activity resumes, if at all?
I tried searching for similar questions, but all questions I found actually asked how to clear the backstack.
Try that:
#Override
public void onBackPressed() {
Intent intent = new Intent(A_Acticity.this, B_Activity.class);
startActivity(intent);
}
Hope it helped! :)
Reading the documentation, there is a way to pop the back stack based on either the transaction name or the id provided by commit. Using the name may be easier since it shouldn't require keeping track of a number that may change and reinforces the "unique back stack entry" logic.
Since you want only one back stack entry per Fragment, make the back state name the Fragment's class name (via getClass().getName()). Then when replacing a Fragment, use the popBackStackImmediate() method. If it returns true, it means there is an instance of the Fragment in the back stack. If not, actually execute the Fragment replacement logic.
private void replaceFragment (Fragment fragment){
String backStateName = fragment.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
if (!fragmentPopped){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(backStateName);
ft.commit();
}
}