17

I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.

As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.

Saber Amani
  • 6,409
  • 12
  • 53
  • 88
  • I too had the same scenario and I used singleton pattern to solve it and I found it to be the better than data passing between fragments which is very expensive task – arjun Feb 17 '17 at 16:33
  • I don't know your exact problem, but did you consider to use interfaces between fragments (and use them within OnPageChangeListener)? – Valentino Feb 17 '17 at 16:44
  • Yes, Its expensive and its too complicated to implement that common interface in activity, fragments, viewpager and blah blah blah. think about you have to change priority of the steps or add more steps between steps. – Saber Amani Feb 17 '17 at 16:51
  • @ValentinoS. Can you provide some resource or example about handling interface within OnPageChangeListener? – Saber Amani Feb 17 '17 at 16:57
  • I'm going to post something, probably won't fit your requirements. Check it soon – Valentino Feb 17 '17 at 17:34
  • When do you create business object? Where do you get the values for business object? Dose your viewpager adapter extends FragmentStatePagerAdapter? – Anurag Singh Feb 22 '17 at 07:21
  • @AnuragSingh Create on first fragment in viewpager, and destroy after sending business data to server, all fragments in viewpager access to business model object till object fill, no the adapter doesn't extend FragmentStatePagerActivity. – Saber Amani Feb 22 '17 at 08:07
  • 1
    Did you consider using `SharedPreferences`? Or it's too complicated? – Valentino Feb 22 '17 at 14:40
  • @ValentinoS. I think `SharedPreferences` is used to keep value for simple data type such as `String`, `Boolean` and etc! – Saber Amani Feb 22 '17 at 14:56
  • Maybe won't help you, but you can save also JSON Object in shared preferences – Valentino Feb 22 '17 at 15:01
  • Instead of using a god-object singleton, consider letting other Fragments know that something has changed by using `LocalBroadcastManager.getInstance(...).sendBroadcast()` and registering/unregistering `BroadcastReceivers` on the `LocalBroadcastManager` – Shark Feb 27 '17 at 16:46

7 Answers7

6

I wouldn't recommend a global singleton. There are two main reasons:

  1. By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
  2. The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().

All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton

For example,

public class MyParentFragment extends Fragment {

  private String mPageOneData;
  private int mPageTwoData;
  private List<Date> mPageThreeData;

  public void setPageOneData(String data) {
    mPageOneData = data;
  }

  ...
}

public class PageOneFragment extends Fragment {

  private void sendDataToParent(String data) {
    Fragment f = getParentFragment();
    if (f != null && f instanceof MyParentFragment) {
      MyParentFragment parent = (MyParentFragment) f;
      f.setPageOneData(data);
    }
  }

}
Jschools
  • 2,698
  • 1
  • 17
  • 18
6

While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?

What I would do is

  1. Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
  2. Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
  3. Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example: if a fragment is not active yet:

    Bundle args = new Bundle(); args.putParcable("businessObject", yourBusinessObjectThatIsParcable); yourFragment.setArguments(args);

    Otherwise: yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);

  4. In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.

  5. When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);

  6. in your last page or last fragment you can pass your object to your activity, here is how to do it

Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.

Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.

Community
  • 1
  • 1
Vilen
  • 5,061
  • 3
  • 28
  • 39
2
  1. you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background. you can restore your data in onCreate() event by using Bundle and getExtras().

  2. you can save your data in application class and the data will still be there in case your process will go into the background.

i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments. I hope i could help :)

ediBersh
  • 1,135
  • 1
  • 12
  • 19
1

Have you checkout EventBus?

I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.

Hope it helps

murielK
  • 1,000
  • 1
  • 10
  • 21
0

I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.

In your MainActivity add this interface:

public interface Updateable {
    public void update();
}

Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:

public class FragmentOne extends Fragment implements MainActivity.Updateable {
    ...
    @Override
    public void update() {
        // YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
        if ( adapter != null ) {
            adapter.notifyDataSetChanged();
        } else {
            Log.d("LOG_TAG", "null");
        }
    }
    ...
}

Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:

@Override
public int getItemPosition(Object object) {    
    if ( object != null && object instanceof FragmentOne ) {
        FragmentOne f = (FragmentOne) object;

        f.update();
    }
    return super.getItemPosition(object);
}

Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        int previousState;
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }

        @Override
        public void onPageSelected(int position) {

        }

        @Override
        public void onPageScrollStateChanged(int state) {

            if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {

                if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
                    Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());

                    viewPagerAdapter.notifyDataSetChanged();
                }

            }
            previousState = state;
        }
    });
Valentino
  • 2,125
  • 1
  • 19
  • 26
  • Thanks for your effort, but unfortunately this will not fit my requirements. I have one Activity and a Fragment inside it, the fragment inside activity has a viewpager and viewpager has its own fragments for each page. I think I should go with singleton which is more clear and easy to maintain. – Saber Amani Feb 17 '17 at 18:01
  • The same approach that I used with Activity and Fragment can be used with your situation (Fragment with view pager and Fragment inside view pager). However, you konw if it fits or not – Valentino Feb 17 '17 at 18:10
0

Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.

You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using

  • Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
  • SharefPreference(preferably) - If you want to use single values.

In both cases you will create a singleton object and access its properties in your fragments.

Haris Qurashi
  • 2,104
  • 1
  • 13
  • 28
0

make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.

it should motivate you

http://www.developerphil.com/parcelable-vs-serializable/

Abdullah Raza
  • 600
  • 3
  • 9