Tag Archives: annotations

BundleButler: Simplifying arguments and savedInstanceState

To make proper use of both Fragment arguments and saved instance state in Android, I found myself frequently writing a great deal of the same boilerplate code. Most of that code looked like this:

public class EditEntryFragment extends Fragment {
    private Entry entry;
    private String title;
    private boolean hasAgreedToTerms = false;
    public static EditEntryFragment createInstance(Entry currentEntry, String title) {
        EditEntryFragment fragment = new EntryEditFragment();
        Bundle args = new Bundle();
        args.putParcelable("entry", currentEntry);
        args.putString("title", title);
        fragment.setArguments(args);
        return fragment;
    }
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();
        if(args != null) {
            entry = (Entry)args.getParcelable("entry");
            title = args.getString(title);
        }
        if(savedInstanceState != null) {
            if(savedInstanceState.containsKey("entry") {
                entry = (Entry)savedInstanceState.getParcelable("entry");
            }
            hasAgreedToTerms = savedInstanceState.getBoolean("hasAgreedToTerms", hasAgreedToTerms);
        }
        // use saved entry to populate form fields / load images, etc.
    }
 
    public void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        state.putParcelable("entry", entry);
        state.putBoolean("hasAgreedToTerms", hasAgreedToTerms);
    }
}

Of course bundling all state into a Parcelable or Bundle like this reduces said boilerplate as we’re not keeping track of all form fields or state individually, but we still have to write code to:

  • Check that arguments and state have been provided (null checks)
  • Check that keys we care about exist
  • Decide when to use the saved state of a field over the argument over the default value
  • Bundle each argument in createInstance
  • Store each field into state

Each of these actions is fairly trivial, but leads to a lot of code that is easy to lose track of or get wrong.

Bundle Butler generates this code for you by making a few assumptions.

  1. Arguments and saved state can be treated similarly. They’re both state (arguments are essentially a bootstrapped state).
  2. Arguments and state will be loaded into fields.

This allows us to annotate fields and call a couple of static methods to accomplish the same thing:

public class EditEntryFragment extends Fragment {
    @Argument private Entry entry;
    @Argument("title") private String title;
    @Argument("terms") private boolean hasAgreedToTerms = false;
    public static EditEntryFragment createInstance(Entry currentEntry, String title) {
        EditEntryFragment fragment = new EntryEditFragment();
        this.entry = currentEntry;        
        this.title = title;
        BundleButler.saveArgs(fragment);
        return fragment;
    }
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        BundleButler.loadArgsWithState(this, savedInstanceState);
        // use saved entry to populate form fields / load images, etc.
    }
 
    public void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        BundleButler.saveState(this, state);
    }
}

The @Argument annotation causes Bundle Butler to generate adapters used by the BundleButler utility class to build an arguments bundle and manage the entries in the state bundle. The annotation takes an optional key to be used in the bundles. If not provided, it will use the name of the field.

Look for it to be added to maven Central shortly. In the mean time you can build it from source: BundleButler on GitHub. It’s still being developed and contributions are appreciated.