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):
01.
public
class
ExampleDialogFragment
extends
DialogFragment {
02.
private
static
final
String KEY_ARG_A =
"argumentA"
;
03.
private
static
final
String KEY_ARG_B =
"argumentB"
;
04.
05.
/**
06.
* Interface to link back to the activity.
07.
*/
08.
public
interface
ExampleDialogResponses {
09.
public
void
doPositiveClick(
long
timestamp);
10.
}
11.
12.
13.
/**
14.
* Pass in variables, store for later use.
15.
*/
16.
public
static
ExampleDialogFragment newInstance(
int
argA, String argB) {
17.
ExampleDialogFragment f =
new
ExampleDialogFragment();
18.
19.
Bundle args =
new
Bundle();
20.
args.putInt(KEY_ARG_A, argA);
21.
args.putString(KEY_ARG_B, argB);
22.
f.setArguments(args);
23.
24.
return
f;
25.
}
26.
27.
/**
28.
* Override the creation of the dialog.
29.
*/
30.
@Override
31.
public
Dialog onCreateDialog(Bundle savedInstanceState) {
32.
// Fetch the information set earlier
33.
int
argA = getArguments().getInt(KEY_ARG_A);
34.
String argB = getArguments().getString(KEY_ARG_B);
35.
36.
// Generate your dialog view or layout here
37.
// view = ...
38.
TextView view =
new
TextView(getActivity());
39.
view.setText(argB + String.valueOf(argA));
40.
41.
// Display the dialog
42.
return
new
AlertDialog.Builder(getActivity())
43.
.setTitle(
"Choose OK or Cancel"
)
44.
.setView(view)
45.
46.
// Cancel
47.
.setNegativeButton(android.R.string.cancel,
null
)
48.
49.
// Click "OK" button handler
50.
.setPositiveButton(android.R.string.ok,
51.
new
DialogInterface.OnClickListener() {
52.
public
void
onClick(DialogInterface dialog,
int
whichButton) {
53.
long
timestamp = System.currentTimeMillis();
54.
((ExampleDialogResponses) getActivity()).doPositiveClick(timestamp);
55.
}
56.
}
57.
)
58.
.create();
59.
}
60.
}
Changing the base class of the activity is easy. While we're at it, implement the fragment response ExampleDialogFragment.ExampleDialogResponses.
Old:
1.
public
class
YourActivity
extends
Activity
New:
1.
public
class
YourActivity
extends
FragmentActivity
implements
ExampleDialogFragment.ExampleDialogResponses
2.
// or if you're using ActionBarSherlock
3.
public
class
YourActivity
extends
SherlockFragmentActivity
implements
ExampleDialogFragment.ExampleDialogResponses
Implementing the dialog response helps keep the code clean. This goes into your Activity class.
1.
@Override
2.
public
void
doPositiveClick(
long
timestamp) {
3.
Log.d(
"doPositiveClick"
, String.valueOf(timestamp));
4.
}
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().
1.
DialogFragment newFragment = ExampleDialogFragment.newInstance(
12345
,
"ABCDE"
);
2.
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.
01.
public
class
ExampleFragment
extends
SherlockFragment {
02.
// These values are automatically retained
03.
private
int
buy =
0
;
04.
private
int
sell =
0
;
05.
06.
// Views are destroyed each time the activity is rotated.
07.
// These are NOT automatically retained by the fragment.
08.
private
Button btnBuy;
09.
private
Button btnSell;
10.
private
TextView tvLabel;
11.
12.
13.
14.
@Override
15.
public
void
onCreate(Bundle savedInstanceState) {
16.
17.
super
.onCreate(savedInstanceState);
18.
19.
setRetainInstance(
true
);
20.
}
21.
22.
23.
@Override
24.
public
View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
25.
View view = inflater.inflate(R.layout.fragment_example, container,
true
);
26.
27.
// Store handles to the controls
28.
btnBuy = (Button) view.findViewById(R.id.btnBuy);
29.
btnSell = (Button) view.findViewById(R.id.btnSell);
30.
tvLabel = (TextView) view.findViewById(R.id.tvLabel);
31.
32.
// Event handlers
33.
btnBuy.setOnClickListener(
new
View.OnClickListener() {
34.
@Override
35.
public
void
onClick(View v) {
36.
buy++;
37.
updateLabel();
38.
}
39.
});
40.
41.
btnSell.setOnClickListener(
new
View.OnClickListener() {
42.
@Override
43.
public
void
onClick(View v) {
44.
sell++;
45.
updateLabel();
46.
}
47.
});
48.
49.
return
view;
50.
}
51.
52.
53.
@Override
54.
public
void
onViewCreated(View view, Bundle savedInstanceState) {
55.
super
.onViewCreated(view, savedInstanceState);
56.
57.
updateLabel();
58.
}
59.
60.
61.
private
void
updateLabel() {
62.
StringBuilder sb =
new
StringBuilder();
63.
sb.append(
"Buy: "
);
64.
sb.append(buy);
65.
sb.append(
", sell: "
);
66.
sb.append(sell);
67.
68.
tvLabel.setText(sb.toString());
69.
}
70.
}
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:
1.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
2.
<
RelativeLayout
3.
xmlns:android
=
"http://schemas.android.com/apk/res/android"
4.
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.
1.
<
fragment
2.
android:id
=
"@+id/fragmentExample"
3.
android:name
=
"twig.nguyen.mustachify2.activities.fragments.ExampleFragment"
4.
tools:layout
=
"@layout/fragment_example"
5.
android:layout_width
=
"wrap_content"
6.
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.
1.
FragmentManager fm = getSupportFragmentManager();
2.
// or getSupportFragmentManager() for ActionBarSherlock users
3.
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.