Cannot see Fragments in ViewPager - android

Although I can scroll in the ViewPager, the physical fragments themselves are not visible for some reason.
FYI - I have an Activity with tabs using a ViewPager. Inside the first tab/fragment, I have the code below. So essentially, I have an App with a ViewPager controlling the tabs and there's another ViewPager inside one of the Tabs which is to control a bunch of images. I have tested the Fragment in question by putting it inside the top level view pager and it works perfectly fine! It's only when I put it inside the view pager in question does it not render anything...
So, this is the hierarchy for a better understanding:
MainActivity has a ...
ViewPager (3 fragments showing the tab content) has a ...
1st TabFragment has a ...
ViewPager (3 fragments showing images) <--- I can't see these, but I can swipe on them for some reason.
Unfortunately... None of the Fragments are visible, HOWEVER... I can still swipe for some reason to the last fragment...Just nothing is visibly shown...
Layout with the ViewPager inside:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Code with ViewPager
final List<Fragment> imageFragments = new ArrayList<>();
for (final UserImage userImage : user.getImages()) {
final SizeImage processed = userImage.getProcessed();
imageFragments.add(UserImageFragment.newInstance(processed.getFullsize()));
}
final ImagesPagerAdapter imagesAdapter = new ImagesPagerAdapter(getFragmentManager(), imageFragments);
viewPager.setAdapter(imagesAdapter);
The PagerAdapter:
public class ImagesPagerAdapter extends FragmentStatePagerAdapter {
#NonNull
private List<Fragment> fragments;
public ImagesPagerAdapter(#NonNull final FragmentManager fragmentManager,
#NonNull final List<Fragment> fragments) {
super(fragmentManager);
this.fragments = fragments;
}
#Override
public Fragment getItem(final int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
The Fragment:
public class UserImageFragment extends Fragment {
#BindView(R.id.image_view)
ImageView imageView;
public static UserImageFragment newInstance(final String url) {
final UserImageFragment fragment = new UserImageFragment();
Bundle args = new Bundle();
args.putString("url", url);
fragment.setArguments(args);
return fragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_user_image, container, false);
ButterKnife.bind(this, view);
final Bundle arguments = getArguments();
if (arguments != null) {
final String url = arguments.getString("url", null);
if (url != null) {
final Uri uri = Uri.parse(url);
Picasso.with(getActivity()).load(url).into(imageView, new Callback() {
#Override
public void onSuccess() {
Toast.makeText(getActivity(), "Success", Toast.LENGTH_SHORT).show();
}
#Override
public void onError() {
Toast.makeText(getActivity(), "Fail", Toast.LENGTH_SHORT).show();
}
});
}
}
return view;
}
}
And here is xml code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/md_red_900">
<ImageView
android:id="#+id/image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#color/md_yellow_500" />
</RelativeLayout>

To show Fragments inside another Fragment, use getChildFragmentManager() instead of getFragmentManager()
final ImagesPagerAdapter imagesAdapter = new
ImagesPagerAdapter(getChildFragmentManager(), imageFragments);
viewPager.setAdapter(imagesAdapter);

It may not be obvious at first sight but don't try to cache or prepare fragments for a fragment adapter. Fragment(State)PagerAdapter.getItem has to return a new item and when the method is called is managed internally by the adapter.
Let the fragment adapter have data it needs to construct fragments at the right time:
final List<UserImage> userImages = user.getImages();
final ImagesPagerAdapter imagesAdapter = new ImagesPagerAdapter(getFragmentManager(), userImages);
viewPager.setAdapter(imagesAdapter);
And implement the fragment adapter so that getItem returns a new fragment:
public class ImagesPagerAdapter extends FragmentStatePagerAdapter {
#NonNull
private List<UserImage> userImages;
public ImagesPagerAdapter(#NonNull final FragmentManager fragmentManager,
#NonNull final List<UserImage> userImages) {
super(fragmentManager);
this.userImages = userImages;
}
#Override
public Fragment getItem(final int position) {
final UserImage userImage = userImages.get(position);
final SizeImage processed = userImage.getProcessed();
return UserImageFragment.newInstance(processed.getFullsize())
}
#Override
public int getCount() {
return userImages.size();
}
}
Here I see two options for improvement:
Replace List<UserImage> with List<SizeImage> or List<whatever getFullSize returns>. The less implementation details you leak to the adapter the better.
If userImage.getProcessed().getFullsize() takes time (accesses disk, performs computation) do that asynchronously in one of UserImageFragment lifecycle methods - defer to when it's needed and don't block UI thread.
Neither FragmentPagerAdapter nor FragmentStatePagerAdapter are built with updates in mind so
Don't try to modify the userImages list.
If the images change replace and reattach the whole adapter.

Related

notifyDataSetChanged() forces scroll up in FragmentStatePagerAdapter

I have a FragmentStatePagerAdapter which is being refreshed once every second with new data for some of his pages.
The problem is that some pages has a lot of content and a vertical scroll, so every second when notifyDataSetChanged() is being called, the scroll is forzed to his upper possition. it is a very abnormal and annoying behaviour.
I find this on stackoverflow: notifyDataSetChanged() makes the list refresh and scroll jumps back to the top
The problem is that these solutions are designed for a normal ViewPager or normal Adapter and can't work with FragmentStatePageAdapter or something, because after trying them i still have the same problem.
This is my adapter:
public class CollectionPagerAdapter extends FragmentStatePagerAdapter {
public CollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new ObjectFragment();
Bundle args = new Bundle();
args.putString(ObjectFragment.ARG_TEXT, children[i]);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return infoTitlesArray.length;
}
#Override
public CharSequence getPageTitle(int position) {
return infoTitlesArray[position];
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
The ViewPager which has the problem:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingTop="4dp"
android:paddingBottom="4dp"
style="#style/CustomPagerTitleStrip"/>
</android.support.v4.view.ViewPager>
Layout of the fragment:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarSize="5dip"
style="#style/CustomScrollBar">
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="left"
android:textSize="16sp"
android:padding="5dp"
style="#style/CustomTextView"/>
</ScrollView>
The java code for the fragment:
public static class ObjectFragment extends Fragment {
public static final String ARG_TEXT = "object";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false);
Bundle args = getArguments();
TextView tv = ((TextView) rootView.findViewById(R.id.text));
tv.setText(Html.fromHtml(args.getString(ARG_TEXT)));
return rootView;
}
}
EDIT:
I've created a demo project. Here are some important pieces.
Use a FragmentStatePagerAdapter subclass.
We need a FragmentStatePagerAdapter base class in order to save the state of the fragment.
Save the scroll position of the ScrollView in onSaveInstanceState(), and set the scroll position to the saved value when the fragment view is (re)created.
Now that we are saving/restoring the fragment state, we put the scroll position in that state:
#Override
public void onSaveInstanceState(Bundle outState) {
int scrollY = scrollView.getScrollY();
outState.putInt("scrollY", scrollY);
super.onSaveInstanceState(outState);
}
and restore it in onCreateView():
if (savedInstanceState != null) {
final int scrollY = savedInstanceState.getInt("scrollY");
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.setScrollY(scrollY);
}
});
}
Set up a listener notification system for updates.
We have an interface called DataUpdateListener that is implemented by the fragment. The activity provides register/unregister methods:
public void addDataUpdateListener(DataUpdateListener listener) {
mListenerMap.put(listener.getPage(), listener);
}
public void removeDataUpdateListener(DataUpdateListener listener) {
mListenerMap.remove(listener.getPage());
}
... and the fragment registers & unregisters with the activity:
in onCreateView():
((MainActivity) getActivity()).addDataUpdateListener(this);
also
#Override
public void onDestroyView() {
((MainActivity) getActivity()).removeDataUpdateListener(this);
super.onDestroyView();
}
then anytime the data changes, the fragments all get an update notification:
for (int i = 0; i < children.length; i++) {
notifyUpdateListener(i, children[i]);
}
Note that nowhere in the code is onNotifyDataSetChanged() called on the view pager adapter.
The demo is on GitHub at https://github.com/klarson2/View-Pager-Data-Update
This is what is causing the scrolling:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
When you call notifyDataSetChanged(), what the ViewPager does is ask you what to do with the pages it already has.
So it will call getItemPosition to find out: where should this page go? You have three options to respond:
Return an index. So if you return 2 for page 0, then the ViewPager will move the page at 0 to 2.
Return POSITION_UNCHANGED. The page will stay exactly where it is now.
Return POSITION_NONE. This means the page should no longer be displayed.
Once the ViewPager knows where the pages are moved to, it will call getItem() for any gaps in the pages.
So if you don't want the scroll to be disturbed, tell the ViewPager where to put the page instead of telling it to get rid of it and create a new one.

Android Set Text of TextView in Fragment that is in FragmentPagerAdapter

This one is driving me nuts. Basically, I want to create a ViewPager and add a few Fragments to it. Then, all I want to do, it set a value in one of the Fragment's TextViews. I can add the Fragments fine, and they attach, but when I go to findViewById() for one of the TextViews in the first Fragment it throws a NullPointerException. I, for the life of me, can't figure out why.
Here's my code so far, let me know if more is needed please.
public class SheetActivity extends FragmentActivity {
// /////////////////////////////////////////////////////////////////////////
// Variable Declaration
// /////////////////////////////////////////////////////////////////////////
private ViewPager viewPager;
private PagerTitleStrip titleStrip;
private String type;
private FragmentPagerAdapter fragmentPager; //UPDATE
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sheet);
viewPager = (ViewPager) findViewById(R.id.viewPager);
titleStrip = (PagerTitleStrip) findViewById(R.id.viewPagerTitleStrip);
// Determine which type of sheet to create
Intent intent = getIntent();
this.type = intent.getStringExtra("type");
FragmentManager manager = getSupportFragmentManager();
switch (type) {
case "1":
viewPager.setAdapter(new InstallAdapter(manager));
break;
case "2":
viewPager.setAdapter(new InstallAdapter(manager));
break;
}
fragmentPager = (FragmentPagerAdapter) viewPager.getAdapter(); //UPDATE
}
#Override
public void onResume() {
super.onResume();
fragmentPager.getItem(0).setText("something"); //UPDATE
}
class MyAdapter extends FragmentPagerAdapter {
private final String[] TITLES = { "Title1", "Title2" };
private final int PAGE_COUNT = TITLES.length;
private ArrayList<Fragment> FRAGMENTS = null;
public MyAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
FRAGMENTS.add(new FragmentA());
FRAGMENTS.add(new FragmentB());
}
#Override
public Fragment getItem(int pos) {
return FRAGMENTS.get(pos);
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public CharSequence getPageTitle(int pos) {
return TITLES[pos];
}
}
}
All of Fragments I created only have the onCreateView() method overridden so I can display the proper XML layout. Other than that they are 'stock'. Why can't I interact with elements in any of the Fragments?
UPDATE:
So do something like this?
public class FragmentA extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle inState) {
return inflater.inflate(R.layout.fragment_a, container, false);
}
public void setText(String text) {
TextView t = (TextView) getView().findViewById(R.id.someTextView); //UPDATE
t.setText(text);
}
}
XML LAYOUT FOR FRAGMENT A
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/someTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="22sp" />
</LinearLayout>
The TextView is located in the fragments layout, not in the ViewPagers or the PagerAdapter, that is causing the NPE. Now, you have 2 options.
The first is the easiest, you should simple move your code for changing the text into the corresponding fragment's class, FragmentA in this case.
Secondly, you could make the TextView into FragmentA static, so it can be accessed by other classes. So your code would look something like this:
....
TextView myText;
#Override
public View onCreateView(....) {
myLayout = ....;
myText = myLayout.findViewById(yourID);
....
}
And then you would change the text from somewhere else (if it's really necessary):
FragmentA.myText.setText("new text");
Explaining method 2
Use the following in your Fragment.
public static void setText(String text) {
TextView t = (TextView) getView().findViewById(R.id.someTextView);
t.setText(text);
}
Then change the text like:
FragmentA.setText("Lulz");
Unless you are planning to change the value at runtime, you can pass the value into the fragment as a parameter. It is done my using a Bundle and passing it as args into a Fragment, which then retrieves it from it's args. More info here. If you implement this, your instantiation of new Fragments might look something like this:
public InstallAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
FRAGMENTS.add(FragmentA.newInstance("<text to set to the TextView>"));
FRAGMENTS.add(FragmentB.newInstance("<text to set to the TextView>"));
}
If, however, you are planning to update the value at runtime (it will change as user is running the app), then you want to use an Interface to channell communication between your fragment and your activity. Info here. This is what it might look like:
//Declare your values for activity;
ISetTextInFragment setText;
ISetTextInFragment setText2;
...
//Add interface
public interface ISetTextInFragment{
public abstract void showText(String testToShow);
}
...
//your new InstallAdapter
public InstallAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
Fragment fragA = new FragmentA();
setText= (ISetTextInFragment)fragA;
FRAGMENTS.add(fragA);
Fragment fragB = new FragmentB();
setText2= (ISetTextInFragment)fragB;
FRAGMENTS.add(fragB);
}
//then, you can do this from your activity:
...
setText.showText("text to show");
...
and it will update your text view in the fragment.
While it can be done "more easily", these methods are recomended because they reduce chances of bugs and make code a lot more readable and maintainable.
EDIT: this is what your Fragment should look like (modified your code):
public class FragmentA extends Fragment implements ISetTextInFragment {
TextView myTextView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle inState) {
View v = inflater.inflate(R.layout.fragment_a, container, false);
myTextView = (TextView)v.findViewbyId(R.id.someTextView)
return v;
}
#Override
public void showText(String text) {
myTextView.setText(text);
}
}
If after that you are still getting a null pointer exception, your TextView is NOT located where it needs to me, namely in the R.layout.fragment_a filem, and it needs to be located there. Unless you are calling the interface method BEFORE the fragment finished loading, of course.
This line:
TextView t = (TextView) findViewById(R.id.someTextViewInFragmentA);
is looking for the view in your ParentActivity. Of course it wont find it and that's when you get your NPE.
Try something like this:
Add a "tag" to your fragments when you add them
Use someFragment = getSupportFragmentManager().findFragmentByTag("your_fragment_tag")
Get the view of the fragment
fragmentView = someFragment.getView();
And finally find your TextView and set the text
TextView t = (TextView) fragmentView.findViewById(R.id.someTextViewInFragmentA);
t.setText("some text");
How about to change this line
TextView t = (TextView) getView().findViewById(R.id.someTextView); //UPDATE
to
TextView t = (TextView) getActivity().findViewById(R.id.someTextView); //UPDATE
then you can try to update "t" with .setText("some_string") inside "SheetActivity".

Gallery type scroll view in android

I am trying to implement a scroller like show in the image below.
I have tried using viewpager but it only shows one item at a time. And I need to show 5 of them and of different sizes. The one in middle needs to be bigger.
Each Item is a frameLayout that contains an ImageView and a TexView, I dont have any problem implementing that part. The problem is it needs to be a scroller and have many items in scroller e.g upto 15 maybe. But should have only 5 items visible at any one time just like shown below. I have tried many implementations. Please some one give me a working example as I have already tried many examples none of them works perfectly. I have waisted more than a week on this one.
You can control it by overriding getPageWidth() in the PagerFragmentAdapter:
#Override
public float getPageWidth(int position) {
return(0.4f);
}
and making sure the size of your images is not too large, so that the page width fits multiple images.
Here are all the steps to set this up:
1) Add a fragment container to your activity layout, where you will load the PhotoPagerFragment:
<!-- PHOTO PAGER FRAGMENT -->
<FrameLayout
android:id="#+id/photoPagerFragmentContainer"
android:layout_width="match_parent"
android:layout_height="150dp"
android:tag="sticky"
android:layout_gravity="center_horizontal" >
</FrameLayout>
2) Inject the PhotoPagerFragment in your activity's onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
//Insert the fragment
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.photoPagerFragmentContainer);
if (fragment == null) {
fragment = new PhotoPagerFragment();
fm.beginTransaction()
.add(R.id.photoPagerFragmentContainer, fragment)
.commit();
}
}
3) Create a layout for your PhotoPagerFragment:
<?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:background="#android:color/black"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="#+id/photoPager"
android:layout_width="fill_parent"
android:layout_height="120dp"
android:layout_marginTop="2dp"/>
</LinearLayout>
4) Create your PhotoPagerFragment:
public class PhotoPagerFragment extends Fragment {
private ViewPager mPhotoPager;
private PagerAdapter mPhotoAdapter;
public static final String TAG = "PhotoPagerFragment";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_photo_pager, container, false);
mPhotoAdapter = new PhotoPagerFragmentAdapter(getActivity().getSupportFragmentManager());
mPhotoPager = (ViewPager) view.findViewById(R.id.photoPager);
mPhotoPager.setAdapter(mPhotoAdapter);
return view;
}
}
5) And the adapter:
public class PhotoPagerFragmentAdapter extends FragmentPagerAdapter {
private int[] Images = new int[] {
R.drawable.photo_1, R.drawable.photo_2,
R.drawable.photo_3, R.drawable.photo_4,
R.drawable.photo_5, R.drawable.photo_6
};
private int mCount = Images.length;
public PhotoPagerFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return PhotoDetailsFragment.newInstance(Images[position]);
}
#Override
public int getCount() {
return mCount;
}
#Override
public float getPageWidth(int position) {
return(0.4f);
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
6) And finally, your PhotoDetailsFragment that will show each image:
public final class PhotoDetailsFragment extends Fragment {
private int photoResId;
private static final String TAG = "PhotoDetailsFragment";
public static final String EXTRA_PHOTO_ID = "com.sample.photo_res_id";
public static PhotoDetailsFragment newInstance(int photoResId) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_PHOTO_ID, photoResId);
PhotoDetailsFragment fragment = new PhotoDetailsFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
photoResId = (Integer)getArguments().getSerializable(EXTRA_PHOTO_ID);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final ImageView image = new ImageView(getActivity());
image.setImageResource(photoResId);
// Hook up the clicks on the thumbnail views
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
...
}
});
LinearLayout layout = new LinearLayout(getActivity());
layout.setLayoutParams(new LayoutParams(-1, -1));
layout.setGravity(Gravity.CENTER);
layout.addView(image);
return layout;
}
}

ViewPager's Fragment's view lost when ViewPager's parent Fragment hidden then shown

I've been seeing some strange behavior with my ViewPager along with my own FragmentStatePagerAdapter.
My View hierarchy goes like this:
-> (1) Fragment root view (RelativeLayout)
-> (2) ViewPager
-> (3) ViewPager's current fragment view
When the Fragment that is responsible for the Fragment root view (1) gets hidden (using .hide() in a fragment transaction) and then shown (with .show()), the fragment view that was currently showing in the ViewPager (3) becomes null, although the fragment still exists. Basically, my ViewPager becomes completely blank/transparent.
The only way I have found to fix this is to call
int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);
after the parent fragment is shown. This somehow triggers the views to be recreated and appear on screen. Unfortunately, this occasionally causes exceptions dealing with the pager adapter calling unregisterDataSetObserver() twice on an old observer.
Is there a better way to do this? I guess what I am asking is:
Why are my fragment views inside my ViewPager getting destroyed when the parent fragment of the ViewPager is hidden?
Update: this also happens when the application is "minimized" and then "restored" (by pressing the home action key and then returning).
Per request, here's my pager adapter class:
public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();
public MyInfoSlidePagerAdapter (FragmentManager fm) {
super(fm);
}
public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
super(fm);
setInfos(newInfos);
}
#Override
public int getItemPosition(Object object) {
int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
return position > 0 ? position : POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
return infos.get(position).getName();
}
#Override
public Fragment getItem(int i) {
return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
}
#Override
public int getCount() {
return infos.size();
}
public Location getMyInfoAtPosition(int i) {
return infos.get(i);
}
public void setInfos(MyInfo[] newInfos) {
infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
}
public int getPositionOfMyInfo(MyInfo info) {
return infos.indexOf(info);
}
}
I've renamed some variables but other than that it is exactly what I have.
You're not providing enough info for your specific issue, so I built a sample project that tries to reproduce your issue: the app has an activity that holds a fragment (PagerFragment) within a relative layout and below this layout I have a button that hides & shows above PagerFragment. PagerFragment has a ViewPager and each fragment within pager adapter simply displays a label - this fragment is named DataFragment. The label list is created in parent activity and passed to PagerFragment and then through its adapter to each DataFragment. Changing the PagerFragment visibility is done with no issues and each time it's becoming visible again it shows the previous shown label.
The key of the issue:
Use Fragment#getChildFragmentManager() when you're creating the viewpager adapter and not getFragmentManager!
Maybe you can compare this simple project with what you have and check where are the differences. So here goes (top-down):
PagerActivity (the only activity in the project):
public class PagerActivity extends FragmentActivity {
private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.pager_activity);
if (savedInstance == null) {
PagerFragment frag = PagerFragment.newInstance(buildPagerData());
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
}
findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeFragmentVisibility();
}
});
}
private List<String> buildPagerData() {
ArrayList<String> pagerData = new ArrayList<String>();
pagerData.add("Robert de Niro");
pagerData.add("John Smith");
pagerData.add("Valerie Irons");
pagerData.add("Metallica");
pagerData.add("Rammstein");
pagerData.add("Zinedine Zidane");
pagerData.add("Ronaldo da Lima");
return pagerData;
}
protected void changeFragmentVisibility() {
Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
if (frag == null) {
Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
return;
}
boolean visible = frag.isVisible();
Log.d("APSampler", "Pager fragment visibility: " + visible);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (visible) {
ft.hide(frag);
} else {
ft.show(frag);
}
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
its layout file pager_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp" >
<Button
android:id="#+id/btnFragments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Hide/Show fragments" />
<RelativeLayout
android:id="#+id/layout_fragments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/btnFragments"
android:layout_marginBottom="4dp" >
</RelativeLayout>
</RelativeLayout>
Observe that I am adding the PagerFragment when the activity is first shown - and the PagerFragment class:
public class PagerFragment extends Fragment {
private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";
private List<String> data;
private ViewPager pagerData;
public static PagerFragment newInstance(List<String> data) {
PagerFragment pagerFragment = new PagerFragment();
Bundle args = new Bundle();
ArrayList<String> argsValue = new ArrayList<String>(data);
args.putStringArrayList(DATA_ARGS_KEY, argsValue);
pagerFragment.setArguments(args);
return pagerFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
data = getArguments().getStringArrayList(DATA_ARGS_KEY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.pager_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
pagerData = (ViewPager) view.findViewById(R.id.pager_data);
setupPagerData();
}
private void setupPagerData() {
PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
pagerData.setAdapter(adapter);
}
}
its layout (only the ViewPager that takes full size):
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager_data"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and its adapter:
public class LocalPagerAdapter extends FragmentStatePagerAdapter {
private List<String> pagerData;
public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
super(fm);
this.pagerData = pagerData;
}
#Override
public Fragment getItem(int position) {
return DataFragment.newInstance(pagerData.get(position));
}
#Override
public int getCount() {
return pagerData.size();
}
}
This adapter creates a DataFragment for each page:
public class DataFragment extends Fragment {
private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";
private String localData;
public static DataFragment newInstance(String data) {
DataFragment df = new DataFragment();
Bundle args = new Bundle();
args.putString(DATA_ARG_KEY, data);
df.setArguments(args);
return df;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
localData = getArguments().getString(DATA_ARG_KEY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.data_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
}
});
((TextView) view.findViewById(R.id.txt_label)).setText(localData);
}
}
and DataFragment's layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="4dp" >
<Button
android:id="#+id/btn_page_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Interogate" />
<TextView
android:id="#+id/txt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
Enjoy coding!
maybe it will help mViewPager.setOffscreenPageLimit(5)
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
View Pager is pretty adamant in keeping keeping its Fragments fresh always and thus optimizing the performance by freeing up memory when a fragment is not used. Clearly that is a valid useful trait in a mobile system. But due to this persistent deallocation of resources the fragment is created everytime it gains focus.
mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);
Here is the documentation.
this Old Post has an interesting Solution for your problem.. Please Refer
For me i changed to getChildFragmentManager() instead of getFragmentManager()
and works good.
Ex:
pagerAdapt = new PagerAdapt(getChildFragmentManager());
I had the same problem. My app (FragmentActivity) has a pager (ViewPager) with 3 framgents. While swiping between the fragments they are destroyed and recreated all the time. Actually it makes no problem in functionality (expect unclosed Cursors), but I was also wondering about this question.
I do not know if there is a workaround to change the behavior of the ViewPager, but I suggest to have a configuration object (maybe a static on) and before destroy save your myViewPager object at the config object.
public class App extends FragmentActivity {
static MyData data;
#Override
protected void onCreate(Bundle savedInstanceState) {
data = (MyData) getLastCustomNonConfigurationInstance();
if (data == null) {
data = new MyData();
data.savedViewPager = myViewPager;
} else {
myViewPager = data.savedViewPager;
}
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
return data;
}
}
public class MyData {
public ViewPager savedViewPager;
}
With this way, you can save the reference to the an object which won't be destroyed hence there is reference to it and you can reload all your crucial objects.
I hope you find my suggestion useful!

Fragment in ViewPager not restored after popBackStack

Problem
A Fragment is not reattached to its hosting ViewPager after returning from another fragment.
Situation
One Activity hosting a Fragment whose layout holds a ViewPager (PageListFragment in the example below). The ViewPager is populated by a FragmentStateViewPagerAdapter. The single Fragments hosted inside the pager (PageFragment in the example below) can open sub page lists, containing a new set of pages.
Behaviour
All works fine as long as the back button is not pressed. As soon as the user closes one of the sub PageLists the previous List is recreated, but without the Page that was displayed previously. Swiping through the other pages on the parent PageList still works.
Code
A sample application can be found on github:
Activity
public class MainActivity extends FragmentActivity {
private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";
public static final String ARG_PARENTS = "Parents";
public void goInto(String mHostingLevel, String mPosition) {
Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
addFragment(hostingFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addBaseFragment();
}
private void addBaseFragment() {
Fragment hostingFragment = newHostingFragment("", "");
addFragment(hostingFragment);
}
private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
Fragment hostingFragment = new PageListFragment();
Bundle args = new Bundle();
args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
hostingFragment.setArguments(args);
return hostingFragment;
}
private void addFragment(Fragment hostingFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
}
PageListFragment
public class PageListFragment extends Fragment {
private String mParentString;
public PageListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hosting, container, false);
}
#Override
public void onResume() {
mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
super.onResume();
}
private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private String mHostingLevel;
public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
super(fm);
this.mHostingLevel = hostingLevel;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
PageFragment pageFragment = new PageFragment();
Bundle args = new Bundle();
args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
args.putInt(PageFragment.ARG_POSITION, position);
pageFragment.setArguments(args);
return pageFragment;
}
#Override
public int getCount() {
return 5;
}
}
}
PageFragment
public class PageFragment extends Fragment {
public static final String ARG_POSITION = "Position";
private String mHostingLevel;
private int mPosition;
public PageFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_page, container, false);
setupTextView(contentView);
setupButton(contentView);
return contentView;
}
private void setupTextView(View contentView) {
mPosition = getArguments().getInt(ARG_POSITION);
mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
TextView text = (TextView) contentView.findViewById(R.id.textView);
text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}
private void setupButton(View contentView) {
Button button = (Button) contentView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openNewLevel();
}
});
}
protected void openNewLevel() {
MainActivity activity = (MainActivity) getActivity();
activity.goInto(mHostingLevel, Integer.toString(mPosition));
}
}
After a lengthy investigation it turns out to be a problem with the fragment manager.
When using a construct like the one above the fragment transaction to reattach the fragment to the page list is silently discarded. It is basically the same problem that causes a
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
when trying to alter the fragments inside the FragmentPager.
The same solution, as for problems with this error, is also applicable here. When constructing the FragmentStatePagerAdapter supply the correct child fragment manager.
Instead of
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
do
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
See also: github
What Paul has failed to mention is, if you use getChildFragmentManager, then you will suffer the "blank screen on back pressed" issue.
The hierarchy in my case was:
MainActivity->MainFragment->TabLayout+ViewPager->AccountsFragment+SavingsFragment+InvestmentsFragment etc.
The problem I had was that I couldn't use childFragmentManagerfor the reason that a click on the item Account view (who resides inside one of the Fragments of the ViewPager) needed to replace MainFragment i.e. the entire screen.
Using MainFragments host Fragment i.e. passing getFragmentManager() enabled the replacing, BUT when popping the back-stack, I ended up with this screen:
This was apparent also by looking at the layout inspector where the ViewPager is empty.
Apparently looking at the restored Fragments you would notice that their View is restored but will not match the hierarchy of the popped state. In order to make the minimum impact and not force a re-creation of the Fragments I re-wrote FragmentStatePagerAdapter with the following changes:
I copied the entire code of FragmentStatePagerAdapter and changed
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
}
with
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(f);
mCurTransaction.attach(f);
return f;
}
}
...
}
This way I am effectively making sure that that the restored Fragments are re-attached to the ViewPager.
Delete all page fragments, enabling them to be re-added later
The page fragments are not attached when you return to the viewpager screen as the FragmentStatePagerAdapter is not re-connecting them. As a work-around, delete all the fragments in the viewpager after popbackstack() is called, which will allow them to be re-added by your initial code.
[This example is written in Kotlin]
//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
val item = childFragmentManager.findFragmentByTag("f$i")
if (item != null) {
adapter.destroyItem(container!!, i, item)
}
}

Categories

Resources