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

View based apps with Conductor

AppFoundry
October 25, 2016

View based apps with Conductor

Presented by Simon Vergauwen at GDG DevFest 2016

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

AppFoundry

October 25, 2016
Tweet

More Decks by AppFoundry

Other Decks in Programming

Transcript

  1. View based apps with
    Conductor

    View Slide

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

    View Slide

  3. AppFoundry

    View Slide

  4. Topics
    Lifecycle
    Master-detail
    Conductor sample
    Animations
    Anko

    View Slide

  5. Lifecycle

    View Slide

  6. Activity Lifecycle

    View Slide

  7. Fragment Lifecycle

    View Slide

  8. 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 Slide

  9. 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 Slide

  10. Conductor lifecycle

    View Slide

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

    View Slide

  12. Master-Detail

    View Slide

  13. Master-detail

    View Slide

  14. Android way

    View Slide

  15. 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 Slide

  16. 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 Slide

  17. 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 Slide

  18. Conductor

    View Slide

  19. Conductor
    github.com/bluelinelabs/Conductor
    bluelinelabs.com

    View Slide

  20. 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 Slide

  21. Conductor

    View Slide

  22. 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 Slide

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

    View Slide

  24. 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 Slide

  25. 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 Slide

  26. 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 Slide

  27. 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 Slide

  28. 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 Slide

  29. 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 Slide

  30. Coding Example
    MasterViewController
    public class MasterViewController extends Controller {

    public MasterViewController() {

    super();

    }


    public MasterViewController(Bundle args) {

    super(args);

    }

    }

    View Slide

  31. 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 Slide

  32. 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 Slide

  33. 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 Slide

  34. 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 Slide

  35. Animations

    View Slide

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

    View Slide

  37. 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 Slide

  38. 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 Slide

  39. 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 Slide

  40. 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 Slide

  41. Conductor Animation

    View Slide

  42. ControllerChangeHandler

    View Slide

  43. ControllerChangeHandler

    View Slide

  44. ControllerChangeHandler

    View Slide

  45. ControllerChangeHandler

    View Slide

  46. ControllerChangeHandler

    View Slide

  47. ControllerChangeHandler

    View Slide

  48. Coding Example
    ArcFadeMoveChangeHandler
    public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {


    public ArcFadeMoveChangeHandlerCompat() {

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

    }


    }

    View Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

  52. 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 Slide

  53. Coding Example
    ArcFadeMoveChangeHandler
    public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {


    public ArcFadeMoveChangeHandlerCompat() {

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

    }


    }

    View Slide

  54. Anko

    View Slide

  55. View Slide

  56. Advanced Layout

    View Slide

  57. Advanced Layout

    View Slide

  58. Advanced Layout

    View Slide

  59. 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 Slide

  60. 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 Slide

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


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    }
    }

    View Slide

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


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true



    }
    }

    }

    View Slide

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


    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)


    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {
    }.lparams(width = matchParent)


    }
    }

    }

    View Slide

  64. 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 Slide

  65. 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 Slide

  66. 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 Slide

  67. 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 Slide

  68. 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 Slide

  69. 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 Slide

  70. 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 Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

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

    View Slide

  75. Thank you!

    View Slide