Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

Information Architecture

Slide 5

Slide 5 text

Information Architecture

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

System Navigation

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

Simple Task

Slide 10

Slide 10 text

Simple Task

Slide 11

Slide 11 text

Simple Task

Slide 12

Slide 12 text

Simple Task

Slide 13

Slide 13 text

Simple Task

Slide 14

Slide 14 text

Complex Task (Multiple Applications)

Slide 15

Slide 15 text

Complex Task (Multiple Applications)

Slide 16

Slide 16 text

Complex Task (Multiple Applications)

Slide 17

Slide 17 text

Complex Task (Multiple Applications)

Slide 18

Slide 18 text

Complex Task (Multiple Applications)

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

Back Button

Slide 21

Slide 21 text

Back Button

Slide 22

Slide 22 text

Back Button

Slide 23

Slide 23 text

Back Button

Slide 24

Slide 24 text

Back Button

Slide 25

Slide 25 text

Back Button

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

Home Button

Slide 28

Slide 28 text

Home Button

Slide 29

Slide 29 text

Home Button

Slide 30

Slide 30 text

Home Button

Slide 31

Slide 31 text

Honeycomb and Beyond

Slide 32

Slide 32 text

Recents Button • An efficient way of switching between recently used applications. • Provides a clear navigation path between multiple ongoing tasks.

Slide 33

Slide 33 text

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.

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

Up vs Back - Peer Activities with Back

Slide 37

Slide 37 text

Up vs Back - Peer Activities with Back

Slide 38

Slide 38 text

Up vs Back - Peer Activities with Back

Slide 39

Slide 39 text

Up vs Back - Peer Activities with Back

Slide 40

Slide 40 text

Up vs Back - Peer Activities with Back

Slide 41

Slide 41 text

Up vs Back - Peer Activities with Up

Slide 42

Slide 42 text

Up vs Back - Peer Activities with Up

Slide 43

Slide 43 text

Up vs Back - Peer Activities with Up

Slide 44

Slide 44 text

Up - Creating a New Task

Slide 45

Slide 45 text

Up - Creating a New Task

Slide 46

Slide 46 text

Up - Creating a New Task

Slide 47

Slide 47 text

Up - Creating a New Task

Slide 48

Slide 48 text

Up - Creating a New Task

Slide 49

Slide 49 text

Up - Creating a New Task

Slide 50

Slide 50 text

Up - Creating a New Task

Slide 51

Slide 51 text

Implementing Up

Slide 52

Slide 52 text

Implementing Up getActionBar().setDisplayHomeAsUpEnabled(true); ... @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // ... } return super.onOptionsItemSelected(item); }

Slide 53

Slide 53 text

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); }

Slide 54

Slide 54 text

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.

Slide 55

Slide 55 text

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);

Slide 56

Slide 56 text

Intent.FLAG_ACTIVITY_ CLUSTERFUCK

Slide 57

Slide 57 text

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.

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

Navigation API

Slide 60

Slide 60 text

Navigation API • Native Up navigation for Jelly Bean (API 16) and above. • Based on hierarchical metadata specified for each in your manifest. • The support library provides equivalent functionality for earlier Android versions via NavUtils. • TaskStackBuilder offers additional utilities for cross-task navigation.

Slide 61

Slide 61 text

Specifying a Parent Activity (API 16+) Use android:parentActivityName attribute: ... ...

Slide 62

Slide 62 text

Specifying a Parent Activity (Support Library) Add the element: ... ...

Slide 63

Slide 63 text

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...

Slide 64

Slide 64 text

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); }

Slide 65

Slide 65 text

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.

Slide 66

Slide 66 text

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); }

Slide 67

Slide 67 text

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.

Slide 68

Slide 68 text

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();

Slide 69

Slide 69 text

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(...)

Slide 70

Slide 70 text

Implementing Back

Slide 71

Slide 71 text

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...

Slide 72

Slide 72 text

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).

Slide 73

Slide 73 text

The Notification Scenario

Slide 74

Slide 74 text

The Notification Scenario

Slide 75

Slide 75 text

The Notification Scenario

Slide 76

Slide 76 text

The Notification Scenario Any previous task that has affinity with Gmail is replaced entirely

Slide 77

Slide 77 text

The Notification Scenario

Slide 78

Slide 78 text

The Notification Scenario

Slide 79

Slide 79 text

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.

Slide 80

Slide 80 text

So Why Fake History?

Slide 81

Slide 81 text

So Why Fake History?

Slide 82

Slide 82 text

So Why Fake History?

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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(…)

Slide 88

Slide 88 text

Navigation Drawer

Slide 89

Slide 89 text

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.

Slide 90

Slide 90 text

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.

Slide 91

Slide 91 text

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();

Slide 92

Slide 92 text

External Activities

Slide 93

Slide 93 text

The “Unexpected App” Scenario

Slide 94

Slide 94 text

The “Unexpected App” Scenario

Slide 95

Slide 95 text

The “Unexpected App” Scenario

Slide 96

Slide 96 text

The “Unexpected App” Scenario

Slide 97

Slide 97 text

The “Unexpected App” Scenario

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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();

Slide 101

Slide 101 text

Summary

Slide 102

Slide 102 text

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.

Slide 103

Slide 103 text

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.

Slide 104

Slide 104 text

finish(); https://github.com/jgilfelt/ThisWayUp