Using LocalBroadcastManager to communicate from Fragment to Activity

EDIT: This question was created as part of one of my first Android projects when I was just starting out with Android application development. I'm keeping this for historical reasons, but you should consider using EventBus or RxJava instead. This is a gigantic mess.

Please DO NOT CONSIDER using this. Thank you.

In fact, if you want something cool that solves the problem of using a single activity with multiple "fragments", then use flowless with custom viewgroups.


I have implemented a way to initiate the creation of Fragments, from Fragments using a broadcast intent through the LocalBroadcastManager to tell the Activity what Fragment to instantiate.

I know this is a terribly long amount of code, but I'm not asking for debugging, it works perfectly as I intended - the data is received, the creation can be parametrized by Bundles, and Fragments don't directly instantiate other Fragments.

public abstract class FragmentCreator implements Parcelable
{
public static String fragmentCreatorKey = "fragmentCreator";
public static String fragmentCreationBroadcastMessage = "fragment-creation";
public static String fragmentDialogCreationBroadcastMessage = "fragment-dialog-creation";

protected Bundle arguments;
protected Boolean hasBundle;

public FragmentCreator(Bundle arguments, boolean hasBundle)
{
    this.arguments = arguments;
    this.hasBundle = hasBundle;
}

protected FragmentCreator(Parcel in)
{
    hasBundle = (Boolean) in.readSerializable();
    if (hasBundle == true && arguments == null)
    {
        arguments = in.readBundle();
    }
}

public Fragment createFragment()
{
    Fragment fragment = instantiateFragment();
    if (arguments != null)
    {
        fragment.setArguments(arguments);
    }
    return fragment;
}

protected abstract Fragment instantiateFragment();

@Override
public int describeContents()
{
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags)
{
    dest.writeSerializable(hasBundle);
    if (arguments != null)
    {
        arguments.writeToParcel(dest, 0);
    }
}

public void sendFragmentCreationMessage(Context context)
{
    Intent intent = new Intent(FragmentCreator.fragmentCreationBroadcastMessage);
    intent.putExtra(FragmentCreator.fragmentCreatorKey, this);
    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}

public void sendDialogFragmentCreationMessage(Context context)
{
    Intent intent = new Intent(FragmentCreator.fragmentDialogCreationBroadcastMessage);
    intent.putExtra(FragmentCreator.fragmentCreatorKey, this);
    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}

This way, a Fragment that is created looks like this:

public class TemplateFragment extends Fragment implements GetActionBarTitle, View.OnClickListener
{
 private int titleId;

public TemplateFragment()
{
    titleId = R.string.app_name;
}

@Override
public int getActionBarTitleId()
{
    return titleId;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    View rootView = inflater.inflate(R.layout.fragment_template, container, false);
    return rootView;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);
}

@Override
public void onClick(View v)
{
}

public static class Creator extends FragmentCreator
{
    public Creator()
    {
        super(null, false);
    }

    public Creator(Bundle bundle)
    {
        super(bundle, true);
    }

    protected Creator(Parcel in)
    {
        super(in);
    }

    @Override
    protected Fragment instantiateFragment()
    {
        return new TemplateFragment();
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<TemplateFragment.Creator> CREATOR = new Parcelable.Creator<TemplateFragment.Creator>()
    {
        @Override
        public TemplateFragment.Creator createFromParcel(Parcel in)
        {
            return new TemplateFragment.Creator(in);
        }

        @Override
        public TemplateFragment.Creator[] newArray(int size)
        {
            return new TemplateFragment.Creator[size];
        }
    };
}
}

The initial container activity that can process the messages looks like this:

        Intent intent = new Intent();
        intent.setClass(this.getActivity(), ContainerActivity.class);
        intent.putExtra(FragmentCreator.fragmentCreatorKey,
                new TemplateFragment.Creator());
        startActivity(intent);

And the Fragments "instantiate other Fragments" like this:

  Bundle bundle = new Bundle();
  bundle.putParcelable("argument", data);
  TemplateFragment.Creator creator = new TemplateFragment.Creator(bundle);
  creator.sendFragmentCreationMessage(getActivity());

And the Container Activity receives the instantiation request:

public class ContainerActivity extends ActionBarActivity implements SetFragment, ShowDialog
{
private BroadcastReceiver mFragmentCreationMessageReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        setFragment((FragmentCreator) intent.getParcelableExtra(FragmentCreator.fragmentCreatorKey));
    }
};

private BroadcastReceiver mFragmentDialogCreationMessageReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        showDialog((FragmentCreator) intent.getParcelableExtra(FragmentCreator.fragmentCreatorKey));
    }
};

@Override
public void onCreate(Bundle saveInstanceState)
{
    super.onCreate(saveInstanceState);
    this.setContentView(R.layout.activity_container);
    getActionBar().setDisplayHomeAsUpEnabled(true);
    if (saveInstanceState == null)
    {
        Fragment fragment = ((FragmentCreator) getIntent().getParcelableExtra(
                FragmentCreator.fragmentCreatorKey)).createFragment();
        if (fragment != null)
        {
            replaceFragment(fragment);
        }
    }
    else
    {
        this.getActionBar()
                .setTitle(
                        ((GetActionBarTitle) (this.getSupportFragmentManager()
                                .findFragmentById(R.id.activity_container_container)))
                                .getActionBarTitleId());
    }
    getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
    {
        public void onBackStackChanged()
        {
            int backCount = getSupportFragmentManager().getBackStackEntryCount();
            if (backCount == 0)
            {
                finish();
            }
        }
    });
}

@Override
protected void onResume()
{
    LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentCreationMessageReceiver,
            new IntentFilter(FragmentCreator.fragmentCreationBroadcastMessage));
    LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentDialogCreationMessageReceiver,
            new IntentFilter(FragmentCreator.fragmentDialogCreationBroadcastMessage));
    super.onResume();
}

@Override
protected void onPause()
{
    super.onPause();
    LocalBroadcastManager.getInstance(this).unregisterReceiver(mFragmentCreationMessageReceiver);
    LocalBroadcastManager.getInstance(this).unregisterReceiver(
            mFragmentDialogCreationMessageReceiver);
}

@Override
public void setFragment(FragmentCreator fragmentCreator)
{
    Fragment fragment = fragmentCreator.createFragment();
    replaceFragment(fragment);
}

public void replaceFragment(Fragment fragment)
{
    if (fragment != null)
    {
        this.setTitle(((GetActionBarTitle) fragment).getActionBarTitleId());
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.activity_container_container, fragment).addToBackStack(null).commit();
    }
}

@Override
public void showDialog(FragmentCreator fragmentCreator)
{
    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fragmentCreator.createFragment();
    if (fragment instanceof DialogFragment)
    {
        DialogFragment df = (DialogFragment) fragment;
        df.show(fm, "dialog");
    }
    else
    {
        Log.e(this.getClass().getSimpleName(), "showDialog() called with non-dialog parameter!");
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
    if (item.getItemId() == android.R.id.home)
    {
        this.onBackPressed();
    }
    return super.onOptionsItemSelected(item);
}
}

My question is, is this actually a good idea, or is this a terrible case of "over-engineering" (creating a Factory for each Fragment and sending it to the Activity in the form of a local broadcast, rather than just casting the Activity of the most possible holder activity's interface and calling the function like that)?

My goal was that this way, I can use the same Activity for holding "branch" fragments, so that I don't need to make one for each menu point. Rather than just re-use the same activity, and divide all logic into fragments. (Currently it doesn't support orientation-based layout organization, I see that downside - and also that this way each Fragment needs to hold a static creator, which is extra 'boilerplate code').

If you know the answer why I shouldn't be using the local broadcast manager for this, I'll be happy to hear the response. I think it's pretty neat, but there's a chance it's just overcomplicating something simple.


Solution 1:

You can use Interface for it so main objective of Fragment re-usability is maintained. You can implement communication between Activity-Fragment OR Fragment-Fragment via using following :

enter image description here

Solution 2:

I am asuming that your moto is Fragment to communicate with its Activity and other Fragments. If this is the case please go throught it.

To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.

Example :

# In fragment

    public class HeadlinesFragment extends ListFragment {

    OnHeadlineSelectedListener mCallback;

    public interface OnHeadlineSelectedListener {        
    public void onArticleSelected(int position);    
    }

   @Override   
   public void onAttach(Activity activity) {        
   super.onAttach(activity);
   mCallback = (OnHeadlineSelectedListener) activity;
   }
   @Override    
   public void onListItemClick(ListView l, View v, int position, long id) {
   mCallback.onArticleSelected(position);    
  }
  }

# In Activity

    public static class MainActivity extends Activity  implements HeadlinesFragment.OnHeadlineSelectedListener{
 public void onArticleSelected(int position) {
  // Do something here
 }
 }

Link: http://developer.android.com/training/basics/fragments/communicating.html