Slide 1

Slide 1 text

Building apps using CustomViews Keishin Yokomaku @ Drivemode, Inc. potatotips #28

Slide 2

Slide 2 text

@KeithYokoma • Keishin Yokomaku at Drivemode, Inc. • Work • Android apps • Android Training and its publication • Like • Bicycle, Photography, Tumblr and Motorsport • AIDL ͸༑ୡ

Slide 3

Slide 3 text

Why not Fragments?

Slide 4

Slide 4 text

Why not Fragments? • Too much complexity • Hard to debug, hard to test

Slide 5

Slide 5 text

Too much complexity

Slide 6

Slide 6 text

Hard to debug, hard to test • Asynchronous operations using FragmentManager/FragmentTransaction. • Tightly coupled with Views. • View and business logic can be placed in a fragment. • But view logic can be placed in CustomViews. • If we decouple business logic from Fragment, what’s left in Fragment?

Slide 7

Slide 7 text

Slide 8

Slide 8 text

–@Piwai at Square, Inc. “I’m deep in fragment spaghetti, how do I escape?”

Slide 9

Slide 9 text

Escape from spaghetti • Advocating Against Android Fragments from Square, Inc. • It’s pointless to have Fragments which are hard to test and debug. • “Use Presenter to isolate business logic into dedicated controllers.” • “Presenter makes the code more readable and facilitates testing.”

Slide 10

Slide 10 text

–You “kk. I got CustomViews is the way to go, but how?”

Slide 11

Slide 11 text

Ways to go with CustomViews • View-based frameworks • Mortar and Flow • Scoop • Rosie • Conductor • screenplay • and so on…

Slide 12

Slide 12 text

The Square way • Flow • manages back stack of the screen flow • executes transition between screens • Mortar • isolates dagger modules for each screens

Slide 13

Slide 13 text

The Square way Activity PathContainer - Transition between screens(Path) - Back stack management CustomView - Contains view logic Path - Declares a screen - Holds a Presenter to work with CustomView - Lifecycle management of CustomView(save states to bundle and restore them from bundle, etc…)

Slide 14

Slide 14 text

Path and Presenter @Layout(R.layout.screen_sample) @WithModule(SampleScreen.Module.class) public class SampleScreen extends Path { private final Something something; public SampleScreen(Something something) { this.something = something; } @dagger.Module(injects = {SampleView.class}, complete = false) public class Module { @Provides Something provideSomething() { return something; } } @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } } }

Slide 15

Slide 15 text

Path and Presenter @Layout(R.layout.screen_sample) @WithModule(SampleScreen.Module.class) public class SampleScreen extends Path { private final Something something; public SampleScreen(Something something) { this.something = something; } @dagger.Module(injects = {SampleView.class}, complete = false) public class Module { @Provides Something provideSomething() { return something; } } @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } } }

Slide 16

Slide 16 text

Path and Presenter @Layout(R.layout.screen_sample) @WithModule(SampleScreen.Module.class) public class SampleScreen extends Path { private final Something something; public SampleScreen(Something something) { this.something = something; } @dagger.Module(injects = {SampleView.class}, complete = false) public class Module { @Provides Something provideSomething() { return something; } } @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } } }

Slide 17

Slide 17 text

Path and Presenter @Layout(R.layout.screen_sample) @WithModule(SampleScreen.Module.class) public class SampleScreen extends Path { private final Something something; public SampleScreen(Something something) { this.something = something; } @dagger.Module(injects = {SampleView.class}, complete = false) public class Module { @Provides Something provideSomething() { return something; } } @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } } }

Slide 18

Slide 18 text

Path and Presenter @Layout(R.layout.screen_sample) @WithModule(SampleScreen.Module.class) public class SampleScreen extends Path { private final Something something; public SampleScreen(Something something) { this.something = something; } @dagger.Module(injects = {SampleView.class}, complete = false) public class Module { @Provides Something provideSomething() { return something; } } @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } } }

Slide 19

Slide 19 text

Path and Presenter public class SampleScreen extends Path { /* emitted */ @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } @Override public void onLoad(Bundle savedInstanceState) { super.onLoad(savedInstanceState); if (!hasView()) return; getView().setSomething(something); } } }

Slide 20

Slide 20 text

Path and Presenter public class SampleScreen extends Path { /* emitted */ @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } @Override public void onLoad(Bundle savedInstanceState) { super.onLoad(savedInstanceState); if (!hasView()) return; getView().setSomething(something); } } }

Slide 21

Slide 21 text

Path and Presenter public class SampleScreen extends Path { /* emitted */ @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } @Override public void onLoad(Bundle savedInstanceState) { super.onLoad(savedInstanceState); if (!hasView()) return; getView().setSomething(something); } } }

Slide 22

Slide 22 text

Path and Presenter public class SampleScreen extends Path { /* emitted */ @Singleton public static class Presenter extends ViewPresenter { private final Something something; @Inject Presenter(Something something) { this.something = something; } @Override public void onLoad(Bundle savedInstanceState) { super.onLoad(savedInstanceState); if (!hasView()) return; getView().setSomething(something); } } }

Slide 23

Slide 23 text

CustomView public class SampleView extends FrameLayout { @Inject SampleScreen.Presenter presenter; public SampleView(Context context, AttributeSet attrs) { super(context, attrs); ObjectGraphService.inject(context, this); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); presenter.takeView(this); } @Override protected void onDetachedFromWindow() { presenter.dropView(this); super.onDetachedFromWindow(); } public void setSomething(Something something) { /* something */ } }

Slide 24

Slide 24 text

CustomView public class SampleView extends FrameLayout { @Inject SampleScreen.Presenter presenter; public SampleView(Context context, AttributeSet attrs) { super(context, attrs); ObjectGraphService.inject(context, this); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); presenter.takeView(this); } @Override protected void onDetachedFromWindow() { presenter.dropView(this); super.onDetachedFromWindow(); } public void setSomething(Something something) { /* something */ } }

Slide 25

Slide 25 text

CustomView public class SampleView extends FrameLayout { @Inject SampleScreen.Presenter presenter; public SampleView(Context context, AttributeSet attrs) { super(context, attrs); ObjectGraphService.inject(context, this); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); presenter.takeView(this); } @Override protected void onDetachedFromWindow() { presenter.dropView(this); super.onDetachedFromWindow(); } public void setSomething(Something something) { /* something */ } }

Slide 26

Slide 26 text

CustomView public class SampleView extends FrameLayout { @Inject SampleScreen.Presenter presenter; public SampleView(Context context, AttributeSet attrs) { super(context, attrs); ObjectGraphService.inject(context, this); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); presenter.takeView(this); } @Override protected void onDetachedFromWindow() { presenter.dropView(this); super.onDetachedFromWindow(); } public void setSomething(Something something) { /* something */ } }

Slide 27

Slide 27 text

The Square way /* How to move to SampleScreen */ // create some object that you would like to pass to the next screen Something something = new Something(); // instantiate SampleScreen with arguments Flow.get(getContext()).set(new SampleScreen(something));

Slide 28

Slide 28 text

The Square way /* How to move to SampleScreen */ // create some object that you would like to pass to the next screen Something something = new Something(); // instantiate SampleScreen with arguments Flow.get(getContext()).set(new SampleScreen(something));

Slide 29

Slide 29 text

Slide 30

Slide 30 text

and ✓ Activity can be Service • Flow and Mortar work on Overlay views ⾠ Still, you need to be careful on the view lifecycle • Not fully escaped from spaghetti of asynchronous things • Use hasView() to check if View is detached or not ✗ Not enough MaterialDesign support • No Shared Element transition support

Slide 31

Slide 31 text

Building apps using CustomViews Keishin Yokomaku @ Drivemode, Inc. potatotips #28