Slide 1

Slide 1 text

Conductor Architecture and Alternatives @chris_h_codes

Slide 2

Slide 2 text

Navigation is hard

Slide 3

Slide 3 text

Navigation is hard • Picking the difference between up and back

Slide 4

Slide 4 text

Navigation is hard • Picking the difference between up and back • Sometimes up is… not up?

Slide 5

Slide 5 text

Navigation is hard • Picking the difference between up and back • Sometimes up is… not up? • What happens when we deep link?

Slide 6

Slide 6 text

Navigation is hard • Picking the difference between up and back • Sometimes up is… not up? • What happens when we deep link? • Bottom navigation gets involved

Slide 7

Slide 7 text

Navigation is hard • Picking the difference between up and back • Sometimes up is… not up? • What happens when we deep link? • Bottom navigation gets involved

Slide 8

Slide 8 text

Google’s making it worse “The bottom navigation bar remains in view when navigating through an app’s hierarchy.” - Google’s new design guidelines

Slide 9

Slide 9 text

There’s a term for this

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Single Activity apps

Slide 12

Slide 12 text

Single Activity apps • Control over UI elements remaining on screen

Slide 13

Slide 13 text

Single Activity apps • Control over UI elements remaining on screen • Easier shared element transitions

Slide 14

Slide 14 text

Single Activity apps • Control over UI elements remaining on screen • Easier shared element transitions • Single and stable entry point for your UI

Slide 15

Slide 15 text

Single Activity apps • Control over UI elements remaining on screen • Easier shared element transitions • Single and stable entry point for your UI
 - Kind of like main() with lifecycle callbacks

Slide 16

Slide 16 text

Fragments…

Slide 17

Slide 17 text

Fragment transactions

Slide 18

Slide 18 text

Fragment transactions val newFragment = NextFragment() val transaction = getSupportFragmentManager().beginTransaction() transaction.replace(R.id.fragment_container, newFragment) transaction.addToBackStack(null) transaction.commit()

Slide 19

Slide 19 text

Fragment transactions val newFragment = NextFragment() val transaction = getSupportFragmentManager().beginTransaction() transaction.replace(R.id.fragment_container, newFragment) transaction.addToBackStack(null) transaction.commitAllowingStateLoss()

Slide 20

Slide 20 text

Fragment transactions val newFragment = NextFragment() val transaction = getSupportFragmentManager().beginTransaction() transaction.replace(R.id.fragment_container, newFragment) transaction.addToBackStack(null) transaction.commitNow()

Slide 21

Slide 21 text

Fragment transactions val newFragment = NextFragment() val transaction = getSupportFragmentManager().beginTransaction() transaction.replace(R.id.fragment_container, newFragment) transaction.addToBackStack(null) transaction.commitNowAllowingStateLoss()

Slide 22

Slide 22 text

Fragment lifecyle

Slide 23

Slide 23 text

Fragment lifecyle

Slide 24

Slide 24 text

Fragment lifecyle

Slide 25

Slide 25 text

Fragment identity crisis

Slide 26

Slide 26 text

Fragment identity crisis

Slide 27

Slide 27 text

Fragment identity crisis class MyFragment : Fragment() { }}

Slide 28

Slide 28 text

Fragment identity crisis class MyFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) }} }}

Slide 29

Slide 29 text

Fragment identity crisis class MyFragment : Fragment() { @Inject lateinit var ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Do some dependency injection... }} }}

Slide 30

Slide 30 text

Fragment identity crisis class MyFragment : Fragment() { @Inject lateinit var ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Do some dependency injection... }} override fun onStart() { super.onStart() // Use your injected dependencies to modify sub-views. }} }}

Slide 31

Slide 31 text

Applying your MV-Whatever

Slide 32

Slide 32 text

Applying your MV-Whatever • Maybe a Fragment is a presenter? A controller? A view?

Slide 33

Slide 33 text

Applying your MV-Whatever • Maybe a Fragment is a presenter? A controller? A view? • Whatever it is, it does a lot!

Slide 34

Slide 34 text

Applying your MV-Whatever • Maybe a Fragment is a presenter? A controller? A view? • Whatever it is, it does a lot! • Receive lifecycle events • Manage view hierarchies • Save instance state • Deal with non-configuration instance object passing • Handle a back stack

Slide 35

Slide 35 text

So what’s Conductor? github.com/bluelinelabs/Conductor

Slide 36

Slide 36 text

How Conductor works Fragment Controller

Slide 37

Slide 37 text

How Conductor works Fragment Controller “Think of it as a lighter-weight and more predictable Fragment alternative with an easier to manage lifecycle.” - The Conductor documentation

Slide 38

Slide 38 text

How Conductor works • Represents a screen; a node in your navigation graph • Must have a View; cannot be headless • Cannot be declared in XML • Survives orientation changes by default • Permits passing arguments in a constructor* abstract class Controller

Slide 39

Slide 39 text

Declaring a Controller class MyController : Controller() { }}

Slide 40

Slide 40 text

Declaring a Controller class MyController : Controller() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.my_layout, container, false) } }}

Slide 41

Slide 41 text

Declaring a Controller class MyController : Controller() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.my_layout, container, false) }} override fun onAttach(view: View) { … } override fun onDetach(view: View) { … } }}

Slide 42

Slide 42 text

Navigating Controllers

Slide 43

Slide 43 text

Navigating Controllers class MainActivity : AppCompatActivity() { }}

Slide 44

Slide 44 text

Navigating Controllers class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } }}

Slide 45

Slide 45 text

Navigating Controllers class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) }} }}

Slide 46

Slide 46 text

Navigating Controllers class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) val container: ViewGroup = findViewById(R.id.container) val router = Conductor.attachRouter(this, container, savedInstanceState) }} }}

Slide 47

Slide 47 text

Navigating Controllers class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) val container: ViewGroup = findViewById(R.id.container) val router = Conductor.attachRouter(this, container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(HomeController())) } }} }}

Slide 48

Slide 48 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { }} }}

Slide 49

Slide 49 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { view.events.ofType() }} }}

Slide 50

Slide 50 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { view.events.ofType().subscribe { }} }} }}

Slide 51

Slide 51 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { view.events.ofType().subscribe { val transaction = RouterTransaction.with(NextController()) }} }} }}

Slide 52

Slide 52 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { view.events.ofType().subscribe { val transaction = RouterTransaction.with(NextController()) router.pushController(transaction) }} }} }}

Slide 53

Slide 53 text

Navigating Controllers class MyController : Controller() { override fun onAttach(view: View) { view.events.ofType().subscribe { val transaction = RouterTransaction.with(NextController()) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) router.pushController(transaction) }} }} }}

Slide 54

Slide 54 text

Handling change

Slide 55

Slide 55 text

Handling change • Swaps the View of one Controller out for another • …or not! You’re in control • Your opportunity to provide custom animations / transitions • Comes in a variety of built in flavours abstract class ControllerChangeHandler

Slide 56

Slide 56 text

Handling change • Comes in a variety of built in flavours class FadeChangeHandler : AnimatorChangeHandler class HorizontalChangeHandler : AnimatorChangeHandler class VerticalChangeHandler : AnimatorChangeHandler

Slide 57

Slide 57 text

Handling change abstract class AnimatorChangeHandler

Slide 58

Slide 58 text

Handling change class FadeAndScaleChangeHandler : AnimatorChangeHandler() { }}

Slide 59

Slide 59 text

Handling change class FadeAndScaleChangeHandler : AnimatorChangeHandler() { override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator { } }}

Slide 60

Slide 60 text

Handling change class FadeAndScaleChangeHandler : AnimatorChangeHandler() { override fun getAnimator(container: ViewGroup, from: View, to: View, isPush: Boolean, toAddedToContainer: Boolean): Animator { val set = AnimatorSet() set.playTogether( ObjectAnimator.ofFloat(to, View.SCALE_X, 0.9f, 1f), ObjectAnimator.ofFloat(to, View.SCALE_Y, 0.9f, 1f), ObjectAnimator.ofFloat(to, View.ALPHA, 0f, 1f)) return set

Slide 61

Slide 61 text

set.playTogether( ObjectAnimator.ofFloat(to, View.SCALE_X, 0.9f, 1f), ObjectAnimator.ofFloat(to, View.SCALE_Y, 0.9f, 1f), ObjectAnimator.ofFloat(to, View.ALPHA, 0f, 1f)) return set Handling change

Slide 62

Slide 62 text

Handling change abstract class TransitionChangeHandler

Slide 63

Slide 63 text

Handling change class ListToDetailsChangeHandler : TransitionChangeHandler() { override fun getTransition(container: ViewGroup, from: View?, to: View?, isPush: Boolean): Transition { } }

Slide 64

Slide 64 text

Serious customisation override fun getTransition(container: ViewGroup, from: View?, to: View?, isPush: Boolean): Transition { return TransitionSet() // Slide the Alarm content on screen. .addTransition(Slide() .addTarget(to.container) .setInterpolator(LinearOutSlowInInterpolator())) // Slide bottom nav bar off screen. .addTransition(BottomViewSlide() .addTarget(from.bottomNav) .setDuration(270) .setInterpolator(sharpCurveInterpolator()))

Slide 65

Slide 65 text

Where do I bind my views?

Slide 66

Slide 66 text

Where do I bind my views? • A Controller is not the V in your MV-Whatever • A good place to initialise the wiring between model and view • Don’t make a Controller do too much

Slide 67

Slide 67 text

Fragments were never a good V in your MV-Whatever

Slide 68

Slide 68 text

Activity

Slide 69

Slide 69 text

Activity

Slide 70

Slide 70 text

Fragment A

Slide 71

Slide 71 text

Fragment A onCreate()

Slide 72

Slide 72 text

Fragment A View A onCreate() onCreateView()

Slide 73

Slide 73 text

Fragment A View A onCreate() onCreateView() onStart() onResume()

Slide 74

Slide 74 text

Fragment A View A onCreate() onCreateView() onStart() onResume() Navigate!

Slide 75

Slide 75 text

Fragment A View A onCreate() onCreateView() onStart() onResume() onPause() onStop()

Slide 76

Slide 76 text

Fragment A onCreate() onCreateView() onStart() onResume() onPause() onStop() onDestroyView()

Slide 77

Slide 77 text

Fragment A Fragment B

Slide 78

Slide 78 text

Fragment A Fragment B onCreate()

Slide 79

Slide 79 text

Fragment A Fragment B onCreate() View B onCreateView() onStart() onResume()

Slide 80

Slide 80 text

Fragment A Fragment B onCreate() View B onCreateView() onStart() onResume() Go back!

Slide 81

Slide 81 text

Fragment A Fragment B onCreate() onCreateView() onStart() onResume() onPause() onStop() onDestroyView()

Slide 82

Slide 82 text

Fragment A

Slide 83

Slide 83 text

Fragment A View A onCreateView()

Slide 84

Slide 84 text

Fragment A View A onCreateView() onCreate()

Slide 85

Slide 85 text

Fragment A View A

Slide 86

Slide 86 text

A pattern I don’t mind

Slide 87

Slide 87 text

A pattern I don’t mind View Controller

Slide 88

Slide 88 text

A pattern I don’t mind View Controller State

Slide 89

Slide 89 text

A pattern I don’t mind View Controller State Event

Slide 90

Slide 90 text

A pattern I don’t mind View Controller Observable Observable

Slide 91

Slide 91 text

A pattern I don’t mind View Controller Observable Observable

Slide 92

Slide 92 text

sealed class State { object Idle : State() object Loading : State() data class Error(val message: String) : State() data class Success(val items: List) : State() }

Slide 93

Slide 93 text

fun display(state: State) { when (state) { is Idle -> is Loading -> is Error -> is Success -> }} }}

Slide 94

Slide 94 text

class HomeView(...) : FrameLayout(...) { fun display(state: State) { when (state) { is Idle -> is Loading -> is Error -> is Success -> }} }} }

Slide 95

Slide 95 text

class HomeView(...) : FrameLayout(...) { fun display(state: State) {

Slide 96

Slide 96 text

class HomeView(...) : FrameLayout(...) { fun display(state: State) { when (state) { is Idle -> is Loading -> is Error -> is Success -> }} }} }}

Slide 97

Slide 97 text

class HomeView(context: Context, ...) : FrameLayout(...) { fun display(state: State) { when (state) { is Idle -> is Loading -> is Error -> is Success -> }} }} }}

Slide 98

Slide 98 text

class HomeView(context: Context, ...) : FrameLayout(...) { // RxBinding helps a lot here val events: Observable = ... fun display(state: State) { when (state) { is Idle -> is Loading -> is Error -> is Success -> }} }} }}

Slide 99

Slide 99 text

class HomeController : Controller() { }}

Slide 100

Slide 100 text

class HomeController : Controller() { override fun onAttach(view: View) { }} override fun onDetach(view: View) { }} }}

Slide 101

Slide 101 text

class HomeController : Controller() { override fun onAttach(view: View) { states.subscribe { view.display(it) }
 view.events.subscribe { .. } }} override fun onDetach(view: View) { }} }}

Slide 102

Slide 102 text

class HomeController : Controller() { private val disposables = CompositeDisposable() override fun onAttach(view: View) { disposables += states.subscribe { view.display(it) } disposables += view.events.subscribe { .. } }} override fun onDetach(view: View) { }} }}

Slide 103

Slide 103 text

class HomeController : Controller() { private val disposables = CompositeDisposable() override fun onAttach(view: View) { disposables += states.subscribe { view.display(it) } disposables += view.events.subscribe { .. } }} override fun onDetach(view: View) { disposables.clear() }} }}

Slide 104

Slide 104 text

class HomeController : Controller() { private val disposables = CompositeDisposable() override fun onAttach(view: View) { disposables += states.subscribe { view.display(it) } disposables += view.events.subscribe { .. } }} override fun onDetach(view: View) { disposables.clear() }} }}

Slide 105

Slide 105 text

class HomeController : Controller() { private val disposables = CompositeDisposable() override fun onAttach(view: View) { if (view !is HomeView) throw IllegalArgumentException() disposables += states.subscribe { view.display(it) } disposables += view.events.subscribe { .. } }} override fun onDetach(view: View) { disposables.clear() }} }}

Slide 106

Slide 106 text

class HomeController : Controller() { }}

Slide 107

Slide 107 text

class HomeController : Controller() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.home, container, false) }} }}

Slide 108

Slide 108 text

R.layout.home

Slide 109

Slide 109 text

What’s to like? • Simple “states go in, events come out” way of thinking • Test output of view states, rather than interactions with methods • Clear separation of concerns

Slide 110

Slide 110 text

More on streams… Managing State with RxJava by Jake Wharton https:/ /youtu.be/0IKHxjkgop4 <> Observable Observable Observable Observable

Slide 111

Slide 111 text

What about Architecture Components?

Slide 112

Slide 112 text

What about Architecture Components • LifecycleOwners • LiveData • ViewModel • Room • Paging • Navigation • WorkManager

Slide 113

Slide 113 text

What about Architecture Components • LifecycleOwners • LiveData • ViewModel

Slide 114

Slide 114 text

ViewModel class MyActivity : AppCompatActivity { fun onCreate(savedInstanceState: Bundle) { val model = ViewModelProviders.of(this).get(MyViewModel.class) model.getUsers().observe(this, { users -> // Update UI. }) } } LiveData

Slide 115

Slide 115 text

ViewModel class MyActivity : AppCompatActivity { fun onCreate(savedInstanceState: Bundle) { val model = ViewModelProviders.of(this).get(MyViewModel.class) model.getUsers().observe(this, { users -> // Update UI. }) } } LifecycleOwner

Slide 116

Slide 116 text

Activity created Activity rotated finish() Finished onCreate onStart onResume onPause onStop onDestroy onCleared() ViewModel scope onCreate onStart onResume onPause onStop onDestroy

Slide 117

Slide 117 text

Does Controller replace ViewModel?

Slide 118

Slide 118 text

dependencies { implementation 'com.bluelinelabs:conductor-archlifecycle:0.1.1' } abstract class LifecycleController : Controller, LifecycleOwner

Slide 119

Slide 119 text

What about the new Navigation component?

Slide 120

Slide 120 text

Out of the box advantages android.arch.navigation:navigation-fragment

Slide 121

Slide 121 text

Out of the box advantages • Avoid touching FragmentTransaction with your bear hands • Simplified deep linking • Type safe passing of arguments between destinations • Visualise and edit your navigation graph

Slide 122

Slide 122 text

No content

Slide 123

Slide 123 text

Can we combine them?

Slide 124

Slide 124 text

Can we combine them? Navigating Conductor and the Navigation Architecture Component Tiven Jeffery https:/ /bit.ly/2MD9LSn (long medium link) GitHub repo https:/ /github.com/prolificinteractive/navigation-conductor

Slide 125

Slide 125 text

Limitations • Navigator feels like the simple cousin of Router • No transitions; only R.anim or R.animator resources • Strange having both Router and NavController hold back stack

Slide 126

Slide 126 text

Summary

Slide 127

Slide 127 text

Summary • Simple in the right ways; configurable when you need it • Good starting point to play with different architectures • An alternative to many architecture components; or an addition

Slide 128

Slide 128 text

Conductor Architecture and Alternatives chris_h_codes chris-horner chrishorner.codes