During the rewrite of my old app Moustachify Everything (made in the days of Android 1.6), I decided to switch it to use a new-old thing called Fragments. They help you persist information during screen orientation changes when activities are destroyed and recreated.
Depending on what the fragments are used for, we'll be needing different files. And of course, you'll probably want to include the v4 support library as well.
Hidden fragments
Hidden fragments allow AsyncTasks and AlertDialogs to persist across rotations. Anyone who has worked with this before will understand that it's tedious, horrible and generally error prone.
Since it's hidden, you won't need to create/edit as many files.
- Create: A class to extend whichever fragment type you need (eg. DialogFragment, regular Fragment or SherlockFragment)
- Modify: Your Activity to extend FragmentActivity or SherlockFragmentActivity
- Modify: Implement the dialog response in your activity from the Interface
- Modify: Use snippet to create fragment when needed, show it using a unique "tag" string
The following is an example which allows you to instantiate a fragment (with arguments), display a custom dialog of choice and then keep itself displayed during rotations.
All you really need to do is make sure the interface suits your needs and the dialog shows the right content.
ExampleDialogFragment.java (click to view):
public class ExampleDialogFragment extends DialogFragment {
private static final String KEY_ARG_A = "argumentA";
private static final String KEY_ARG_B = "argumentB";
/**
* Interface to link back to the activity.
*/
public interface ExampleDialogResponses {
public void doPositiveClick(long timestamp);
}
/**
* Pass in variables, store for later use.
*/
public static ExampleDialogFragment newInstance(int argA, String argB) {
ExampleDialogFragment f = new ExampleDialogFragment();
Bundle args = new Bundle();
args.putInt(KEY_ARG_A, argA);
args.putString(KEY_ARG_B, argB);
f.setArguments(args);
return f;
}
/**
* Override the creation of the dialog.
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Fetch the information set earlier
int argA = getArguments().getInt(KEY_ARG_A);
String argB = getArguments().getString(KEY_ARG_B);
// Generate your dialog view or layout here
// view = ...
TextView view = new TextView(getActivity());
view.setText(argB + String.valueOf(argA));
// Display the dialog
return new AlertDialog.Builder(getActivity())
.setTitle("Choose OK or Cancel")
.setView(view)
// Cancel
.setNegativeButton(android.R.string.cancel, null)
// Click "OK" button handler
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
long timestamp = System.currentTimeMillis();
((ExampleDialogResponses) getActivity()).doPositiveClick(timestamp);
}
}
)
.create();
}
}
Changing the base class of the activity is easy. While we're at it, implement the fragment response ExampleDialogFragment.ExampleDialogResponses.
Old:
public class YourActivity extends Activity
New:
public class YourActivity extends FragmentActivity implements ExampleDialogFragment.ExampleDialogResponses
// or if you're using ActionBarSherlock
public class YourActivity extends SherlockFragmentActivity implements ExampleDialogFragment.ExampleDialogResponses
Implementing the dialog response helps keep the code clean. This goes into your Activity class.
@Override
public void doPositiveClick(long timestamp) {
Log.d("doPositiveClick", String.valueOf(timestamp));
}
And finally, this is how you display the fragment. First we initialise the dialog with newInstance() and the given arguments, and then we display it with show().
DialogFragment newFragment = ExampleDialogFragment.newInstance(12345, "ABCDE");
newFragment.show(getFragmentManager(), "dialog");
For ActionBarSherlock user, replace getFragmentManager() with getSupportFragmentManager().
The "dialog" string is simply a unique name for this fragment within the context of the activity.
Layout fragments
Normal layout fragments contain a layout and are treated like a View, except it retains non-view information during rotations. This is something which has bothered many developers for a long time.
Disclaimer! Something to keep in mind is that view information is not retained as they are destroyed along with the activity.
Normal activity fragments which display a layout will require a little more work than hidden fragments.
- Create: A new layout for the fragment
- Create: A class for the new fragment extending Fragment or SherlockFragment
- Modify: The Activity layout to include a fragment
- Modify: Activity to extend FragmentActivity or SherlockFragmentActivity
There isn't anything special about creating a layout for your new fragment. Just like any other layout, you can put it in /res/layout. I won't bother pasting the code to this post. Check out the sample code for fragment_example.xml (click to view) if you're really that curious.
The main attraction is the fragment class ExampleFragment.java.
public class ExampleFragment extends SherlockFragment {
// These values are automatically retained
private int buy = 0;
private int sell = 0;
// Views are destroyed each time the activity is rotated.
// These are NOT automatically retained by the fragment.
private Button btnBuy;
private Button btnSell;
private TextView tvLabel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_example, container, true);
// Store handles to the controls
btnBuy = (Button) view.findViewById(R.id.btnBuy);
btnSell = (Button) view.findViewById(R.id.btnSell);
tvLabel = (TextView) view.findViewById(R.id.tvLabel);
// Event handlers
btnBuy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buy++;
updateLabel();
}
});
btnSell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sell++;
updateLabel();
}
});
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updateLabel();
}
private void updateLabel() {
StringBuilder sb = new StringBuilder();
sb.append("Buy: ");
sb.append(buy);
sb.append(", sell: ");
sb.append(sell);
tvLabel.setText(sb.toString());
}
}
Now to include the fragment in your activity layout. You'll need to add the "tools" namespace and add the fragment element to where it should be created.
To add the tools namespace, open the layouts file and go to the top. Add in the tools line:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" <!-- This one! -->
The fragment itself should be treated like a normal view, bound by height and width rules of the parent. Most of this can be done via the WYSIWYG editor.
<fragment
android:id="@+id/fragmentExample"
android:name="twig.nguyen.mustachify2.activities.fragments.ExampleFragment"
tools:layout="@layout/fragment_example"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
The important attributes are:
- android:id: the fragment an ID so you can refer to it via code
- android:name: maps it to the given fragment class
- tools:layout: fill the fragment with elements from the given layout
That's all the setup done! By now you should have a fully working preview in the layout editor.
Accessing fragment via code
Lastly in order to make use of your new toys, you'll have to be able to retrieve them.
FragmentManager fm = getSupportFragmentManager();
// or getSupportFragmentManager() for ActionBarSherlock users
fragment = (ExampleFragment) fm.findFragmentById(R.id.fragmentExample);
Some key points to remember:
- having the fragment defined in the XML means initialisation is already done for you
- onCreate() tells the fragment it'll be retained.
- onCreateView() is when you inflate the layout and initialise stuff.
- onViewCreated() is where you fill in the initialised views and the retained variables.
And that's it! Your fragment will handle the instantiation pains so you don't have to worry about them as much.