I use a master/detail flow and in every fragment I have a viewpager containing some forms but the thing is when I click on some other links from the master list, the content of the viewpager is lost. How to save this content so everytime I click on the same choice I find my data.
My code :
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
if(id.contains("1"))
{
Bundle arguments = new Bundle();
ItemDetailFragment fragment = new ItemDetailFragment();
fragment.setRetainInstance(true);
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.item_detail_container, fragment).commit();
}
else if(id.contains("2"))
{
Bundle arguments = new Bundle();
ItemDetailFragment2 fragment = new ItemDetailFragment2();
fragment.setRetainInstance(true);
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.item_detail_container, fragment).commit();
}
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, ItemDetailActivity.class);
detailIntent.putExtra(ItemDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
EDIT:
I tried to modify my code this way :
if (id.contains("1")) {
Fragment currFragment = fm.findFragmentByTag(lastTag);
ItemDetailFragment newFragment = (ItemDetailFragment) fm.findFragmentByTag("f"+1);
FragmentTransaction ft = fm.beginTransaction();
ft.hide(currFragment);
if(newFragment==null)
{lastTag="f"+1;
newFragment=new ItemDetailFragment();
ft
.replace(R.id.item_detail_container, newFragment);}
else {
ft.show(newFragment);
}
ft.commit();
}
But i got a NullPointerException at android.support.v4.app.BackStackRecord.run(BackStackrecord.java:656)
Try to call setRetainInstance(true) in your fragments onCreate()
As writed here, if you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced.
You can hide current fragment, instead replacing, then add new. Like that:
int currPage = 0;
public void goToPage(int num) {
FragmentManager fm = getSupportFragmentManager();
Fragment currFragment = fm.findFragmentByTag("f" + currPage);
Fragment newFragment = fm.findFragmentByTag("f" + num);
FragmentTransition ft = fm.beginTransaction();
ft.hide(currFragment);
if(newFragment == null) {
// First use. Create new instance for page.
newFragment = new MyFragment();
ft.add(R.id.item_detail_container, newFragment, "f" + num);
} else {
// Fragment for page already added. Just show it.
ft.show(newFragment);
}
ft.commit();
this.currPage = num;
}
And don't forget to specify tag for initial page fragment too.
You can implement the onPause() or onStop() methods of your fragments and save the data when that methods are called, which happens automatically when they are removed.
Or you could perhaps go with the savedInstanceState.
It's better to save in the arrayList. Onclick store the image id in the arrayList.
Related
Hi I have read this Difference between add(), replace(), and addToBackStack(). I have a confusion that If I add multiple fragments like below then If I press back button from fragment2 then will fragment1 will open ? If so then what is the use of addToBackStack as add already maintaining a stack.
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment fragment1 = new Fragment();
ft.add(R.id.llContainer, fragment1, "fragment_one");
Fragment fragment2 = new Fragment();
ft.add(R.id.llContainer, fragment2, "fragment_two");
ft.commit();
Well if you call multiple times add method on FragmentTransaction like this
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment fragment1 = new Fragment();
ft.add(R.id.llContainer, fragment1, "fragment_one");
Fragment fragment2 = new Fragment();
ft.add(R.id.llContainer, fragment2, "fragment_two");
ft.commit();
then both the fragments that been added to FragmentTransaction will be shown as overlapping.
Now clicking back will close the application. It won't start the previous fragment.
Hope this is what you were looking for.
Add method will not add your Fragment in BackStack. You need to verify once again.
While looking into code of addToBackStack
#Override
public FragmentTransaction addToBackStack(String name) {
if (!mAllowAddToBackStack) {
throw new IllegalStateException(
"This FragmentTransaction is not allowed to be added to the back stack.");
}
mAddToBackStack = true;
mName = name;
return this;
}
Flag mAddToBackStack = true; enabled which value is false by default. And this is the flag which is being used to add fragment into backstack. Have a look into below methods calls
#Override
public int commit() {
return commitInternal(false);
}
int commitInternal(boolean allowStateLoss) {
......
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
.....
}
So what you observed is not correct. Something you are missing
I am pretty new to android development so I am curious how to work properly with Fragments.
My application contains a BottomNavigationActivity which switches between 3 fragments with this code:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_montage_order_detail, fragment).commit();
I am storing the Fragments in a List<Fragment> to avoid loosing the current state. But everytime I replace the fragment with another the method onDestroy() is called.
I know, I know I could add and remove the fragment in the fragmentmanager instead of replacing it. I googled alot and most of the tutorials tell me to replace the fragment.
Whats the common way to keep a fragments state without recreating it on every call?
Find the solution
It will not recreate fragment anytime
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().add(R.id.content_montage_order_detail, fragment).commit();
Use fragment TAG at time of creation of fragment then when you want to get it again use findFragmentByTag. if fragment already created then old one will be find by fragment manager.
Fragment previousFragment = fragmentManager.findFragmentByTag("TAG");
I suggest you use show,not forreplace
protected void addFragmentStack(Fragment fragment) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (this.mContent != fragment) {
if (fragment.isAdded()) {
ft.hide(this.mContent).show(fragment);
} else {
ft.hide(this.mContent).add(getFragmentViewId(), fragment);
}
this.mContent = fragment;
}
ft.commit();
}
Try using switchFragment to switch fragment, it will show fragment if it is already added.
Use fragmentTransaction.show method to re-use existing fragment i.e. saved instance.
public void switchFragment (Fragment oldFragment, Fragment newFragment, int frameId) {
boolean addFragment = true;
FragmentManager fragmentManager = getFragmentManager ();
String tag = newFragment.getArguments ().getString (BaseFragment.TAG);
Fragment fragment = fragmentManager.findFragmentByTag (tag);
// Check if fragment is already added
if (fragment != null && fragment.isAdded ()) {
addFragment = false;
}
// Hide previous fragment
String oldFragmentTag = oldFragment.getArguments ().getString (BaseFragment.TAG);
if (!tag.equals (oldFragmentTag)) {
FragmentTransaction hideTransaction = fragmentManager.beginTransaction ();
Fragment fragment1 = fragmentManager.findFragmentByTag (oldFragmentTag);
hideTransaction.hide (fragment1);
hideTransaction.commit ();
}
// Add new fragment and show it
FragmentTransaction addTransaction = fragmentManager.beginTransaction ();
if (addFragment) {
addTransaction.add (frameId, newFragment, tag);
addTransaction.addToBackStack (tag);
}
else {
newFragment = fragmentManager.findFragmentByTag (tag);
}
addTransaction.show (newFragment);
addTransaction.commit ();
}
Ya, you can also manage the state by managing the backstack.
My activity is composed of 3 nested Fragments. There is my MainFragment that is displayed by default, ProductFragment that can be called from it, then DetailFragment can be called from ProductFragment.
I can go back and forth between my ProductFragment and DetailFragment. By doing so, the popStackBack method is accumulating similar fragments. Then, if I click on the back button, It will go back through all the Fragments as many time I called them.
What is the proper way to avoid the same Fragment to be kept in the back stack ?
EDIT :
I firstly call my main fragment :
if (savedInstanceState == null) {
getFragmentManager()
.beginTransaction()
.add(R.id.container, new SearchFragment(), "SEARCH_TAG")
.commit();
}
Here is the code that calls the fragments from the activity :
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_from_bottom, R.animator.exit_to_top, R.animator.enter_from_bottom, R.animator.exit_to_top);
ft.replace(R.id.container, new FactFragment(), "FACT_TAG");
ft.addToBackStack("FACT_TAG");
ft.commit();
Then, on back click :
#Override
public void onBackPressed() {
getFragmentManager().popBackStack();
}
I tried to get the tag of my current fragment and execute some specific code related to it but it doesn't work well. I also tried to addToBackStack() only when current Fragment wasn't already added to the backStack but it messed up my fragment view.
Use fragment's method isAdded() to evaluate the insertion. For example:
if(!frag.isAdded()){
//do fragment transaction and add frag
}
Here is my solution. Maybe dirty but it works. I implemented a method that returns the tag of the fragment that is displayed before clicking the on back button :
public String getActiveFragment() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
return null;
}
String tag = getFragmentManager()
.getBackStackEntryAt(getFragmentManager()
.getBackStackEntryCount() - 1)
.getName();
return tag;
}
Then, on my onBackPressed() method :
// Get current Fragment tag
String currentFrag = getActiveFragment();
if(currentFrag.equals("PRODUCT_TAG")) {
// New transaction to first Fragment
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_from_right, R.animator.exit_to_left, R.animator.enter_from_right, R.animator.exit_to_left);
ft.replace(R.id.container, new SearchFragment(), "MAIN_TAG");
ft.commit();
} else {
// Go to Fragment-1
getFragmentManager().popBackStack();
}
Here is my handy and simple solution to check for duplicate insertion through fragment manager
at first, I check if it is first time intention for adding fragment and then I check if the fragment is presented using fragment manager
Fragment fragment = getSupportFragmentManager().findFragmentByTag("firstFragment");
if (fragment == null) {
getSupportFragmentManager().beginTransaction().add(R.id.frameLayout, new FirstFragment(), "firstFragment")
.addToBackStack(null)
.commit();
}else if(!fragment.isAdded()){
getSupportFragmentManager().beginTransaction().add(R.id.frameLayout, new FirstFragment(), "firstFragment")
.addToBackStack(null)
.commit();
}
Here is my solution:
Fragment curFragment = fragmentManager.findFragmentById(R.id.frameLayout);
if(curFragment != null
&& curFragment.getClass().equals(fragment.getClass())) return;
// add the fragment to BackStack here
Xamarin.Android (C#) version:
var curFragment = fragmentManager.FindFragmentById(Resource.Id.frameLayout);
if (curFragment != null
&& curFragment.GetType().Name == fragment.GetType().Name) return;
// add the fragment to BackStack here
I have a navigation drawer that works well. By well I mean I can navigate through all the fragments tied to the Navigation drawer (A,B,C,D). My issue arises here. Lets say am in Fragment A, in this fragment I have a button which on clicking should ideally replace the fragment with another one say Frag Z, and once I am in Frag Z I can return back to frag A.
To achieve this I use the below code. However the new fragment Z appears transparent on top of Fragment A. What could be the problem:
Bundle bundle = new Bundle();
bundle.putInt("int", 1);
bundle.putString("str","string" );
fragmentTransaction = getFragmentManager().beginTransaction();
FragZ fragmentz = new FragZ();
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.some_container, fragmentz);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
EDIT:
I have found a workaround to this as shown below,however it does not completely solve my problem since the newly displayed fragment is still shown with the navigation drawer capable of being toggled,but it atleast removed the overlaying of the fragments. I would still want a solution to launching an independent fragment
Bundle bundle = new Bundle();
bundle.putInt("int", 1);
bundle.putString("str","string" );
FragZ fragmentz = new FragZ();
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
fragment.setArguments(bundle);
ft.replace(R.id.main, fragment);
ft.addToBackStack(null);
ft.commit();
Create a method in your activity to launch fragments. Whenever you want to display the fragment, just call in with a number or string to represent the fragment. You can use the same function to launch fragments from your navigation drawer.
For example:
public void displayView(int position) {
Fragment fragment = null;
currentFragmentNumber = position;
switch (position) {
case 0:
fragment = new frag1();
break;
case 1:
fragment = new frag2();
break;
case 2:
fragment = new frag3();
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment)
.addToBackStack("tag").commit();
} else {
// error in creating fragment
Log.e(TAG, "Error in creating fragment");
}
}
You can call this function from your activity by using:
((**ActivityName**) getActivity()).displayView(0);
Im trying to load an new fragment when a method is called. This method creates a new fragment and "replaces" the other fragment:
private void showTestFragment(Fragment oldFragment, boolean addBackStack, BaseAdapter adapter, int position) {
Cursor cursor = (Cursor)adapter.getItem(position);
if(cursor != null){
int idx = cursor.getColumnIndexOrThrow(Episode._ID);
long rowId = cursor.getLong(idx);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(oldFragment != null){
Log.i(TAG, "Removing the old fragment");
fragmentTransaction.remove(oldFragment);
}
TestFragment testFragment = new TestFragment();
testFragment.setId(rowId);
fragmentTransaction.add(android.R.id.content, testFragment);
if(addBackStack){
Log.i(TAG, "Added to the backstack");
fragmentTransaction.addToBackStack(TAG);
}
fragmentTransaction.commit();
Fragment f = getFragmentManager()
.findFragmentById(R.id.index);
Log.i(TAG, "after commit, frag is "+ f);
}
}
This works fine, until i go back. The last fragment, should be removed when i go back. Before i'm going to implement methods on the activities
public void onBackPressed(){}
to remove the last fragment, i want to know if i handle the fragment change correctly. It looks like i'm missing something here..
If you really want to replace the fragment then use replace() methode instead of doing a remove() and an add().
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(..............);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Don't forget to do the addToBackStack(null) so your previous state will be added to the backstack allowing you to go back with the back button.
See also https://developer.android.com/reference/android/app/FragmentTransaction.html#replace(int, android.app.Fragment, java.lang.String) .
Another good source is http://developer.android.com/guide/components/fragments.html (search for replace() function).
Just remove first and the call super.onBackPressed
public void onBackPressed(){
// here remove code for your last fragment
super.onBackPressed();
}
//lets wait for systems back to finish
super.onBackPressed();
//here we believe a fragment was popped, so we need to remove the fragment from ourbackstack
if(fragmentBackStack.size()>0)
{
Log.d("custombackstack","before back: "+fragmentBackStack.size()+" current:"+fragmentBackStack.peek());
fragmentBackStack.pop();
}
//after popping is the size > 0, if so we set current fragment from the top of stack, otherwise we default to home fragment.
if(fragmentBackStack.size()>0)
{
Log.d("custombackstack","after back: "+fragmentBackStack.peek());
currentFragment = fragmentBackStack.peek();
}
else
{
//back stack empty
currentFragment = HOME_FRAGMENT;
}