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. 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) ...
  2. 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);
 }
  3. Android N - Multi-window • Does not affect lifecycle events

    • One topmost “activity” • Continue even while paused • Configuration change
  4. 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>
  5. 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>
  6. 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.
  7. Conductor Components • Controller (View) • Router (Backstack Handling) •

    ControllerChangeHandler (Animations) • RouterTransaction (Backstack Entry)
  8. 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>
  9. 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--;
 }
 
 }
  10. 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() { } 
 }
  11. 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);
 }
 }
  12. 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()));
 }
 }
 }
  13. 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();
 }
 }
 }
  14. Coding Example MasterViewController public class MasterViewController extends Controller {
 public

    MasterViewController() {
 super();
 }
 
 public MasterViewController(Bundle args) {
 super(args);
 }
 }
  15. 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);
 } }
  16. Coding Example MasterViewController public class MasterViewController extends Controller {
 @Override


    protected void onAttach(View view) {
 super.onAttach(view);
 dataProvider.getData().forEach(item -> itemAdapter.addItem(item));
 } }
  17. 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)
 );
 }
 } }
  18. 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)
 );
 } } }
  19. 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>
  20. 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();
  21. 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();
  22. 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();
  23. Coding Example ArcFadeMoveChangeHandler public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {
 


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


    public ArcFadeMoveChangeHandlerCompat() {
 super(new ArcFadeMoveChangeHandler(), new HorizontalChangeHandler());
 }
 
 }
  29. 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%
  30. 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>
  31. Coding Example MainActivity class MainActivity : AppCompatActivity() {
 
 override

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

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

    fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 
 coordinatorLayout {
 fitsSystemWindows = true
 
 appBarLayout { }.lparams(width = matchParent)
 
 } }
 }
  34. 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)
 
 } }
 }
  35. 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()
 }
 } }
 }
  36. 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()
 }
 } }
 }
  37. 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()
 } 
 }
  38. 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
 } 
 }
  39. 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 
 }
  40. 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
 } 
 }
  41. 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
 } 
 }
  42. 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
 } 
 }
  43. 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()