How to save state in fragment - android

I have 4 button to replace fragment in activity [fragment A , fragment B , fragment C , fragment D] and then I replace fragment A to activity and I change value in fragment A after that I replace fragment B to fragment A and replace fragment C to fragment B . But I want to replace fragment A to fragment C . How to save state in fragment A.
Code when I commit fragment
private void beginFragmentTransaction(BaseFragment fragment) {
String tag = fragment.getClass().getName();
currentFragmentTag = tag;
boolean fragmentPopped = getChildFragmentManager().popBackStackImmediate(tag, 0);
if (!fragmentPopped) {
getChildFragmentManager().beginTransaction()
.replace(R.id.container, fragment, tag)
.addToBackStack(tag)
.commit();
}
}
Diagram to replace
fragment A -------> fragment B
fragment B -------> fragment C
fragment C -------> fragment A
PS. I don't want to use back button to back to fragment A , I want to replace fragment A and restore data in the first commit.

Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null.
FYI : this is a sample code. Just for your reference.
public class MainFragment extends Fragment {
private String title;
private double rating;
private int year;
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString(TITLE, "Gladiator");
savedInstanceState.putDouble(RATING, 8.5);
savedInstanceState.putInt(YEAR, 2000);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
title = savedInstanceState.getString(TITLE);
rating = savedInstanceState.getDouble(RATING);
year = savedInstanceState.getInt(YEAR);
}
}
FYI : This really a good thread check this also Once for all, how to correctly save instance state of Fragments in back stack?

If you want to save the state of previous tabs and don't want to refresh/recreate view use this code and change the value according to the tabs limit
ViewPager mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(2);

you can show and hide fragments for saving the states,
or use the navigation component latest version

Related

How to listen when a fragment is on the screen

What I need is exactly an onResume method (as it works for activities) for a specific fragment. I'm adding the fragment (let's say fragment A) to the back stack, and opening another fragment (fragment B) (again adding to back stack) from fragment A. I want to update toolbar when fragment B is closed and fragment A is on screen again. I expect onCreateView to get called but it's not getting called when I pop fragment B. I also tried adding an OnBackStackChangedListener to fragment A but then I cannot track which fragment is on the screen when the back stack changes.
So my question is how to make onCreateView get called when I turn back to fragment A. And if this is not a good practice, how else can I track this event?
Edit
I'm showing new fragments with this code:
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(tag)
.commit();
Should I change it somehow to make onCreateView get called? Since I'm adding new fragment B on existing fragment A (I can even click on a button which is in fragment A when B is on the screen), when I pop fragment B, nothing changes with fragment A's situation.
Override this method in the Fragment and check the boolean value
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//Log.e("setUserVisibleHint", "isVisibleToUser " + isVisibleToUser);
}
Put the code that you need to be executed whenever the fragment becomes visible/is hidden in this method, according to the isVisibleToUser boolean value
Did you try OnBackStackChangedListener this way?
public class BlankFragment2 extends Fragment {
public BlankFragment2() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager()==null)
return;
Fragment fr = getFragmentManager().findFragmentById(R.id.container)//id of your container;
if (fr instanceof BlankFragment2) {
//On resume code goes here
}
}
});
return inflater.inflate(R.layout.fragment_blank_fragment2, container, false);
}
}
I hope this solution will works.
1) Put/call addOnBackStackChangedListener on your Activity
getSupportFragmentManager().addOnBackStackChangedListener(backStacklistener);
2) Define backStacklistener inside your Activity
FragmentManager.OnBackStackChangedListener backStacklistener = new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentById(R.id.fragment);
if(fragment instanceof OutboxFragment) {
OutboxFragment currFrag = (OutboxFragment) fragment;
currFrag.onFragmentResume();
}
}
}
};
3) Provide a method on your fragment that you want to be triggered. In this case I create method named onFragmentResume()
public void onFragmentResume() {
MainActivity activity = (MainActivity) getActivity();
activity.showFab();
// or do another thing here
}
Good luck!

Add a fragment to FrameLayout and hide the fragment below

Situation:
I have an activity with a FrameLayout in which I change fragments.
I use:
getSupportFragmentManager().beginTransaction()
.replace(R.id.content, fragment)
.addToBackStack("name")
.commit)
All works fine the problem is when I go back in the stack the previous fragment is reloaded and all the data is lost.
Possible solution:
restore fragment state - I want to avoid this because most of the data is retrieved from the server and it takes a lot of time
use .add(R.id.content, fragment) instead of .add(R.id.content, fragment) but in this case my fragments must have a solid background otherwise they overlay each other. The problem is that I can't set a solid background because of some design constraints.
Question:
How can i use '.add(R.id.content,fragment)' and somehow hide the fragment below it so it won't overlay and I can go back to the previous fragment in the state I left it.
First I would say that there's no need to add the fragment to the backstack if you don't want the user to go back to a previous fragment.
To answer the other question, the FragmentManager has a "hide" method that you can use to keep a fragment in the FragmentManager, but hide it from the user. Then use "show" to reveal it again.
final Fragment oldFragment = methodToGetFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.hide(oldFragment)
.addToBackStack("name")
.commit)
Like stated in the first sentence, the Fragment is going to be popped and the old fragment will be shown when the user presses "back". If you don't want that to happen, then simply remove addToBackStack().
for save data
you can use Activity when replacing fragments, activity is live .
public class MotherActivity extends ActionBarActivity {
private Data data = null;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_adv);
Fragment oldFragment = methodToGetFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.hide(oldFragment)
.addToBackStack("name")
.commit)
}
public void setData(Data data){
....
}
public void getData(){
....}
}
public class FirstFragment extends Fragment {
private AdvActivity act;
......
public void onAttach(Activity activity) {
super.onAttach(activity);
// get data from internet
Data data=getData();
// and save data in mother activity
activity.setData(data);
}
second fragment:
public class SecondFragment extends Fragment {
private AdvActivity act;
private Data data;
......
public void onAttach(Activity activity) {
super.onAttach(activity);
// get data from mother activity
data=activity.getData();
}

Manage Title with Fragment Stack Changed

I am using Fragment activity and a number of fragments to get added and poped out on back press.
Supposed I am adding Fragment B from Fragment A. The title of action bar that I set in A changes by navigating to B; but it is not restoring to A when I pop the fragment B out of the fragment stack as onResume of fragment is not being called.
I am using the code:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().add(R.id.container, fragmentToReplace, tag).addToBackStack(tag).commit();
and to pop out the last fragment:
fragmentManager.popBackStack();
How to manage this. Please suggest.
You can use onBackStackChanged listener which will be called whenever a fragment is popped back. To add the listener use this:
getFragmentManager().addOnBackStackChangedListener(this);
In Override function, check the fragment which is popped and according to it you can change the actionbar title:
#Override
public void onBackStackChanged() {
//Check the fragment from the FrameLayout Container R.id.container
// Depending on the Fragment you can change the Title
setTitle("xyz");
}
Create new class in your package and extend with Fragment says TestFragment and create abstract method in it
abstract public String getTitle();
than extend your every fragment with TestFragment and override getTitle() in your fragments.
public String getTitle(){
return "YOUR_FRAGMENT_NAME";
}
getSupportFragmentManager().addOnBackStackChangedListener(
new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.content_frame);
this.setTitle(fragment.getTitle());
}
});

Fragment calling fragments loosing state on screen rotation

Hi i created a project with a default "Navigation Drawer Activity".
So i have a MainActivity with a fragment with is replaced for each item on menu.
One of the menus is "Customers" with shows a list of customers.
From customers fragment i can see the Interests of this customers, with is a Fragment(CustomerListFragment) calling the interests(InterestsListFragment).
There is even more levels, but to be short that's enough.
This is the code on MainActivity that i use to call fragment from fragment and pass data between
public void passData(Object[] data, Fragment f) {
Bundle args = new Bundle();
args.putSerializable("PASSED_DATA", data);
f.setArguments(args);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, f)
.addToBackStack("")
.commit();
}
And i use like :
mCallbacks.passData(new Object[]{c}, new OpportunityListFragment());
The problem is that when i rotate the phone does not matter from wich level of activity i have, it comes back to the first fragment called(CustomerListFragment), and if i click "Back" on cellphone it gets back to where i was when i rotate the phone.
What do i have to do, to avoid this kind of problem? why it gets back to the first activity evoked if i am replacing fragments?
The answer from ste-fu is correct but let's explore programmatically. There is a good working code in Google documentation # Handling Runtime Changes. There are 2 code snippets that you have to do.
1) Code snippet:
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
Note: Code uses FragmentManager to find the current Fragment. If fragment is null, then the UI or app has not been executed. if not null, then you can get data from RetainedFragment object.
2) Need to retain the Fragment state.
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
Note: setRetainInstance is used in OnCreate. And subclassing the Fragment is recommended, naming it RetainedFragment, used on snippet 1.
When you change screen orientation your parent Activity is destroyed and recreated. Unless you persist the level structure in some fashion, it will always appear as when you first started the activity. You can either use the bundle object, or for more complicated objects you need to persist it to a database.
Either way, onSaveInstanceState is your friend. Then in your onCreate method you need to check the bundle or database, and the set the fragment accordingly.

Bring Fragment to Front (No fragment recreation)

I have three fragments F1 F2 F3 F4 all are accessible from sidebar.
all four can be called at any time and in any order,
Now I want if, F1 is already clicked(created) then never again create F1, but only bring back fragment F1 to front using fragment manager. Same for all other fragment
So far i tried this for every fragment in my container (FRAGMENT ACTIVITY)
if (fragmentManager.findFragmentByTag("apps")==null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment newFragment = new CategoriesFragment();
transaction.replace(R.id.content_frame, newFragment, "apps");
transaction.addToBackStack("apps");
transaction.commit();
} else{
}
If part ensures me NO fragment is recreated (If its created already) again, but what should i write in else part so that already created fragment can be brought to front in View Hierarchy
Please Help, i'm stuck at this for 2 days.
I would put this code in activity class, that must have FrameLayout with id R.id.fragment_container.
private Fragment1 F1;
private Fragment2 F2;
private Fragment3 F3;
private Fragment4 F4;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
F1 = new Fragment1();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F1).commit();
F2 = new Fragment2();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F2).commit();
F3 = new Fragment3();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F3).commit();
F4 = new Fragment4();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F4).commit();
//if needed show F1
getSupportFragmentManager().beginTransaction().show(F1).commit();
}
And add this for button click:
public void onBtnClick(View view){
if(mShowF1){
getSupportFragmentManager().beginTransaction().show(F1).commit();
getSupportFragmentManager().beginTransaction().hide(F2).commit();
getSupportFragmentManager().beginTransaction().hide(F3).commit();
getSupportFragmentManager().beginTransaction().hide(F4).commit();
}
//...
}
On button click(s) you can show, that fragment that you want and hide others.
NOTE (#developer1011):
For use after activity save state call commitAllowingStateLoss (). Use with care, because fragment is not restored with activity restoration.
NOTE:
MainActivity should implement OnFragmentInteractionListener for each Fragment.
public class MainActivity extends FragmentActivity implements Fragment1.OnFragmentInteractionListener, Fragment2.OnFragmentInteractionListener, Fragment3.OnFragmentInteractionListener, Fragment4.OnFragmentInteractionListener {//..
#Override
public void onFragmentInteraction(Uri uri) {
//
}
}
Get the fragment by tag and replace it in the container,
else{
Fragment existingFragment = (CategoriesFragment)fragmentManager.findFragmentByTag("apps");
transaction.replace(R.id.content_frame,existingFragment, "apps");
transaction.addToBackStack("apps");
transaction.commit();
}
UPDATE:
you can use hide and show fragment to avoid recreation.instead of using "transaction.replace()"
fragmentTransaction.hide(<oldFragment>);
fragmentTransaction.show(<newFragment>);
JAVA:
If you are just trying to add a Fragment without having to worry about recreating it then I think this method I have wrote to add Fragment will do you job.
public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {
FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
manager.findFragmentByTag ( tag );
FragmentTransaction ft = manager.beginTransaction ();
if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
ft.add ( fragmentHolderLayoutId, fragment, tag );
ft.addToBackStack ( tag );
ft.commit ();
}
else {
for (Fragment frag : manager.getFragments()){
ft.hide(frag)
}
ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
}
}
Kotlin:
fun attachFragment(fragmentHolderLayoutId: Int, fragment: Fragment?, tag: String?) {
val manager: FragmentManager = supportFragmentManager
val ft: FragmentTransaction = manager.beginTransaction()
if (manager.findFragmentByTag(tag) == null) { // No fragment in backStack with same tag..
ft.add(fragmentHolderLayoutId, fragment!!, tag)
ft.addToBackStack(tag)
ft.commit()
} else {
//Hide other fragments
for (frag in manager.fragments){
ft.hide(frag)
}
//Shows the selected fragment.
ft.show(manager.findFragmentByTag(tag)!!).commit()
}
}
Use a simple ArrayList<Fragment> for your Fragments, and add them in order, so that you know get(0) will get F1, get(1) gets F2, etc.
Create the Fragments as singletons. In each fragment add a static field and method:
private static Fragment mMyInstance = null;
public static Fragment newInstance() {
if (mMyInstance == null) {
mMyInstance = new F1();
}
return mMyInstance;
}
Create the Fragments with the static method and add them to the ArrayList.
In each Fragment add the setRetainInstance(true); command to the onCreate() method.
Now when you add the Fragment with the FragmentManager, onCreate() will only be called the first time, but onCreateView() will be called every time. You want to inflate the view and wire the widgets each time, just en case your Activity got recreated because of a configuration change. But you can check something you add to see if it's the first time or not, and reset the widgets to their previous state if not. So, you will need member variables in your Fragments to keep track of their state. Override onStop() to save state, and reapply it in onCreateView() after wiring up the widgets.
Then when the sidebar button is pressed, you get the Fragment that corresponds to that button, remove the previous Fragment, and add the current one with the FragmentManager (or just use the replace() command instead of remov()/add()).
If you are using the Support Fragment, then this static method does the job.
/**
* Takes a Fragment TAG and tries to find the fragment in the manager if it exists and bring it to front.
* if not, will return false;
* #param manager
* #param tag
*/
public static boolean resurfaceFragment(FragmentManager manager, String tag ){
Fragment fragment = manager.findFragmentByTag(tag);
FragmentTransaction transaction = manager.beginTransaction();
if (fragment!=null){
for (int i = 0; i < manager.getFragments().size(); i++) {
Fragment f = manager.getFragments().get(i);
transaction.hide(f);
}
transaction.show(fragment).commit();
return true;
}
return false;
}

Categories

Resources