Slide 1

Slide 1 text

Android TV: Building apps with Google’s Leanback Library

Slide 2

Slide 2 text

Joe Birch Android Engineer @Buffer @hitherejoe / hitherejoe.com

Slide 3

Slide 3 text

What is Android TV?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Build on Material

Slide 6

Slide 6 text

Build on Material Casual Consumption

Slide 7

Slide 7 text

Build on Material Casual Consumption Cinematic Experience

Slide 8

Slide 8 text

Build on Material Casual Consumption Cinematic Experience Simplicity

Slide 9

Slide 9 text

Navigation Getting around

Slide 10

Slide 10 text

D-Pad controls

Slide 11

Slide 11 text

Focus based Navigation

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Setting up Getting your project ready

Slide 14

Slide 14 text

Slide 15

Slide 15 text

github.com/hitherejoe/vineyard github.com/hitherejoe/bourbon

Slide 16

Slide 16 text

BrowseFragment Display browsable content to the user

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

setBrandColor(ContextCompat.getColor(this, R.color.fastlane_background));

Slide 21

Slide 21 text

Color color = ContextCompat.getColor(this, R.color.accent); setSearchAffordanceColor(color);

Slide 22

Slide 22 text

Drawable badge = ContextCompat.getDrawable( this, R.drawable.banner_shadow); setBadgeDrawable(badge);

Slide 23

Slide 23 text

setHeadersState(HEADERS_ENABLED);

Slide 24

Slide 24 text

setHeadersState(HEADERS_HIDDEN);

Slide 25

Slide 25 text

setHeadersState(HEADERS_DISABLED);

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Browse Fragment Header Item Presenter Header Item List Row Array Object Adapter Post Adapter

Slide 29

Slide 29 text

Browse Fragment Header Item Presenter Header Item List Row Array Object Adapter Post Adapter

Slide 30

Slide 30 text

public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }

Slide 31

Slide 31 text

public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }

Slide 32

Slide 32 text

Slide 33

Slide 33 text

public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }

Slide 34

Slide 34 text

@Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { HeaderItem headerItem = ((ListRow) o).getHeaderItem(); setIconDrawable(headerItem.getName(), viewholder.iconImage); TextView label = viewHolder.headerText; label.setText(headerItem.getName()); }

Slide 35

Slide 35 text

public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // release bitmaps if used } }

Slide 36

Slide 36 text

setHeaderPresenterSelector(new PresenterSelector() { @Override public Presenter getPresenter(Object o) { return new IconHeaderItemPresenter(); } });

Slide 37

Slide 37 text

Browse Fragment Header Item Presenter Header Item List Row Array Object Adapter Array Object Adapter

Slide 38

Slide 38 text

Header Item List Row Array Object Adapter

Slide 39

Slide 39 text

ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(this); rowAdapter.add(…); HeaderItem header = new HeaderItem(headerPosition, tag); mRowsAdapter.add(new ListRow(header, rowAdapter));

Slide 40

Slide 40 text

Browse Fragment Array Object Adapter

Slide 41

Slide 41 text

setOnItemViewClickedListener(mOnItemViewClickedListener); setOnItemViewSelectedListener(mOnItemViewSelectedListener);

Slide 42

Slide 42 text

@Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { // Do stuff with clicked item object }

Slide 43

Slide 43 text

@Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { // Do stuff with selected item object }

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());

Slide 47

Slide 47 text

BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity()); backgroundManager.attach(getActivity().getWindow());

Slide 48

Slide 48 text

BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity()); backgroundManager.attach(getActivity().getWindow()); backgroundManager.setBitmap(resource);

Slide 49

Slide 49 text

BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity()); backgroundManager.attach(getActivity().getWindow()); backgroundManager.setBitmap(resource); // Don’t forget to release!! backgroundManager.release();

Slide 50

Slide 50 text

SearchFragment Allow users to search for content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

@Override public boolean onQueryTextChange(String newQuery) @Override public boolean onQueryTextSubmit(String query)

Slide 53

Slide 53 text

Post Results (Array Object Adapter) Search Results (Array Object Adapter) Row Adapter (Array Object Adapter) Focused item triggers Post search

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

VerticalGridFragment Display a grid of browsable content to the user

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

VerticalGridPresenter gridPresenter = new VerticalGridPresenter(); gridPresenter.setNumberOfColumns(NUM_COLUMNS); setGridPresenter(gridPresenter);

Slide 58

Slide 58 text

PlaybackActivity Display media content on screen

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

mSession = new MediaSession(this, getString(R.string.app_name); mSession.setCallback(new MediaSessionCallback()); mSession.setActive(true); setMediaController(new MediaController(this, mSession.getSessionToken());

Slide 61

Slide 61 text

PlaybackOverlayFragment Display playback controls to the user

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

mMediaController = getActivity().getMediaController(); mMediaController.registerCallback(mMediaControllerCallback); private class MediaControllerCallback extends MediaController.Callback { @Override public void onPlaybackStateChanged(@NonNull PlaybackState state) { } @Override public void onMetadataChanged(@NonNull MediaMetadata metadata) { } }

Slide 65

Slide 65 text

ArrayObjectAdapter (Row Adapter) ArrayObjectAdapter (Related Posts) ArrayObjectAdapter (Primary Actions) ArrayObjectAdapter (Secondary Actions) PlayBackControlsRow Meta Data

Slide 66

Slide 66 text

ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector(); mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); mPlaybackControlsRow .setPrimaryActionsAdapter(mPrimaryActionsAdapter); mPlaybackControlsRow .setSecondaryActionsAdapter(mPrimaryActionsAdapter);

Slide 67

Slide 67 text

public class Action { private Drawable mIcons; private CharSequence mLabel1; private CharSequence mLabel2; private ArrayList mKeyCodes; … }

Slide 68

Slide 68 text

mPlayPauseAction = new PlayPauseAction(getActivity()); mRepeatAction = new RepeatAction(getActivity()); mSkipNextAction = new SkipNextAction(getActivity()); mSkipPreviousAction = new SkipPreviousAction(getActivity()); mPrimaryActionsAdapter.add(mPlayPauseAction); mPrimaryActionsAdapter.add(mSkipNextAction); mPrimaryActionsAdapter.add(mSkipPreviousAction); mSecondaryActionsAdapter.add(mRepeatAction);

Slide 69

Slide 69 text

playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() { public void onActionClicked(Action action) { if (action.getId() == mPlayPauseAction.getId()) { togglePlayback(mPlayPauseAction.getIndex() == PlayPauseAction.PLAY); } else if (action.getId() == mSkipNextAction.getId()) { next(true); } else if (action.getId() == mSkipPreviousAction.getId()) { prev(true); } else if (action.getId() == mRepeatAction.getId()) { loopVideos(); } if (action instanceof PlaybackControlsRow.MultiAction) { notifyChanged(action); } } });

Slide 70

Slide 70 text

mMediaController.getTransportControls().play(); mMediaController.getTransportControls().pause; mMediaController.getTransportControls().skipToNext(); mMediaController.getTransportControls().skipToPrevious(); mMediaController.getTransportControls().fastForward; mMediaController.getTransportControls().rewind(); mMediaController.getTransportControls() .sendCustomAction(CUSTOM_ACTION_AUTO_LOOP, null);

Slide 71

Slide 71 text

Post item = (Post) mPlaybackControlsRow.getItem(); item.description = description; item.username = username; mPlaybackControlsRow.setTotalTime((int) duration); mPlaybackControlsRow.setImageDrawable(resource); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(bufferedT

Slide 72

Slide 72 text

Post item = (Post) mPlaybackControlsRow.getItem(); item.description = description; item.username = username; mPlaybackControlsRow.setTotalTime((int) duration); mPlaybackControlsRow.setImageDrawable(resource); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(bufferedTime);

Slide 73

Slide 73 text

ArrayObjectAdapter (Adapter Rows) ArrayObjectAdapter (Related Posts) ArrayObjectAdapter (Primary Actions) ArrayObjectAdapter (Secondary Actions) PlayBackControlsRow Meta Data

Slide 74

Slide 74 text

GuidedStepFragment Display a set of selectable options to the user

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

@Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getString(…); String description = getString(…); Drawable icon = getActivity().getDrawable(…); return new GuidanceStylist.Guidance( title, description, "", icon); }

Slide 78

Slide 78 text

@Override public void onCreateActions( @NonNull List actions, Bundle savedInstanceState) { GuidedAction guidedAction = new GuidedAction.Builder() .id(…) .title(…) .description(…) .checkSetId(OPTION_CHECK_SET_ID) .build(); guidedAction.setChecked(isChecked); actions.add(guidedAction); }

Slide 79

Slide 79 text

ErrorFragment Display an error message to the user (Because things don’t always go as planned)

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

ErrorFragment errorFragment = new ErrorFragment(); errorFragment.setTitle(…); errorFragment.setMessage(…); errorFragment.setButtonText(…); errorFragment.setButtonClickListener(…);

Slide 82

Slide 82 text

Custom Views Because your app doesn’t have to look boring

Slide 83

Slide 83 text

Tag Card Tag Card View Base Card View Text View Image View

Slide 84

Slide 84 text

Tag Card TagCardView cardView = new TagCardView(parent.getContext()); Tag post = (Tag) item; TagCardView cardView = (TagCardView) viewHolder.view; if (post.tag != null) { cardView.setCardText(post.tag); cardView.setCardIcon(R.drawable.ic_tag); }

Slide 85

Slide 85 text

Icon Card Icon Card View Base Card View Text View Image View Text View

Slide 86

Slide 86 text

Icon Card IconCardView cardView = new IconCardView(parent.getContext()); Option option = (Option) item; IconCardView cardView = (IconCardView) viewHolder.view; if (option.tag != null) { cardView.setCardIcon(R.drawable.ic_loop); cardView.setTitleText(option.title); cardView.setValueText(option.title); }

Slide 87

Slide 87 text

Loading Card Loading Card View Base Card View Progress Bar

Slide 88

Slide 88 text

Loading Card LoadingCardView cardView = new LoadingCardView(parent.getContext()); IconCardView cardView = (IconCardView) viewHolder.view; cardView.setIsLoading(true);

Slide 89

Slide 89 text

Live Card

Slide 90

Slide 90 text

Live Card Live Card View Base Card View Preview Card View Looping Video View Progress Bar Image View View (Transparent Overlay) Video View

Slide 91

Slide 91 text

Live Card LiveCardView cardView = new LiveCardView(parent.getContext()); Post post = (Post) item; LiveCardView cardView = (LiveCardView) viewHolder.view; if (post.videoUrl != null) { cardView.setTitleText(post.description); cardView.setContentText(post.username); cardView.setVideoUrl(post.videoUrl); Glide.with(cardView.getContext()) .load(post.thumbnailUrl) .centerCrop() .error(mDefaultCardImage) .into(cardView.getMainImageView()); }

Slide 92

Slide 92 text

Leanback Cards https://github.com/hitherejoe/LeanbackCards

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

Google Guidelines

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

Testing.

Slide 97

Slide 97 text

onView(withId(R.id.title_orb)) .perform(click()); onView(withId(R.id.browse_headers)) .perform(RecyclerViewActions .actionOnItemAtPosition(i, click())); onView(withItemText(post.description, R.id.browse_container_dock)) .perform(click()); Dig deep and remember, everything has IDs!

Slide 98

Slide 98 text

Android N(utella?)

Slide 99

Slide 99 text

Picture-in-Picture

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

Slide 102

Slide 102 text

@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPicture(); return; } }

Slide 103

Slide 103 text

@Override public void onPictureInPictureChanged(boolean inPictureInPicture) { if (inPictureInPicture) { // Hide the controls in picture-in-picture mode. } else { // Restore the playback UI based on the playback status. } }

Slide 104

Slide 104 text

@Override public void onPause() { if (mInPictureInPicture) { // Continue playback } // If paused but not in PIP, pause playback if necessary }

Slide 105

Slide 105 text

TV Recording

Slide 106

Slide 106 text

Sharing Code

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

github.com/hitherejoe/bourbon

Slide 112

Slide 112 text

What’s next?

Slide 113

Slide 113 text

The future of TV

Slide 114

Slide 114 text

No content

Slide 115

Slide 115 text

No content

Slide 116

Slide 116 text

No content

Slide 117

Slide 117 text

Resources Official Android TV Documentation github.com/hitherejoe/vineyard Google Plus Android TV Community github.com/hitherejoe/AndroidTvBoilerplate github.com/hitherejoe/leanbackcards medium.com/@hitherejoe