I have a navigation drawer activity, with one fragment having a view pager and tabs. All 4 fragments are fetching data from a server. My problem is that the view pager is loading the first 2 fragments therefore my first fragment doesn't show a content at first because of the delay of the rest api. So the second fragment is being created and shown before the data in the first fragment is parsed and shown. How can I solve this?
This is my fragment container
public class FragmentMoviesContainer extends KFragment {
private MainActivity activity;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies_container, container, false);
activity = (MainActivity) getActivity();
assert activity != null;
activity.setVisibleFragment(this);
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
ViewPager mViewPager = rootView.findViewById(R.id.container);
TabLayout tabLayout = rootView.findViewById(R.id.tabs);
mViewPager.setAdapter(mSectionsPagerAdapter);
tabLayout.setupWithViewPager(mViewPager);
return rootView;
}
#Override
public void onResume() {
super.onResume();
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null)
actionBar.setTitle(R.string.movies);
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return replaceFragmentMovies(Constants.STRINGS.UPCOMING);
case 1:
return replaceFragmentMovies(Constants.STRINGS.NOW_PLAYING);
case 2:
return replaceFragmentMovies(Constants.STRINGS.POPULAR);
case 3:
return replaceFragmentMovies(Constants.STRINGS.TOP_RATED);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.coming_soon);
case 1:
return getString(R.string.now_playing);
case 2:
return getString(R.string.popular);
case 3:
return getString(R.string.top_rated);
default:
return "";
}
}
#Override
public int getCount() {
return 4;
}
private FragmentMovies replaceFragmentMovies(String type) {
FragmentMovies fragmentMovies = new FragmentMovies();
fragmentMovies.setType(type);
return fragmentMovies;
}
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
}
#Override
public void update(ModelService service, boolean reload) {
}
}
Here's my fragment showed in the tabs
public class FragmentMovies extends KFragment implements MoviesAdapter.OnLoadMoreListener {
private MainActivity activity;
private ModelService service;
private RecyclerView moviesRv;
private String type;
public void setType(String type) {
this.type = type;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
activity = (MainActivity) getActivity();
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
return rootView;
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
if ((objects != null && !objects.isEmpty()) && (responseID == Constants.UPCOMING || responseID == Constants.NOW_PLAYING || responseID == Constants.POPULAR
|| responseID == Constants.TOP_RATED)) {
Section section = (Section) objects.get(0);
MovieListAdapter adapter = new MovieListAdapter(getContext(), section.getMovieList());
moviesRv.setAdapter(adapter);
}
}
#Override
public void update(final ModelService service, final boolean reload) {
boolean hasConnection = Connection.isNetworkAvailable(getContext());
if (hasConnection && service != null) {
final int responseId = getResponseID();
service.getMovies(type, "", false, responseId, reload);
} else {
// progressBar.setVisibility(View.GONE);
DialogHelper.noConnectionDialog(getContext());
}
}
private int getResponseID() {
switch (type) {
case Constants.STRINGS.UPCOMING:
return Constants.UPCOMING;
case Constants.STRINGS.NOW_PLAYING:
return Constants.NOW_PLAYING;
case Constants.STRINGS.POPULAR:
return Constants.POPULAR;
case Constants.STRINGS.TOP_RATED:
return Constants.TOP_RATED;
default:
return 0;
}
}
#Override
public void onLoadMore(MoviesAdapter adapter) {
}
#Override
public void onResume() {
super.onResume();
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setTitle("Movies");
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
activity.getAddFab().hide();
}
#Override
public void onDestroy() {
super.onDestroy();
}
}
The method update calls the rest api url and fetches the data. This is a framework I created based on AsyncTask. The list of objects then is returned to the fragment parsed in the method onServiceResponse where I create the adapter and show the data. The problem is that the second fragment is being created before the method onServiceResponse of the first fragment.
You should make api call from the first fragment and after getting the result you should make the rest of the calls. Let me know if you need any help with the code. I think this should be straight forward.
After Looking your code, there are Two things to inflate Fragments on to tabs.
Use single Fragment for all tabs.
Use individual fragment for every tab.
in the First case, if you are calling APIs form fragment that kind of problem occurs(As yours).
in the Second case APIs, the call will be in individual fragment and there will not be such kind of problem.
So the first solution to your problem is to use individual fragment for every tab.
And if really want to use single fragment for every tab then maintain the sequence of API calling for every instance of the fragment for every tab.
As you are doing in fragment like:
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
in this case you are calling service and and then you are reading setting your view.
The scenario is that here API call will be in the background but the code below API call will execute. Due to that if the response of API any fragment comes then that fragment view will be populated. So Solution of that scenario is that put your API call method in fragment and then call APIs and maintain calls.
if any help just comments. thanks.
I think the accepted answer is not very explanatory, so for anyone coming across this in future, this is what I did. I am calling my REST API from the on create method of the activity hosting the fragments and viewpager and using a single fragment class for 6 tabs by creating 6 instances of the fragment class. But the catch here is, dont set up the viewpager in onCreate, rather set it after the API call receives a successful response, after the data has been saved inside some object. So now the data is ready to be displayed within the fragment when it is first presented.
You should add this code on your one of your fragment.
Handler().postDelayed({
//api call
}, 3000)
So that two fragment can not do api call at same time when you use viewpager.
Related
I have an Activity using a FragmentStatePagerAdapter. If I launch another activity that changes some data involved with what is displayed, the view is not updated.
If the adapter is handling tabs, each to show different aspects of the same object via Fragments,
if an object attribute is changed by an activity launched from a page handled by the adapter,
and the adapter notifyDataSetChanged is called in onActivityResult, the data in the tab view is not getting updated, as I expect it should be.
I cannot figure out why.
In the activity class:
public class EventDetailActivity extends AppCompatActivity
{
public ViewPager viewPager;
public PagerAdapter adapter; // This extends FragmentStatePagerAdapter
public TabLayout tabLayout;
public Event currentEvent; // ****** Contains the data to display in tabs
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_event_detail);
currentEvent = (Event)getIntent().getSerializableExtra(Event.EVENT_KEY); // ***** The object on display was serialized to pass in the intent.
// Serializing it in the initial intent is not a problem, because it is saved in the database within this activity,
// and the calling activity gets the update via the database.
tabLayout = (TabLayout) findViewById (R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().
setText(getResources().getString(R.string.details)));
... Add other tabs. ...
tabLayout.setTabGravity (TabLayout.GRAVITY_FILL);
viewPager = (ViewPager) findViewById (R.id.pager);
adapter = new PagerAdapter(currentEvent, getSupportFragmentManager(), tabLayout.getTabCount());
viewPager.setAdapter (adapter);
viewPager.addOnPageChangeListener (new TabLayout.TabLayoutOnPageChangeListener (tabLayout));
tabLayout.setOnTabSelectedListener (new TabLayout.OnTabSelectedListener () {
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem (tab.getPosition ());
}
});
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId ())
{
case R.id.menu_edit:
Intent intent = new Intent(EventDetailActivity.this, EditEventActivity.class);
intent.putExtra(Event.EVENT_KEY, currentEvent); // TODO Event via serialization, or event id only ?
//intent.putExtra("id", currentEvent.getId());
startActivityForResult(intent, EDIT_EVENT_REQUEST);
return true;
... other cases
}
}
#Override
protected void onActivityResult (int requestCode, int resultCode, Intent data)
{
if (requestCode == EDIT_EVENT_REQUEST)
{
switch (resultCode)
{
case RESULT_CANCELED:
// Nothing to do
return;
case RESULT_EVENT_MODIFIED:
// Cause event view for this activity to update.
// When the edit activity was started, the Event was serialized.
// An updated Event is passed back in the result.
//currentEvent = (Event)data.getSerializableExtra(Event.EVENT_KEY);
//System.out.println("Modified event returned: " + currentEvent.getEventTitle());
// Alternatively, Load the Event from the database:
try
{
HashMap attr = MyApp.getDatabase().getEventById(currentEvent.getId());
currentEvent.load(attr);
System.out.println("Event reloaded: " + currentEvent.getEventTitle());
}
catch (PersistenceException ex)
{
// TODO handle error
}
// FIXME: In both cases the received event is correct, but the UI is not updated.
// The adapter still references the object that was passed to the edit activity as serialized data
// So must give the adapter the object just deserialized/loaded here.
adapter.setEvent(currentEvent); // ***** notifyDataSetChanged() is called within this, but not updating the view !!!!!!!!
return;
case RESULT_EVENT_UPDATE_FAILED:
// Nothing to do
return;
}
}
}
...
}
The adapter:
public class PagerAdapter extends FragmentStatePagerAdapter
{
/** The event on display */
private Event m_event;
public PagerAdapter (Event event, FragmentManager fm)
{
super(fm);
m_event = event;
}
public void setEvent (Event event)
{
m_event = event;
notifyDataSetChanged(); // ****** Attempting to trigger update of displayed data, but the view does not update.
}
#Override
public Fragment getItem (int position)
{
Fragment f;
switch (position)
{
case 0:
f = new DetailsFragment();
break;
... other tab fragments
default:
return null;
}
// ******* FIXME?: The problem with passing serialized event to the fragment is that the fragment does not reference our event.
... each fragment references a COPY of the event.
// The updated event is passed back in the result... then set in the adapter.... BUT NOT IN FRAGMENTS
... BUT FRAGMENTS GET CREATED HERE AS NECESSARY TO VIEW, AND WILL GET THE MODIFIED EVENT IN THIS ARGUMENTS BUNDLE:
Bundle bundle = new Bundle();
bundle.putSerializable(Event.EVENT_KEY, m_event);
// Maybe just pass the event id in arguments, and the fragment gets the event from the database?? Sounds inefficient, and I think should not be necessary.
//bundle.putLong(Event.EVENT_ID_KEY, m_event.getId());
f.setArguments(bundle);
return f;
}
...
}
public class DetailsFragment extends Fragment
{
/** Event to display */
private Event m_event = null;
... UI TextView object declarations to show various attributes ...
public DetailsFragment() {
// Required empty public constructor
}
private void update ()
{
if (m_event == null)
{
... set views empty ...
return;
}
... set views for attributes of m_event ...
}
#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
View view = inflater.inflate (R.layout.fragment_event_details, container, false);
//initalize widgets ...
// Get event for view
Bundle args = getArguments();
m_event = (Event)args.getSerializable(Event.EVENT_KEY); // ***** Get the event passed in arguments to this fragment
update();
return view;
}
}
Try override method in your FragmentStatePagerAdapter
#Override
public int getItemPosition(#NonNull Object object) {
return POSITION_NONE;
}
Im having an issue that only appears after several hours of inactivity, I researched it ive tried various ways of fixing it to no avail. The issue is after my app has been dormant for several hours the references for my fragments are null, however; they still exist in the frag manager. I use the references to pull the tag, or id by findfragmentby...() so I can call specific methods within them for updating themselves and what not. The fragments are dynamic and have a UI. I have several activities and a service that are called on by the main activity. I can close the app and resume, call activities, pull info from the service, close, use the back button, all without an issue. To give you an idea of how the app is structured...
public class appClass extends Application {
public Fragment fragmentA;
public Fragment fragmentB;
public Fragment fragmentC;
#Override
public void onCreate() {
super.onCreate();
new fragmentTemplate();
fragemntA = fragmentTemplate.newInstance(getDbName(), usefuldata, "A List");
new fragmentTemplate();
fragemntB = fragmentTemplate.newInstance(getDbName(), usefuldata, "B list");
new fragmentTemplate();
fragemntC = fragmentTemplate.newInstance(getDbName(), usefuldata, "C list");
}
}
Moving on to activity where fragments are used in a viewager...
public class mainActivity extends AppCompatActivity implements ...listeners{
appClass myAppClass;
FragmentManager FragMgr;
ViewPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myAppClass = (appClass) getApplication();
setTheme(myAppClass.getAppTheme());
setContentView(R.layout.activity_my_layout);
//toolbar actionbar stuff
FragMgr = getSupportFragmentManager();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new ViewPagerAdapter(FragMgr));
//tab setup
}
//inner class pager adapter is here
}
This is my pager adapter
class ViewPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener{
Fragment fragment;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (myAppClass.fragmentA != null) {
fragment = myAppClass.fragemntA ;
}
break;
case 1:
if (myAppClass.fragmentB != null) {
fragment = myAppClass.fragmentB ;
}
break;
case 2:
if (myAppClass.fragmentC != null) {
fragment = myAppClass.fragmentC ;
}
break;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
}
I have a FAB and its listener looks like this
#Override
public void onClick(View v) {
Fraggment fragment;
int i = viewPager.getCurrentItem();
if (v.getId() == floatingActionButton.getId()) {
switch (i) {
case 0:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentA.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 1:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentB.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 2:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentC.getTag());
fragment.addItem(fragment.getSomeString());
break;
}
}
}
code for a fragment
public class fragmentTemplate extends Fragment implements RecyclerAdapter.aListener {
private appClass myAppclassReference;
private RecyclerView recyclerView;
private View view;
private FragmentTitle;
public static fragmentTemplate newInstance(String a, String b, String c) {
Bundle args = new Bundle();
args.putString(KEY_A, a);
args.putString(KEY_B, b);
args.putString(KEY_C, c);
fragmentTemplate fragment = new fragmentTemplate();
fragment.setArguments(args);
return fragment;
}
public String getFragmentTitle() {
return fragmentTitle;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
view = inflater.inflate(R.layout.list, container, false);
myAppclassReference= ((appClass) getActivity().getApplication());
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
//get list is a local function that loads a list from a db source
RecyclerAdapter recycler = new RecyclerAdapter(getActivity(), getList());
recycler.setListener(this);
recyclerView.setAdapter(recycler);
recyclerView.setLayoutManager(newLearLayoutManager(getActivity()));
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {}};
return view;
}
}
When things go wonky the app does not crash right away, the tabs still scroll, the viewpager still scrolls, but it is empty, its not until I hit the FAB do I get a nullpointerexception, trying to invoke a method on a nullpointer reference within the onClick Listener does it actually crash.
This is happening because you are messing up with the way that the Android Framework handles Fragments for you. When the ViewPagerAdapter gets Fragments from you in getItem(int), it's using the FragmentManager that you gave it to attach the Fragments. Once the Activity is killed because of low memory, the FragmentManager will automatically create new instances of your Fragments. At this point there are two copies of the fragments, the ones the FragmentManager created and the ones you recreated in your appClass.
You should never keep references to your Fragments. The FragmentManager is free to destroy them and create new ones. If you need to communicate between the Activity and the Fragments in the ViewPager, you can either make the Fragment ask its Activity for commands, use an Event Bus, or explore the sketchy solutions here.
I am having trouble with an Activity that fires off a command to a fragment in a ViewPager using a FragmentNotification interface. Everything works well until either the app is in the background for a long period of time or the orientation changes. At that point the Activity seems to lose connection to the Fragment.
My Activity code:
public class MyActivity extends FragmentActivity implements MyFragment3.FragmentNotification {
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
MyFragment1 fragOne = new MyFragment1();
MyFragment2 fragTwo = new MyFragment2();
MyFragment3 fragThree = new MyFragment3();
boolean toggle = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
setContentView(R.layout.activity_main);
// Create the adapter that will return a fragment for each of the three primary sections
// of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(2);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setClickable(true);
mViewPager.setCurrentItem(0);
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if (fragThree != null) {
fragThree.doSomething();
toggle = false;
return false;
} else {
}
}
return super.onKeyDown(keyCode, event);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment;
if(i==0){
fragment = fragOne;
}else if(i==1){
fragment = fragTwo;
}else{
fragment = fragThree;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return getString(R.string.title_section1).toUpperCase();
case 1: return getString(R.string.title_section2).toUpperCase();
case 2: return getString(R.string.title_section3).toUpperCase();
}
return null;
}
}
//Receive an event notification from a fragment
// #Override
public void fragmentAction(int actionType) {
if (actionType == MyFragment3.TOGGLE_ACT) {
toggle = true;
}
}
}
My Fragment Code:
public class MyFragment3 extends Fragment {
View mView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mView = ....
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (FragmentNotification) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
public void doSomething(){
mView.setVisibility(View.GONE);
...
}
public interface FragmentNotification {
public void fragmentAction(int actionType);
}
}
As mentioned, everything works well until some state change, and then it appears the activity loses reference to the fragment present in the viewpager, even though it is being displayed properly until the back button is pressed.
I believe I need to restore the connection by supplying a bundle from my Fragment's onSaveInstanceState, but have no idea how to get started.
Any help would be greatly appreciated.
Thanks,
Josh
You are blindly creating instances of your three fragments, in data member initializers (!), even if those fragments already exist. Bear in mind that Android recreates all of your existing fragments on a configuration change. Hence, on a configuration change, none of those newly-created fragments will get used, as the ViewPager will use the ones Android recreated for it. You can see this in the implementation of instantiateItem() in FragmentPagerAdapter (source code is in your SDK).
The concept that "when pressing BACK I want to do something special with my third fragment in the pager" is not something that ViewPager supports all that well. I would encourage you to find some other solution to whatever problem you are trying to solve with that logic.
The fragments I use in my ViewPager instance are quite resource intensive, so I'd only like to load one at a time. When I try the following:
mViewPager.setOffscreenPageLimit(0);
mViewPager.setAdapter(mPagerAdapter);
My FragmentStatePagerAdapter.getItem(int position) override function is called 3 times, which is what happens when I call mViewPager.setOffscreenPageLimit(1). I would expect it to only be called once, because I specified 0 offscreen pages.
I believe I'm calling everything correctly, because if I call mViewPager.setOffscreenPageLimit(2), FragmentStatePagerAdapter.getItem(int position) is called 5 times as I would expect.
Does ViewPager require a minimum of 1 offscreen pages, or am I doing something wrong here?
The best way that I found was setUserVisibleHint
add this to your fragment
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// load data here
}else{
// fragment is no longer visible
}
}
Does ViewPager require a minimum of 1 offscreen pages
Yes. If I am reading the source code correctly, you should be getting a warning about this in LogCat, something like:
Requested offscreen page limit 0 too small; defaulting to 1
You can try like this :
public abstract class LazyFragment extends Fragment {
protected boolean isVisible;
/**
* 在这里实现Fragment数据的缓加载.
* #param isVisibleToUser
*/
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
protected void onVisible(){
lazyLoad();
}
protected abstract void lazyLoad();
protected void onInvisible(){}
protected abstract void lazyLoad();
protected void onInvisible(){}
First Add
boolean isFragmentLoaded = false;
than
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && !isFragmentLoaded) {
//Load Your Data Here like.... new GetContacts().execute();
isFragmentLoaded = true;
}
else{
}
}
this may be old thread but this seems to work for me. Override this function :
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
if ( menuVisible ) {
/**
* Load your stuffs here.
*/
} else {
/**
* Fragment not currently Visible.
*/
}
}
happy codings...
ViewPager is default to load the next page(Fragment) which you can't change by setOffscreenPageLimit(0). But you can do something to hack.
You can implement onPageSelected function in Activity containing the ViewPager. In the next Fragment(which you don't want to load), you write a function let's say showViewContent() where you put in all resource consuming init code and do nothing before onResume() method. Then call showViewContent() function inside onPageSelected. Hope this will help.
in my case i wanted to start some animations in views, but with setUserVisibleHint got some issues ...
my solution is :
1/ addOnPageChangeListener for your adapter :
mViewPager.addOnPageChangeListener(this);
2/ implement OnPageChangeListener :
public class PagesFragment extends Fragment implements ViewPager.OnPageChangeListener
3/ override the 3 methodes :
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
}
#Override
public void onPageSelected(int position)
{
}
#Override
public void onPageScrollStateChanged(int state)
{
}
4/ declare and initialize this variable on your class
private static int mTabState = 1;
notice : i have three fragments in my adapter, and use mTabState for setCurrentItem and current position of adapter which recognize which fragment is show to user in time ...
5/ in onPageSelected method add this codes :
if (mTabState == 0 || position == 0)
{
Intent intent = new Intent("animation");
intent.putExtra("current_position", position);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}
if previous page or current page is page 0(fragment in position 0) then do this stuff
6/ now in your fragment class (fragment in position 0 of adapter), you must create broadcast receiver and register it in onResume method and unregister it onPause methos :
BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
if (Objects.equals(intent.getAction(), "animation"))
{
int currentPosition = intent.getIntExtra("current_position", 0);
if (currentPosition == 0)
{
startAnimation();
setViewsVisible();
} else
{
setViewsInvisible();
}
}
}
};
#Override
public void onResume()
{
super.onResume();
LocalBroadcastManager.getInstance(mContext).registerReceiver(broadcastReceiver, new IntentFilter("animation"));
}
#Override
public void onPause()
{
super.onPause();
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(broadcastReceiver);
}
summary : i have Fragment Pager Adapter witch shows Three Fragments in it, I want show some Animations on Views in Fragment in Position 0 of Adapter, For this I use BroadcastReceiver. When Fragment is Picked I start the Animation method and shows the Views to User, When Fragment is not Showing to User I try to Invisible Views...
View Pager With only one Page :
This is February 2021: I have able to add only one page with viewPager. The approach is with ViewPager, FragmentPagerAdapter, Tablayout, and a fragment. In my case, I can populate many Pages with many tabs, or only one Page with one Tab. When one tab and one page, on swipe left or right, I can manage to change the chapter of my document (which I want to show next). And when many pages and many tabs, I can change the entire book of documents.
In main Activity Oncreate: (Here is my working code approach, I have changed nothing here from my working code):
if(getIntent()!=null){
if(getIntent().getStringExtra("ONLY_TAFHEEM")!=null)
sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, true);
else
sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
}else {
sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
}
final ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);
tabsLayout = findViewById(R.id.tabs);
tabsLayout.animate();
tabsLayout.setupWithViewPager(viewPager);
In Adapter :
#NonNull
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.sendData(mContext, postion, suraName, suraId, ayahId, ARABIC_AYAH, BENGALI_AYAH, actualDbNames[position], tafsirDisplayNames[position]);
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return tafsirDisplayNames[position];
}
#Override
public int getCount() {
// this is the tricky part // Show pages according to array length. // this may only one // this is the tricky part :
return tafsirDisplayNames.length;
}
And at last the fragments public constructor :
public static PlaceholderFragment sendData(Context mContext, int tabIndex, String suraName, String suraId, String ayahNumber, String arabicAyah, String bengaliAyah, String actualDbName, String displayDbName) {
Log.i("PlaceHolder", "Tafhim sendData: " + bengaliAyah);
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle bundle = new Bundle();
mContext_ = mContext;
BENGALI_AYAH = bengaliAyah;
_DISPLAY_DB_NAME = displayDbName;
bundle.putInt(ARG_SECTION_NUMBER, tabIndex);
bundle.putString(SURA_NAME, suraName);
bundle.putString(SURA_ID, suraId);
bundle.putString(AYAH_NUMBER, ayahNumber);
bundle.putString(ARABIC_AYAH, arabicAyah);
bundle.putString(ACTUAL_DB_NAME, actualDbName);
bundle.putString(DISPLAY_DB_NAME, displayDbName);
fragment.setArguments(bundle);
return fragment;
}
That's all, just passing the array (of Tab Labels) to the adapter, (it may only one element, in case, for one page), with my need, I can populate one page or more page, and according to this it populate one tab or many tabs : in the above code the array is: tafsirDisplayNames. I can also create the array manually in adapter, when the adapter first called, Or, recreate the array with +-elements, on recreate the MainActivity.
Please Try This Code for resolve issue of refreshing view in Viewpager....
/* DO NOT FORGET! The ViewPager requires at least “1” minimum OffscreenPageLimit */
int limit = (mAdapter.getCount() > 1 ? mAdapter.getCount() - 1 : 1);
mViewPager.setOffscreenPageLimit(limit);
for the "instantiateItem" function, just prepare the fragment, but don't load the heavy content.
Use "onPageChangeListener" , so that each time you go to a specific page, you load its heavy content and show it.
I kind of have the same problem. I found some useful code on this site and transform it.
The min int for mViewPager.setOffscreenPageLimit(...); is 1, so even if you change it to 0 you will still have 2 pages loaded.
First thing to do is to create a static int we will call maxPageCount and override FragmentStatePagerAdapter method getCount() to return maxPageCount:
#Override
public int getCount() {
return maxPageCount;
}
Create then a static method accessible from any where in the program that will allow you to change this maxCount:
public static void addPage(){
maxPageCount++; //or maxPageCount = fragmentPosition+2
mFragmentStatePagerAdapter.notifyDataSetChanged(); //notifyDataSetChanged is important here.
}
Now initialize maxPageCount to 1. When ever you want you can add another page.
In my case when I needed the user to treat the current page first before generated the other. He do it and then, without problem can swipe to the next page.
Hope it help someone.
Use This
// create boolean for fetching data
private boolean isViewShown = false;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getView() != null) {
isViewShown = true;
// fetchdata() contains logic to show data when page is selected mostly asynctask to fill the data
fetchData();
} else {
isViewShown = false;
}
}
// step 1: add BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT FragmentPagerAdapter contractor
public class BottomTabViewPager extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public BottomTabViewPager(FragmentManager manager) {
super(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
#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);
}
public void addTabs(String title) {
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
// return null;
}
}
// step 2: You can try like this :
public class MyFragment extends Fragment {
public MyFragment() {
// 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_ui, container, false);
return view;
}
#Override
public void onResume() {
super.onResume();
/**
* Load your stuffs here.
*/
}
}
I have narrowed my problem down to being a problem with the FragmentManager retaining instances of old fragments and my viewpager being out of sync with my FragmentManager. See this issue: http://code.google.com/p/android/issues/detail?id=19211#makechanges. I still have no clue how to solve this. Any suggestions?
I have tried to debug this for a long time and any help would be greatly appreciated. I am using a FragmentPagerAdapter which accepts a list of fragments like so:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
The implementation is standard. I am using ActionBarSherlock and v4 computability library for Fragments.
My problem is that after leaving the app and opening several other applications and coming back, the fragments lose their reference back to the FragmentActivity (ie. getActivity() == null). I can not figure out why this is happening. I tried to manually set setRetainInstance(true); but this does not help. I figured that this happens when my FragmentActivity gets destroyed, however this still happens if I open the app before I get the log message. Are there any ideas?
#Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
The adapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
}
One of my Fragments stripped but I commented everything out that's stripped and it still doesn't work.
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
#Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
#Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
#Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
#Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
#Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
The update method is called when a user interacts with a different fragment and the getActivity is returning null. Here is the method the other fragment is calling:
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
I believe that when the app is destroyed then created again instead of just starting the app up to the default fragment the app starts up and then viewpager navigates to the last known page. This seems strange, shouldn't the app just load to the default fragment?
You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.
If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.
Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.
Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.
The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.
Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.
I found simple solution which worked for me.
Make your fragment adapter extends FragmentStatePagerAdapter instead of FragmentPagerAdapter and override method onSave to return null
#Override
public Parcelable saveState()
{
return null;
}
This prevent android from recreating fragment
One day later I found another and better solution.
Call setRetainInstance(true) for all your fragments and save references to them somewhere. I did that in static variable in my activity, because it's declared as singleTask and fragments can stay the same all the time.
This way android not recreate fragments but use same instances.
I solved this issue by accessing my fragments directly through the FragmentManager instead of via the FragmentPagerAdapter like so. First I need to figure out the tag of the fragment auto generated by the FragmentPagerAdapter...
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.viewpager+":"+pos;
}
Then I simply get a reference to that fragment and do what I need like so...
Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);
Inside my fragments I set the setRetainInstance(false); so that I can manually add values to the savedInstanceState bundle.
#Override
public void onSaveInstanceState(Bundle outState) {
if(this.my !=null)
outState.putInt("myId", this.my.getId());
super.onSaveInstanceState(outState);
}
and then in the OnCreate i grab that key and restore the state of the fragment as necessary. An easy solution which was hard (for me at least) to figure out.
Global working tested solution.
getSupportFragmentManager() keeps the null reference some times and View pager does not create new since it find reference to same fragment. So to over come this use getChildFragmentManager() solves problem in simple way.
Don't do this:
new PagerAdapter(getSupportFragmentManager(), fragments);
Do this:
new PagerAdapter(getChildFragmentManager() , fragments);
Do not try to interact between fragments in ViewPager. You cannot guarantee that other fragment attached or even exists. Istead of changing actionbar title from fragment, you can do it from your activity. Use standart interface pattern for this:
public interface UpdateCallback
{
void update(String name);
}
public class MyActivity extends FragmentActivity implements UpdateCallback
{
#Override
public void update(String name)
{
getSupportActionBar().setTitle(name);
}
}
public class MyFragment extends Fragment
{
private UpdateCallback callback;
#Override
public void onAttach(SupportActivity activity)
{
super.onAttach(activity);
callback = (UpdateCallback) activity;
}
#Override
public void onDetach()
{
super.onDetach();
callback = null;
}
public void updateActionbar(String name)
{
if(callback != null)
callback.update(name);
}
}
You can remove the fragments when destroy the viewpager, in my case, I removed them on onDestroyView() of my fragment:
#Override
public void onDestroyView() {
if (getChildFragmentManager().getFragments() != null) {
for (Fragment fragment : getChildFragmentManager().getFragments()) {
getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
}
}
super.onDestroyView();
}
After a few hours of looking for a similar issue I think a have another solution. This one at least it worked for me and I only have to changed a couple of lines.
This is the problem I had, I have an activity with a view pager that uses a FragmentStatePagerAdapter with two Fragments. Everything works fine until I force the activity to get destroyed (developer options) or I rotate the screen. I do keep a reference to the two fragments after they get created inside the method getItem.
At that point the activity will be created again and everything works fine at this point but I have lost the reference to my fragmetns as getItem doesn't' get called again.
This is how I fixed that problem, inside the FragmentStatePagerAdapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object aux = super.instantiateItem(container, position);
//Update the references to the Fragments we have on the view pager
if(position==0){
fragTabOne = (FragOffersList)aux;
}
else{
fragTabTwo = (FragOffersList) aux;
}
return aux;
}
You won't get a call on getItem again if the adapter already has a reference to it internally, and you shouldn't change that. Instead you can get the fragment it's being used by looking at this other method instantiateItem() which will be called for each of your fragments.
Hope that helps anyone.
Since people don't tend to read comments, here is an answer that mostly duplicates what I wrote here:
the root cause of the issue is the fact that android system does not call getItem to obtain fragments that are actually displayed, but instantiateItem. This method first tries to lookup and reuse a fragment instance for a given tab in FragmentManager. Only if this lookup fails (which happens only the first time when FragmentManager is newly created) then getItem is called. It is for obvious reasons not to recreate fragments (that may be heavy) for example each time a user rotates his device.
To solve this, instead of creating fragments with Fragment.instantiate in your activity, you should do it with pagerAdapter.instantiateItem and all these calls should be surrounded by startUpdate/finishUpdate method calls that start/commit fragment transaction respectively. getItem should be the place where fragments are really created using their respective constructors.
List<Fragment> fragments = new Vector<Fragment>();
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);
adapter.startUpdate(viewPager);
fragments.add(adapter.instantiateItem(viewPager, 0));
fragments.add(adapter.instantiateItem(viewPager, 1));
// and so on if you have more tabs...
adapter.finishUpdate(viewPager);
}
class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager manager) {super(manager);}
#Override public int getCount() {return 2;}
#Override public Fragment getItem(int position) {
if (position == 0) return new Fragment0();
if (position == 1) return new Fragment1();
return null; // or throw some exception
}
#Override public CharSequence getPageTitle(int position) {
if (position == 0) return getString(R.string.tab0);
if (position == 1) return getString(R.string.tab1);
return null; // or throw some exception
}
}
Since the FragmentManager will take care of restoring your Fragments for you as soon as the onResume() method is called I have the fragment call out to the activity and add itself to a list. In my instance I am storing all of this in my PagerAdapter implementation. Each fragment knows it's position because it is added to the fragment arguments on creation. Now whenever I need to manipulate a fragment at a specific index all I have to do is use the list from my adapter.
The following is an example of an Adapter for a custom ViewPager that will grow the fragment as it moves into focus, and scale it down as it moves out of focus. Besides the Adapter and Fragment classes I have here all you need is for the parent activity to be able to reference the adapter variable and you are set.
Adapter
public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {
public final String TAG = this.getClass().getSimpleName();
private final int COUNT = 4;
public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;
private int mCurrentPage = 0;
private boolean mScrollingLeft;
private List<SummaryTabletFragment> mFragments;
public int getCurrentPage() {
return mCurrentPage;
}
public void addFragment(SummaryTabletFragment fragment) {
mFragments.add(fragment.getPosition(), fragment);
}
public GrowPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<SummaryTabletFragment>();
}
#Override
public int getCount() {
return COUNT;
}
#Override
public Fragment getItem(int position) {
return SummaryTabletFragment.newInstance(position);
}
#Override
public void onPageScrollStateChanged(int state) {}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
adjustSize(position, positionOffset);
}
#Override
public void onPageSelected(int position) {
mCurrentPage = position;
}
/**
* Used to adjust the size of each view in the viewpager as the user
* scrolls. This provides the effect of children scaling down as they
* are moved out and back to full size as they come into focus.
*
* #param position
* #param percent
*/
private void adjustSize(int position, float percent) {
position += (mScrollingLeft ? 1 : 0);
int secondary = position + (mScrollingLeft ? -1 : 1);
int tertiary = position + (mScrollingLeft ? 1 : -1);
float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
float scaleDown = mScrollingLeft ? 1.0f - percent : percent;
float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;
if (scaleUp < BASE_SIZE)
scaleUp = BASE_SIZE;
if (scaleDown < BASE_SIZE)
scaleDown = BASE_SIZE;
// Adjust the fragments that are, or will be, on screen
SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;
if (current != null && next != null) {
// Apply the adjustments to each fragment
current.transitionFragment(percentIn, scaleUp);
next.transitionFragment(percentOut, scaleDown);
if (afterNext != null) {
afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
}
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
// Keep track of which direction we are scrolling
mScrollingLeft = (oldl - l) < 0;
}
}
Fragment
public class SummaryTabletFragment extends BaseTabletFragment {
public final String TAG = this.getClass().getSimpleName();
private final float SCALE_SIZE = 0.8f;
private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;
private String mTitleText;
private Integer mColor;
private boolean mInit = false;
private Float mScale, mPercent;
private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;
public String getTitleText() {
return mTitleText;
}
public void setTitleText(String titleText) {
this.mTitleText = titleText;
}
public static SummaryTabletFragment newInstance(int position) {
SummaryTabletFragment fragment = new SummaryTabletFragment();
fragment.setRetainInstance(true);
Bundle args = new Bundle();
args.putInt("position", position);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);
setupViews();
configureView();
return mRoot;
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mColor = savedInstanceState.getInt("color", Color.BLACK);
}
configureView();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("color", mColor);
super.onSaveInstanceState(outState);
}
#Override
public int getPosition() {
return getArguments().getInt("position", -1);
}
#Override
public void setPosition(int position) {
getArguments().putInt("position", position);
}
public void onResume() {
super.onResume();
mAdapter = mActivity.getPagerAdapter();
mAdapter.addFragment(this);
mCurrentPosition = mAdapter.getCurrentPage();
if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
mInit = true;
transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
return;
}
if (getPosition() == mCurrentPosition && !mInit) {
mInit = true;
transitionFragment(0.00f, 1.0f);
}
}
private void setupViews() {
mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
mTitle = (TextView) mRoot.findViewById(R.id.title);
}
private void configureView() {
Fonts.applyPrimaryBoldFont(mLeft, 15);
Fonts.applyPrimaryBoldFont(mRight, 15);
float[] size = UiUtils.getScreenMeasurements(mActivity);
int width = (int) (size[0] * SCALE_SIZE);
int height = (int) (size[1] * SCALE_SIZE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
mBackground.setLayoutParams(params);
if (mScale != null)
transitionFragment(mPercent, mScale);
setRandomBackground();
setTitleText("Fragment " + getPosition());
mTitle.setText(getTitleText().toUpperCase());
mLeft.setText(getTitleText().toUpperCase());
mRight.setText(getTitleText().toUpperCase());
mLeft.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showNextPage();
}
});
mRight.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showPrevPage();
}
});
}
private void setRandomBackground() {
if (mColor == null) {
Random r = new Random();
mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
}
mBackground.setBackgroundColor(mColor);
}
public void transitionFragment(float percent, float scale) {
this.mScale = scale;
this.mPercent = percent;
if (getView() != null && mCover != null) {
getView().setScaleX(scale);
getView().setScaleY(scale);
mCover.setAlpha(percent);
mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
}
}
#Override
public String getFragmentTitle() {
return null;
}
}
My solution: I set almost every View as static. Now my app interacts perfect. Being able to call the static methods from everywhere is maybe not a good style, but why to play around with code that doesn't work? I read a lot of questions and their answers here on SO and no solution brought success (for me).
I know it can leak the memory, and waste heap, and my code will not be fit on other projects, but I don't feel scared about this - I tested the app on different devices and conditions, no problems at all, the Android Platform seems to be able handle this. The UI gets refreshed every second and even on a S2 ICS (4.0.3) device the app is able to handle thousands of geo-markers.
I faced the same issue but my ViewPager was inside a TopFragment which created and set an adapter using setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).
I fixed this issue by overriding onAttachFragment(Fragment childFragment) in the TopFragment like this:
#Override
public void onAttachFragment(Fragment childFragment) {
if (childFragment instanceof OnboardingDiamondsFragment) {
mChildFragment = (ChildFragment) childFragment;
}
super.onAttachFragment(childFragment);
}
As known already (see answers above), when the childFragmentManager recreate itself, it also create the fragments which were inside the viewPager.
The important part is that after that, he calls onAttachFragment and now we have a reference to the new recreated fragment!
Hope this will help anyone getting this old Q like me :)
I solved the problem by saving the fragments in SparceArray:
public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> fragments = new SparseArray<>();
public SaveFragmentsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.append(position, fragment);
return fragment;
}
#Nullable
public Fragment getFragmentByPosition(int position){
return fragments.get(position);
}
}
Just so you know...
Adding to the litany of woes with these classes, there is a rather interesting bug that's worth sharing.
I'm using a ViewPager to navigate a tree of items (select an item and the view pager animates scrolling to the right, and the next branch appears, navigate back, and the ViewPager scrolls in the opposite direction to return to the previous node).
The problem arises when I push and pop fragments off the end of the FragmentStatePagerAdapter. It's smart enough to notice that the items change, and smart enough to create and replace a fragment when the item has changed. But not smart enough to discard the fragment state, or smart enough to trim the internally saved fragment states when the adapter size changes. So when you pop an item, and push a new one onto the end, the fragment for the new item gets the saved state of the fragment for the old item, which caused absolute havoc in my code. My fragments carry data that may require a lot of work to refetch from the internet, so not saving state really wasn't an option.
I don't have a clean workaround. I used something like this:
public void onSaveInstanceState(Bundle outState) {
IFragmentListener listener = (IFragmentListener)getActivity();
if (listener!= null)
{
if (!listener.isStillInTheAdapter(this.getAdapterItem()))
{
return; // return empty state.
}
}
super.onSaveInstanceState(outState);
// normal saving of state for flips and
// paging out of the activity follows
....
}
An imperfect solution because the new fragment instance still gets a savedState Bundle, but at least it doesn't carry stale data.