9

I have the following situation:

I have an Activity that hosts a ViewPager, and I have 4 Fragments;

the ViewPager at the beginning contains Fragment A,

when the user swipes on the ViewPager Fragment B goes into the ViewPager, then Fragment C and Fragment D ...etc...

Now as soon as the FragmentPagerAdapter is instantiated at least 2 of the Fragments are created.

This poses 2 problem:

  1. Every Fragment needs to perform network calls, but I do not want to do unnecessary ones (I do not want to make network calls for Fragment B, if the user never swipes to Fragment B );
  2. similar to 1.), I need to show a ProgessDialog when a Fragment perform network calls, but I do not want to show dialogs from Fragment B if the user never goes to it...

Please what kind of pattern should I use in such a circumstance?

Activity

public class PagerActivity extends ActionBarActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_layout);

ViewPager pager=(ViewPager)findViewById(R.id.pager);
TabPageIndicator tabs=(TabPageIndicator)findViewById(R.id.titles);

pager.setAdapter(buildAdapter());
tabs.setViewPager(pager);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}

FragmentPagerAdapter

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {


@Override
public int getCount() {
    return (4);
}

@Override
public Fragment getItem(int position) {


    if (position == 1) {
        if (dashbardFragment == null)
            dashbardFragment = DashBoardFragment.newInstance(position);
        return dashbardFragment;
    }
    if (position == 0) {
        if (listOfParticipantFragment == null)
            listOfParticipantFragment = ListOfParicipantsFragment
            .newInstance(position);
        return listOfParticipantFragment;
    }

}

1 Fragment

public class ListOfParicipantsFragment extends Fragment {

public static ListOfParicipantsFragment newInstance(int position) {
    ListOfParicipantsFragment frag = new ListOfParicipantsFragment();
    return (frag);
}

public static String getTitle(Context ctxt, int position) {
    return myApplication.getContext().getResources().getString(R.string.list_of_participants_fragment_title);
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View result = inflater.inflate(R.layout.guest_list_fragment_layout,
            container, false);

    return (result);
}
user229044
  • 232,980
  • 40
  • 330
  • 338
Lisa Anne
  • 4,482
  • 17
  • 83
  • 157
  • 1
    I don't understand what are you looking for with your question. If there were a standard way it would already be in the documentation, no? – user Oct 18 '14 at 13:30
  • Use one of the async request frameworks like [RoboSpice](https://github.com/stephanenicolas/robospice) etc. Fragments can post request, cancel requests and re-collect request even after they are re-created due to changes like orientation. – S.D. Oct 22 '14 at 13:03
  • Fail bad design your user will have to wait for the screen to update when it could've been done off screen. – danny117 Oct 22 '14 at 20:10
  • 1
    @danny117 No it is not a bad design. It is a very common problem. consider you have an app that has 5 tabs and in each tabs there is a ListView with an image in each rows, something like google play, appstore or a lot of other apps. when are you going to load the data of these tabs? "it could've been done off screen" can you tell me because I just created one like this and I had to take this approach that is in each fragment that is really visible to the user I must download the relevant listview images and data. – mmlooloo Oct 23 '14 at 08:29
  • I too think this is actually a bad design and for the following reasons: When using ViewPager expected behavior is to have next and previous fragments ready for use. Even the UI behavior is setup so user can peek at next/previous item and they expect data to be there. I do agree that your special case might need this unordinary behavior but then again you should not use ViewPager because you are breaking it's expected patterns. Think about using fm.replace(fragment) instead. – Igor Čordaš Oct 23 '14 at 10:06
  • I made something that can be used to offset heavy lifting on a fragment used with a fragmentstatepageadapter until the fragment comes into view see below. – danny117 Oct 23 '14 at 18:57
  • Could you help me? I have the same problem. If you have solved it please share the correct answer – bhaskar Dec 07 '18 at 05:03

7 Answers7

8

Try this, in each fragment override below method and call your function when it is visible:

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisible()){
        if(isVisibleToUser){
            Log.d("MyTag","My Fragment is visible");
        }else{
            Log.d("MyTag","My Fragment is not visible");
        }
    }
}

EDIT

Note: This is only useful when using a FragmentPagerAdapter or FragmentStatePagerAdapter

Paul Burke
  • 25,496
  • 9
  • 66
  • 62
mmlooloo
  • 18,937
  • 5
  • 45
  • 64
  • if you want to use `getActivity` you should first check if fragment has an activity or not. you can do it like my first if or replace it by `isResumed()`. or by `(getActivity() != null)` good luck! – mmlooloo Oct 18 '14 at 14:41
  • Can't rely on `setUserVisibleHint`. – Paul Burke Oct 19 '14 at 09:57
  • @PaulBurke why? `google` added this because of exactly these situations. you can read the document for more information. – mmlooloo Oct 19 '14 at 10:02
  • 1
    My mistake. `setUserVisibleHint` defaults to true, however, `FragmentPagerAdapter` updates this field. So, while `setUserVisibleHint` is inherently useless, it becomes useful when in a pager adapter. – Paul Burke Oct 19 '14 at 10:08
  • @mmlooloo actually trying it it seems that `isVisible()` returns false always... strange – Lisa Anne Oct 20 '14 at 06:03
  • yes it is strange, because I have a sample code with this and it is ok, you can change it to `FragmentStatePagerAdapter` it is better and efficient for large number of fragments. you can also use `isResumed()` instead of `isVisible`. – mmlooloo Oct 20 '14 at 06:11
  • What @PaulBurke says is correct, whit a `FragmentPagerAdapter`, `setUserVisibleHint` does not default to true and hence it can be used (without `isVisible()` which does default to true) – Lisa Anne Oct 22 '14 at 04:59
1

Basically what you want to do, is, find which fragment is currently being viewed when you swipe. And then, do your network calls.

You can take advantage of the ViewPager listeners to get notified when the user swipes to a new page. Docs : http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html#onPageSelected(int)

This will give you the position of the View. But i'm assuming that what you want is the actual fragment, which is a bit more tricky.

But, this has been answered already in here : Is it possible to access the current Fragment being viewed by a ViewPager?

Hope it helps

Community
  • 1
  • 1
ehanoc
  • 2,187
  • 18
  • 23
1

Let me introduce my idea to you:

  • getCurrentItem()
    Method of ViewPager
  • getItem(int position)
    Method of FragmentPagerAdapter Return the Fragment associated with a specified position.

You can define an Interface holding the method for Network I/O like

public Interface INetworkOnFragment{
 void handle(){
         //...
           }  
}

And implement it on your fragments and handle their own business logic (Network calls).

In main Activity ,set ViewPager.OnPageChangeListener on ViewPager object like here:

    pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener(){
public void onPageScrollStateChanged(int state){
//donothing
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
//donothing
}
public void onPageSelected(int position){
       INetworkOnFragment interface =(INetworkOnFragment) (pager.getAdapter().getItem(position));//get the current fragment and call handle method on it,dont need to care about whichever fragment it is . 
interface.handle()
}
});

The most important is onPageSelected(int position),Inside the callback it get the current fragment and call handle method on it,dont need to care about whichever fragment it is .

Remember the handle method are called in Activity and not in fragments.All the Network calls are implemention of Interface,which make it easy to deal in Activity.

Andrew Carl
  • 802
  • 1
  • 8
  • 15
  • Your current code in onPageSelected() will fail. Simply calling `getItem()` on the adapter doesn't retrieve the fragment used by the adapter. – user Oct 18 '14 at 12:47
  • 1
    @Luksprog Give me detail why I can't get the Fragment http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html#getItem%28int%29 – Andrew Carl Oct 18 '14 at 12:52
  • Anyway it would be easy to create a method in the adapter that gives the Fragment. – Lisa Anne Oct 18 '14 at 13:12
  • The solution is very reasonable, +1, but is this the standard solution? I do not think I am the only one faced with this problem, is it possible that there is no "standard" approach to deal with it? – Lisa Anne Oct 18 '14 at 13:14
  • 3
    From my point of view,I suggest you may give yourself more time to think over the best design pattern for your application ,but not pay too much time looking for that 'standard' approach that maybe doesn't exist.After times and times' refactor ,you would achieve more than ever. – Andrew Carl Oct 18 '14 at 13:26
  • Anyway my solution seems good and deserves your try.Thks. :) – Andrew Carl Oct 18 '14 at 13:58
  • you know if you called the interface onIntoView you'd really have something that could be used possibly to start animations etc. I would also add something to insure the fragment had actually implimented the interface before calling the method. OnIntoView.class.isAssignableFrom(fragment.getClass()) before actually calling it to prevent exceptions. – danny117 Oct 23 '14 at 13:40
1

Check out the solution from my answer here

1) Create LifecycleManager Interface The interface will have two methods (onPauseFragment and onResumeFragment) and each ViewPager’s Fragment will implement it
2) Let each Fragment implement the interface
3) Implement interface methods in each fragment - start your AsyncTask in onResumeFragment
4) Call interface methods on ViewPager page change You can set OnPageChangeListener on ViewPager and get callback each time when ViewPager shows another page
5) Implement OnPageChangeListener to call your custom Lifecycle methods

Community
  • 1
  • 1
LordRaydenMK
  • 13,074
  • 5
  • 50
  • 56
1

Create a page into view method for FragmentStatePagerAdapter which calls a method on the fragment when the fragment comes into view.

Implement the OnPageIntoView interface in your fragment.

public class SomethingDifferent extends Fragment implements OnPageIntoView {
...
/*
 * Called when this page comes into view
 * 
 * @see com.gosylvester.bestrides.SettingFragmentPagerSupport.MyAdapter.
 * OnPageIntoView#onPageIntoView()
 */
@Override
public void onPageIntoView() {
    // this is just some random example code
    // that does some heavy lifting it only runs when the fragment
    // frist comes into view
    if (fragmentActivity != null) {
        if (lrc == null) {
            lrc = new ClientServiceLocationRecorder(
                    new WeakReference<Context>(
                            fragmentActivity.getApplicationContext()),
                    lrcCallback);
        }

        // get a status message from the location recorder
        lrc.sndMessageToLocationRecorder(ServiceLocationRecorder.MSG_RECORD_STATUS);
    }
}

Create a custom FragmentStatePagerAdapter Override the setPrimaryItem method and if the object can be cast to the interface then call through the interface one time only.

public static class MyAdapter extends FragmentStatePagerAdapter {

    public interface OnPageIntoView {
        public void onPageIntoView();
    }

    private Fragment mCurrentFragment;

    //bonus method to get the current fragment
    public Fragment getCurrentFragment() {
        return mCurrentFragment;
    }

    static int lastPosition = -1;

    @Override
    public void setPrimaryItem(ViewGroup container, int position,
            Object object) {
        //quickly determine if the primary item has changed
        //and one time only call through interface
        if (position != lastPosition) {
            lastPosition = position;
            //determine if this is fragment it should be but lets avoid 
            //class cast exceptions
            if (Fragment.class.isAssignableFrom(object.getClass())) {
                mCurrentFragment = ((Fragment) object);
                //determine if the onPageIntoView interface has
                //been implemented in the fragment
                //if so call the onPageIntoView
                if (OnPageIntoView.class.isAssignableFrom(mCurrentFragment
                        .getClass())) {
                    ((OnPageIntoView) mCurrentFragment).onPageIntoView();
                }
            }
        }
        super.setPrimaryItem(container, position, object);
    }
}
danny117
  • 5,581
  • 1
  • 26
  • 35
0

It seems easy to me,what you need is Fragments onResume() method. This will be called only when your fragment is VISIBLE to user.

Here you can add logic to initiate your network call. It guarantees that your fragment is in visible mode.

See this

enter image description here

However you can optimize your network calls logic, using LoaderManager with AsyncTaskLoader pattern.

Loaders take care of screen orientation changes & they cache data for you. So that network call is not initiated twice for same operation.

From Android documentation

Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:

They are available to every Activity and Fragment.
They provide asynchronous loading of data.
They monitor the source of their data and deliver new results 

when the content changes. They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data.

You can use any Asynchronous HTTP lib for network calls like Retrofit

i found one tutorial for AsyncTaskLoader & LoaderManager @ this link, below are some quotes from tutorial

Loaders aren't trivial, so why use them in the first place? Well, in most cases, you would use them in the same scenarios where you've been using AsyncTasks; in fact, some loader subclasses extend AsyncTask. Just as AsyncTasks are used to perform any long-running operation that would tie up the user thread and ultimately throw the dreaded Application Not Responding (ANR), loaders perform in the same manner with the same purpose. The main difference is loaders are specialized for loading data. As such, loaders offer a number of efficiency and convenience benefits.

Akhil
  • 6,667
  • 4
  • 31
  • 61
  • "It guarantees that your fragment is in visible mode." sorry but it is not correct in this situation. because when you are using `FragmentPagerAdapter` and `FragmentStatePagerAdapter` the two neighbor fragments are in `Resume` mode and if you check `isVisible` always returns true. You can not prevent adapter to load neighbor fragments because of UX. that means `setOffscreenPageLimit(0);` dose not work at all. – mmlooloo Oct 21 '14 at 13:50
  • Indeed what @mmlooloo says is correct, experimented just now – Lisa Anne Oct 22 '14 at 04:50
0

@Andrew Carl provide good idea. I also use the similar approach in my projects. I think it's more generalized.

Create an interface:

public interface ViewPagerFragment {
    void onSelected();

    void onDeselected();
}

And this common helper:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;

public class ViewPagerHelper implements ViewPager.OnPageChangeListener {
    private final FragmentManager mFragmentManager;
    private final ViewPager mViewPager;
    private int mSelectedPage;

    public ViewPagerHelper(FragmentManager fragmentManager, ViewPager viewPager) {
        mFragmentManager = fragmentManager;
        mViewPager = viewPager;
        mSelectedPage = -1;
    }

    @Override
    public void onPageSelected(int position) {
        Fragment previous = findViewPagerChildFragment(mFragmentManager, mViewPager, mSelectedPage);
        if (previous instanceof ViewPagerFragment) {
            ((ViewPagerFragment) previous).onDeselected();
        }

        Fragment current = findViewPagerChildFragment(mFragmentManager, mViewPager, position);
        if (current instanceof ViewPagerFragment) {
            ((ViewPagerFragment) current).onSelected();
        }

        mSelectedPage = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // empty
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        // empty
    }

    public static Fragment findViewPagerChildFragment(FragmentManager manager, ViewPager pager, int position) {
        if (pager == null) {
            return null;
        }

        String tag = "android:switcher:" + pager.getId() + ":" + position;
        return manager.findFragmentByTag(tag);
    }
}

Now you may use them for any purpose:

Fragment:

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

public class MyFragment extends Fragment implements ViewPagerFragment {
    private boolean mSelected;

    public static MyFragment newInstance(int position) {
        Bundle args = new Bundle();
        args.putInt("position", position);

        MyFragment result = new MyFragment();
        result.setArguments(args);
        return result;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        TextView result = new TextView(inflater.getContext());
        result.setText("Position: " + getPosition());
        return result;
    }

    private int getPosition() {
        return getArguments().getInt("position");
    }

    @Override
    public void onSelected() {
        mSelected = true;
        start();
    }

    @Override
    public void onDeselected() {
        mSelected = false;
    }

    private void start() {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Activity activity = getActivity();
                if (activity == null) {
                    return;
                }

                if (!mSelected) {
                    Toast.makeText(activity, "Fragment #" + getPosition() + " stopped", Toast.LENGTH_SHORT).show();
                    return;
                }

                TextView textView = (TextView) activity.findViewById(R.id.text);
                if (textView != null) {
                    textView.setText("Fragment #" + getPosition() + " works: " + System.nanoTime() % 10000);
                }

                handler.postDelayed(this, 150);
            }
        }, 150);
    }
}

Activity:

import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
        viewPager.setOnPageChangeListener(new ViewPagerHelper(getSupportFragmentManager(), viewPager));
        viewPager.setAdapter(new MyAdapter(getSupportFragmentManager()));
    }
}

Adapter:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class MyAdapter extends FragmentPagerAdapter {
    public MyAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(position);
    }

    @Override
    public int getCount() {
        return 42;
    }
}

Check complete demo on github.

Oleksii K.
  • 5,359
  • 6
  • 44
  • 72