Say bye to Fragments with Conductor & Kotlin

Say bye to Fragments with Conductor & Kotlin

Updated slides for DroidKaigi 2018

72ffb135de71bef2c4a11961634edc6a?s=128

Miguel Beltran

January 28, 2018
Tweet

Transcript

  1. Say bye to Fragments with Conductor & Kotlin Miquel Beltran

    Android Dev @ nebenan.de @Miqubel
  2. @Miqubel Slides & Code:

  3. @Miqubel

  4. @Miqubel

  5. Activity

  6. Activity Router Activity Router

  7. Activity Router Controller 1 Controller 2 Controller 3 View

  8. class MainActivity : AppCompatActivity() { private lateinit var router: Router

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) router = Conductor.attachRouter(this, controller_container, savedInstanceState) } @Miqubel
  9. <com.bluelinelabs.conductor.ChangeHandlerFrameLayout android:id="@+id/controller_container" android:layout_width="match_parent" android:layout_height="match_parent"/> @Miqubel

  10. class MainActivity : AppCompatActivity() { private lateinit var router: Router

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) router = Conductor.attachRouter(this, controller_container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(MainController())) } } @Miqubel
  11. Controller @Miqubel

  12. class SimpleController : Controller() { override fun onCreateView(inflater: LayoutInflater, container:

    ViewGroup): View { val view = inflater.inflate(R.layout.controller_simple, container, false) // Do something with the view return view } } @Miqubel
  13. @Miqubel

  14. router.pushController( RouterTransaction.with( OtherSimpleController() ) ) @Miqubel

  15. override fun onBackPressed() { if (!router.handleBack()) { super.onBackPressed() } }

    router.popCurrentController() or @Miqubel
  16. Transitions @Miqubel

  17. @Miqubel

  18. router.pushController( RouterTransaction.with(DummyController()) .popChangeHandler(HorizontalChangeHandler()) .pushChangeHandler(HorizontalChangeHandler()) ) router.pushController( RouterTransaction.with(DummyController()) .popChangeHandler(FadeChangeHandler()) .pushChangeHandler(FadeChangeHandler()) )

    @Miqubel
  19. class CustomChangeHandler : AnimatorChangeHandler() { override fun getAnimator( container: ViewGroup,

    from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator { val animator = AnimatorSet() to?.let { animator.play( ObjectAnimator .ofFloat(to, View.SCALE_X, 0f, 1f) ) animator.play( ObjectAnimator .ofFloat(to, View.SCALE_Y, 0f, 1f) ) } return animator } }
  20. @Miqubel

  21. override fun getAnimator(…): Animator { val animator = AnimatorSet() to?.let

    { animator.play(…) // The "to" View has a child with id "image" animator.play( ObjectAnimator .ofFloat(to.image, View.ROTATION, -360f, 1f) ) } return animator } @Miqubel
  22. @Miqubel

  23. Lifecycle @Miqubel

  24. New Controller instance @Miqubel

  25. onCreateView Controller is on top Pushed to Router New Controller

    instance @Miqubel
  26. onCreateView onDestroyView Controller is on top Pop from Router Config

    change. etc. New Controller instance @Miqubel
  27. onCreateView onDestroyView Controller is on top Controller back on top

    New Controller instance @Miqubel
  28. onCreateView onDestroyView Controller is on top New Controller instance Removed

    from Router @Miqubel ❌
  29. Controller Class Properties @Miqubel

  30. class MyController(val parameter: String) : Controller() { override fun onCreateView(…):

    View { val view = inflater.inflate(…) view.textView.text = "My favorite city is $parameter" return view } } ❌ DON’T! @Miqubel
  31. class MyController(bundle: Bundle) : Controller(bundle) { constructor(parameter: String) : this(Bundle().apply

    { putString(EXTRA_PARAMETER, parameter) }) // Access arguments like class properties private val parameter by lazy { args.getString(EXTRA_PARAMETER) } //… view.textView.text = "My favorite city is $parameter" @Miqubel
  32. Accessing Views @Miqubel

  33. fun changeText(text: String) { // access to views with KAE

    view?.run { textView.text = text } } @Miqubel
  34. class MyController : Controller() { // Warning! this will leak

    on onDestroyView lateinit var textView: TextView override fun onCreateView(…): View { val view = inflater.inflate(…) // Store a reference to the textView for later textView = view.textView return view } ❌ DON’T! @Miqubel
  35. Conductor & Arch. Comp. @Miqubel

  36. model = ViewModelProviders .of(activity as AppCompatActivity) .get(MyViewModel::class.java) model.getLiveData() .observe( activity

    as AppCompatActivity, Observer<String> { // Use the LiveData values } ) ❌ DON’T! @Miqubel
  37. abstract class BaseViewModelController : LifecycleController() { private val viewModelStore =

    ViewModelStore() fun viewModelProvider(factory): ViewModelProvider { return ViewModelProvider(viewModelStore, factory) } } @Miqubel Ref: https://github.com/bluelinelabs/Conductor/pull/405
  38. class ViewModelController : BaseViewModelController() { model = viewModelProvider() .get(MyViewModel::class.java) model.getLiveData()

    .observe(this, Observer<String> { //… }) @Miqubel
  39. Dependency Injection (Dagger) @Miqubel

  40. class MyDaggerController : Controller() { @Inject lateinit var value: String

    override fun onCreateView(…): View { val view = inflater.inflate(…) (activity!!.application as App).component.inject(this) return view } } @Miqubel
  41. Testing Controllers @Miqubel

  42. @RunWith(AndroidJUnit4::class) class TestableControllerEspressoTest { @get:Rule val activity = ActivityTestRule<TestActivity>( TestActivity::class.java

    ) } @Miqubel
  43. @RunWith(AndroidJUnit4::class) class TestableControllerEspressoTest { @get:Rule val activity = ActivityTestRule… val

    controller = TestableController() @Before fun setUp() { activity.runOnUiThread { activity.activity.router.setRoot( RouterTransaction.with(controller) ) } } } @Miqubel
  44. @RunWith(AndroidJUnit4::class) class TestableControllerEspressoTest { //… @Test fun a_click_action() { //

    Call to Controller methods directly activity.runOnUiThread { controller.onClickDoAction() } // Check views onView(withId(R.id.textView)) .check(matches(withText(“Test is running"))) } } @Miqubel
  45. Recap @Miqubel View based apps on Android

  46. Recap @Miqubel View based apps on Android Manage your view

    stack
  47. Recap @Miqubel View based apps on Android Manage your view

    stack Nice transition animations
  48. Recap @Miqubel View based apps on Android Manage your view

    stack Nice transition animations Surviving configuration changes
  49. Recap @Miqubel View based apps on Android Manage your view

    stack Nice transition animations Surviving configuration changes Compatible with pres. patterns
  50. Recap @Miqubel View based apps on Android Manage your view

    stack Nice transition animations Surviving configuration changes Compatible with pres. patterns Easy to test with Espresso
  51. Library: https://github.com/bluelinelabs/Conductor Talk sample code: https://github.com/miquelbeltran/conductor-talk- demo Ask Me Anything:

    @Miqubel ありがとう! /ˌɑɹiˈɡɑtoʊ/