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.
Related
I'm pretty new to Android.
This is my scenario: I have a simple app with 3 tabs. In each tab i want to use one or more fragments. This is the situation:
Tab 1:
Fragment A
Tab 2:
Fragment B
Fragment C
Fragment D
Tab 3:
Fragment E
Fragment F
In "Tab 1" I have no issue. All works pretty good. Issues arise when I need to move in "Tab 2" and "Tab 3".
In Tab 2 I have to propagate some parameters from "Fragment B" to "Fragment C" and from "Fragment C" to "Fragment D".
Then it can happen that when user clicks on some button in "Fragment D" I have to pass to "Tab 3" and I have to propagate some parameters from "Fragment D" to "Fragment E".
In my main Activity for Tab handling I'm using these components:
android.support.design.widget.TabLayout
android.support.v4.view.ViewPager
android.support.v4.app.FragmentStatePagerAdapter (I created a custom
class)
android.support.design.widget.TabLayout.OnTabSelectedListener (I created a custom class)
My very simple FragmentStatePagerAdapter extension is:
public class MyOwnPageAdapter extends FragmentStatePagerAdapter {
private int numeroTab;
public MyOwnPageAdapter(FragmentManager fm, int numeroTab) {
super(fm);
this.numeroTab = numeroTab;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return new FragmentA() ;
case 1:
return new FragmentB() ;
case 2:
return new FragmentC() ;
default:
return null;
}
}
#Override
public int getCount() {
return numeroTab;
}
}
My very simple TabLayout.OnTabSelectedListener extension is:
public class TabSelectedListener implements TabLayout.OnTabSelectedListener {
private ViewPager viewPager;
public TabSelectedListener(ViewPager viewPager){
this.viewPager = viewPager;
}
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
I'm able in switching fragments inside tabs that is in Tab 2 i can switch from Fragment B to Fragment C and so on. I'm having issues in passing parameters between fragments and above all from Fragment D in Tab 2 to Fragment E in Tab 3
In my Fragments implementation byt using the android.support.v4.app.FragmentManager I can add and remove views (e.g. fragments) by doing something like this:
mFragmentManager.beginTransaction().add(rootView.getId(),mListaEdificiFragment, "BUILDS").addToBackStack(null).commit();
The problem is the param propagation that since the FragmentStatePagerAdapter seems to cache views it happens that the fragment constructor is called but the onCreate and onCreateView are no more called so I can't handle the propagated parameters.
Is there any solution to this? Or am I simply wrong in my navigation pattern? I would like to avoid to collapse Fragment B,Fragment C and Fragment D in one "big view" where to hide some section (the same for Fragment E e Fragment F)
Any suggestion is more then welcome
Angelo
One simple solution to transfer a variable value from one fragment to another is shared preferences (can also be used to transfer values from one activity to another too). Shared preference will save data against variables that will persist across all the activities and fragments in an android app.
Now in your case, lets assume you want to transfer a value name = angelo from your fragment A to fragment B. In your fragment A, write this code:
Button updateName = findViewById(R.id.btnupdateTeamName);
updateTeamName .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString("name", "angelo");
editor.commit();
}
});
When executed, the above code will update a value name with angelo in shared preferences. This will be available throughout your app.
For more info about shared preference, check out this official document.
I write my Fragment like this for passing data to it.
public class MyFragment extends Fragment {
private static String ARG_PARAM1 = "data";
private String data;
public MyFragment() {
// Required empty public constructor
}
public static MyFragment newInstance(String data) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, data);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
data = getArguments().getString(ARG_PARAM1);
}
}
}
Now data can be passed to the Fragment by calling MyFragment.newInstance("Hello"). I hope this helps.
I have faced a similar issue in my project.
In my case, I have viewpager and each tab has multiple fragment.
So one of the simple solutions is to use LiveData and ViewModel.
In your Tab2:
Fragment B
Fragment C
Fragment D
TabTwoViewModel (with live data)
In mutable Live data observer this live data to Fragment B, C, and D.
When you update live data object, Live data notify automatically all fragment.
Finally I got a solution.
Since the main problem is the fragments communication, I followed the official documentation
Let's suppose I have Fragment A with list of articles and Fragment B where to see the selected article detail, in my Fragment A i wrote:
public class FragmentA extends Fragment {
private OnArticleSelectionListener mCallback;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
#Override
public void onStart() {
super.onStart();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activityedifici, container, false);
return rootView;
}
public interface OnArticleSelectionListener {
void onArticleSelection(String articleId);
}
#Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
public void setOnArticleSelectionListener(OnArticleSelectionListener mCallback) {
this.mCallback = mCallback;
}
}
As you can see I declared the following interface
public interface OnArticleSelectionListener {
void onArticleSelection(String articleId);
}
This is the article selection listener.
In my Main Activity I wrote the following:
public class MainActivity implements FragmentA.OnArticleSelectionListener{
//All my own stuffs
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FragmentA){
FragmentA ef = (FragmentA)fragment;
ef.setOnArticleSelectionListener(this);
}
}
#Override
public void onArticleSelection(String articleId) {
if( getSupportFragmentManager().findFragmentByTag(TAG_ARTICLE_DETAIL) != null ){
//FragmentB is the article detail and it has already been created and cached
FragmentB dcf = (FragmentB)getSupportFragmentManager().findFragmentByTag(TAG_ARTICLE_DETAIL);
dcf.updateArticleDetail( articleId );
}else{
//FragmentB is the article detail and it has never been created I create and replace the container with this new fragment
FragmentB dcf = new FragmentB();
//Parameter propagation
Bundle args = new Bundle();
args.putString(FragmentB.ARG_ARTICLE_ID, articleId);
dcf.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.container_articles, dcf, TAG_ARTICLE_DETAIL);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
In this way I'm able in intercepting events in FragmentA and propagate them to the FragmentB; when I need to open a Tab all remains the same and finally (after transaction.commit() or the dcf.updateArticleDetail( articleId )) I do the following tabLayout.getTabAt(2).select(); and the third tab (tab index starts from 0) is open and the Detail is showed.
I hope this can be useful
Angelo
Im having an issue that only appears after several hours of inactivity, I researched it ive tried various ways of fixing it to no avail. The issue is after my app has been dormant for several hours the references for my fragments are null, however; they still exist in the frag manager. I use the references to pull the tag, or id by findfragmentby...() so I can call specific methods within them for updating themselves and what not. The fragments are dynamic and have a UI. I have several activities and a service that are called on by the main activity. I can close the app and resume, call activities, pull info from the service, close, use the back button, all without an issue. To give you an idea of how the app is structured...
public class appClass extends Application {
public Fragment fragmentA;
public Fragment fragmentB;
public Fragment fragmentC;
#Override
public void onCreate() {
super.onCreate();
new fragmentTemplate();
fragemntA = fragmentTemplate.newInstance(getDbName(), usefuldata, "A List");
new fragmentTemplate();
fragemntB = fragmentTemplate.newInstance(getDbName(), usefuldata, "B list");
new fragmentTemplate();
fragemntC = fragmentTemplate.newInstance(getDbName(), usefuldata, "C list");
}
}
Moving on to activity where fragments are used in a viewager...
public class mainActivity extends AppCompatActivity implements ...listeners{
appClass myAppClass;
FragmentManager FragMgr;
ViewPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myAppClass = (appClass) getApplication();
setTheme(myAppClass.getAppTheme());
setContentView(R.layout.activity_my_layout);
//toolbar actionbar stuff
FragMgr = getSupportFragmentManager();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new ViewPagerAdapter(FragMgr));
//tab setup
}
//inner class pager adapter is here
}
This is my pager adapter
class ViewPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener{
Fragment fragment;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (myAppClass.fragmentA != null) {
fragment = myAppClass.fragemntA ;
}
break;
case 1:
if (myAppClass.fragmentB != null) {
fragment = myAppClass.fragmentB ;
}
break;
case 2:
if (myAppClass.fragmentC != null) {
fragment = myAppClass.fragmentC ;
}
break;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
}
I have a FAB and its listener looks like this
#Override
public void onClick(View v) {
Fraggment fragment;
int i = viewPager.getCurrentItem();
if (v.getId() == floatingActionButton.getId()) {
switch (i) {
case 0:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentA.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 1:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentB.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 2:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentC.getTag());
fragment.addItem(fragment.getSomeString());
break;
}
}
}
code for a fragment
public class fragmentTemplate extends Fragment implements RecyclerAdapter.aListener {
private appClass myAppclassReference;
private RecyclerView recyclerView;
private View view;
private FragmentTitle;
public static fragmentTemplate newInstance(String a, String b, String c) {
Bundle args = new Bundle();
args.putString(KEY_A, a);
args.putString(KEY_B, b);
args.putString(KEY_C, c);
fragmentTemplate fragment = new fragmentTemplate();
fragment.setArguments(args);
return fragment;
}
public String getFragmentTitle() {
return fragmentTitle;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
view = inflater.inflate(R.layout.list, container, false);
myAppclassReference= ((appClass) getActivity().getApplication());
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
//get list is a local function that loads a list from a db source
RecyclerAdapter recycler = new RecyclerAdapter(getActivity(), getList());
recycler.setListener(this);
recyclerView.setAdapter(recycler);
recyclerView.setLayoutManager(newLearLayoutManager(getActivity()));
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {}};
return view;
}
}
When things go wonky the app does not crash right away, the tabs still scroll, the viewpager still scrolls, but it is empty, its not until I hit the FAB do I get a nullpointerexception, trying to invoke a method on a nullpointer reference within the onClick Listener does it actually crash.
This is happening because you are messing up with the way that the Android Framework handles Fragments for you. When the ViewPagerAdapter gets Fragments from you in getItem(int), it's using the FragmentManager that you gave it to attach the Fragments. Once the Activity is killed because of low memory, the FragmentManager will automatically create new instances of your Fragments. At this point there are two copies of the fragments, the ones the FragmentManager created and the ones you recreated in your appClass.
You should never keep references to your Fragments. The FragmentManager is free to destroy them and create new ones. If you need to communicate between the Activity and the Fragments in the ViewPager, you can either make the Fragment ask its Activity for commands, use an Event Bus, or explore the sketchy solutions here.
I am currently using the following code to transition a block on the right side of the screen to a shared element on the left:
FragmentDetail newFragment = FragmentDetail.newInstance(id);
setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_move));
setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
View block = view.findViewById(R.id.blocks);
block.setTransitionName("block");
newFragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_move));
newFragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
newFragment.setTransitionId(block.getTransitionName());
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.container, newFragment);
trans.addToBackStack(null);
trans.addSharedElement(block, block.getTransitionName());
trans.commit();
This works exactly how I want, but I would like to reverse the effect upon pressing the back button, animating the item back in. As is, the explode animation plays, but the transition does not.
Any help is greatly appreciated.
Thanks
Josh
KOTLIN with Android Navigation Component
For anyone who's here looking for the answer to this question when you're using the Android Navigation component, you can make the reverse transition animation work by adding these lines to the onViewCreated function of the starting fragment:
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
You would generally use this if you are opening the second fragment by clicking on a RecyclerView item.
Let's say you have two fragments, A and B, and A commits a fragment transaction to start fragment B.
Then that means the exit and reenter transitions should be set on A and the enter and return transitions should be set on B.
It looks like you are calling setSharedElementReturnTransition on the calling fragment instead of the called fragment (newFragment, in this case), which might be causing the problem.
BTW, you should consider calling the set*****Transition() and setSharedElement*****Transition() methods in your fragment's onCreate() method instead of immediately before a fragment transaction is committed. If a fragment is destroyed and recreated, these transitions will be forgotten... so setting them in onCreate() is much safer.
switch from
trans.replace(R.id.container, newFragment);
to
trans.hide(oldFragment).add(R.id.container, newFragment).show(newFragment)
and it should work (as in my case).
reverting a shared fragment transition seems to only work if you hide the old one, instead of replacing it.
I have met the same problem with you.Buy I have found the solution.
You know, there are many causes for this problem. I just show my way.
Hope that can help you.
there are two fragments.
one have a RecyclerView widget:
ListFragment.java
public class ListFragment extends Fragment implements RecyclerItemInter {
#Bind(R.id.recycler_view)
RecyclerView recyclerView;
private OnListItemClickListener onListItemClickListener;
public void setOnListItemClickListener(ListFragment.OnListItemClickListener onListItemClickListener) {
this.onListItemClickListener = onListItemClickListener;
}
public ListFragment() {
}
public static ListFragment newInstance() {
ListFragment fragment = new ListFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
ButterKnife.bind(this, view);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
RecyclerAdapter2 adapter = new RecyclerAdapter2(BEAUTY_BEANS);
recyclerView.setAdapter(adapter);
adapter.setItemInter(this);
return view;
}
private static final BeautyBean[] BEAUTY_BEANS = {
new BeautyBean("Avril Lavigne1", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne2", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne3", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne4", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne5", "Avril was born in Canada, the Canadian singer, songwriter creators, actors.")
};
#Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
#Override
public void onItemClick(View view, int position) {
}
#Override
public void onIvClick(RecyclerAdapter2.ViewHolder holder, int position) {
OtherFragment otherFragment = OtherFragment.newInstance();
otherFragment.setSharedElementEnterTransition(new CustomTransition());
otherFragment.setSharedElementReturnTransition(new CustomTransition());
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
otherFragment.setEnterTransition(new Fade());
setExitTransition(new Fade());
}*/
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_layout, otherFragment)
.addToBackStack(null)
.addSharedElement(holder.getPicIv(), getString(R.string.transition_img))
.commit();
}
then you should set the TransitionName to every ImageView in the RecyclerView:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
if (items[position].getImageId() != 0) {
holder.getPicIv().setImageResource(items[position].getImageId());
}
ViewCompat.setTransitionName(holder.getPicIv(), String.valueOf(position) + "_beauty");
holder.getTitleTv().setText(items[position].getName());
holder.getDescTv().setText(items[position].getDesc());
holder.getLinearLayout().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != itemInter) {
itemInter.onItemClick(holder.itemView, position);
}
}
});
holder.getPicIv().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != itemInter) {
itemInter.onIvClick(holder, position);
}
}
});
}
click the list jump to the OtherFragment.
OtherFragment.java
public class OtherFragment extends Fragment {
public OtherFragment() {
}
public static OtherFragment newInstance() {
OtherFragment fragment = new OtherFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_other, container, false);
}
}
fragment_other.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jacksen.supportlibrarydemo.fragment.OtherFragment">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/bg_detail_header"
android:transitionName="#string/transition_img" />
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_backup_white_36dp"
android:transitionName="#string/transition_fab"
app:borderWidth="0dp"
app:elevation="5dp"
app:pressedTranslationZ="10dp"
app:rippleColor="#color/color_gray" />
the crux of the problem is in this xml.
at the beginning, i set the attribute "transitionName" on and its father layout.
Actually we don't need to add the attribute on father Layout.
Just add transitionName to what you want to transform.
OK, that is my solution.
The Joe Muller answer is correct, I wrote it for Java
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
startPostponedEnterTransition();
view.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
This resolve issue if the start transition is inside an adapter
I've read some question about this kind of problem but i can't solve it.
I've had this:
public class Classwork extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_homework);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
}
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a DummySectionFragment (defined as a static inner class
// below) with the page number as its lone argument.
Fragment fragment = new DummySectionFragment();
Bundle args = new Bundle();
args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
args.putInt("posizione", position);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
//code for number of pages
}
#Override
public CharSequence getPageTitle(int position) {
//code for return the title
}
}
public class DummySectionFragment extends Fragment {
public static final String ARG_SECTION_NUMBER = "section_number";
public DummySectionFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//query to populate ListView
//ListView with CustomAdapter
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> myAdapter, View myView, int myItemInt, long mylng) {
//Start new activity DialogDeleteAddGradeCopy
}
});
myDbHelper.close();
return rootView;
}
}
}
I've created it with the Eclipse wizard.
A DummyFragment contains a ListView, that is populated (correctly) with a SQLite query.
With a click on an item of the ListView a "dialog" (actually is an activity with a dialog style) is displayed.
Dialog:
public class DialogDeleteAddGradeCopy extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//retrive data form Intent for the update in the db
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
//update the db
finish();
}
}
});
}
This dialog allows the user to update (with db query) one of the values of the clicked item.
All is working fine, except the update of the Fragment. The update query is ok (db is up-to-date when the dialog is closed), but to update the Fragment i need to swipe right or left two times and turn back (in this way the fragment is re-created so all works fine).
How can i force updating/re-creating the Fragment?
Thanks in advance.
Sorry for my bad English...
Normally when using multiple instances of Fragment (which I'm guessing you are based on the swipe left/swipe right comment) you don't want to update the content using an Intent as you would with an activity but using an Interface.
If the data is coming from another Fragment hosted by the same activity, say in a ViewPager or tab set up, you would set up an interface between the information sending fragment and the host activity and then call a public method to update the reciving fragment from within the host activity. The basics of using an interface to update fragments can be found here. I don't know the specifics of your whole project but I'd look into this.
As for what's posted:
You're code is not behaving properly because getItem is not proper in this context. When you are updating information by passing an intent and need to retrieve a new Bundle of information you should use
#Override
protected void onNewIntent(Intent intent) {
// TODO Auto-generated method stub
super.onNewIntent(intent);
}
You may have to set an activity flag to get this to work (for example FLAG_ACTIVITY_SINGLE_TOP) but basicly the idea is onNewIntent will be updated when a new intent is passed to your activity. Using getItem as you have it now will only update when the fragment is initially created and the args are set.
I have narrowed my problem down to being a problem with the FragmentManager retaining instances of old fragments and my viewpager being out of sync with my FragmentManager. See this issue: http://code.google.com/p/android/issues/detail?id=19211#makechanges. I still have no clue how to solve this. Any suggestions?
I have tried to debug this for a long time and any help would be greatly appreciated. I am using a FragmentPagerAdapter which accepts a list of fragments like so:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
The implementation is standard. I am using ActionBarSherlock and v4 computability library for Fragments.
My problem is that after leaving the app and opening several other applications and coming back, the fragments lose their reference back to the FragmentActivity (ie. getActivity() == null). I can not figure out why this is happening. I tried to manually set setRetainInstance(true); but this does not help. I figured that this happens when my FragmentActivity gets destroyed, however this still happens if I open the app before I get the log message. Are there any ideas?
#Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
The adapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
}
One of my Fragments stripped but I commented everything out that's stripped and it still doesn't work.
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
#Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
#Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
#Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
#Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
#Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
The update method is called when a user interacts with a different fragment and the getActivity is returning null. Here is the method the other fragment is calling:
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
I believe that when the app is destroyed then created again instead of just starting the app up to the default fragment the app starts up and then viewpager navigates to the last known page. This seems strange, shouldn't the app just load to the default fragment?
You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.
If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.
Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.
Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.
The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.
Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.
I found simple solution which worked for me.
Make your fragment adapter extends FragmentStatePagerAdapter instead of FragmentPagerAdapter and override method onSave to return null
#Override
public Parcelable saveState()
{
return null;
}
This prevent android from recreating fragment
One day later I found another and better solution.
Call setRetainInstance(true) for all your fragments and save references to them somewhere. I did that in static variable in my activity, because it's declared as singleTask and fragments can stay the same all the time.
This way android not recreate fragments but use same instances.
I solved this issue by accessing my fragments directly through the FragmentManager instead of via the FragmentPagerAdapter like so. First I need to figure out the tag of the fragment auto generated by the FragmentPagerAdapter...
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.viewpager+":"+pos;
}
Then I simply get a reference to that fragment and do what I need like so...
Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);
Inside my fragments I set the setRetainInstance(false); so that I can manually add values to the savedInstanceState bundle.
#Override
public void onSaveInstanceState(Bundle outState) {
if(this.my !=null)
outState.putInt("myId", this.my.getId());
super.onSaveInstanceState(outState);
}
and then in the OnCreate i grab that key and restore the state of the fragment as necessary. An easy solution which was hard (for me at least) to figure out.
Global working tested solution.
getSupportFragmentManager() keeps the null reference some times and View pager does not create new since it find reference to same fragment. So to over come this use getChildFragmentManager() solves problem in simple way.
Don't do this:
new PagerAdapter(getSupportFragmentManager(), fragments);
Do this:
new PagerAdapter(getChildFragmentManager() , fragments);
Do not try to interact between fragments in ViewPager. You cannot guarantee that other fragment attached or even exists. Istead of changing actionbar title from fragment, you can do it from your activity. Use standart interface pattern for this:
public interface UpdateCallback
{
void update(String name);
}
public class MyActivity extends FragmentActivity implements UpdateCallback
{
#Override
public void update(String name)
{
getSupportActionBar().setTitle(name);
}
}
public class MyFragment extends Fragment
{
private UpdateCallback callback;
#Override
public void onAttach(SupportActivity activity)
{
super.onAttach(activity);
callback = (UpdateCallback) activity;
}
#Override
public void onDetach()
{
super.onDetach();
callback = null;
}
public void updateActionbar(String name)
{
if(callback != null)
callback.update(name);
}
}
You can remove the fragments when destroy the viewpager, in my case, I removed them on onDestroyView() of my fragment:
#Override
public void onDestroyView() {
if (getChildFragmentManager().getFragments() != null) {
for (Fragment fragment : getChildFragmentManager().getFragments()) {
getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
}
}
super.onDestroyView();
}
After a few hours of looking for a similar issue I think a have another solution. This one at least it worked for me and I only have to changed a couple of lines.
This is the problem I had, I have an activity with a view pager that uses a FragmentStatePagerAdapter with two Fragments. Everything works fine until I force the activity to get destroyed (developer options) or I rotate the screen. I do keep a reference to the two fragments after they get created inside the method getItem.
At that point the activity will be created again and everything works fine at this point but I have lost the reference to my fragmetns as getItem doesn't' get called again.
This is how I fixed that problem, inside the FragmentStatePagerAdapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object aux = super.instantiateItem(container, position);
//Update the references to the Fragments we have on the view pager
if(position==0){
fragTabOne = (FragOffersList)aux;
}
else{
fragTabTwo = (FragOffersList) aux;
}
return aux;
}
You won't get a call on getItem again if the adapter already has a reference to it internally, and you shouldn't change that. Instead you can get the fragment it's being used by looking at this other method instantiateItem() which will be called for each of your fragments.
Hope that helps anyone.
Since people don't tend to read comments, here is an answer that mostly duplicates what I wrote here:
the root cause of the issue is the fact that android system does not call getItem to obtain fragments that are actually displayed, but instantiateItem. This method first tries to lookup and reuse a fragment instance for a given tab in FragmentManager. Only if this lookup fails (which happens only the first time when FragmentManager is newly created) then getItem is called. It is for obvious reasons not to recreate fragments (that may be heavy) for example each time a user rotates his device.
To solve this, instead of creating fragments with Fragment.instantiate in your activity, you should do it with pagerAdapter.instantiateItem and all these calls should be surrounded by startUpdate/finishUpdate method calls that start/commit fragment transaction respectively. getItem should be the place where fragments are really created using their respective constructors.
List<Fragment> fragments = new Vector<Fragment>();
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);
adapter.startUpdate(viewPager);
fragments.add(adapter.instantiateItem(viewPager, 0));
fragments.add(adapter.instantiateItem(viewPager, 1));
// and so on if you have more tabs...
adapter.finishUpdate(viewPager);
}
class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager manager) {super(manager);}
#Override public int getCount() {return 2;}
#Override public Fragment getItem(int position) {
if (position == 0) return new Fragment0();
if (position == 1) return new Fragment1();
return null; // or throw some exception
}
#Override public CharSequence getPageTitle(int position) {
if (position == 0) return getString(R.string.tab0);
if (position == 1) return getString(R.string.tab1);
return null; // or throw some exception
}
}
Since the FragmentManager will take care of restoring your Fragments for you as soon as the onResume() method is called I have the fragment call out to the activity and add itself to a list. In my instance I am storing all of this in my PagerAdapter implementation. Each fragment knows it's position because it is added to the fragment arguments on creation. Now whenever I need to manipulate a fragment at a specific index all I have to do is use the list from my adapter.
The following is an example of an Adapter for a custom ViewPager that will grow the fragment as it moves into focus, and scale it down as it moves out of focus. Besides the Adapter and Fragment classes I have here all you need is for the parent activity to be able to reference the adapter variable and you are set.
Adapter
public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {
public final String TAG = this.getClass().getSimpleName();
private final int COUNT = 4;
public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;
private int mCurrentPage = 0;
private boolean mScrollingLeft;
private List<SummaryTabletFragment> mFragments;
public int getCurrentPage() {
return mCurrentPage;
}
public void addFragment(SummaryTabletFragment fragment) {
mFragments.add(fragment.getPosition(), fragment);
}
public GrowPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<SummaryTabletFragment>();
}
#Override
public int getCount() {
return COUNT;
}
#Override
public Fragment getItem(int position) {
return SummaryTabletFragment.newInstance(position);
}
#Override
public void onPageScrollStateChanged(int state) {}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
adjustSize(position, positionOffset);
}
#Override
public void onPageSelected(int position) {
mCurrentPage = position;
}
/**
* Used to adjust the size of each view in the viewpager as the user
* scrolls. This provides the effect of children scaling down as they
* are moved out and back to full size as they come into focus.
*
* #param position
* #param percent
*/
private void adjustSize(int position, float percent) {
position += (mScrollingLeft ? 1 : 0);
int secondary = position + (mScrollingLeft ? -1 : 1);
int tertiary = position + (mScrollingLeft ? 1 : -1);
float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
float scaleDown = mScrollingLeft ? 1.0f - percent : percent;
float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;
if (scaleUp < BASE_SIZE)
scaleUp = BASE_SIZE;
if (scaleDown < BASE_SIZE)
scaleDown = BASE_SIZE;
// Adjust the fragments that are, or will be, on screen
SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;
if (current != null && next != null) {
// Apply the adjustments to each fragment
current.transitionFragment(percentIn, scaleUp);
next.transitionFragment(percentOut, scaleDown);
if (afterNext != null) {
afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
}
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
// Keep track of which direction we are scrolling
mScrollingLeft = (oldl - l) < 0;
}
}
Fragment
public class SummaryTabletFragment extends BaseTabletFragment {
public final String TAG = this.getClass().getSimpleName();
private final float SCALE_SIZE = 0.8f;
private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;
private String mTitleText;
private Integer mColor;
private boolean mInit = false;
private Float mScale, mPercent;
private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;
public String getTitleText() {
return mTitleText;
}
public void setTitleText(String titleText) {
this.mTitleText = titleText;
}
public static SummaryTabletFragment newInstance(int position) {
SummaryTabletFragment fragment = new SummaryTabletFragment();
fragment.setRetainInstance(true);
Bundle args = new Bundle();
args.putInt("position", position);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);
setupViews();
configureView();
return mRoot;
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mColor = savedInstanceState.getInt("color", Color.BLACK);
}
configureView();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("color", mColor);
super.onSaveInstanceState(outState);
}
#Override
public int getPosition() {
return getArguments().getInt("position", -1);
}
#Override
public void setPosition(int position) {
getArguments().putInt("position", position);
}
public void onResume() {
super.onResume();
mAdapter = mActivity.getPagerAdapter();
mAdapter.addFragment(this);
mCurrentPosition = mAdapter.getCurrentPage();
if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
mInit = true;
transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
return;
}
if (getPosition() == mCurrentPosition && !mInit) {
mInit = true;
transitionFragment(0.00f, 1.0f);
}
}
private void setupViews() {
mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
mTitle = (TextView) mRoot.findViewById(R.id.title);
}
private void configureView() {
Fonts.applyPrimaryBoldFont(mLeft, 15);
Fonts.applyPrimaryBoldFont(mRight, 15);
float[] size = UiUtils.getScreenMeasurements(mActivity);
int width = (int) (size[0] * SCALE_SIZE);
int height = (int) (size[1] * SCALE_SIZE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
mBackground.setLayoutParams(params);
if (mScale != null)
transitionFragment(mPercent, mScale);
setRandomBackground();
setTitleText("Fragment " + getPosition());
mTitle.setText(getTitleText().toUpperCase());
mLeft.setText(getTitleText().toUpperCase());
mRight.setText(getTitleText().toUpperCase());
mLeft.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showNextPage();
}
});
mRight.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showPrevPage();
}
});
}
private void setRandomBackground() {
if (mColor == null) {
Random r = new Random();
mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
}
mBackground.setBackgroundColor(mColor);
}
public void transitionFragment(float percent, float scale) {
this.mScale = scale;
this.mPercent = percent;
if (getView() != null && mCover != null) {
getView().setScaleX(scale);
getView().setScaleY(scale);
mCover.setAlpha(percent);
mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
}
}
#Override
public String getFragmentTitle() {
return null;
}
}
My solution: I set almost every View as static. Now my app interacts perfect. Being able to call the static methods from everywhere is maybe not a good style, but why to play around with code that doesn't work? I read a lot of questions and their answers here on SO and no solution brought success (for me).
I know it can leak the memory, and waste heap, and my code will not be fit on other projects, but I don't feel scared about this - I tested the app on different devices and conditions, no problems at all, the Android Platform seems to be able handle this. The UI gets refreshed every second and even on a S2 ICS (4.0.3) device the app is able to handle thousands of geo-markers.
I faced the same issue but my ViewPager was inside a TopFragment which created and set an adapter using setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).
I fixed this issue by overriding onAttachFragment(Fragment childFragment) in the TopFragment like this:
#Override
public void onAttachFragment(Fragment childFragment) {
if (childFragment instanceof OnboardingDiamondsFragment) {
mChildFragment = (ChildFragment) childFragment;
}
super.onAttachFragment(childFragment);
}
As known already (see answers above), when the childFragmentManager recreate itself, it also create the fragments which were inside the viewPager.
The important part is that after that, he calls onAttachFragment and now we have a reference to the new recreated fragment!
Hope this will help anyone getting this old Q like me :)
I solved the problem by saving the fragments in SparceArray:
public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> fragments = new SparseArray<>();
public SaveFragmentsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.append(position, fragment);
return fragment;
}
#Nullable
public Fragment getFragmentByPosition(int position){
return fragments.get(position);
}
}
Just so you know...
Adding to the litany of woes with these classes, there is a rather interesting bug that's worth sharing.
I'm using a ViewPager to navigate a tree of items (select an item and the view pager animates scrolling to the right, and the next branch appears, navigate back, and the ViewPager scrolls in the opposite direction to return to the previous node).
The problem arises when I push and pop fragments off the end of the FragmentStatePagerAdapter. It's smart enough to notice that the items change, and smart enough to create and replace a fragment when the item has changed. But not smart enough to discard the fragment state, or smart enough to trim the internally saved fragment states when the adapter size changes. So when you pop an item, and push a new one onto the end, the fragment for the new item gets the saved state of the fragment for the old item, which caused absolute havoc in my code. My fragments carry data that may require a lot of work to refetch from the internet, so not saving state really wasn't an option.
I don't have a clean workaround. I used something like this:
public void onSaveInstanceState(Bundle outState) {
IFragmentListener listener = (IFragmentListener)getActivity();
if (listener!= null)
{
if (!listener.isStillInTheAdapter(this.getAdapterItem()))
{
return; // return empty state.
}
}
super.onSaveInstanceState(outState);
// normal saving of state for flips and
// paging out of the activity follows
....
}
An imperfect solution because the new fragment instance still gets a savedState Bundle, but at least it doesn't carry stale data.