Working with Fragments on Tablet Applications
You create a new activity for your table app, and now you have to add fragments to it. There’s no need to write new fragments to use with the tablet activity. However, you need to make some changes to your fragments to make sure they work properly in this new environment.
Communicating between fragments
If you try to add a task with your tablet application, it doesn’t show the edit fragment next to the list fragment as you expect, but instead opens a new activity to add a task.
The reason that your app opens the edit fragment in a new activity, rather than adjacent to the list fragment, is that it’s still doing exactly what it was told. It’s still executing the old phone behavior, which is to start a new activity for each fragment.
The code that does it is this line in ReminderListFragment
:
startActivity(new Intent(this, ReminderEditActivity.class).putExtra(
ReminderProvider.COLUMN_ROWID, id));
It should be easy to change, right? Not so fast. If you change this line to make it work for tablets, you “break” your existing phone app. The problem is that you want one behavior (the existing one) for phones and another behavior for tablets.
To solve this problem, you create an abstract method named edit Reminder()
that does one thing for phones and another thing for tablets. You then replace the existing call to startActivity()
with this updated method.
The key is to realize that because you need one version of editReminder()
for phones and one for tablets, you need to put the editReminder()
in two places — one that runs on phones and one that runs on tablets. Where does one class run on phones and another class run on tablets? In the activity, of course. So you put editReminder()
in the phone and tablet Activity
classes.
To create the editReminder()
callback for your fragment to call, follow these steps:
1. Create a new OnEditReminder.java
interface, and put the edit Reminder()
method in it, like this:
package com.dummies.android.taskreminder;
public interface OnEditReminder {
public void editReminder(long id);
}
2. Implement this method in the ReminderListActivity
for the phones.
Modify ReminderListActivity
by adding the bits in bold:
package com.dummies.android.taskreminder;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class ReminderListActivity extends FragmentActivity implements
OnEditReminder
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reminder_list);
// Switch to tablet activity and finish this one if user is on a tablet.
if (isTablet()) {
startActivity(new Intent(this, ReminderListAndEditorActivity.class));
finish();
return;
}
}
@Override
public void editReminder(long id) {
startActivity(new Intent(this, ReminderEditActivity.class).putExtra(
ReminderProvider.COLUMN_ROWID, id));
}
}
The editReminder()
call is identical, line-for-line, to what you had in the ReminderListFragment
, except that now it’s in the ReminderListActivity
instead.
3. Call this method from the fragment:
Visit ReminderListFragment
and modify the onOptionsItem Selected()
and onListItemClick()
methods as follows:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
caseR.id.menu_insert:
((OnEditReminder) getActivity()).editReminder(0);
return true;
caseR.id.menu_settings:
startActivity(new Intent(getActivity(), TaskPreferences.class));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
((OnEditReminder) getActivity()).editReminder(id);
}
Now, rather than call startActivity
() directly in each method, the app calls getActivity()
to get the activity, casting it to an OnEditReminder
and then calling editReminder()
.
Casting is normally frowned on in Java because it’s often unsafe. However, it’s safe to cast the result of getActivity()
to an OnEdit Reminder
because the ReminderListFragment
will always be in either ReminderListActivity
or ReminderListAndEditor Activity
, and both implement OnEditReminder
. If ever you want to add the fragment to another activity, ensure that it, too, implements OnEditReminder
.
4. Implement the same OnEditReminder
interface in your ReminderListAndEditorActivity
for tablets.
Add the code in bold to your ReminderListAndEditorActivity
:
public class ReminderListAndEditorActivity extends FragmentActivity implements OnEditReminder
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reminder_list_and_editor);
}
@Override
public void editReminder(long id) {
// TBD
}
}
Check out the next section to see exactly how to implement edit Reminder()
for the tablet app.
Before you do that, you need to address one more subtle interaction between fragments. Take a look at the OnClickListener
for the mConfirmButton
in ReminderEditFragment
and you’ll see this line:
getActivity().finish();
No good. Calling getActivity().finish()
from the phone app returns the user to the list activity, but calling it from the tablet app closes the app. Clearly, from the tablet app you want to remove the edit fragment, not finish the entire activity.
To fix this problem, follow these steps:
1. Create a new interface named OnFinishEditor
, and make it look like this:
package com.dummies.android.taskreminder;
public interface OnFinishEditor {
public void finishEditor();
}
2. Modify the ReminderEditFragment
to call this interface instead of calling finish()
directly.
Replace both instances of getActivity().finish()
with the following (there should be two):
((OnFinishEditor) getActivity()).finishEditor();
3. Modify the two activities of ReminderEditFragment
to add this interface and implement the finishEditor()
method.
Add the following to your ReminderEditActivity
:
public class ReminderEditActivity extends FragmentActivity implements
OnFinishEditor
{
@Override
public void finishEditor() {
finish();
}
}
This is the same code that used to be called in your OnClickListener
and onLoadFinished()
.
4. Add the following code to ReminderListAndEditorActivity
:
public class ReminderListAndEditorActivity extends FragmentActivity implements
OnEditReminder, OnFinishEditor
{
@Override
public void finishEditor() {
FragmentManager fragmentManager = getSupportFragmentManager();
→6
FragmentTransaction transaction = fragmentManager.beginTransaction();
→7
Fragment previousFragment = fragmentManager
.findFragmentByTag(ReminderEditFragment.DEFAULT_EDIT_FRAGMENT_TAG);
→9
transaction.remove(previousFragment);
→10
transaction.commit();
→11
}
}
As before, you’re using the FragmentManager
and a Fragment Transaction
to manage the adding and removing of fragments from the activity. Here’s what the code does:
→6 Asks the FragmentActivity
for the FragmentManager
.
→7 Starts the FragmentTransaction
by calling begin Transaction()
.
→9 Asks the FragmentManager
to find the previous fragment named DEFAULT_EDIT_FRAGMENT_TAG
, if any. This name must agree with the name that you used when you initially added the fragment in editReminder()
.
→10 Removes the fragment. If previousFragment
was null
, this line does nothing.
→11 Commits the transaction. Remember that every call to begin Transaction()
must end with a call to commit()
.
Adding fragment transactions
Fundamentally, editReminder()
should show an edit fragment for the task that the user tapped. Or it should show an empty edit fragment if the user tapped the Add button on the action bar so that the user can add a new task. If a user taps several tasks in a row, editReminder()
should replace the existing edit fragment with a new one representing the last item.
You’ve already put fragments into activities using XML in your reminder_list.xml
layout. Because you want to dynamically add and remove fragments, this time you use Java instead of XML. The process isn’t hard; it’s just a little different from XML.
When you want to add or remove fragments in Java, you need to use the FragmentManager
to begin a FragmentTransaction
. You then make your changes and call commit()
on the FragmentTransaction
, much like you might do when interacting with a database transaction.
Inside editReminder()
, add the following code:
/**
* Set the edit fragment, replacing the existing fragment if there’s one
* already there.
*/
@Override
public void editReminder(long id) {
ReminderEditFragment fragment = new ReminderEditFragment(); →7
Bundle arguments = new Bundle(); →8
arguments.putLong(ReminderProvider.COLUMN_ROWID, id); →9
fragment.setArguments(arguments); →10
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction(); →13
transaction.replace(R.id.edit_container, fragment,
ReminderEditFragment.DEFAULT_EDIT_FRAGMENT_TAG); →15
transaction.addToBackStack(null); →16
transaction.commit(); →17
}
Here’s how the code works:
→7 Creates a new ReminderEditFragment
fragment.
Fragments must have no-argument constructors. All arguments go into a bundle.
→8–10 Tells the fragment which task is being edited. An ID of 0
indicates that a new fragment is being created.
→13 Gets the FragmentManager
by calling FragmentActivity.getSupportFragmentManager()
, and then calls begin Transaction()
to start a new fragment transaction.
→15 Calls FragmentTransaction.replace()
to replace the existing fragment with the new fragment. The edit_container
view tells you where to place the fragment, fragment
tells you which fragment to use, and ReminderEditFragment.DEFAULT_EDIT_FRAGMENT_TAG
reveals the fragment name.
→16 Calls addToBackStack()
and passes null
for the optional state name.
This line requires a little explanation. Think about what happens whenever you start a new activity — it is added to the activity stack and, if users tap the Back button, they return to the previous activity. This standard interaction is expected by users for almost all activities.
The default behavior for fragments, though, is the opposite. By default, when you add a fragment to an activity, it doesn’t go on the back stack. So a user who taps the Back button exits the activity rather than removes the fragment you just added. This may not be what you want to happen. If you want the Back button to remove the fragment, you need to call addToBackStack()
.
When adding fragments dynamically, think about what your users are most likely to expect the Back button to do. In this case, when the user taps a list item to display an edit fragment, it’s reasonable for him to expect to be able to tap the Back button to close the edit fragment.
→17 Every call to beginTransaction()
must be accompanied by a call to commit()
. This is where Android does the actual work to add fragments to, or remove them from, your activity.
Congratulations — you should now have a fully implemented version of your phone application running on your tablet.