Stopping Exoplayer onSwipe of ViewPager - java

I am using a ViewPager with single fragment instance in which I am showing Media files like Images, Videos, Audio.
I have implemented ExoPlayer for handling Video & Audio files. And Glide for images.
To avoid the memory leaks I am releasing the ExoPlayer object like this in ItemViewerFragment.java :
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
trackSelector = null;
simpleExoPlayerView.setPlayer(null);
}
}
#Override
public void onStart() {
super.onStart();
if (player == null && currentGalleryModel != null) {
initializePlayer();
}
}
#Override
public void onResume() {
super.onResume();
if (player == null && currentGalleryModel != null) {
initializePlayer();
}
}
#Override
public void onPause() {
super.onPause();
releasePlayer();
}
#Override
public void onStop() {
super.onStop();
releasePlayer();
}
And in onViewCreated() I am initializing view like this :
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (currentGalleryModel.isVideo() || currentGalleryModel.isAudio()) {
simpleExoPlayerView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
initializePlayer();
} else if (currentGalleryModel.isImage() || currentGalleryModel.isGif()) {
simpleExoPlayerView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
Glide.with(this)
.load(currentGalleryModel.getFilePath())
.placeholder(android.R.color.black)
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
}
}
I am using FragmentStatePagerAdapeter. This is the getItem method :
#Override
public Fragment getItem(int position) {
return ItemViewerFragment.newInstance(mItems.get(position));
}
I am not able to detect the onPause of the fragment on first swipe of Viewpager. On second swipe video/audio files are stopped playing.
In activity I have tried adding .addOnPageChangeListener :
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
try {
((ItemViewerFragment)mAdapter.getItem(mPreviousPos)).imHiddenNow();
} catch (Exception e) {
e.printStackTrace();
}
mPreviousPos = position;
}
And in ItemViewerFragment.java :
public void imHiddenNow(){
releasePlayer();
}
Still video/audio keeps on playing.
Here is a Video Link to the Screencast.
Demo project GitHub link.

I followed an approach of maintaining HashMap of fragment objects inside PagerAdapter
Declare an interface :
interface FragmentLifecycle {
void onPauseFragment()
}
Implement an interface in Fragment.
public void onPauseFragment() {
if (simpleExoPlayer != null){
simpleExoPlayer.setPlayWhenReady(false);
}
}
Store all the fragment objects in a HashMap<Integer,Fragment> with there respective positions as a key. Declare hashmap inside PagerAdapter. Also declare one getter method for accessing fragment objects from hashmap. e.g.
#Override
public Fragment getItem(int position) {
ItemViewerFragment fragment = ItemViewerFragment.newInstance(mItems.get(position));
mFragments.put(position,fragment);
return fragment;
}
public ItemViewerFragment getMapItem(int position) {
return mFragments.get(position);
}
In activity where you have declared viewPager keep one variable currentPosition and implement ViewPager.OnPageChangeListener.
Inside onPageSelected method,
#Override
public void onPageSelected(int position) {
if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
currentPosition = position;
}

Edit from the future: you should never hold a reference to Fragment instances directly inside a FragmentPagerAdapter, because it can cause crashes after process death.
Here is the code for the pager adapter:
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Here is the scroll Listener:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Stop media here.
}
#Override
public void onPageSelected(int position) {
//Save your previous position here.
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
For the media you can use a for Loop and add all the fragments to the list at once and then use this for efficiency :
viewPager.setOffscreenPageLimit(3);
This will make sure only 3 instances of your fragment are available which is enough.
For using single fragment i would suggest you to do it like this:
public MyFragment() {
}
//This is to send a file to the fragment if you need it.
public static MyFragment newInstance(File file) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("file", file);
fragment.setArguments(bundle);
return fragment;
}
Then in the onCreate of Fragment you can retrieve your file like this:
File file;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
file = getArguments().getSerializable("file");
}
Now add your fragments to pager like this:
for (int i = 0; i < totalFiles; i++) {
viewPagerAdapter.addFragment(MyFragment.newInstance(fileList.get(i));
}
Hope this helps.

It's been more then a year since I used Exoplayer & I kinda tackled a similar problem. Please note that the APIs have changed a little bit so take the following code just to get an idea on how to implement a potential solution. Please let me know if it doesn't work, I'll look into the APIs further and get back to you.
Coming to the solution:
private int mPlayerCurrentPosition;
private int getCurrentPlayerPosition() {
return mExoPlayer.getCurrentPosition();
}
// call this from onPause
private void releaseExoplayer() {
mPlayerCurrentPosition = getPlayerCurrentPosition();
mExoPlayer.setPlayWhenReady(false);
mExoPlayer.release(); // this will make the player object eligible for GC
}
private void resumePlaybackFromPreviousPosition(int prevPosition) {
mExoPlayer.seekTo(mPlayerCurrentPosition );
}

The problem is that onPause and onResume are not called when the fragment visibility changed in ViewPager.
The solution is to add 2 visibility events: losingVisibility and gainVisibility.
Why is it a great solution?
Because you keep the framework managing Fragment cache and lifecycle.
We just add the callbacks needed to pause and resume our media in our fragment.
Step-by-step:
The below code is just an explanation for my code. Check Step*.java classes to see full implementation.
Create losingVisibility and gainVisibility methods in YourFragment.java:
public class YourFragment extends Fragment {
/**
* This method is only used by viewpager because the viewpager doesn't call onPause after
* changing the fragment
*/
public void losingVisibility() {
// IMPLEMENT YOUR PAUSE CODE HERE
savePlayerState();
releasePlayer();
}
/**
* This method is only used by viewpager because the viewpager doesn't call onPause after
* changing the fragment
*/
public void gainVisibility() {
// IMPLEMENT YOUR RESUME CODE HERE
loadVideo();
}
}
Call losingVisibility and gainVisibility every time a new page is selected (onPageSelected) in YourActivity.java:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
YourFragment cachedFragmentLeaving = mYourPagerAdapter.getCachedItem(mCurrentItem);
if (cachedFragmentLeaving != null) {
cachedFragmentLeaving.losingVisibility();
}
mCurrentItem = position;
YourFragment cachedFragmentEntering = mYourPagerAdapter.getCachedItem(mCurrentItem);
if (cachedFragmentEntering != null) {
cachedFragmentEntering.gainVisibility();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Add getCachedItem to YourPagerAdapter.java:
The 3rd step is adding a method to retrieve cached fragments. To do it we must cache a reference to a fragment created (overriding instantiateItem) and release the same reference (overriding destroyItem).
public class YourPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<YourFragment> mFragmentsHolded = new SparseArray<>();
#NonNull
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object fragment = super.instantiateItem(container, position);
if(fragment instanceof StepFragment) {
mFragmentsHolded.append(position, (StepFragment) fragment);
}
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
mFragmentsHolded.delete(position);
super.destroyItem(container, position, object);
}
public YourFragment getCachedItem(int position) {
return mFragmentsHolded.get(position, null);
}
}

i know its so late but i hope it helps anyone ...
viewPager keeps offscreen fragment started ..
so you have:
prev offscreen fragment (start) --> visible fragment (working)--> next offscreen fragment (start)
solution is you can override Fragment.setUserVisibleHint() and handle play/pause video..

Related

How to pause a fragment in ViewPager + TabLayout when a different tab is selected?

Right now I have a tablayout with a ViewPager + adapter below it. I want to pause a fragment when a different tab is selected (not destroy/pop it, just pause it) and resume it when the fragment's tab is selected.
public class PagerFragment extends Fragment {
ViewPagerAdapter pagerAdapter;
ViewPager pager;
TabLayout tabLayout;
int position;
public PagerFragment() {
super();
}
public void setPosition(int position) {
this.position = position;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onPause() {
super.onPause();
pagerAdapter.clear();
pagerAdapter.notifyDataSetChanged();
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onResume() {
super.onResume();
if (!isAdded()) {
pagerAdapter.instantiateItem(pager, position);
}
}
#SuppressLint("InflateParams")
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_pager, null);
pagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
setPosition(position);
pagerAdapter.setDevicesList(devicesList);
pager = root.findViewById(R.id.pager);
pager.setAdapter(pagerAdapter);
tabLayout = root.findViewById(R.id.tab_layout);
for (int i = 0; i < devicesList.size(); i++) {
tabLayout.addTab(tabLayout.newTab().setText(String.valueOf(devicesList.get(i).getName())));
}
TabLayout.Tab tab = tabLayout.getTabAt(position);
if (tab != null) {
tab.select();
pager.setCurrentItem(position);
tabLayout.setupWithViewPager(pager);
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
pager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
int pos = tab.getPosition();
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(pager));
return root;
}
}
public class ViewPagerAdapter extends FragmentPagerAdapter {
ArrayList<ConnectedBluetoothDevice> devicesList;
ArrayList<DataViewFragment> dataViewFragments;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Initializes the adaptor with the device list and the list of data fragments.
* #param devicesList List of devices to be displayed in their own data fragment.
*/
public void setDevicesList(ArrayList<ConnectedBluetoothDevice> devicesList) {
this.devicesList = devicesList;
dataViewFragments = new ArrayList<>();
for (int i = 0; i < devicesList.size(); i++) {
dataViewFragments.add(new DataViewFragment(devicesList.get(i)));
}
}
/**
* GetItem
* #param position position of item in the viewPager list to be returned
* #return fragment to view
*/
#NonNull
public Fragment getItem(int position) {
if (dataViewFragments != null && position < dataViewFragments.size()) {
return dataViewFragments.get(position);
} else {
return new DataViewFragment(null);
}
}
#Override
public int getCount() {
return devicesList.size();
}
/**
* Updates the tabs in tabLayout while swiping pages
* #param position of the viewPage
* #return title of the tab
*/
public CharSequence getPageTitle(int position) {
super.getPageTitle(position);
return devicesList.get(position).getName();
}
public void clear() {
dataViewFragments.clear();
}
}
Some notes:
I'm instructed to fix this bug in ViewPager and FragmentPagerAdapter, so please don't recommend me solutions in ViewPager2 and FragmentStatePagerAdapter :)
I heard that setOffscreenPageLimit(0); isn't the best practice so I want to avoid that situation (especially since I only need to pause the fragment but not destroy it)
This depends on the version of ViewPager you are using as it was added later.
Your are using a deprecated constructor
So as long as your using a recent version of Viewpager you should use the non deprecated constructor:-
public FragmentPagerAdapter(
#NonNull FragmentManager fm,
#FragmentPagerAdapter.Behavior int behavior
)
This constructor with the behaviour BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
then only the current Fragment is in the RESUMED state. All other fragments are capped at STARTED
To get to the STARTED lifecycle state from RESUMED onPause is called.
so change
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
to
public ViewPagerAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
This method is used to for calling functions to run only when the specified tab is selected.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// add your tab functions here
} else {
// do nothing
}
}

Share real time Data From Viewpager Fragment to Activity

I have Fragments with a play button in my fragment. Whenever the user presses the play button from the ViewPager fragment, it calls a function in host activity which plays the player.
I am trying to implement the live data by using a ViewModel but unsuccessful.
public class SlidePlayerFragment extends Fragment {
private MyViewModel viewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View view = inflater.inflate(R.layout.fragment_slide_player, container, false);
ImageView playBtn = view.findViewById(R.id.playBtn);
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewModel.getIsPlaying().setValue(true);
viewModel.getCheck().setValue("Checking");
}
});
return view;
}
}
}
Here's my PagerAdapter
public class MyViewPagerAdapter extends FragmentStatePagerAdapter {
private int pages;
public MyViewPagerAdapter(FragmentManager fm,int count) {
super(fm);
pages = count;
}
#Override
public Fragment getItem(int i) {
return new SlidePlayerFragment();
}
#Override
public int getCount() {
return pages;
}
}
Here is the view model function
private MutableLiveData<Boolean> isPlaying;
public MutableLiveData<Boolean> getIsPlaying() {
if (isPlaying == null)
isPlaying = new MutableLiveData<>();
Log.d("TAG","Checking "+isPlaying.getValue());
return isPlaying;
}
And here is the observer segment
final Observer<Boolean> isPlaying= isPlaying -> {
//code here if playing then do some task here
};
viewModel.getIsPlaying().observe(this,isPlaying);
Note I already tried to find the predefined solution but I didn't find anything.
You do not really have to have a ViewModel for this purpose. You might just consider having public functions in the Activity which hosts the Fragments. For example, let us consider you have the following public functions in your PlayerActivity which launches all the other fragments.
public void play(String songName) {
viewModel.getIsPlaying().setValue(true);
viewModel.getCheck().setValue("Checking");
// Other actions for playing
}
Now, you might just consider the following onClick function instead of the one you are having now.
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((PlayerActivity) getActivity()).play(songName);
}
});
Hope that helps!

Java.lang.IllegalStateException: The application PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged android

I am trying to use a static class to pass value to a view instead of using intent as I have to pass a large amount of data. Sometimes I get this error and couldn't find out what is the main reason
Error :-
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 101, found: 200
My Pager class
public class MemeDetailActivity extends AppCompatActivity implements OnDialogClickListner<Void> {
private ViewPager mPager;
private List<Datum> mList;
private ScreenSlidePagerAdapter pagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meme_detail);
mList = DataResult.getInstance().getData();
DataResult.getInstance().resetData();
if (mList != null)
startPager(selected_position);
else
Toast.makeText(this, "Error loading data", Toast.LENGTH_SHORT).show();
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
}
private void startPager(int position) {
pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(pagerAdapter);
pagerAdapter.notifyDataSetChanged();
mPager.setOffscreenPageLimit(0);
mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
new Interactors(MemeDetailActivity.this).updateView(Util.getAccessToken(), mList.get(position).getId(), new OnFinishListner<ViewsRM>() {
#Override
public void onFinished(ViewsRM result) {
}
#Override
public void onFailed(String msg) {
}
});
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
mPager.setCurrentItem(position);
}
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
//todo entry point 3 : showing the image in the viewpager
#Override
public Fragment getItem(int position) {
Fragment fragment = new fragment_mypager_new();
Bundle bundle = new Bundle();
if (frmMyMemes)
bundle.putParcelable("MY_DATA", myList);
bundle.putSerializable("DATA", mList.get(position));
fragment.setArguments(bundle);
return fragment;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public int getCount() {
return mList.size();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
}
My static class
public class DataResult<T> {
private static DataResult instance;
private List<T> data = null;
protected DataResult() {
}
public static synchronized DataResult getInstance() {
if (instance == null) {
instance = new DataResult();
}
return instance;
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public void resetData() {
data = null;
}
}
How I call the activity
Intent intent = new Intent(getActivity(), MemeDetailActivity.class);
DataResult.getInstance().setData(mListA);
look at your code:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
this code is use to refresh view when view change. it is also called notifyDataSetChanged()
its optional ovverride method so you can remove it.
Take a look at this answer: ViewPager PagerAdapter not updating the View
or else you can Change the FragmentStatePagerAdapter to FragmentPagerAdapter
Sometimes this error occurs.
Here is my suggestion:
In your DataResult->getData()
return a copy of the data.
DataResult is a singleton and holds the data. When your view gets the data, the view gets the data reference witch the DataResult is holding. Then setData() would change the data reference. Here comes the IllegalStateException.
Hope it would work :)
I think your DataResult class is not necessary.
According to your comment you are getting the error while you are updating the data set. I think you are getting data from some REST API or another web service. Then assign the data you gets from the API to a temporary Array List and update that temporary array list while you are getting new data. Don't touch the mListA variable until you finish receiving data. After complete getting data from the API assign the temporary array list you used to mListA directly in one line.
mListA = tmpList;
Then again call
mList = mListA;
pagerAdapter.notifyDataSetChanged();
This should solve your error.
try using the following way:
mList=new ArrayList<Datum>();
mList.addAll(DataResult.getInstance().getData());
DataResult.getInstance().resetData();
It may because you first setAdapter() and notifyDataSetChanged in method initUI();
Then, you instantiate the List<?> data in method initData(), the two methods in different lifetime of fragment.
It's the problem about fragment's lifecycle
#Override
public void restoreState(Parcelable state, ClassLoader loader) {}
#Override
public Parcelable saveState() {return null;}
#Add in your adapter after getCount();

Android - Save/restore state with FragmentStatePageAdapter

I made this class:
public class My_ViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<My_Fragment> fragments=new ArrayList<>();
private ArrayList<String> tabTitles=new ArrayList<>();
public My_ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getItemPosition(Object object) {
for (int i=0;i<fragments.size();i++){
Class<? extends My_Fragment> clazz= fragments.get(i).getClass();
if (clazz.isInstance(object)){
return i;
}
}
return POSITION_NONE;
}
#Override
public int getCount() {
return fragments.size();
}
public void addFragment(My_Fragment fragment, String tabTitle){
fragments.add(fragment);
tabTitles.add(tabTitle);
notifyDataSetChanged();
}
public void removeFragment(int position){
fragments.remove(position);
tabTitles.remove(position);
notifyDataSetChanged();
}
public My_Fragment getFragmentAtTab(int position){
return fragments.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitles.get(position);
}
}
I'm adding/removing fragments/tabs dynamically, and i want to save their state through configuration changes.
I implemented onSaveInstanceState():
int tabs=tabLayout.getTabCount();
for (int i=0;i<tabs;i++){
outState.putInt("TabsCount",tabs);
outState.putString("Tab_"+i,getTabTitle(i));
outState.putString("FragmentClassAtTab_"+i,my_viewPagerAdapter.getFragmentAtTab(i).getClass().getName());
}
and i implemented onRestoreInstanceState():
for (int i=0;i<tabs;i++){
try {
My_Fragment fragment= (My_Fragment) Class.forName(savedInstanceState.getString("FragmentClassAtTab_"+i)).newInstance();
addTab(fragment ,savedInstanceState.getString("Tab_"+i));
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
addTab method is:
public void addTab(My_Fragment fragment, String title){
if (fragment!=null) my_viewPagerAdapter.addFragment(fragment,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
}
THE PROBLEM:
Every fragment i add gets recreated "automatically" when configuration change occurs, so its constructor gets called everytime. onCreateView gets called on these instances and not on the ones recreated by my code.
I need onCreateView to get called on fragments instantiated in my tabs in order to set their listviews.
Fragments created "automatically" get replaced by new fragment instances through my onRestoreInstanceState method, but no onCreateView method gets called on these new instances.
I think i'm doing it wrong and maybe there is a better way to save tabs and fragments throught configuration changes... Any help is appreciated.
I once created something similar, which in my case looked like this:
public class DisplayPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "DisplayPagerAdapter";
SparseArray<DisplayFragment> registeredFragments = new SparseArray<DisplayFragment>();
private final Context context;
private final DisplayCoreModule display;
private final FragmentManager fm;
private boolean isAddOrRemoving;
public DisplayPagerAdapter(Context context, FragmentManager fm,
DisplayCoreModule display) {
super(fm);
this.context = context;
this.display = display;
this.fm = fm;
Log.d(TAG, "pages " + display.getPagesCount());
}
public void notifySizeChangingDataSetChange() {
isAddOrRemoving = true;
notifyDataSetChanged();
isAddOrRemoving = false;
}
#Override
public int getCount() {
int count = (display != null && display.getPagesCount() > 0) ? display
.getPagesCount() : 1;
return count;
}
#Override
public int getItemPosition(Object object) {
DisplayFragment frag = (DisplayFragment) object;
if (!display.containsPageId(frag.getPageId())) {
// this will update the 'no information' page with id -1
return POSITION_NONE;
}
if (isAddOrRemoving) {
// recreate all for simplicity
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem " + position);
return DisplayFragment.newInstance(position);
}
#Override
public CharSequence getPageTitle(int position) {
if (display != null && display.getPagesCount() > 0) {
return context.getString(R.string.page) + " " + (position + 1);
} else {
return super.getPageTitle(position);
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem " + position);
DisplayFragment fragment = (DisplayFragment) super.instantiateItem(
container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem " + position);
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
public SparseArray<DisplayFragment> getRegisteredFragments() {
return registeredFragments;
}
}
It has been a long time since I wrote this, so are not able to go into great detail.
However, the key thing to note (I would think), is that I have an external 'bookkeeper' (the DisplayCoreModule). Whenever the amount of fragments would change in DisplayCoreModule, I simply invoked notifySizeChangingDataSetChange() to make the FragmentStatePagerAdapter refresh content.
Regaring some of your problems, FragmentStatePagerAdapter will not invoke onCreateView of your fragments until they are displayed. Usually this means only three fragments: the one you currently look at and one from either side of that fragment.
Perhaps, come to think of it, the root of your problem is missing getItemPosition() with POSITION_NONE. Seem to recall that it was neccesarry for dynamic content to be updated. To make notifyDataSetChanged have a proper effect.
HACKY SOLUTION:
I simply modified my addTab method:
public void addTab(My_Fragment fragment, String title){
boolean createFragment=true;
if (getTabCount()>0){
//all tabs must have different names
for (int i=0;i<getTabCount();i++){
if (getTabTitle(i).equals(title)==true){
createFragment=false;
break;
}
}
}
if (createFragment && fragment!=null){
//this ensures that if a fragment of the same type already exists, that should be the one to be added, tabs are made of fragments of different classes, so there will be no problem while checking this.
for (Fragment f: my_activity.getSupportFragmentManager().getFragments()){
if (fragment.getClass().isInstance(f)){
my_viewPagerAdapter.addFragment((My_Fragment)f,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
return;
}
}
my_viewPagerAdapter.addFragment(fragment,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
}
}

Refresh Fragment in ViewPager using FragmentStatePagerAdapter

I am using a viewPager for its swipe abilities to swipe between two Fragments. However I want to refresh the Fragment contents on swipe. The FragmentStatePagerAdapter does a very good job of caching so its hard to get the Fragment to reload.
I've tried putting everything in the onResume method but that has no effect.
My Adapter is pretty simple:
public class AdapterHomePre extends FragmentStatePagerAdapter {
ArrayList<Fragment> mFrags = new ArrayList<Fragment>();
public AdapterHomePre(FragmentManager fm) {
super(fm);
mFrags.add(new FragmentOne());
mFrags.add(new FragmentTwo());
}
#Override
public int getCount() {
return mFrags.size();
}
#Override
public Fragment getItem(int position) {
return mFrags.get(position);
}
}
I've tried all sorts of things as suggested on stack overflow, including as suggested this:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Which makes no difference.
I don't really want to destroy the fragment each time, but I've not even been able to get that working.
What I ended up doing was editing the Fragment that sets up the viewPager so it forces the child Fragment to reload:
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int i, float v, int i2) {
//Not used
}
#Override
public void onPageSelected(int position) {
//Force the fragment to reload its data
Fragment f = mAdapter.getItem(position);
f.onResume();
}
#Override
public void onPageScrollStateChanged(int i) {
//Not used
}
});
try
mViewPager.setOffscreenPageLimit(1);

Categories

Resources