View based apps with Conductor

103e1ebcacd620770cf32a36b9aba17e?s=47 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.

103e1ebcacd620770cf32a36b9aba17e?s=128

AppFoundry

October 25, 2016
Tweet

Transcript

  1. View based apps with Conductor

  2. Your host Simon Vergauwen Android developer simon.vergauwen@appfoundry.be @vergauwen_simon github.com/nomisRev

  3. AppFoundry

  4. Topics Lifecycle Master-detail Conductor sample Animations Anko

  5. Lifecycle

  6. Activity Lifecycle

  7. Fragment Lifecycle

  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) ...
  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);
 }
  10. Conductor lifecycle

  11. Android N - Multi-window • Does not affect lifecycle events

    • One topmost “activity” • Continue even while paused • Configuration change
  12. Master-Detail

  13. Master-detail

  14. Android way

  15. Coding Example Layout - Main <android.support.design.widget.CoordinatorLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:fitsSystemWindows="true">
 


    <android.support.design.widget.AppBarLayout
 android:id="@+id/app_bar"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:theme="@style/AppTheme.AppBarOverlay">
 
 <android.support.v7.widget.Toolbar
 android:id="@+id/toolbar"
 android:layout_width="match_parent"
 android:layout_height="?attr/actionBarSize"
 app:popupTheme="@style/AppTheme.PopupOverlay"/>
 
 </android.support.design.widget.AppBarLayout>
 
 <FrameLayout
 android:id="@+id/frameLayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
 <include layout="@layout/item_list"/>
 </FrameLayout> 
 </android.support.design.widget.CoordinatorLayout>
  16. Coding Example Layout - Master res/layout/item_list <android.support.v7.widget.RecyclerView
 android:id="@+id/item_list"
 android:layout_width="match_parent"
 android:layout_height="match_parent"


    app:layoutManager="LinearLayoutManager"/>
  17. Coding Example Layout - Master res/layout-w900dp/item_list <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="horizontal">


    
 <android.support.v7.widget.RecyclerView
 android:id="@+id/item_list"
 android:layout_width="@dimen/item_width"
 android:layout_height=“match_parent" app:layoutManager="LinearLayoutManager"/>
 
 <FrameLayout
 android:id="@+id/item_detail_container"
 android:layout_width="0dp"
 android:layout_height="match_parent"
 android:layout_weight="3"/>
 
 </LinearLayout>
  18. Conductor

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

  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.
  21. Conductor

  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'

  23. Conductor Components • Controller (View) • Router (Backstack Handling) •

    ControllerChangeHandler (Animations) • RouterTransaction (Backstack Entry)
  24. Coding Example Layout - Main <android.support.design.widget.CoordinatorLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:fitsSystemWindows="true">
 


    <android.support.design.widget.AppBarLayout … />
 
 <com.bluelinelabs.conductor.ChangeHandlerFrameLayout
 android:id="@+id/container"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>
 
 </android.support.design.widget.CoordinatorLayout>
  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--;
 }
 
 }
  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() { } 
 }
  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);
 }
 }
  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()));
 }
 }
 }
  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();
 }
 }
 }
  30. Coding Example MasterViewController public class MasterViewController extends Controller {
 public

    MasterViewController() {
 super();
 }
 
 public MasterViewController(Bundle args) {
 super(args);
 }
 }
  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);
 } }
  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));
 } }
  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)
 );
 }
 } }
  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)
 );
 } } }
  35. Animations

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

    Natural
  37. Coding Example Vanilla Android res/transition <transitionSet android:duration="@android:integer/config_longAnimTime"
 android:interpolator="@android:interpolator/linear">
 <changeBounds />


    <arcMotion
 android:maximumAngle="90"
 android:minimumHorizontalAngle="90"
 android:minimumVerticalAngle="0"/>
 </transitionSet>
  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();
  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();
  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();
  41. Conductor Animation

  42. ControllerChangeHandler

  43. ControllerChangeHandler

  44. ControllerChangeHandler

  45. ControllerChangeHandler

  46. ControllerChangeHandler

  47. ControllerChangeHandler

  48. Coding Example ArcFadeMoveChangeHandler public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {
 


    public ArcFadeMoveChangeHandlerCompat() {
 super(new ArcFadeMoveChangeHandler(), new HorizontalChangeHandler());
 }
 
 }
  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) { }
 
 }
  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) { }
 }
  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) {
 }
 
 }
  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;
 }
 }
  53. Coding Example ArcFadeMoveChangeHandler public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {
 


    public ArcFadeMoveChangeHandlerCompat() {
 super(new ArcFadeMoveChangeHandler(), new HorizontalChangeHandler());
 }
 
 }
  54. Anko

  55. None
  56. Advanced Layout

  57. Advanced Layout

  58. Advanced Layout

  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%
  60. Coding Example Anko <android.support.design.widget.CoordinatorLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:fitsSystemWindows="true">
 
 <android.support.design.widget.AppBarLayout
 android:layout_width="match_parent"


    android:layout_height="wrap_content">
 
 <android.support.v7.widget.Toolbar
 android:id="@+id/toolbar"
 android:layout_width="match_parent"
 android:layout_height="?attr/actionBarSize"/>
 
 </android.support.design.widget.AppBarLayout>
 
 <com.bluelinelabs.conductor.ChangeHandlerFrameLayout
 android:id="@+id/container"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
 
 </android.support.design.widget.CoordinatorLayout>
  61. Coding Example MainActivity class MainActivity : AppCompatActivity() {
 
 override

    fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState) 
 } }
  62. Coding Example MainActivity class MainActivity : AppCompatActivity() {
 
 override

    fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 
 coordinatorLayout {
 fitsSystemWindows = true
 
 
 } }
 }
  63. Coding Example MainActivity class MainActivity : AppCompatActivity() {
 
 override

    fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 
 coordinatorLayout {
 fitsSystemWindows = true
 
 appBarLayout { }.lparams(width = matchParent)
 
 } }
 }
  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)
 
 } }
 }
  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()
 }
 } }
 }
  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()
 }
 } }
 }
  67. Coding Example <CoordinatorLayout
 layout_width="match_parent"
 layout_height="match_parent"
 fitsSystemWindows="true">
 
 <AppBarLayout
 layout_width="match_parent"
 layout_height="wrap_content">


    
 <Toolbar
 id="@+id/toolbar"
 layout_width="match_parent"
 layout_height="?attr/actionBarSize"/>
 
 </AppBarLayout>
 
 <ChangeHandlerFrameLayout
 id="@+id/container"
 layout_width="match_parent"
 layout_height="match_parent"
 layout_behavior=“@string/scroll_behaviour"/>
 
 </CoordinatorLayout> 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()
 } 
 }
  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
 } 
 }
  69. Coding Example View injection class MasterViewLayout : LayoutInjector<MasterViewController> { 


    fun bindLayout(masterView: MasterViewController): View =
 masterView.activity.UI {
 linearLayout { 
 configuration(orientation = Orientation.PORTRAIT) {
 
 }
 
 configuration(orientation = Orientation.LANDSCAPE) {
 
 } 
 }
 }.view 
 }
  70. Coding Example View injection class MasterViewLayout : LayoutInjector<MasterViewController> { 


    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
 } 
 }
  71. Coding Example View injection class MasterViewLayout : LayoutInjector<MasterViewController> { 


    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
 } 
 }
  72. Coding Example View injection class MasterViewLayout : LayoutInjector<MasterViewController> { 


    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
 } 
 }
  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 : View> T.widthProcent(procent: Int): Int =
 getAppUseableScreenSize().x.toFloat()
 .times(procent.toFloat() / 100).toInt()
  74. Questions? Simon Vergauwen Android Developer simon.vergauwen@appfoundry.be @twitternaam github.com/nomisRev

  75. Thank you!