Search code examples
androidandroid-fragmentsandroid-lifecycle

How to pass complex, non serializable object to android fragments


Hello fellow Android developers,

I wanna know how do you guys pass complex non serializable (& non parcelable) object to fragments. (such as Listener, Api client, ...)

Let me explain my use case:

The use case

I'm building an Android application composed of one "host" activity and 3 fragments. Currently I'm passing the object using a custom constructor on the fragment (bad practice I know).

The fragments constructors looks like the following:

/**
 * Do not remove ever or you'll face RuntimeException
 */
public FirstFragment() {
}

public FirstFragment(Session session,
                     ApiClient apiClient,
                     FirebaseAnalytics firebaseAnalytics) {
    mSession = session;
    mApiClient = apiClient;
    mFirebaseAnalytics = firebaseAnalytics;
}

And I'm using them in the host activity like this

private FirstFragment getFirstFragment() {
    if (mFirstFragment == null) {
        mFirstFragment = new FirstFragment(mSession, mApiClient, mFirebaseAnalytics);
    }
    return mHomeFragment;
}

[...]

private void loadFragment(Fragment fragment, String tag) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.frame_container, fragment, tag);
    transaction.commit();
}

[...]

private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case FIRST_FRAGMENT_RES_ID:
                    toolbar.setTitle(R.string.first_fragment_title);
                    loadFragment(getFirstFragment(), "first_fragment");
                    return true;
                [...]
            }
            return false;
        }
    };

This solution works well almost all the time. But sometimes (and I don't know when exactly) the default constructor is invoked and therefore all local members are null.

Possible solutions

To solve the problem I'm thinking about the following solutions:

Singletons, singletons everywhere

Most of the objects I'm passing are singletons therefore I can access them in the default constructor of the fragments:

public FirstFragment() {
    mSession = Session.getInstance(getContext());
    mApiClient = ApiClient.getInstance(getContext());
    mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
}

Problems

However the above solution wouldn't work if I need to pass a callback or something. How can it be done like this then?

Access the objects using parent activity

I think it's one of the ugliest possible solutions because it will couple the Fragments to the parent activity. The idea is something like this

public FirstFragment() {
    mSession = Session.getInstance(getContext());
    mApiClient = ApiClient.getInstance(getContext());
    mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
    mListener = (Listener) getActivity(); // <- will works because parent activity implement the interface
}

Using broadcast & receiver

The idea is to keep passing singleton everywhere and use broadcast & receiver instead of listener.

How do you guys managed this scenario?

Thanks in advance !


Solution

  • It's been several months and I have now come up with a different solution.

    For the UI related data

    For the UI related stuff I'm now using the androidx livedata

    For the complex non serializable data

    My use case was to pass complex object to the fragment, such as manager, parent activity (trough a listener), etc... The approach I have taken is by injecting these data manually from the parent activity.

    The first things to do was to remove the objects from the fragment constructor and use the default constructor instead, so that I won't face any instantiation errors.

    Then I have created an inject() method on the fragment classes that look like this:

    public void inject(BillingManager billingManager, Listener listener) {
        mBillingManager = billingManager;
        mListener = listener;
    }
    

    Each fragment will have their own inject method width the objects that should be injected as parameters.

    In the parent activity I have override the onAttachFragment() method to handle the fragment attach process:

    @Override
    public void onAttachFragment(@NonNull Fragment fragment) {
        super.onAttachFragment(fragment);
        if (fragment.getClass().equals(FirstFragment.class)) {
            ((FirstFragment) fragment).inject(mBillingManager, this);
        } else if (fragment.getClass().equals(HomeFragment.class)) {
            ((HomeFragment) fragment).inject(this);
        }
    }
    

    Simple, and now everything work great.