The Android platform runs on a myriad of devices both phone and tablet. With this brings a variety of screen sizes that the OS supports by smoothly resizing your layouts to fit each one. While this convenience allows your app's UI to scale accordingly, it does not mean you're fully leveraging the real estate a device's screen has to offer. Specifically, designing your UI to more efficiently display content on tablets will go a long way when creating an optimized user-experience.

Below is a common example that illustrates how an app might be optimized for a tablet.

With the extra surface area, you can show content to your user more effectively. In addition, you can minimize the number of clicks it takes for a user to complete a particular task in your app.

Gmail App

Google's Gmail app is a great example of this.

For phones, you have the entire screen dedicated to a single activity or fragment as expected. You also see a temporary navigation drawer that sits above all content when in open state.

For tablets, there's a dual-pane layout with the user's inbox in the left-pane. The right-pane is dedicated to the email details of whichever list item the user clicks. It's also important to point out the optimized navigation drawer.

What you see is a variation of the persistent navigation drawer - called mini-variant - that changes its width depending on the open-closed state and remains clipped under the app bar. When in closed state the navigation drawer is still visible and displays icons for the top-level screens. This allows the user to quickly navigate back to screens high in the navigation hierarchy without having to toggle the drawer.

Although I won't thoroughly cover the mini-variant navigation drawer, I am going to describe how to design your app to support phones and tablets. I will use Google's Gmail application as a point of reference throughout the rest of this post. You can find a sample application here that puts everything in this post together.

Golden Rules for a Flexible Design

The Android developer site mentions three general guidelines for building apps that effectively support phone and tablets:

  • Build your activity designs based on fragments
  • Use the action bar
  • Implement flexible layouts

The main take-away from these guidelines:

You want to build your apps using fragment because they allow reuse by decoupling specific content and/or functionality from an activity. So instead of building out each screen as a unique activity, you can use a single activity and host multiple fragments.

Alternate Resource Files

Create a Single-Pane and Multi-Pane Layout

For Phones - layout/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/contentContainer"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:baselineAligned="false"
 android:orientation="horizontal">
 <FrameLayout
 android:id="@+id/fragmentContainer"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>
LinearLayout>

For Tablets - layout-sw600dp/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/contentContainer"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:baselineAligned="false"
 android:orientation="horizontal">
 <FrameLayout
 android:id="@+id/leftFragmentContainer"
 android:layout_width="0dp"
 android:layout_height="match_parent"
 android:layout_weight=".3" />
 <FrameLayout
 android:id="@+id/rightFragmentContainer"
 android:layout_width="0dp"
 android:layout_height="match_parent"
 android:layout_weight=".7" />
LinearLayout>

Boolean Resource Files

The system will choose the correct layout file depending on the device's screen size. Yet, how your app responds to user interaction and adds content to any particular screen will depend on a good pattern that can determine which screen you're dealing with.

Create four boolean resource files.

values/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <bool name="isTablet">falsebool>
 <bool name="isPortrait">falsebool>
resources>

values-port/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <bool name="isTablet">falsebool>
 <bool name="isPortrait">truebool>
resources>

values-sw600dp/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <bool name="isTablet">truebool>
 <bool name="isPortrait">falsebool>
resources>

values-sw600dp-port/content_view.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <bool name="isTablet">truebool>
 <bool name="isPortrait">truebool>
resources>

Adding Content to the Screen

Now that we are able to determine whether the running device is a phone or tablet, and if in portrait or landscape mode, it will become much easier to appropriately add content to the screen.

When the hosting activity's onCreate() method runs, you can run a simple if-else statement to determine if you should add both InboxFragment and EmailFragment to the screen or just one of them. If isTablet renders true we know that our activity's layout is layout-sw600dp/content_view.xml because we used the same qualifier sw600dp to define isTablet as true. Therefore, you would add InboxFragment to R.id.leftFragmentContainer and EmailFragment to R.id.rightFragmentContainer. Otherwise the running device is a phone, in which case the host activity's layout is layout/content_view.xml. Then you'd just add InboxFragment to the FrameLayout R.id.fragmentContainer. Your activity's onCreate() might look something like this:


@Overrided
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);

 // set boolean flags so we know the devices configuration
 isTablet = getResources().getBoolean(R.bool.isTablet);
 isPortrait = getResources().getBoolean(R.bool.isPortrait);

 if(savedInstanceState==null) {
 // we are launching this activity for the first time
 // so add content to screen
 if(isTablet) {
 // add the inbox fragment and email fragment
 InboxFragment inboxFragment = new InboxFragment();
 FragmentTransaction leftFragmentTransaction = getChildFragmentManager().beginTransaction();
 leftFragmentTransaction.replace(R.id.leftFragmentContainer, inboxFragment).commit();

 EmailFragment emailFragment = new EmailFragment();
 FragmentTransaction rightFragmentTransaction = getChildFragmentManager().beginTransaction();
 rightFragmentTransaction.replace(R.id.rightFragmentContainer, emailFragment).commit();

 } else {
 // add the inbox fragment
 InboxFragment inboxFragment = new InboxFragment();
 FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
 fragmentTransaction.replace(R.id.fragmentContainer, inboxFragment).commit();
 }
 }
}

Using the code above will work fine to add the correct fragments to your layout. However, it may not be the most scalable solution if you have an activity that may host several fragments. It will quickly become tedious adding this code for every scenario that a new fragment is added to the screen. Instead, you should delegate this to a method that you can call anywhere.

Put this method in the host activity that displays the layout files we created earlier.


public void showFragment(Fragment fragment, boolean primary, boolean addToBackStack, String backStackTag) {
 if(fragment == null) {
 return;
 }

 FragmentTransaction lft = getSupportFragmentManager().beginTransaction();
 lft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
 if (isTablet) {
 if (primary) {
 if (addToBackStack) {
 lft.replace(R.id.leftFragmentContainer, fragment).addToBackStack(backStackTag).commit();
 } else {
 lft.replace(R.id.leftFragmentContainer, fragment).commit();
 }
 } else {
 if (addToBackStack) {
 lft.replace(R.id.rightFragmentContainer, fragment).addToBackStack(backStackTag).commit();
 } else {
 lft.replace(R.id.rightFragmentContainer, fragment).commit();
 }
 }
 } else {
 if (addToBackStack) {
 lft.replace(R.id.fragmentContainer, fragment).addToBackStack(backStackTag).commit();
 } else {
 lft.replace(R.id.fragmentContainer, fragment).commit();
 }
 }
}

Now when you add a fragment to the screen just instantiate the fragment and set the remaining parameters according to how you plan manage it. The boolean parameter primary determines whether fragment is to be added to the right or left-pane. When true this fragment is added to the left containter of a multi-pane screen (for tablets). Otherwise it will be added to the right-pane with the id R.id.rightFragmentContainer. If the running device is a phone then we're dealing with a single-pane screen and this parameter is ignored. In this case, the fragment will replace the existing fragment on the screen. The boolean parameter addToBackStack is to be set to true if you want to add this transaction to the backstack. The last parameter backStackTag is the optional tag name for the fragment you're adding.

Now your onCreate() method got a lot simpler.


@Override
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 isTablet = getResources().getBoolean(R.bool.isTablet);
 isPortrait = getResources().getBoolean(R.bool.isPortrait);

 if(savedInstanceState==null) {
 if(isTablet){
 showFragment(new InboxFragment(), true, true, InboxFragment.class.getSimpleName());
 showFragment(new EmailFragment(), false, true, EmailFragment.class.getSimpleName());
 } else {
 showFragment(new InboxFragment(), true, true, InboxFragment.class.getSimpleName());
 } 
 }
}

Inter-Fragment Communication

In order to handle UI events appropriately for phone and tablet, you're going to need a way to facilitate how information is passed between fragments.

You will need to define a callback interface in each fragment that can be used to forward events to the host activity. The goal is to avoid direct fragment-to-fragment communication and allow our hosting Activity to facilitate all communication channels between fragments. Doing so makes your fragments more reusable because they are self-contained.

For instance, in the Gmail app when the user clicks an email in their inbox, you'd want to forward that click event up to the hosting activity. Your host activity would then handle updating any content on the screen. If the device is a tablet then content would be added to the right pane. If it is a phone then you might add a new fragment to the screen. Defining this interface may look like the following:


public class InboxFragment extends Fragment{
 ...
 OnListItemSelectedListener listItemSelectedCallback;

 // Host Activity must implement this interface
 public interface OnListItemSelectedListener {
 public void onListItemSelected(int position);
 }

 @Override
 public void onAttach(Context context) {
 super.onAttach(context);
 // This makes sure that the host activity has implemented
 // the callback interface. If not, it throws an exception
 try {
 listItemSelectedCallback = (OnListItemSelectedListener) context;
 } catch (ClassCastException e) {
 throw new ClassCastException(context.toString()
 + " must implement OnListItemSelectedListener");
 }
 }
}

Now when the user clicks an email from the users inbox, we would call this method in the OnClickListener of the email list. It would look something like this:


public class InboxFragment extends Fragment {
 ...
 @Override
 public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
 OnListItemSelectedListener.onListItemSelected(i);
 }
}

Defining this interface will allow you to reuse InboxFragment anywhere as long as the fragments hosting activity implements OnListItemSelectedListener.

When onListItemSelected is called you can then display the correct content.


public class HostActivity extends AppCompatActivity {
 ...
public void onListItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
 if (isTablet) {
 showFragment(EmailFragment.newInstance(i), false, true, EmailFragment.class.getSimpleName());
 } else {
 showFragment(EmailFragment.newInstance(i), true, true, EmailFragment.class.getSimpleName());
 }
}

The App Bar

Regardless of whether your app supports both phones and tablets, it is generally accepted as good practice to provide an app bar. We are going to use Toolbar as our app bar implementation.

Adding Your Toolbar

Make your application theme extend one of appcompat's NoActionBar themes. This will ensure the system doesn't include an ActionBar as part of the app's Theme.


Create a toolbar layout to include in the activity's layout


<?xml version="1.0" encoding="utf-8"?>

Position your toolbar at the top of layout/content_view.xml


In your activity's onCreate() define a reference to your toolbar and set it as the activity's action bar


Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(myToolbar);

Split Toolbar for Tablets

In most cases you'll want a toolbar dedicated to each pane when your device is displaying a multi-pane view. This will be useful if you have action items that are pertinent to a particular fragment (or pane). The Gmail screen shot below illustrates an example of this. The search icon below is specific to the left pane where the user's inbox is displayed. Therefore, the icon is deliberately placed in alignment with the edge of the left pane. Doing so makes it clear the search is applicable to content in the inbox.

To support two toolbars, you simply include a second Toolbar in your layout. In your layout-sw600dp/content_view.xml add the following code instead of . However, keep the single include tag in your layout/content_view.xml.


In the activity you'll set up your primary toolbar the same as you did in the previous section. However, you need make a reference to secondaryToolbar if the running device is a tablet.


secondaryToolbar = (Toolbar) findViewById(R.id.secondaryToolbar);

Adding Action Items with a Split Toolbar

Managing your action items will have to be handled differently when implementing a split toolbar. Moreover, if you add your action items the same way one would using one toolbar, your app bar will look like the following.

This is because we set R.id.toolbar as the activity's toolbar when we called setSupportActionBar(myToolbar). Needless to say, this app bar would still work, though aligning your action icons with their corresponding fragments will be more intuitive to users; it's a visual cue the action item behaves according to content in the pane below it. So in order for your action items to look like the screen shot below, you'll have to do the following.

Add your action icons to the secondary toolbar for action items relevant only to the right pane fragment.


@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 if (isTablet) {
 ((MainActivity) getActivity()).getSecondaryToolbar().getMenu().clear();
 ((MainActivity) getActivity()).getSecondaryToolbar().inflateMenu(R.menu.edit_menu);
 ((MainActivity) getActivity()).getSecondaryToolbar().setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
 @Override
 public boolean onMenuItemClick(MenuItem item) {
 return onOptionsItemSelected(item);
 }
 });
 } else {
 inflater.inflate(R.menu.delete_menu, menu);
 }
}

When the running device is a tablet, your action items will be added to the secondary toolbar sitting above the right pane. In doing so, you must also uniquely handling action item events by setting an OnMenuItemClickListener and forwarding these click events for your onOptionItemSelected() callback.

The lifecycle of your toolbar is connected to your host activity's lifecycle, so remember to clear the toolbar when your fragment is destroyed-or else your action icons will stick around after removing your fragment.


@Override
public void onDestroyView() {
 super.onDestroyView();
 Toolbar toolbar;
 if (isTablet) {
 toolbar = ((BaseDrawerActivity) getActivity()).getSecondaryToolbar();
 } else {
 toolbar = ((BaseDrawerActivity) getActivity()).getToolbar();
 }
 if (toolbar != null) {
 toolbar.getMenu().clear();
 }
}

Adding Up Nav

Since you're already using ActionBarDrawerToggle it will be crucial to make sure your up nav icon is displayed in accordance with your navigation hierarchy. Remember, by default, the ActionBarDrawerToggle will display the up nav icon after a drawer item is clicked. This may not coincide with the design of your app.

The host activity is simply swaping fragments in and out when the user nagivates between the two screens above. On tablets, this will all be displayed using a multi-pane layout so your up navigation is not necessary. For phones, though, you will need to add the up nav icon when the user is viewing an email.

The following method should be placed in the host activity. This will allow you display the up nav icon whenever you feel it is appropriate.


public void showUpNav() {
 if (!isTable) {
 getDrawerToggle().setDrawerIndicatorEnabled(false);
 getToolbar().setNavigationIcon(getResources().getDrawable(R.drawable.ic_action_back));
 getDrawerToggle().setToolbarNavigationClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 getFragmentManager.popBackstackImmediate();
 }
 });
 }
}

Now you can turn on up nav in the onListItemSelected when an email list item clicked.


public class HostActivity extends AppCompatActivity {
 ...
 public void onListItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
 if (isTablet) {
 showFragment(EmailFragment.newInstance(i), false, true, EmailFragment.class.getSimpleName());
 } else {
 // add up nav 
 showUpNav();
 showFragment(EmailFragment.newInstance(i), false, true, EmailFragment.class.getSimpleName());
 }
}

You can additionally remove the up nav and revert back to the homeburger icon when necessary.


public void setUpNavOff() {
 if (!isTable) {
 drawerToggle.setDrawerIndicatorEnabled(true);
 }
}

Conclusion

Offering a unique experience on phones and tablets is necessary for a superior user-experience. Unfortunately, there is no cookie-cutter solution for doing this. Your implementation will be contingent on the design of your app. The topics covered in this blog post were meant to identify core areas you need to address when supporting phones and tablets.