Save RecyclerView State inside Fragment in ViewPager created inside another Fragment - android

I have a ViewPager inside a Fragment
public class FragmentPager extends BaseFragment {
#Bind(R.id.viewpager)
ViewPager viewPager;
#Bind(R.id.detail_tabs)
TabLayout detailTabs;
private Activity mActivity;
private PagerAdapter mPagerAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mPagerAdapter = new PagerAdapter(getChildFragmentManager());
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pager, container, false);
ButterKnife.bind(this, v);
viewPager.setAdapter(mPagerAdapter);
final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources()
.getDisplayMetrics());
viewPager.setPageMargin(pageMargin);
detailTabs.setupWithViewPager(viewPager);
return v;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
#Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
}
I am already using FragmentStatePagerAdapter to manage my fragments in ViewPager. Each Fragment has a RecyclerView. On clicking each item of the RecyclerView I am replacing the FragmentPager with FragmentDetail. When I navigate back to the FragmentPager, the RecyclerView starts from the beginning instead of position scrolled.

I think you change your fragment with fragmentTransaction replace method. You need to do it with fragmentTransaction add method. See the below
Basic difference between add() and replace() method of Fragment

Related

Moving Data from Activity to Another Activity's TabFragment's Fragment

I want to move some data from my Mainactivity to a Fragment...which is inside a tab fragment (which handles a SectionPagerAdapter)....which is inside a fragment in Mainactivity2.
I feel its too complicated and cant get it done.
GameEndedActivity (See wrong and correct variable...these are to be transferred)
public class GameEndedActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_ended);
int correct = getIntent().getIntExtra("correct", 0);
int wrong = getIntent().getIntExtra("wrong", 0);
}
}
TabFragment
public class TabFragment extends Fragment {
private int tabsCount;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_tab, container, false);
if(this.getContext() instanceof ChallengeOverviewActivity){
tabsCount = 2;
} else {
tabsCount = 3;
}
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this.getContext(), getFragmentManager(), tabsCount);
ViewPager viewPager = view.findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);
TabLayout tabs = view.findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
return view;
}
}
ResultFragment (Transfer data here - wrong and correct variables)
public class ResultDetailsFragment extends Fragment {
private TextView tvWrong, tvCorrect;
private AnswerListener answerListener;
public interface AnswerListener {
public void correct(int i);
public void wrong(int i);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_result_details, container, false);
tvCorrect = view.findViewById(R.id.tvCorrect);
tvWrong = view.findViewById(R.id.tvWrong);
return view;
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
if (context != null) {
answerListener = (AnswerListener) context;
}
}
}
One way to do this is the add arguments to the fragment when you create an instance of it. In your activity when you create the fragment you want to attach you add some arguments to it. These arguments can then be accessed from the fragments onCreate method.
Just do this same thing again when you are in your fragment and create your second one.
Check this answer out which explains more in detail how to do it: https://stackoverflow.com/a/17436739/964887

Android memory leak issue when using ViewPagerAdapter with nested fragments

I have a fragment, fragment A, which holds a ViewPager. The ViewPager loads different fragments which the user can swipe through "indefinitely" (I use a really high number of pages/loops to emulate this). When a user clicks on the current ViewPager fragment, then fragment A with the ViewPager is replaced by fragment B in the fragment manager. When the user returns from fragment B, the backstack is popped using popBackStackImmediate(). If the user repeats this action several times, the heap begins to fill up by about 100kb at a time until the app starts to become sloppy and malfunction as the memory fills up. I'm unsure what exactly is causing this, can anyone help?
My fragment A with the ViewPager:
public class MainFragment extends Fragment {
private MainWearActivity mMainWearActivity;
View view;
private int currentPage;
private ViewPager pager;
private ViewPagerAdapter adapter;
private LinearLayout helpIcons;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
adapter = new ViewPagerAdapter(this.getChildFragmentManager());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
// Scrolling menu
pager = (ViewPager) view.findViewById(R.id.watchNavPager);
pager.setAdapter(adapter);
pager.addOnPageChangeListener(adapter);
// Set current item to the middle page
pager.setCurrentItem(Consts.FIRST_PAGE);
currentPage = Consts.FIRST_PAGE;
// Set number of pages
pager.setOffscreenPageLimit(4);
// Set no margin so other pages are hidden
pager.setPageMargin(0);
return view;
}
#Override
public void onDestroyView() {
pager = null;
super.onDestroyView();
}
}
My adapter class:
public class ViewPagerAdapter extends FragmentPagerAdapter implements
ViewPager.OnPageChangeListener {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position)
{
position = position % Consts.PAGES;
switch(position){
case Consts.AUDIO_POS:
return new AdapterAudioFragment();
case Consts.VOICE_POS:
return new AdapterVoiceFragment();
case Consts.MAIL_POS:
return new AdapterMailFragment();
case Consts.INFO_POS:
return new AdapterInfoFragment();
default:
return null;
}
}
#Override
public int getCount()
{
return Consts.PAGES * Consts.LOOPS; // (4 * 1000)
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
}
One of my fragments that the adapter loads (they are all pretty much the same):
public class AdapterAudioFragment extends Fragment {
private ImageView menuImg;
private TextView menuText;
private LinearLayout rootView;
private MainWearActivity mMainWearActivity;
private View.OnClickListener imgClickListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
imgClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mMainWearActivity.replaceFragment(mMainWearActivity.getFragment(Consts.FRAG_AUDIO), Consts.FRAG_AUDIO);
}
};
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// Get root view of the fragment layout
rootView = (LinearLayout) inflater.inflate(
R.layout.fragment_nav_object, container, false);
// Set the current menu image and text
menuImg = (ImageView) rootView.findViewById(R.id.fragment_image);
menuImg.setImageResource(R.mipmap.ic_audio);
menuImg.setOnClickListener(imgClickListener);
menuText = (TextView) rootView.findViewById(R.id.menuTxt);
menuText.setText(Consts.MENU_HEADER_AUDIO);
// Set the current menu selection
mMainWearActivity.setCurrentSelection(Consts.AUDIO_POS);
return rootView;
}
}
I have a feeling that the adapter's fragments are all being created but never destroyed and piling up in the heap but I can't figure out how to resolve this. Do I need to call destroyItem in the adapter and manually destroy them? Any help would be most appreciated, thanks.
Adding this to Fragment stopped leaks for me:
#Override
public void onDestroyView() {
super.onDestroyView();
viewPager.setAdapter(null);
}
Looking at the source code, the problem seems to be that when calling ViewPager#setAdapter the view will register itself as observer for the adapter. So each time onViewCreated is called your pager adapter instance will have reference of the newly created view.
There is a specific PagerAdapter for your needs - FragmentStatePagerAdapter
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

Android ViewPager Nested Fragment Lifecycle

I am using nested fragments and viewPager together. Following is my code for HomeFragment which is one of the fragments in viewPager. HomeFragment holds another fragment called FeedFragment which is just a RecyclerView. Inside FeedFragment I am using a callback interface which notifies HomeFragment that FeedFragment was successfully created. The problem I am having is, first time HomeFragment is created, mListener value is set to an instance of HomeFragment inside onCreateView and everything is loaded properly. When I move to a different fragment in viewPager and go back to HomeFragment, HomeFragment onCreateView method is called and mListener is again set to an instance of HomeFragment and after that onCreateView method is called from FeedFragment but mListener is null this time.
Following is the code for FeedFragment:
public class FeedFragment extends Fragment {
RecyclerView feedRecyclerView;
FeedRecyclerViewAdapter feedRecyclerViewAdapter;
private OnCompleteListener mListener;
public FeedFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_feed, container, false);
feedRecyclerView = (RecyclerView) view.findViewById(R.id.postFeedItemContainer);
feedRecyclerViewAdapter = new FeedRecyclerViewAdapter(getActivity());
feedRecyclerView.setAdapter(feedRecyclerViewAdapter);
feedRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mListener.onComplete(feedRecyclerViewAdapter, this);
return view;
}
public interface OnCompleteListener {
void onComplete(
FeedRecyclerViewAdapter feedRecyclerViewAdapter, FeedFragment feedFragment);
}
public void setmListener(OnCompleteListener mListener) {
this.mListener = mListener;
}
}
Following is the code for HomeFragment:
public class HomeFragment extends Fragment implements FeedFragment.OnCompleteListener {
// presenter instance
private HomeFragmentPresenter homeFragmentPresenter = HomeFragmentPresenter.getInstance();
public HomeFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
FeedFragment feedFragment = new FeedFragment();
feedFragment.setmListener(this);
fragmentTransaction.add(R.id.fragmentHomeFeedContainer, feedFragment);
fragmentTransaction.commit();
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false);
}
#Override
public void onComplete(FeedRecyclerViewAdapter feedRecyclerViewAdapter, FeedFragment feedFragment) {
homeFragmentPresenter.fetchDataForAdapter(feedRecyclerViewAdapter, feedFragment);
}
}

ViewPager inside fragment, how to retain state?

In my application the fragment activity holds two fragments, Fragment A and Fragment B. Fragment B is a view pager that contains 3 fragments.
In my activity, to prevent that the fragment is recreated on config changes:
if(getSupportFragmentManager().findFragmentByTag(MAIN_TAB_FRAGMENT) == null) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, new MainTabFragment(), MAIN_TAB_FRAGMENT).commit();
}
Code for Fragment B:
public class MainTabFragment extends Fragment {
private PagerSlidingTabStrip mSlidingTabLayout;
private LfPagerAdapter adapter;
private ViewPager mViewPager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.adapter = new LfPagerAdapter(getChildFragmentManager());
this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
this.mViewPager.setAdapter(adapter);
this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
this.mSlidingTabLayout.setViewPager(this.mViewPager);
}
}
Code for the adapter:
public class LfPagerAdapter extends FragmentPagerAdapter {
private static final int NUM_ITEMS = 3;
private FragmentManager fragmentManager;
public LfPagerAdapter(FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
Log.d("TEST","TEST");
switch (position) {
case 1:
return FragmentC.newInstance();
case 2:
return FragmentD.newInstance();
default:
return FragmentE.newInstance();
}
}
}
My problem is that I am not able to retain the state of the view pager an its child fragments on orientation changes.
Obviously this is called on every rotation:
this.adapter = new LfPagerAdapter(getChildFragmentManager());
which will cause the whole pager to be recreated, right? As a result
getItem(int position)
will be called on every rotation and the fragment will be created from scratch and losing his state:
return FragmentC.newInstance();
I tried solving this with:
if(this.adapter == null)
this.adapter = new LfPagerAdapter(getChildFragmentManager());
in onViewCreated but the result was that on rotation the fragments inside the pager where removed.
Any ideas how to correctly retain the state inside the pager?
You will need to do 2 things to resolve the issue:
1) You should use onCreate method instead of onViewCreated to instantiate LfPagerAdapter;
i.e.:
public class MainTabFragment extends Fragment {
private PagerSlidingTabStrip mSlidingTabLayout;
private LfPagerAdapter adapter;
private ViewPager mViewPager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
this.adapter = new LfPagerAdapter(getChildFragmentManager());
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
this.mViewPager.setAdapter(adapter);
this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
this.mSlidingTabLayout.setViewPager(this.mViewPager);
}
}
2) You will need to extend FragmentStatePagerAdapter instead of FragmentPagerAdapter
Android will automatically recreate your activity on configuration without this line android:configChanges="keyboardHidden|orientation|screenSize" in you manifest so that you can handle onConfiguration change yourself.
The only way then is to use onSaveInstanceState() in both your activityFragement to save viewPager state(current position for example) and in fragments where you need to save stuff
Example on how you can save current position of viewpager and restore it onConfiguration change
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
int position = mViewPager.getCurrentItem();
outState.Int("Key", position );
}
#Override //then restore in on onCreate();
public void onCreated(Bundle savedInstanceState) {
super.onCreated(savedInstanceState);
// do stuff
if (savedInstanceState != null) {
int position= savedInstanceState.getInt("Key");
mViewPager.setCurrentItem(position)
}
}
Of course, this is a very basic example.
Ps: to restore in fragment use onActivityCreated() instead of onCreate() method.
Here is another example on how to retain state : Click me!

FragmentTabHost, Nested Fragments and ViewPager

I have an activity which makes use of a FragmentTabHost, so each tab has a fragment inside it. In the first tab, I have 2 nested fragments, one on the top half of the screen, the other on the bottom half. The fragment on the bottom uses a ViewPager to scan through several LinearLayouts. It all works pretty well.
Until you move to another tab, and return to the first. The bottom nested fragment no longer appears, but the one on top does.
Here's some code demonstrating my usage:
This is how I'm using my main activity:
public class MainActivity extends SherlockFragmentActivity
{
private FragmentTabHost mTabHost;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout_vert);
mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
mTabHost.addTab(mTabHost.newTabSpec("TAB1").setIndicator("", getResources().getDrawable(R.drawable.tab1_selector)), Tab1Fragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("TAB2").setIndicator("", getResources().getDrawable(R.drawable.tab2_selector)), Tab2Fragment.class, null);
}
}
Here's how I'm using the fragment for tab1:
public class Tab1Fragment extends SherlockFragment
{
private NestedFragment1 mNestedFrag1 = null;
private NestedFragment2 mNestedFrag2 = null;
#Override
public void onCreate(Bundle inSavedInstanceState)
{
super.onCreate(inSavedInstanceState);
mNestedFrag1 = new NestedFragment1();
mNestedFrag2 = new NestedFragment2();
FragmentManager fragManager = getChildFragmentManager();
FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.add(R.id.nested_frag1_container, mNestedFrag1, "Frag1");
fragTransaction.add(R.id.nested_frag2_container, mNestedFrag2, "Frag2");
fragTransaction.commit();
}
#Override
public void onDestroy()
{
FragmentManager fragManager = getChildFragmentManager();
FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.remove(mNestedFrag1);
fragTransaction.remove(mNestedFrag2);
fragTransaction.commit();
}
}
And here is how I'm using the 2nd nested fragment (the one that disappears):
public class NestedFragment2 extends SherlockFragment
{
private MyPageAdapter mPageAdapter;
private ViewPager mViewPager;
#Override
public void onCreate(Bundle inSavedInstanceState)
{
super.onCreate(inSavedInstanceState);
mPageAdapter = new MyPageAdapter();
}
#Override
public void onDestroy()
{
super.onDestroy();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View toReturn = inflater.inflate(R.layout.nested_fragment1_layout_top, container, false);
mViewPager = (ViewPager)toReturn.findViewById(R.id.viewpager);
mViewPager.setAdapter(mPageAdapter);
mViewPager.setOffscreenPageLimit(5);
return toReturn;
}
private class MyPageAdapter extends PagerAdapter
{
#Override
public Object instantiateItem(ViewGroup container, int position)
{
View page = getActivity().getLayoutInflater().inflate(R.layout.view_pager_controls_layout, container, false);
container.addView(page);
return page;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
container.removeView((View)object);
}
#Override
public int getCount()
{
return 5;
}
#Override
public float getPageWidth(int position)
{
return 1;
}
#Override
public boolean isViewFromObject(View view, Object object)
{
return(view == object);
}
}
}
Thoughts?
Sounds as if it is being destroyed when you move to a different tab. Try setting ViewPager.setOffscreenPageLimit() to a high number and see if it still happens.
Most likely it's because you should be using a childFragmentManager for the nested fragments, instead of the one obtained from the activity.
From a "top level" fragment whenever you gonna do the transaction to include the nested fragment use getChildFragmentManager() instead of getFragmentManager()
I figured it out. The above code was not the problem. The problem had to do with the way I was handling the PagerAdapter inflation. I wasn't doing it exactly how I showed above. The way I was actually using it was preventing an inflation of the views from happening.

Categories

Resources