Write better code with Conductor's Controller

Kittinun Vantasin

About me

Mobile Enthusiastic

💖 Kotlin + Open source

Work at Mercari (Merpay) in Tokyo, Japan

github.com/kittinunf

@kittinunf

Agenda

1. How to architect your components in Android app  

2. How Conductor fits into this

Design Philosophy

Using classes to separate Concerns ! ! !

Storage, Networking, Parsing, Routing, Business logic, Display

Easily abused! >_< 😱

Abused ? Why?

Business Logic

View Setup / Loading

Model observation

Datasource interaction / Network

Handling permission / API differences

Screen

Problems

Hard to understand 😕

Hard to test ❌

Fragile to change 💔

7,000 lines seems acceptable 🙅

Do something?

So, Yet another arch?

NO

MVP, MVVM, MVI, M** ... (🔮 🙅‍♂️ we aren't talking about arch today™)

Then, what?

Slowly changing the way you design your code structure and screen

With 6 simple Concepts

Concept #1

Use appropriate data structures & code structures

Data structures

~ 80 % of the time choose List<T>

When you need something more like association e.g. A <-> B tries Map<A, B>

Ownership, does this contains that? picks Set<T>

Need something like a hierarchy use Tree<T>

Code structures

  
  fun getFoos(): List<Foo> {
     val size = bars.size
     val arr = ArrayList()
     for (i in 1..size) {
       arr.add(Foo(bars[i].bar))
     }
     return arr
  }  
  
  fun getFoos(): List<Foo> =
    bars.map { Foo(it) }

Use the right patterns where it is appropriate

Don't overuse it

Learn tools' strength and weakness!

Concept #2

Naming affects intentions!

2 Hardest Probs in CS

1. Cache Validation

2. Naming things

3. Off-by-one errors 󠀫󠀫󠀫󠀫󠀫󠀫󠀫🤔

Naming!

*View should belong to View layer

e.g. View, TextView, CalendarView

*Manager should belong to **Controller** Layer

e.g. LocationManager, AlarmManager, SearchManager

*Data, *Cursor usually a helper to Model layer

e.g. SqlLiteDatabase, SqlLiteCursor

Please choose GOOD NAMES!

Concept #3

Activity™ and Fragment are just a regular VIEW

Responsibilities

Loads Views

Layout Views

Perform Animation on Views

Show/hide Views

Transition Views

It is all about Views ...

So ... Maybe it is View?

If they are only doing View stuffs ...

Implications?

No networking

No data persistent layer

No navigation (Activity™)

No business logic

No model observation

Just displaying stuffs ...

Concept #4

Views are usually small™

Smallness

Views are small components of a screen

They do only one job and do it really well

A View != A screen

Small Fragments

A Fragment != A screen

You don't have to fill the screen with one fragment!

Use multiple fragments where it makes sense!!

Concept #5

Views are great when they use as composable/reusable units

Composing

Adding small things to make big things

The single most important skill as software engineer

Reusing

We don't rewrite TextView? we don't rewrite RecyclerView?

We adjust the style and reuse them

We adjust the style and reuse them

We adjust the style and reuse them

We adjust the style and reuse them

WHY DO WE DO THAT WITH FRAGMENT ?

Reusing cont.

It is not ​🙅

It is a ​🙆‍♀️

ShoppingListItemFragment

ListItemFragment with Adapter that shows ShoppingItem

AddressFormFragment

Fragment that shows multiple EditTextFragment as a form item 

ImageView subclass that hits network

ImageRemoteFragment with an image provider (local / network)

Concept #6

We need an actual place for our logic 

Case study

1. Install/Uninstall + Open app

2. Read What's new

3. Rate App

4. Contextual information

5. Related application

6. Read Description

Logic

  1. Model observation

  2. Determine Action

  3. Observe the state

  4. List related apps

Actions

  1. Uninstall/Install Open

  2. Rate the app

  3. Go to the different app

  4. See the images

  5. Search and more ...

If we do this in one Fragment, it would be horrible to maintain

Then, how ?

GooglePlayStoreItemInfoFragment

StoreHeaderFragment

StoreWhatsNewFragment

StoreContextualLinkFragment

StoreRelatedItemFragment

StoreDescriptionFragment

How?

Then, how ?

StoreItemInfoF

StoreHeaderF

StoreWhatsNewF

StoreContextLinkF

StoreRelatedItemF

StoreDescriptionF

How?

?

What is this?

?

It can be anything!

MVVM - it is View Model

MVP - it is Presenter

MVC - it is a Controller™

M** - it is a something that sits in the middle managing components

A better code structure

1. Use appropriate data structures & code structures

2. Naming affects intentions!

3. Activity™ and Fragment are just a regular VIEW

4. Views are usually small™

5. Views are great when they use as composable/reusable units

6. We need an actual place for our logic 

It looks like we are done here ...

but ...

Exception java.lang.IllegalStateException: 
 Can not perform this action after onSaveInstanceState
android.support.v4.app
 .FragmentManagerImpl.checkStateLoss (FragmentManagerImpl.java:1538)
android.support.v4.app
 .FragmentManagerImpl.enqueueAction (FragmentManagerImpl.java:1556)
android.support.v4.app
 .BackStackRecord.commitInternal (BackStackRecord.java:696)
android.support.v4.app
 .BackStackRecord.commit (BackStackRecord.java:662)

We don't like Fragments?

Entering Conductor

Promote the View-based Application

Fragment alternative (much simpler)

Just works™ (with a little setup)

Conductor

Activity

Router

Controller 1..n

View

Setup Conductor

class SampleActivity : AppCompatActivity() {

  private lateinit var router: Router

  override fun onCreate(bundle: Bundle?) {
    super.onCreate(bundle)
    setContentView(R.layout.activity_sample)

    router = Conductor.attachRouter(
               this, 
               container, 
               bundle)

    if (!router.hasRootController()) {
      router.setRoot(RouterTransaction.with(
                     MainController()
                     ))
    }
  }
}
<com.bluelinelabs.conductor.
  ChangeHandlerFrameLayout
  
  android:id="@+id/container"
  android:layout_width=
      "match_parent"
  android:layout_height=
      "match_parent"

/>

Setup Controller

class MainController : Controller() {
  override fun onCreateView(
    inflater: LayoutInflater, 
    container: ViewGroup): View = 
      inflater.inflate(R.layout.controller_demo, container, false).apply {
         //setup your view
      }
}

Similar to Fragment counterparts

Easier lifecycle!

Navigate!

Use Router to push/pop controllers

  
  router.pushController(RouterTransaction.with(
                        AnotherController()))
          

animation, horizontal/vertical/fade?

  
  router.pushController(
   RouterTransaction.with(AnotherController()).
     pushChangeHandler(HorizontalChangeHandler()).
     popChangeHandler(HorizontalChangeHandler())
  )
          

How about Lifecycle?

New instance (init)

onCreateView

Controller is on top!

onDestroyView

Controller is on top again

Send to back / Configuration Change e.g.

Remove from Router / Got killed by System

SURVIVES CONFIGURATION CHANGE

Benefits of Controller

Simple to understand lifecycle

Transaction executed immedietely

Survive the configuration change

Extensible, Compatible with AAC, Rx ... and others

Implications?

Go back and read the whole slide from beginning again

:%s/Fragment/Controller/g

End results

Really tiny Fragment Controller (< 300 LOCs)

Almost never subclass View (android.view.View)

Reusable Controller / Clear separation

Happy Coder 🎉

Q & A

メルカリ

We are hiring!!

kittinun.f@mercari.com

Write better code with Conductor's Controller

By Kittinun Vantasin

Write better code with Conductor's Controller

  • 2,326