I have 3 fragments that need to be in a ViewPager. These fragment will hold dynamic information retrieved from a database. I understand that on an orientation change, the activity and fragments are destroyed and recreated. But I was under the impression by its name, that the FragmentStatePagerAdapter will save the state of the fragment. Apparently, I was wrong because every time I did something to the fragment, then change orientation, the fragment is reverted back to how it was laid out in the layout xml file.
As I was debugging, I noticed that on orientation change, the Adapter's getItem() method was never invoked - meaning that it wasn't recreated. So then how come the fragment state reverted back to its original state?
How do I save the fragment state using the FragmentStatePagerAdapter?
Please note that I have been following this tutorial and used their version of the SmartFragmentStatePagerAdapter.java class to manage the fragment dynamically.
And the following are my sample codes.
PageLoader.java - This interface allows MainActivity to manage the loading of the fragment pages dynamically at run time.
public interface PageLoader {
void loadPage(int from, int target);
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements PageLoader {
MyPagerAdapter adapter;
DirectionalViewPager pager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the ViewPager and set it's PagerAdapter so that it can display items
pager = (DirectionalViewPager) findViewById(R.id.vpPager);
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager.setOffscreenPageLimit(5);
pager.setAdapter(adapter);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int position = pager.getCurrentItem();
if (position > 0) pager.setCurrentItem(position - 1);
return true;
}
@Override
public void loadPage(int from, int target) {
PageLoader fragment = (PageLoader) adapter.getRegisteredFragment(target);
fragment.loadPage(from, target);
}
}
MyPagerAdapter.java
public class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
private static final int NUM_ITEMS = 4;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
// Returns total number of pages
@Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show Frag1
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 1: // Fragment # 0 - This will show Frag1 different title
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 2: // Fragment # 1 - This will show Frag2
return Frag2.newInstance(position, Frag2.class.getSimpleName());
default:
return Frag3.newInstance(position, Frag3.class.getSimpleName());
}
}
// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
}
Frag1.java Frag2.java Frag3.java - these are all the same, except for the numbering.
public class Frag1 extends Fragment implements PageLoader {
// Store instance variables
private String title;
private int page;
private TextView txtView;
// newInstance constructor for creating fragment with arguments
public static Frag1 newInstance(int page, String title) {
Frag1 fragmentFirst = new Frag1();
Bundle args = new Bundle();
args.putInt("someInt", page);
args.putString("someTitle", title);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
page = getArguments().getInt("someInt", 0);
title = getArguments().getString("someTitle");
}
// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(page + " - " + title);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
@Override
public void loadPage(int from, int target) {
txtView.setText(txtView.getText() + "\nThis message was created from" + from + " to " + target);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager
android:id="@+id/vpPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager>
</LinearLayout>
frag1.xml frag2.xml frag3.xml - again these are all the same except for the numbering
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#cc2">
<TextView
android:id="@+id/txt_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="txt_frag1"
/>
<Button
android:id="@+id/btn_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="btn_frag1"
android:textSize="26dp" />
</LinearLayout>
PLEASE tell me how I can use the FragmentStatePagerAdapter to save the "State" of my fragments. I've been scouring the internet from 9am to 9pm today... 12 hours... I really need some help figuring this out. Thanks in advance!
EDIT Try this:
Add another instance variable to your fragment:
private String text; // this is part of saved state
Set this variable in loadPage
:
@Override
public void loadPage(int from, int target) {
text = txtView.getText().toString() + "\nThis message was created from" + from + " to " + target;
txtView.setText(text);
}
Override onSaveInstanceState
to save this variable:
@Override
public void onSaveInstanceState(Bundle outState);
outState.putString("text", text);
super.onSaveInstanceState(outState);
}
Then restore the the TextView
state using this variable:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null) {
// not null means we are restoring the fragment
text = savedInstanceState.getString("text");
} else {
text = "" + page + " - " + title;
}
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(text);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
Any time you want something in your fragment to stay the same when things like configuration changes occur, this is how you would track the state, then save and restore it.
This is where you would use onSaveInstanceState
for fragments and activities.
This is the method you would override to save any necessary state. Anything you change in your fragment that you want to have recreated on configuration change must be saved and then restored during onCreate
or onCreateView
.
So if you're trying to restore the text created in loadPage
, you would create a class-level String for the text, set it in loadPage
, save that in the onSaveInstanceState
override, and then restore in in onCreateView
from the savedInstanceState
parameter.
Now here's the kicker: You are noticing that getItem
on your adapter isn't called after a config change. But did you notice that your fragment is still there (even though it wasn't how you left it)? Keep in mind that the activity has a FragmentManager
that is managing the fragments and their transactions. When the activity goes to config change, it saves its state. The FragmentManager
and all of the active fragments are part of that state. Then the fragments are restored in such a way that adapter.getItem
isn't called.
Turns out, that SmartFragmentPagerAdapter
isn't so smart. It can't recreate its registeredFragments
array after a configuration change, so it's really not very useful. I would discourage you from using it.
So how do you send events to off-page fragments when the ViewPager
has appropriated the fragment's tag for its own use?
The technique I use is to define event listener interfaces, and have the fragments register as listeners with the activity. When I fire an event, it's by calling a method on the activity that notifies its active listeners. I give a pretty complete example of this in this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With