Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
What to Upload to SlideShare
Loading in …3
×
1 of 56

Conductor vs Fragments

1

Share

Download to read offline

Talk about framework Conductor, its main features, difference from the Fragments.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Conductor vs Fragments

  1. 1. Conductor vs. Fragments Fedorets Konstantin Senior Android developer
  2. 2. What is Conductor? • Small, yet full-featured framework that allows building View-based Android applications. • Was made to simplify creating android applications and handling screens backstack. • Conductor is architecture-agnostic and does not try to force any design decisions on the developer.
  3. 3. Main features • Simpler lifecycle than fragments have • Application can be made using only one Activity • Immediately executed transactions • Persistent state • Easy navigation handling • Support standard animations • Simple integration MVP, MVVM, MVC
  4. 4. Under the hood • Conductor uses Fragment in retain instance mode to handle backstack state • All Controllers survive when change configuration happened
  5. 5. Main components • Controller • Router • ControllerChangeHandler • ControllerTransaction
  6. 6. Controller • The Controller is the View wrapper. • Listen Activity’s lifecycle events like onActivityStarted(), onActivityResumed() etc. • Has integration with onActivityResult(), onRequestPermissionsResult() etc.
  7. 7. Router • Implements navigation and backstack handling for Controllers. • Sends onActivityResult() only to requested Controller • Sends Activity’s events all parent and child controllers
  8. 8. ControllerChangeHandler • Responsible for swapping Controllers • Performs animations and transitions between Controllers.
  9. 9. ChangeHandlers ControllerChangeHanlder SimpleSwapChangeHandler AnimatorChangeHandler FadeChangeHandler HorizontalChangeHandler VerticalChangeHandler TransitionChangeHandler AutoTransitionChangeHandler
  10. 10. ControllerTransaction • Backstack entry • Used to define data about adding Controllers.
  11. 11. Controller/Activity Components Router Backstack ControllerTransaction ChangeHandlers Child Controller
  12. 12. Official Lifecycle
  13. 13. Actual lifecycle https://distillery.com/blog/innovating-android-development-introduction-conductor/ Kirill Akhmetov ©
  14. 14. Example 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, controllerContainer, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(ParentController())) } } override fun onBackPressed() { if (!router.handleBack()) { super.onBackPressed() } } } class ParentController : Controller() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.controller_parent, container, false) } }
  15. 15. Example 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, controllerContainer, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(ParentController())) } } }
  16. 16. Example 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, controllerContainer, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(ParentController())) } } }
  17. 17. Example override fun onBackPressed() { if (!router.handleBack()) { super.onBackPressed() } }
  18. 18. Example class ParentController : Controller() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.controller_parent, container, false) } }
  19. 19. Child Routers & Controllers public final Router getChildRouter(@NonNull ViewGroup container) • Creates router to push child controllers • Useful to create advanced layouts, such as Master/Detail. • Each child controller have reference to parent via getParent() method
  20. 20. Target Controllers • setTargetController() creates and saves link to the target controller. • Link to the target controller will be saved and restored when controller will be recreated
  21. 21. ChangeHandlerFrameLayout <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout"> <LinearLayout> <android.support.design.widget.AppBarLayout android:id="@+id/appbar_view"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar_view"/> <FrameLayout android:id="@+id/toolbar_content_view"/> </android.support.design.widget.AppBarLayout> <com.bluelinelabs.conductor.ChangeHandlerFrameLayout android:id="@+id/controller_container" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <android.support.design.widget.NavigationView android:id="@+id/navigation_drawer_view"/> </android.support.v4.widget.DrawerLayout>
  22. 22. ChangeHandlerFrameLayout public class ChangeHandlerFrameLayout extends FrameLayout implements ControllerChangeListener { private int inProgressTransactionCount; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return (inProgressTransactionCount > 0) || super.onInterceptTouchEvent(ev); } @Override public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { inProgressTransactionCount++; } @Override public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { inProgressTransactionCount--; } }
  23. 23. Lifecycle Listener • LifecycleListener listens all contoller’s lifecycle events. • It is very useful to integrate other libraries like Mosby(MVP, MVI), Android Architecture Components and others.
  24. 24. public static abstract class LifecycleListener { public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { } public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { } public void preCreateView(@NonNull Controller controller) { } public void postCreateView(@NonNull Controller controller, @NonNull View view) { } public void preAttach(@NonNull Controller controller, @NonNull View view) { } public void postAttach(@NonNull Controller controller, @NonNull View view) { } public void preDetach(@NonNull Controller controller, @NonNull View view) { } public void postDetach(@NonNull Controller controller, @NonNull View view) { } public void preDestroyView(@NonNull Controller controller, @NonNull View view) { } public void postDestroyView(@NonNull Controller controller) { } public void preDestroy(@NonNull Controller controller) { } public void postDestroy(@NonNull Controller controller) { } public void preContextAvailable(@NonNull Controller controller) { } public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) { } public void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) { } public void postContextUnavailable(@NonNull Controller controller) { } public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) { } public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) { } public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) { } public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) { } }
  25. 25. Retain View Modes • RELEASE_DETACH • RETAIN_DETACH
  26. 26. Router Pager Adapter • Subclass of PagerAdapter • Used to show pages on screen with saving and restoring states of all child backstacks for each pages.
  27. 27. DialogController • Right now it’s not a part of Conductor framework. • To show dialog we need add Controller to view hierarchy. • Adds an empty view to hierarchy and shows dialog in onAttach() method.
  28. 28. Constructors class BaseController : Controller { constructor() : super() constructor(args: Bundle?) : super(args) }
  29. 29. Constructors class ContactDetailsController : BaseController { companion object { private const val KEY_CONTACT_ID = "ContactDetailsController.contact_id" fun newInstance(contactId: Int): ContactDetailsController { return ContactDetailsController(Bundle().apply { putInt(KEY_CONTACT_ID, contactId) }) } } }
  30. 30. Constructors class ContactDetailsController : BaseController { constructor() : super() constructor(args: Bundle?) : super(args) private val contactId: Int by lazy { args.getInt(KEY_CONTACT_ID) } }
  31. 31. Integration other libs • Integration with • Mosby • Android Architecture Components(ver. 0.1.1) • RxJava1, 2
  32. 32. Kotlin Android extensions object ViewBinder { fun setup(target: Any, view: View) fun tearDown(target: Any) }
  33. 33. Kotlin Android extensions fun <V : View> Controller.bindView(id: Int) : ReadOnlyProperty<Controller, V> = required(id, viewFinder) fun <V : View> Controller.bindOptionalView(id: Int) : ReadOnlyProperty<Controller, V?> = optional(id, viewFinder) fun <V : View> Controller.bindViews(vararg ids: Int) : ReadOnlyProperty<Controller, List<V>> = required(ids, viewFinder) fun <V : View> Controller.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty<Controller, List<V>> = optional(ids, viewFinder)
  34. 34. Kotlin Android extensions // Usage private val drawerLayout by bindView<DrawerLayout>(R.id.drawer_layout) private val toolbarView by bindView<Toolbar>(R.id.toolbar_view) private val navigationView by bindView<NavigationView>(R.id.navigation_drawer_view) private val controllerContainer by bindView<ViewGroup>(R.id.controller_container)
  35. 35. Sample App • Written on Kotlin • Uses base ChangeHandlers • Uses Drawer
  36. 36. Base Controller abstract class BaseController : Controller { constructor() : super() constructor(args: Bundle?) : super(args) }
  37. 37. Base Controller abstract class BaseController : Controller { constructor() : super() constructor(args: Bundle?) : super(args) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { val view = inflateView(inflater, container) ViewBinder.setup(this, view) onBindView(view) return view } abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View override fun onDestroyView(view: View) { ViewBinder.tearDown(this) super.onDestroyView(view) } open fun onBindView(view: View) { } }
  38. 38. Parent Controller class ParentController : BaseController { interface ToolbarController { val title: String? } constructor() : super() constructor(args: Bundle?) : super(args) }
  39. 39. Parent Controller class ParentController : BaseController { private val controllerContainer by bindView<ViewGroup>(R.id.controller_container) private lateinit var controllersRouter: Router override fun onBindView(view: View) { super.onBindView(view) setHasOptionsMenu(true) controllersRouter = getChildRouter(controllerContainer) } }
  40. 40. Parent Controller private var currentItemId: Int? = null private fun handleMenuClick(item: MenuItem): Boolean { if (currentItemId == item.itemId) { return true } return when (item.itemId) { R.id.menu_contacts -> { startController(ContactsController(), item.itemId) true } R.id.menu_profile -> { startController(ProfileController(), item.itemId) true } else -> false } } private fun <T> startController(controller: T, itemId: Int) where T : Controller, T : ToolbarController { controllersRouter.setRoot(RouterTransaction.with(controller)) toolbarView.title = controller.title currentItemId = itemId }
  41. 41. Parent Controller private var currentItemId: Int? = null private fun handleMenuClick(item: MenuItem): Boolean { if (currentItemId == item.itemId) { return true } return when (item.itemId) { R.id.menu_contacts -> { startController(ContactsController(), item.itemId) true } R.id.menu_profile -> { startController(ProfileController(), item.itemId) true } else -> false } } private fun <T> startController(controller: T, itemId: Int) where T : Controller, T : ToolbarController { controllersRouter.setRoot(RouterTransaction.with(controller)) toolbarView.title = controller.title currentItemId = itemId }
  42. 42. Contacts Controllers
  43. 43. Contacts Controllers class ContactsController : BaseController, ParentController.ToolbarController { private val recyclerView by bindView<RecyclerView>(R.id.contacts_list) override fun onBindView(view: View) { super.onBindView(view) recyclerView.adapter = ContactsAdapter(object : ContactsAdapter.Listener { override fun onContactSelected(contactData: ContactData) { rootRouter()?.pushController( RouterTransaction.with( ContactDetailsController.newInstance(contactData.id) ) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) } }) }
  44. 44. Contacts Controllers class ContactsController : BaseController, ParentController.ToolbarController { private val recyclerView by bindView<RecyclerView>(R.id.contacts_list) override fun onBindView(view: View) { super.onBindView(view) recyclerView.adapter = ContactsAdapter(object : ContactsAdapter.Listener { override fun onContactSelected(contactData: ContactData) { rootRouter()?.pushController( RouterTransaction.with( ContactDetailsController.newInstance(contactData.id) ) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) } }) }
  45. 45. Contact Details Controller
  46. 46. Contact Details Controller class ContactDetailsController : BaseController { constructor() : super() constructor(args: Bundle?) : super(args) private val contactId: Int by lazy { args.getInt(KEY_CONTACT_ID) } companion object { private const val KEY_CONTACT_ID = "ContactDetailsController.contact_id" fun newInstance(contactId: Int): ContactDetailsController { return ContactDetailsController(Bundle().apply { putInt(KEY_CONTACT_ID, contactId) }) } } }
  47. 47. Contact Details Controller override fun onAttach(view: View) { super.onAttach(view) ContactsManager.find(contactId)?.apply { nameTextView.text = name phoneTextView.text = phone addressTextView.text = address emailTextView.text = email } }
  48. 48. Contact Details Controller override fun onBindView(view: View) { super.onBindView(view) initToolbar() } private fun initToolbar() { toolbarView.setNavigationOnClickListener { router.popCurrentController() } toolbarView.inflateMenu(R.menu.contact_details_menu) toolbarView.setOnMenuItemClickListener { item -> return@setOnMenuItemClickListener when (item.itemId) { R.id.edit -> { router.pushController( RouterTransaction.with( EditContactController.newInstance(contactId) ) .pushChangeHandler(FadeChangeHandler()) .popChangeHandler(FadeChangeHandler()) ) true } else -> false } } }
  49. 49. Contact Details Controller override fun onBindView(view: View) { super.onBindView(view) initToolbar() } private fun initToolbar() { toolbarView.setNavigationOnClickListener { router.popCurrentController() } toolbarView.inflateMenu(R.menu.contact_details_menu) toolbarView.setOnMenuItemClickListener { item -> return@setOnMenuItemClickListener when (item.itemId) { R.id.edit -> { router.pushController( RouterTransaction.with( EditContactController.newInstance(contactId) ) .pushChangeHandler(FadeChangeHandler()) .popChangeHandler(FadeChangeHandler()) ) true } else -> false } } }
  50. 50. Pros and Cons • Less code • Simple lifecycle management • Synchronous transactions • Easy to create support for popular frameworks (Mosby, Rx, Architecture components) • No documentation • Bad compatibility with some libraries(e.g. Easy Permission) • Maintenance (last commit was in October)
  51. 51. https://www.appbrain.com/stats/libraries/details/conductor/conductor
  52. 52. Thanks

×