$30 off During Our Annual Pro Sale. View Details »

This Way Up: Implementing Effective Navigation on Android

This Way Up: Implementing Effective Navigation on Android

Presented at Droidcon London 2013

Jeff Gilfelt

October 25, 2013
Tweet

More Decks by Jeff Gilfelt

Other Decks in Technology

Transcript

  1. This Way Up Implementing Effective Navigation https://github.com/jgilfelt/ThisWayUp

  2. Effective Navigation • We always know where we are and

    how to get where we want to go. • There is structure based on the relationships of information. • Navigation mechanisms are predictable, reliable and consistent.
  3. Consistent Navigation • Eliminates the learning process. • Gives users

    a sense of mastery. • Makes the device (and Android itself) feel like a cohesive experience that people want to continue to use.
  4. Information Architecture

  5. Information Architecture

  6. Navigation Design Resources Structure in Android App Design (I/O 2013)

    http://www.youtube.com/watch?v=XpqyiBR0lJ4 Designing Effective Navigation http://developer.android.com/training/design-navigation/ index.html
  7. System Navigation

  8. Tasks • A task is a collection of activities that

    users interact with when performing a certain job. • Activities are arranged in a stack (the "back stack") in the order in which they are launched. • A task is not a process. • A task is not an application.
  9. Simple Task

  10. Simple Task

  11. Simple Task

  12. Simple Task

  13. Simple Task

  14. Complex Task (Multiple Applications)

  15. Complex Task (Multiple Applications)

  16. Complex Task (Multiple Applications)

  17. Complex Task (Multiple Applications)

  18. Complex Task (Multiple Applications)

  19. Back Button • The current activity is popped from the

    top of the stack (the activity is finish()ed). • The previous activity in the stack is brought forward (the state of its UI is restored). • Some exceptions: • Dismiss the soft keyboard if visible. • Dismiss any dialogs or floating windows. • Exit any contextual action mode. • Pop any local FragmentManager back stack.
  20. Back Button

  21. Back Button

  22. Back Button

  23. Back Button

  24. Back Button

  25. Back Button

  26. Home Button • Puts the current task in the background

    and takes the user directly to the home screen. • Task Affinity allows the home screen to act as a task switcher as well as a launcher. • If a background task with the same affinity as the launch Activity already exists, it is simply brought to the foreground.
  27. Home Button

  28. Home Button

  29. Home Button

  30. Home Button

  31. Honeycomb and Beyond

  32. Recents Button • An efficient way of switching between recently

    used applications. • Provides a clear navigation path between multiple ongoing tasks.
  33. Recents UI • Evolution of the home button long-press UI

    present in legacy Android versions. • Unfortunately a dedicated button and the UI itself is still optional for OEMs building new Android devices.
  34. Action Bar • Introduced standardised navigation modes for view switching

    within an activity. • Introduced a new in-app navigation mechanism based on the hierarchical relationships between screens: Up.
  35. Up Button • Provides consistent structural navigation as opposed to

    the temporal navigation provided by the system Back button. • Ensures a user always remains within your app. • Should never be present in the topmost (root) activity of your application.
  36. Up vs Back - Peer Activities with Back

  37. Up vs Back - Peer Activities with Back

  38. Up vs Back - Peer Activities with Back

  39. Up vs Back - Peer Activities with Back

  40. Up vs Back - Peer Activities with Back

  41. Up vs Back - Peer Activities with Up

  42. Up vs Back - Peer Activities with Up

  43. Up vs Back - Peer Activities with Up

  44. Up - Creating a New Task

  45. Up - Creating a New Task

  46. Up - Creating a New Task

  47. Up - Creating a New Task

  48. Up - Creating a New Task

  49. Up - Creating a New Task

  50. Up - Creating a New Task

  51. Implementing Up

  52. Implementing Up getActionBar().setDisplayHomeAsUpEnabled(true); ... @Override public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) { case android.R.id.home: // ... } return super.onOptionsItemSelected(item); }
  53. Implementing Up - The Wrong Way getActionBar().setDisplayHomeAsUpEnabled(true); ... @Override public

    boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // fuck it finish(); } return super.onOptionsItemSelected(item); }
  54. What’s Wrong? • We violate the model of structural navigation.

    • Simply finishing the activity could take us anywhere - it might even take us out of the app! • Android devices provide a Back button for this type of navigation, so your app should not add a Back button to the UI.
  55. Activity Intent Flags FLAG_ACTIVITY_BROUGHT_TO_FRONT FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS FLAG_ACTIVITY_FORWARD_RESULT FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

    FLAG_ACTIVITY_MULTIPLE_TASK FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NO_ANIMATION FLAG_ACTIVITY_NO_HISTORY FLAG_ACTIVITY_NO_USER_ACTION FLAG_ACTIVITY_PREVIOUS_IS_TOP FLAG_ACTIVITY_REORDER_TO_FRONT FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_ACTIVITY_SINGLE_TOP FLAG_ACTIVITY_TASK_ON_HOME Intent intent = new Intent(this, MyActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent);
  56. Intent.FLAG_ACTIVITY_ CLUSTERFUCK

  57. Intent Flags - Why They Suck • It’s not really

    an API. • Some flags only work in exact combinations. • Many flags are not relevant for most 3rd party apps (unless you’re building a launcher replacement). • Overlap/conflict with activity launchMode. • Confusing documentation. • Implementation can become a process of trial and error.
  58. None
  59. Navigation API

  60. Navigation API • Native Up navigation for Jelly Bean (API

    16) and above. • Based on hierarchical metadata specified for each <activity> in your manifest. • The support library provides equivalent functionality for earlier Android versions via NavUtils. • TaskStackBuilder offers additional utilities for cross-task navigation.
  61. Specifying a Parent Activity (API 16+) Use android:parentActivityName attribute: <application

    ... > ... <!-- The main/home activity (it has no parent activity) --> <activity android:name="com.example.myfirstapp.MainActivity" ...> ... </activity> <!-- A child of the main activity --> <activity android:name="com.example.myfirstapp.DisplayMessageActivity" android:label="@string/title_activity_display_message" android:parentActivityName="com.example.myfirstapp.MainActivity" > </activity> </application>
  62. Specifying a Parent Activity (Support Library) Add the <meta-data> element:

    <application ... > ... <!-- The main/home activity (it has no parent activity) --> <activity android:name="com.example.myfirstapp.MainActivity" ...> ... </activity> <!-- A child of the main activity --> <activity android:name="com.example.myfirstapp.DisplayMessageActivity" android:label="@string/title_activity_display_message" android:parentActivityName="com.example.myfirstapp.MainActivity" > <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.example.myfirstapp.MainActivity" /> </activity> </application>
  63. Implementing Up getActionBar().setDisplayHomeAsUpEnabled(true); • No additional code is required for

    API 16+. • A small amount of boilerplate code is required in onOptionsItemSelected(…) if you are supporting earlier Android versions...
  64. Implementing Up (Support Library) If we know we can never

    launch the Activity from a different task (i.e. deep link), we can trivially call: @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask(this); return true; } return super.onOptionsItemSelected(item); }
  65. Implementing Up (Support Library) • If an Activity provides an

    intent filter that allows other apps to launch it, we need to check for this condition and start a new task (with appropriate back history). • Use NavUtils.shouldUpRecreateTask(…) to check if we need a new task. • Use TaskStackBuilder to create a new task.
  66. Implementing Up (Support Library) @Override public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) { case android.R.id.home: Intent upIntent = NavUtils.getParentActivityIntent(this); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // This activity is NOT part of this app's task, so create a new task // when navigating up, with a synthesized back stack. TaskStackBuilder.create(this) // Add all of this activity's parents to the back stack .addNextIntentWithParentStack(upIntent) // Navigate up to the closest parent .startActivities(); } else { // This activity is part of this app's task, so simply // navigate up to the logical parent activity. NavUtils.navigateUpTo(this, upIntent); } return true; } return super.onOptionsItemSelected(item); }
  67. Supplying Intent Extras (API 16+) If a parent activity expects

    extras to be supplied, override getParentActivityIntent(): @Override public Intent getParentActivityIntent() {! ! Intent parent = super.getParentActivityIntent(); parent.putExtra(CategoryActivity.ARG_CATEGORY, "books"); return parent; } Or override onPrepareNavigateUpTaskStack(…) for complete control of the task stack.
  68. Supplying Intent Extras (Support Library) For Up within the same

    task: Intent upIntent = NavUtils.getParentActivityIntent(this); upIntent.putExtra(CategoryActivity.ARG_CATEGORY, "books"); NavUtils.navigateUpTo(this, upIntent); For a new task stack, use editIntentAt(…): TaskStackBuilder tsb = TaskStackBuilder.create(this) .addParentStack(this); // Add the required Intent extras as appropriate tsb.editIntentAt(tsb.getIntentCount() - 1) .putExtra(CategoryActivity.ARG_CATEGORY, "books"); // Navigate up to the closest parent tsb.startActivities();
  69. ActionBarCompat • Provides the same native Up functionality as Jelly

    Bean. • No onOptionsItemSelected(…) boilerplate code is required. Activity (API 16+): getParentActivityIntent() onCreateNavigateUpTaskStack(...) onPrepareNavigateUpTaskStack(...) onNavigateUp() navigateUpTo(...) shouldUpRecreateTask(...) ActionBarActivity (ActionBarCompat): getSupportParentActivityIntent() onCreateSupportNavigateUpTaskStack(...) onPrepareSupportNavigateUpTaskStack(...) onSupportNavigateUp() supportNavigateUpTo(...) supportShouldUpRecreateTask(...)
  70. Implementing Back

  71. Implementing Back • Do nothing - the system handles Back

    for us. • Avoid overriding onBackPressed() unless you have some custom UI state to dismiss - always call the super implementation! • However, we need to ensure we manage the task state and create a “synthetic” back stack in a couple of cases...
  72. Changes Since Honeycomb (API 11) • The back button's behavior

    is local to the current task and should not capture navigation across different tasks. • Tasks are rooted at the home screen (launcher).
  73. The Notification Scenario

  74. The Notification Scenario

  75. The Notification Scenario

  76. The Notification Scenario Any previous task that has affinity with

    Gmail is replaced entirely
  77. The Notification Scenario

  78. The Notification Scenario

  79. The Notification Scenario • We can’t just push the Gmail

    message activity on to the current Play Store task - it makes no sense there! • Blindly adding it to any existing task for Gmail will result in an unpredictable task state. • Replacing the task stack entirely makes Back more predictable for users. • The Recents button is the modern mechanism for switching context back to a prior task.
  80. So Why Fake History?

  81. So Why Fake History?

  82. So Why Fake History?

  83. Hours or days later... So Why Fake History?

  84. Hours or days later... So Why Fake History?

  85. Hours or days later... So Why Fake History?

  86. Hours or days later... WTF? So Why Fake History?

  87. Deep Link From a Notification or Widget Notification.Builder builder =

    new Notification.Builder(this) .setContentTitle("Direct Notification") .setContentText("This will 'deep link' into a content activity") .setAutoCancel(true) // Construct a new task stack and supply the pending intent .setContentIntent(TaskStackBuilder.create(this) .addParentStack(ContentViewActivity.class) .addNextIntent(new Intent(this, ContentViewActivity.class) .putExtra(ContentViewActivity.EXTRA_TEXT, "Hello world!")) .getPendingIntent(REQUEST_CODE, PendingIntent.FLAG_CANCEL_CURRENT)); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.notify("direct_tag", R.id.direct_notification, builder.build()); Use the same pattern to for a home screen widget to supply RemoteViews.setOnClickPendingIntent(…)
  88. Navigation Drawer

  89. Navigation Drawer • Recently standardised in the framework Support Library

    and design guidelines. • Unlike top-level action bar navigation modes it can be accessed from anywhere in your app. • Allows for cross-navigation from lower levels when your app has deep navigation branches.
  90. Navigation Drawer - Root Level • The drawer indicator should

    appear alongside the icon or logo in the action bar - tapping it will open or close the drawer. • Navigation targets at the top level should work the same as with tab and list navigation modes - a view switch should occur (typically implemented using Fragments) with no back history created.
  91. Navigation Drawer - To/From Lower Levels • For activities that

    don’t have a corresponding entry in the navigation drawer, Up navigation applies as normal. The drawer should be accessible using the bezel swipe gesture only. • Navigation from the drawer should perform a targeted or selective Up and recreate the task: TaskStackBuilder.create(this) .addParentStack(MainActivity.class) .addNextIntent(new Intent(this, MainActivity.class) .putExtra(MainActivity.NAV_POSITION, position)) .startActivities();
  92. External Activities

  93. The “Unexpected App” Scenario

  94. The “Unexpected App” Scenario

  95. The “Unexpected App” Scenario

  96. The “Unexpected App” Scenario

  97. The “Unexpected App” Scenario

  98. Hours or days later... The “Unexpected App” Scenario

  99. Hours or days later... WTF? The “Unexpected App” Scenario

  100. Launching External Activities FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET should be used when launching an

    activity that represents a logical break in your application flow: Intent externalActivityIntent = new Intent(Intent.ACTION_PICK); externalActivityIntent.setType("image/*"); externalActivityIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(externalActivityIntent); Already done for us in ShareCompat: ShareCompat.IntentBuilder.from(this) .setType("text/plain") .setText("I'm sharing!") .startChooser();
  101. Summary

  102. Summary • Embrace modern navigation conventions - don’t just treat

    your task as an infinite stack of unordered activities. • Include parent activity metadata in your manifest and use the framework navigation APIs provided. • TaskStackBuilder is crucial.
  103. Summary • Don’t test in isolation and consider all navigation

    paths in to and out of your app. • The navigation design guidelines are not rules, but you should fully understand them and have good reasons to not adhere to them.
  104. finish(); https://github.com/jgilfelt/ThisWayUp