My main application has a blanck FrameLayout the reason for this is so that I can on the go, add and replace fragments to it...
I have had no issue with this until now. I have successfully added the first fragment and the onCreate gets called etc, then in my code using a button I replace the FrameLayout with my second fragment which seems to execute, however not showing? as onActivityCreated() does not get called.
My main application calls FrameLayout xml file known as send_activity
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
sendControlFragment sendControlFragment = new sendControlFragment();
peerItemFragment fragmentList = new peerItemFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.send_activity);
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
sendControlFragment controlFragment = new sendControlFragment();
// Add the fragment to the 'fragment_container' FrameLayout
fragmentTransaction.add(R.id.fragment_container, controlFragment).commit();
}
}
The above works perfectly...
The button which I call the second fragment, I got told to initiate a new FragmentTransaction which I have done, however this does not call the onActivityCreated() and skips to fragmentList.addPeers(mService.peerList);
This is great that it is calling the addPeers() method however without the onActivityCreated() then it crashes!
public void authenticateClick(View v) {
mService.peerSearch();
peerItemFragment peerFragment = new peerItemFragment();
FragmentTransaction fragmentTransactionauthen = fragmentManager.beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
fragmentTransactionauthen.replace(R.id.fragment_container, peerFragment);
fragmentTransactionauthen.addToBackStack(null);
// Commit the transaction
fragmentTransactionauthen.commit();
//pass peer list to fragment to display
fragmentList.addPeers(mService.peerList);
}
Can you please suggest a solution as .replace currently to my knowledge doesnt call the onActivityCreated() method...
Because i was using a listfragment I had to replace my listView id to the following, as this was causing fatal error!
android:id="#android:id/list"
it now works and replaces fragments.
Related
I am trying to do the following use case in Android Fragments. I have 2 fragments.
Fragment A -> Fragment B
When a user does something in Fragment B, I want to have the back stack as follows
Fragment A -> Fragment C. So, when the user presses back I want the user to go back to Fragment A.
I have tried the following
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.addToBackStack(null)
.commitAllowingStateLoss();
The problem here is that I can see Fragment A for a short period of time before Fragment C is shown
If I do the following
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.addToBackStack(null)
.commitNowAllowingStateLoss();
I get the error
This transaction is already being added to the back stack
I can get Fragment C to show up if I do this BUT
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.commitNowAllowingStateLoss();
This works and I don't see Fragment A and see Fragment C but the back button takes the user out of the application. So, is it possible that we can pop the back stack of the fragment and then add another fragment to the back stack w/o showing Fragment A AND the back button takes the user back to Fragment A
Here is an easy method to add fragments to fragments or to adapters within fragments...
from your base activity, make your fragment manager static. assume this activity is called dashboard.
static FragmentManager support;
Don't forget to initialize this in onCreate.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_dashboard);
support = getSupportFragmentManager();
define your new fragment inside your adapter or fragment.
users_item_fragment dialog = new users_item_fragment();
//also, let's add some data...
Bundle args = new Bundle();
args.putString("device", devicesList.get(position));
use the following method to add the fragment easily wherever you would like
//pick an easily remembered tag
public void replace(Fragment fragment, String tag){
FragmentManager man = dashboard.support;
FragmentTransaction fragt = man.beginTransaction();
if(!fragment.isAdded()) {
dashboard.lastTag = dashboard.fragtag;//not needed, but helpful w/ backpresses
fragt.add(R.id.fragment_container, fragment, tag)
.hide(man.findFragmentByTag(fragtag)).commit();
dashboard.fragtag = dashboard.tag;//not needed, but helpful w/ backpresses
}
if(fragment.isAdded() && fragment.isHidden()) {
dashboard.lastTag = dashboard.fragtag;//not needed, but helpful w/ backpresses
fragt.show(fragment);
fragt.hide(man.findFragmentByTag(fragtag)).commit();
dashboard.fragtag = dashboard.tag;//not needed, but helpful w/ backpresses
}
}
To implement this with backpresses working correctly, add this in you onBackPress method of your main activity:
#Override
public void onBackPressed() {
FragmentManager man = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = man.beginTransaction();
fragmentTransaction.hide(getSupportFragmentManager().findFragmentByTag(fragtag))
.show(getSupportFragmentManager().findFragmentByTag(lastTag)).commit();
fragtag = lastTag;// holds the last fragment
}
}
It's easy to see the logic here and easy to manipulate back press events using this.
I have one Activity that launches a Fragment. I open my Navigation Drawer and go to a second Fragment in that same Activity. I rotate the device and instead of remaining on that second Fragment, I'm brought back to the first one. How can you save the current Fragment that you're on so that when the Activity is destroyed it opens the appropriate Fragment? Here is what I have tried but it's not working. This is in my Activity:
private FragmentTransaction ft;
private Fragment fragment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_page);
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
fragment = getFragmentManager().getFragment(savedInstanceState,"currentFrag");
ft.replace(R.id.fragment_container, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}else{
fragment = new HomeFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
}
}
.....
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getFragmentManager().putFragment(outState, "currentFrag", fragment);
}
This is the exception in my log cat
java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.FragmentTransaction android.app.FragmentTransaction.replace(int, android.app.Fragment)' on a null object reference
Clearly the current Fragment isn't being saved. Is this generally the correct way to solve this?
This question has nothing to do with the general understanding of what a NullPointerException is but rather how to persist Fragment states when the screen is rotated.
In your fragment constructor, add the following line:
setRetainInstance(true);
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change).
I don't see where you initialize your FragmentTransaction, sorry.
Maybe you miss this:
fragment = getFragmentManager().getFragment(savedInstanceState,"currentFrag");
ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment);
Is it possible to keep a Fragment running while I call replace from FragmentManager to open a new Fragment?
Basically I don't want to pause a Fragment when navigating (via replace method) to another Fragment.
Is it possible?
Or the correct approach is, always, instantiate a new Fragment every time I need to open it and restore its previous state?
Thanks!
FragmentManger replace method will destroy the previous fragment completely, So in each transaction onDestroyView(), onDestroy() and onDetach() will get called on previous fragment. If you want to keep your fragment running you can instead use FragmentManger hide() and show() methods! It hides and shows the fragments without destroying them.
so first add both fragments to fragment manager and also hide the second fragment.
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA)
.add(R.id.new_card_container,FragmentB)
.hide(FragmentB)
.commit();
Note that you can only call show() on hidden fragment. So here you can't call show() on FragmentA but it's not a problem because by hiding and showing FragmentB you can get replacement effect you want.
And here is a method to go back and forth between your fragments.
public void showOtherFragment() {
if(FragmentB.isHidden()){
fragmentManager.beginTransaction()
.show(FragmentB).commit();
} else {
fragmentManager.beginTransaction()
.hide(FragmentB).commit();
}
}
Now if you put log message in fragment callback method you will see there is no destruction (except for screen orientation change!), even view will not get destroyed since onDistroyView doesn't get called.
There is only one problem and that is, first time when application starts onCreateView() method get called one time for each fragment (and it should be!) but when the orientation changes onCreateView() gets called twice for each fragment and that's because fragments once created as usual and once because of there attachment to FragmentManger (saved on bundle object) To avoid that you have two options 1) detach fragments in onSaveInstaneState() callback.
#Override
protected void onSaveInstanceState(Bundle outState) {
fragmentManager.beginTransaction()
.detach(FragmentA)
.detach(FragmentB)
.commit();
super.onSaveInstanceState(outState);
}
It's working but view state will not get updated automatically, for example if you have a EditText its text will erase each time orientation change happens. of course you can fix this simply by saving states in the fragment but you don't have to if you use the second option!
first i save a Boolean value in onSaveInstaneState() method to remember witch fragment is shown.
#Override
protected void onSaveInstanceState(Bundle outState) {
boolean isFragAVisible = true;
if(!FragmentB.isHidden())
isFragAVisible = false;
outState.putBoolean("isFragAVisible",isFragAVisible);
super.onSaveInstanceState(outState);
}
now in activity onCreate method i check to see if savedInstanceState == null. if yes do as usual if not activity is created for second time. so fragment manager already contains the fragments. So instead i'm getting a reference to my fragments from fragment manager. also i make sure correct fragment is shown since its not recovered automatically.
fragmentManager = getFragmentManager();
if(savedInstanceState == null){
FragmentA = new FragmentA();
FragmentB = new FragmentB();
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA, "fragA")
.add(R.id.new_card_container, FragmentB, "fragB")
.hide(FragmentB)
.commit();
} else {
FragmentA = (FragmentA) fragmentManager.findFragmentByTag("fragA");
FragmentB = (FragmentB) fragmentManager.findFragmentByTag("fragB");
boolean isFragAVisible = savedInstanceState.getBoolean("isFragAVisible");
if(isFragAVisible)
fragmentManager.beginTransaction()
.hide(FragmentB)
.commit();
else
fragmentManager.beginTransaction()
.hide(FragmetA) //only if using transaction animation
.commit();
}
By now your fragment will work perfectly if are not using transaction animation. If you do, you also need to show and hide FragmentA. So when you want to show FragmentB first hide FragmentA then show FragmentB (in the same transaction) and when you want to hide FragmentB hide it first and also show FragmentA (again in the same transaction). Here is my code for card flip animation (downloaded from developer.goodle.com)
public void flipCard(String direction) {
int animationEnter, animationLeave;
if(direction == "left"){
animationEnter = R.animator.card_flip_right_in;
animationLeave = R.animator.card_flip_right_out;
} else {
animationEnter = R.animator.card_flip_left_in;
animationLeave = R.animator.card_flip_left_out;
}
if(cardBack.isHidden()){
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter, animationLeave)
.hide(cardFront)
.show(cardBack)
.commit();
} else {
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter,animationLeave)
.hide(cardBack)
.show(cardFront)
.commit();
}
}
getFragmentManager().beginTransaction()
.replace(R.id.graph_fragment_holder, new GraphFragment(), "GRAPH_FRAGMENT")
.commit();
getFragmentManager().beginTransaction()
.replace(R.id.list_fragment_holder, new ListFragment(), "LIST_FRAGMENT")
.commit();
//getFragmentManager().executePendingTransactions();
GraphFragment graphFragment = (GraphFragment) getFragmentManager().findFragmentByTag("GRAPH_FRAGMENT");
graphFragment.setData(data);
ListFragment listFragment = (ListFragment) getFragmentManager().findFragmentByTag("LIST_FRAGMENT");
listFragment.setData(data);
I've supplied a tag so I'm not sure why findFragmentByTag() returns null.
What I've tried from reading other questions:
this.setRetainInstance(true) in the oncreate of both fragments.
Both fragment constructors are empty public fragmentName(){}.
tried executePendingTransactions after adding the fragments.
tried add instead of replace on the fragments (edited)
I was confused about this for a long time. First, you need to save the fragment you are replacing by pushing it onto the back stack. The tag you supply is put on the fragment you are adding, not the one you are pushing onto the back stack. Later, when you do push it onto the back stack, that tag goes with it. Here's code with objects broken out to make it easier to trace. You must call 'addToBackStack' before 'commit'.
GraphFragment grFrag = new GraphFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, grFrag, "GRAPH_FRAGMENT");
// grFrag is about to become the current fragment, with the tag "GRAPH_FRAGMENT"
tr.addToBackStack(null);
// 'addToBackStack' also takes a string, which can be null, but this is not the tag
tr.commit();
// any previous fragment has now been pushed to the back stack, with it's tag
ListFragment liFrag = new ListFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, liFrag, "LIST_FRAGMENT");
// liFrag is is about to become the current fragment, with the tag "LIST_FRAGMENT"
tr.addToBackStack(null);
tr.commit();
// 'grFrag' has now been pushed to the back stack, with it's tag being "GRAPH_FRAGMENT"
Call getFragmentManager().executePendingTransactions() after fragment transaction.
getFragmentManager()
.beginTransaction()
.replace(R.id.container, new ExampleFragment(), "YOUR TAG HERE");
.commit();
//after transaction you must call the executePendingTransaction
getFragmentManager().executePendingTransactions();
//now you can get fragment which is added with tag
ExampleFragment exampleFragment = getFragmentManager().findFragmentByTag("YOUR TAG HERE");
I was having the same problem of findFragmentByTag() always returning null.
Eventually I tracked it down, I was overriding onSaveInstanceState() in my Activity but not calling super. As soon as I fixed that findFragmentByTag() returned the Fragment as expected.
You can use
fragmentTransaction.addToBackStack(yourFragmentTag);
After that you can reuse it with
getSupportFragmentManager().findFragmentByTag(yourFragmentTag);
Answered here, just need to call getSupportFragmentManager().executePendingTransactions(); after your findByTag or findById
In my case I had to create a class level FragmentManager object and then use it instead of using getSupportFragmentManager() directly.
public class Main extends BaseActivity {
FragmentManager fragmentManager;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmain);
fragmentManager = getSupportFragmentManager();
initFrag1();
}
private void initFrag1() {
String name = Frag1.class.getSimpleName();
if (fragmentManager.findFragmentByTag(name) == null) {
fragmentManager.beginTransaction()
.add(R.id.frag_container, new Frag1(), name)
.addToBackStack(name)
.commit();
}
}
}
In my MainActivity, I am launching a fragment using the following:
private void displayView() {
Log.d("displayView", "in select item");
// update the main content by replacing fragments
Fragment fragment = null;
fragment = new WorkoutsFragment();
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.main_container, fragment)
.addToBackStack("fragBack")
.commit();
} else {
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
}
This loads my fragment correctly, and I am able to see it, however, when I hit the back button it exits the application. I would like it to go back to MainActivity if possible.
Is this improper handling of a fragment? If so, what would be the correct way of approaching this?
Thanks!
You can override onBackPressed in your main activity and not call super.onBackPressed. In the overriden method, you can remove the fragment from the fragment manager.