Upgrade to Pro — share decks privately, control downloads, hide ads and more …

View based apps with Conductor

View based apps with Conductor

Single Activity apps without using Fragments! Get rid of all lifecycle hell and take full advantage of the power that is Android Views.

Simon Vergauwen

October 23, 2016
Tweet

More Decks by Simon Vergauwen

Other Decks in Programming

Transcript

  1. View based apps with
    Conductor

    View full-size slide

  2. Your host
    Simon Vergauwen
    Android developer
    [email protected]
    @vergauwen_simon
    github.com/nomisRev

    View full-size slide

  3. Topics
    Lifecycle
    Master-detail
    Conductor sample
    Animations
    Anko

    View full-size slide

  4. Activity Lifecycle

    View full-size slide

  5. Fragment Lifecycle

    View full-size slide

  6. Coding Example
    java.lang.NullPointerException
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:960)
    at android.support.v4.app.FragmentManagerImpl.performPendingDeferredStart(FragmentManager.java:768)
    at android.support.v4.app.FragmentManagerImpl.startPendingDeferredFragments(FragmentManager.java:1104)
    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.onLoadComplete(LoaderManager.java:410)
    at android.support.v4.content.Loader.deliverResult(Loader.java:103)
    at android.support.v4.content.CursorLoader.deliverResult(CursorLoader.java:81)
    at android.support.v4.content.CursorLoader.onStartLoading(CursorLoader.java:126)
    at android.support.v4.content.Loader.startLoading(Loader.java:197)
    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.start(LoaderManager.java:262)
    at android.support.v4.app.LoaderManagerImpl.doStart(LoaderManager.java:710)
    at android.support.v4.app.Fragment.onStart(Fragment.java:981)
    at android.support.v4.app.Fragment.performStart(Fragment.java:1332)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:906)
    at android.support.v4.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1240)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:612)
    ...

    View full-size slide

  7. Coding Example
    View Lifecycle
    package android.view;
    /**

    * Interface definition for a callback to be invoked when this view is attached

    * or detached from its window.

    */

    public interface OnApplyWindowInsetsListener {

    public void onViewAttachedToWindow(View v);


    public void onViewDetachedFromWindow(View v);

    }

    View full-size slide

  8. Conductor lifecycle

    View full-size slide

  9. Android N - Multi-window
    • Does not affect lifecycle events
    • One topmost “activity”
    • Continue even while paused
    • Configuration change

    View full-size slide

  10. Master-Detail

    View full-size slide

  11. Master-detail

    View full-size slide

  12. Coding Example
    Layout - Main
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">


    android:id="@+id/app_bar"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:theme="@style/AppTheme.AppBarOverlay">


    android:id="@+id/toolbar"

    android:layout_width="match_parent"

    android:layout_height="?attr/actionBarSize"

    app:popupTheme="@style/AppTheme.PopupOverlay"/>




    android:id="@+id/frameLayout"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:layout_behavior="@string/appbar_scrolling_view_behavior">






    View full-size slide

  13. Coding Example
    Layout - Master
    res/layout/item_list
    android:id="@+id/item_list"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:layoutManager="LinearLayoutManager"/>

    View full-size slide

  14. Coding Example
    Layout - Master
    res/layout-w900dp/item_list
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="horizontal">


    android:id="@+id/item_list"

    android:layout_width="@dimen/item_width"

    android:layout_height=“match_parent"
    app:layoutManager="LinearLayoutManager"/>


    android:id="@+id/item_detail_container"

    android:layout_width="0dp"

    android:layout_height="match_parent"

    android:layout_weight="3"/>



    View full-size slide

  15. Conductor
    github.com/bluelinelabs/Conductor
    bluelinelabs.com

    View full-size slide

  16. Conductor
    A small, yet full-featured framework that allows building View-
    based Android applications.
    Conductor provides a light-weight wrapper around standard
    Android Views that does just about everything you'd want.

    View full-size slide

  17. Dependencies
    • compile ‘com.bluelinelabs:conductor:2.0.3'
    • compile ‘com.bluelinelabs:conductor-support:2.0.3'
    • compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3'

    View full-size slide

  18. Conductor Components
    • Controller (View)
    • Router (Backstack Handling)
    • ControllerChangeHandler (Animations)
    • RouterTransaction (Backstack Entry)

    View full-size slide

  19. Coding Example
    Layout - Main
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">




    android:id="@+id/container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"/>



    View full-size slide

  20. Coding Example
    ChangeHandlerFrameLayout
    public class ChangeHandlerFrameLayout extends FrameLayout
    implements ControllerChangeListener {


    private int inProgressTransactionCount;


    @Override

    public void onChangeStarted(Controller to, Controller from, boolean isPush,
    ViewGroup container, ControllerChangeHandler handler) {

    inProgressTransactionCount++;

    }


    @Override

    public void onChangeCompleted(Controller to, Controller from, boolean isPush,
    ViewGroup container, ControllerChangeHandler handler) {

    inProgressTransactionCount--;

    }


    }

    View full-size slide

  21. Coding Example
    ChangeHandlerFrameLayout

    public class ChangeHandlerFrameLayout extends FrameLayout
    implements ControllerChangeListener {


    private int inProgressTransactionCount;


    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

    return (inProgressTransactionCount > 0) || super.onInterceptTouchEvent(ev);

    }


    public void onChangeStarted() { }

    public void onChangeCompleted() { }

    }

    View full-size slide

  22. Coding Example
    MainActivity
    public class MainActivity extends AppCompatActivity {

    @BindView(R.id.container) ChangeHandlerFrameLayout container;

    private Router router;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);


    router = Conductor.attachRouter(this, container, savedInstanceState);

    }

    }

    View full-size slide

  23. Coding Example
    MainActivity
    public class MainActivity extends AppCompatActivity {
    @BindView(R.id.container) ChangeHandlerFrameLayout container;

    private Router router;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);


    router = Conductor.attachRouter(this, container, savedInstanceState);

    if (!router.hasRootController()) {

    router.setRoot(RouterTransaction.with(new MasterViewController()));

    }

    }

    }

    View full-size slide

  24. Coding Example
    MainActivity
    public class MainActivity extends AppCompatActivity {
    @BindView(R.id.container) ChangeHandlerFrameLayout container;

    private Router router;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);


    router = Conductor.attachRouter(this, container, savedInstanceState);

    if (!router.hasRootController()) {

    router.setRoot(RouterTransaction.with(new MasterDetailController()));

    }

    }


    @Override

    public void onBackPressed() {

    if (router != null && !router.handleBack()) {

    super.onBackPressed();

    }

    }

    }

    View full-size slide

  25. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {

    public MasterViewController() {

    super();

    }


    public MasterViewController(Bundle args) {

    super(args);

    }

    }

    View full-size slide

  26. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {


    @Override

    protected View onCreateView(LayoutInflater inflater, ViewGroup container) {

    View view = inflater.inflate(R.layout.master_list_controller, container, false);

    unbinder = ButterKnife.bind(this, view);

    setUpRecyclerView();

    return view;

    }
    @Override

    protected void onDestroyView(View view) {

    unbinder.unbind();

    super.onDestroyView(view);

    }
    }

    View full-size slide

  27. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {

    @Override

    protected void onAttach(View view) {

    super.onAttach(view);

    dataProvider.getData().forEach(item -> itemAdapter.addItem(item));

    }
    }

    View full-size slide

  28. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {
    private void onRowSelected(int index) {

    Item item = itemAdapter.getItem(index);

    if (item != null) {

    showDetail(

    new DetailViewController(item.name(), item.colorId(), item.getDrawResId()),

    getCircularRevealChangeHandler(index)

    );

    }

    }
    }

    View full-size slide

  29. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {

    private void showDetail(Controller controller,ControllerChangeHandler changeHandler) {

    if (detailContainer != null) {

    getChildRouter(detailContainer, TAG).setRoot(RouterTransaction.with(controller));

    } else {

    getRouter().pushController(

    RouterTransaction.with(controller)
    .tag(DETAIL_CONTROLLER)

    .pushChangeHandler(controllerChangeHandler)

    .popChangeHandler(controllerChangeHandler)

    );

    }
    }
    }

    View full-size slide

  30. Animations
    • Meaningful Motion
    • Richer experience
    • Fluent
    • Natural

    View full-size slide

  31. Coding Example
    Vanilla Android
    res/transition
    android:duration="@android:integer/config_longAnimTime"

    android:interpolator="@android:interpolator/linear">


    android:maximumAngle="90"

    android:minimumHorizontalAngle="90"

    android:minimumVerticalAngle="0"/>


    View full-size slide

  32. Coding Example
    Vanilla Android
    SecondFragment secondFragment = SecondFragment.newInstance();

    FragmentTransaction fragmentTransaction = getActivity()

    .getSupportFragmentManager()

    .beginTransaction();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    } else {

    }

    fragmentTransaction

    .replace(R.id.content_frame, secondFragment)

    .addToBackStack(SecondFragment.FRAGMENT_TAG)

    .commit();

    View full-size slide

  33. Coding Example
    Vanilla Android
    SecondFragment secondFragment = SecondFragment.newInstance();

    FragmentTransaction fragmentTransaction = getActivity()

    .getSupportFragmentManager()

    .beginTransaction();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    Transition transition = TransitionInflater.from(getContext())

    .inflateTransition(R.transition.arc_shared);

    secondFragment.setSharedElementEnterTransition(transition);

    secondFragment.setEnterTransition(new Fade());

    secondFragment.setReturnTransition(transition);

    setExitTransition(new Fade());

    fragmentTransaction.addSharedElement(floatingActionButton,

    getString(R.string.fab_transition_name));

    } else {


    }

    fragmentTransaction

    .replace(R.id.content_frame, secondFragment)

    .addToBackStack(SecondFragment.FRAGMENT_TAG)

    .commit();

    View full-size slide

  34. Coding Example
    Vanilla Android
    SecondFragment secondFragment = SecondFragment.newInstance();

    FragmentTransaction fragmentTransaction = getActivity()

    .getSupportFragmentManager()

    .beginTransaction();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {


    } else {

    fragmentTransaction

    .setCustomAnimations(android.R.anim.slide_in_left,

    android.R.anim.slide_out_right,

    android.R.anim.slide_in_left,

    android.R.anim.slide_out_right);

    }

    fragmentTransaction

    .replace(R.id.content_frame, secondFragment)

    .addToBackStack(SecondFragment.FRAGMENT_TAG)

    .commit();

    View full-size slide

  35. Conductor Animation

    View full-size slide

  36. ControllerChangeHandler

    View full-size slide

  37. ControllerChangeHandler

    View full-size slide

  38. ControllerChangeHandler

    View full-size slide

  39. ControllerChangeHandler

    View full-size slide

  40. ControllerChangeHandler

    View full-size slide

  41. ControllerChangeHandler

    View full-size slide

  42. Coding Example
    ArcFadeMoveChangeHandler
    public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {


    public ArcFadeMoveChangeHandlerCompat() {

    super(new ArcFadeMoveChangeHandler(), new HorizontalChangeHandler());

    }


    }

    View full-size slide

  43. Coding Example
    HorizontalChangeHandler
    public class HorizontalChangeHandler extends AnimatorChangeHandler {

    @Override

    protected Animator getAnimator(@NonNull ViewGroup container, View from, View to,
    boolean isPush, boolean toAddedToContainer) { }


    @Override

    protected void resetFromView(@NonNull View from) { }


    }

    View full-size slide

  44. Coding Example
    HorizontalChangeHandler
    public class HorizontalChangeHandler extends AnimatorChangeHandler {

    @Override
    protected Animator getAnimator(@NonNull ViewGroup container, View from, View to,

    boolean isPush, boolean toAddedToContainer) {

    AnimatorSet animSet = new AnimatorSet();


    if (isPush && from != null) {

    animSet.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, -from.getWidth()));

    }

    if (isPush && to != null) {

    animSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, to.getWidth(), 0));

    }


    return animatorSet;

    }

    protected void resetFromView(@NonNull View from) { }

    }

    View full-size slide

  45. Coding Example
    ArcFadeMoveChangeHandler
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)

    public class ArcFadeMoveChangeHandler extends TransitionChangeHandler {


    @Override

    @NonNull

    protected Transition getTransition(@NonNull ViewGroup container, View from,

    View to, boolean isPush) {

    }


    }

    View full-size slide

  46. Coding Example
    ArcFadeMoveChangeHandler
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)

    public class ArcFadeMoveChangeHandler extends TransitionChangeHandler {


    @Override

    @NonNull

    protected Transition getTransition(@NonNull ViewGroup container, View from,

    View to, boolean isPush) {

    TransitionSet transition = new TransitionSet()
    .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
    .addTransition(new Fade(Fade.OUT))

    .addTransition(new TransitionSet()

    .addTransition(new ChangeBounds()))

    .addTransition(new Fade(Fade.IN));


    transition.setPathMotion(new ArcMotion());
    transition.setInterpolator(new LinearInterpolator());

    transition.setDuration(500);

    return transition;

    }

    }

    View full-size slide

  47. Coding Example
    ArcFadeMoveChangeHandler
    public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {


    public ArcFadeMoveChangeHandlerCompat() {

    super(new ArcFadeMoveChangeHandler(), new HorizontalChangeHandler());

    }


    }

    View full-size slide

  48. Advanced Layout

    View full-size slide

  49. Advanced Layout

    View full-size slide

  50. Advanced Layout

    View full-size slide

  51. Anko performance test
    Specs ANKO XML Diff
    ALCATEL

    ONE
    TOUCH
    Mediatek MT6572

    Dual-core 1.3GHz Cortex-A7

    512MB RAM
    169.33 ms 608.66 ms 359%
    HUAWEI
    Y300
    Qualcomm MSM8225

    Dual-core 1.0 GHz Cortex-A5
    512 MB RAM
    593.66 ms
    3435.33
    ms
    578%
    HUAWEI
    Y330
    Mediatek MT6572
    Dual-core 1.3 GHz Cortex-A7
    512MB
    162.33 ms 984 ms 606%
    Samsung
    Galaxy
    S2
    Exynos 4210 Dual
    Dual-core 1.2 GHz Cortex-A9
    1 GB RAM
    207.33 ms 753.66 ms 363%

    View full-size slide

  52. Coding Example
    Anko
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">


    android:layout_width="match_parent"

    android:layout_height="wrap_content">


    android:id="@+id/toolbar"

    android:layout_width="match_parent"

    android:layout_height="?attr/actionBarSize"/>




    android:id="@+id/container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>



    View full-size slide

  53. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    }
    }

    View full-size slide

  54. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true



    }
    }

    }

    View full-size slide

  55. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {
    }.lparams(width = matchParent)


    }
    }

    }

    View full-size slide

  56. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {

    toolbar {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) elevation = 4f

    }.lparams(width = matchParent, height = actionBarSize())
    }.lparams(width = matchParent)


    }
    }

    }

    View full-size slide

  57. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {

    toolbar {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) elevation = 4f

    }.lparams(width = matchParent, height = actionBarSize())
    }.lparams(width = matchParent)


    changeHandlerFrameLayout()

    .lparams(width = matchParent, height = matchParent) {

    behavior = AppBarLayout.ScrollingViewBehavior()

    }

    }
    }

    }

    View full-size slide

  58. Coding Example
    MainActivity
    class MainActivity : AppCompatActivity() {
    private var toolBar: Toolbar? = null

    private var container: ViewGroup? = null

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {

    toolBar = toolbar {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) elevation = 4f

    }.lparams(width = matchParent, height = actionBarSize())
    }.lparams(width = matchParent)


    container = hangeHandlerFrameLayout()

    .lparams(width = matchParent, height = matchParent) {

    behavior = AppBarLayout.ScrollingViewBehavior()

    }

    }
    }

    }

    View full-size slide

  59. Coding Example
    layout_width="match_parent"

    layout_height="match_parent"

    fitsSystemWindows="true">


    layout_width="match_parent"

    layout_height="wrap_content">


    id="@+id/toolbar"

    layout_width="match_parent"

    layout_height="?attr/actionBarSize"/>




    id="@+id/container"

    layout_width="match_parent"

    layout_height="match_parent"

    layout_behavior=“@string/scroll_behaviour"/>



    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {

    toolBar = toolbar {

    if (SDK_INT >= LOLLIPOP)
    elevation = 4f

    }.lparams(width = matchParent,
    height = actionBarSize())
    }.lparams(width = matchParent)


    changeHandlerFrameLayout()

    .lparams(width = matchParent,
    height = matchParent) {

    behavior = ScrollingBehavior()

    }

    }

    View full-size slide

  60. Coding Example
    View injection
    class MasterViewLayout {

    fun bindLayout(masterView: MasterViewController): View =

    masterView.activity.UI {

    }.view

    }
    fun unbind(masterView: MasterViewController) {

    container = null

    recyclerView = null

    detailContainer = null

    }

    }

    View full-size slide

  61. Coding Example
    View injection
    class MasterViewLayout : LayoutInjector {

    fun bindLayout(masterView: MasterViewController): View =

    masterView.activity.UI {

    linearLayout {

    configuration(orientation = Orientation.PORTRAIT) {


    }


    configuration(orientation = Orientation.LANDSCAPE) {


    }

    }

    }.view

    }

    View full-size slide

  62. Coding Example
    View injection
    class MasterViewLayout : LayoutInjector {

    fun bindLayout(masterView: MasterViewController): View {

    return masterView.activity.UI {

    masterView.background = linearLayout {

    configuration(orientation = Orientation.PORTRAIT) {

    recyclerView {

    init()

    }.lparams(width = matchParent, height = matchParent)

    }


    configuration(orientation = Orientation.LANDSCAPE) {

    }

    }

    }.view

    }

    }

    View full-size slide

  63. Coding Example
    View injection
    class MasterViewLayout : LayoutInjector {

    fun bindLayout(masterView: MasterViewController): View {

    return masterView.activity.UI {

    linearLayout {

    configuration(orientation = Orientation.PORTRAIT) {

    recyclerView {

    init()

    }.lparams(width = matchParent, height = matchParent)

    }


    configuration(orientation = Orientation.LANDSCAPE) {

    recyclerView {

    init()

    }.lparams(width = dip(275), height = matchParent)


    frameLayout {

    }.lparams(width = matchParent, height = matchParent)

    }

    }

    }.view

    }

    }

    View full-size slide

  64. Coding Example
    View injection
    class MasterViewLayout : LayoutInjector {

    fun bindLayout(masterView: MasterViewController): View {

    return masterView.activity.UI {

    masterView.background = linearLayout {

    configuration(orientation = Orientation.PORTRAIT) {

    masterView.recyclerView = recyclerView {

    init()

    }.lparams(width = matchParent, height = matchParent)

    }


    configuration(orientation = Orientation.LANDSCAPE) {

    masterView.recyclerView = recyclerView {

    init()

    }.lparams(width = dip(275), height = matchParent)


    masterView.detailContainer = frameLayout {

    }.lparams(width = matchParent, height = matchParent)

    }

    }

    }.view

    }

    }

    View full-size slide

  65. Coding Example
    Runtime layouts
    configuration(orientation = Orientation.LANDSCAPE, smallestWidth = 700) {

    recyclerView {

    init()

    }.lparams(width = widthProcent(50), height = matchParent)


    frameLayout {


    }.lparams(width = matchParent, height = matchParent)

    }
    fun T.widthProcent(procent: Int): Int =

    getAppUseableScreenSize().x.toFloat()

    .times(procent.toFloat() / 100).toInt()

    View full-size slide

  66. Questions?
    Simon Vergauwen
    Android Developer
    [email protected]
    @twitternaam
    github.com/nomisRev

    View full-size slide