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

Navigation patterns on Android and something new

Navigation patterns on Android and something new

Matheus Cassiano Candido

August 25, 2017
Tweet

More Decks by Matheus Cassiano Candido

Other Decks in Technology

Transcript

  1. "

  2. • Usually done using Intents • Composed of tasks •

    Tasks have launch modes • Backstack control is messy • Activities know too much Navigation
  3. Fragments •Supposed to be a smaller Activity •They have their

    Own Lifecycle •Have their own back stack management
  4. • Transactions are asynchronous • Sometimes this yields a bunch

    of IllegalStateExceptions • Enters commitNow() • BUT you can’t add transactions to the back stack: might break the order of async transactions Fragment trouble
  5. • The Android OS can kill any activity in our

    process to free up memory • To make this transparent to the user, it gives us onSaveInstanceState() and saves state into a Bundle • If commit() and onSaveInstanceState() are called at the same time: $ • That transaction won’t be remembered as it was never recorded as part of the Activity IllegalStateExceptions
  6. • Favoring ViewGroups • Simple Stack (Zhuinden) • Conductor (BlueLines)

    • Scoop (Lyft) • Flow (Square) & Mortar (sort of dead?) • Pancakes (Matt Logan) • Magellan (Wealthfront) Fragment alternatives Newish trend: single activity apps. Jake Wharton/Square started doing this way back though.
  7. • Rewrite of the whole Fragment whole thing. Most of

    their components map to something Fragments offer • Controller -> Fragment • Router -> FragmentManager • ControllerTransaction -> FragmentTransaction • ControllerChangeHandler -> Describes animation in and out of Controllers Conductor
  8. • Simple lifecycle • Only implementation (that I know of)

    that receives onActivityResult, onRequestPermissionResult, etc callbacks • popCurrentController(), popController(), pushController(), replaceTopController(), popToRoot() and the list goes on Conductor
  9. • Encapsulation/Decoupling • Beautiful transitions on lower APIs • Transactions

    are executed synchronously • The equivalent implementation of a FragmentTransaction is Parcelable so it saves its state Advantages adopting this path
  10. • Also known as Directors or Application Controllers (as described

    in Patterns of Enterprise Application Architecture by Fowler) • Implemented successfully for iOS by Soroush Khanlou and is buzzing right now • It can be a simple POJO Coordinator
  11. • Can be used to hold user input/data during flows

    (e.g.: registration flows, photo upload, multiple step flows w/ user input in general) • Allow easy reuse of entire flows inside of your application (I have actually implemented this in production and it works!) Coordinator
  12. • In iOS the main requirement to implement this pattern

    is the NavigationController • A navigation controller object manages the currently displayed screens using the navigation stack, which is represented by an array of view controllers. Coordinator
  13. So the only thing we need for this to work

    is access to the back stack!
 '
  14. public interface Coordinator {
 
 void start();
 
 void setDetachListener(CoordinatorDetachListener

    listener);
 }
 public interface CoordinatorDetachListener {
 
 void onDetached(Coordinator coordinator);
 }
 public interface BaseCoordinatorListener {
 
 void controllerPopped(Controller controller);
 
 }
  15. public class AppCoordinator implements CoordinatorDetachListener {
 
 private final Router

    router;
 private HomeCoordinator homeCoordinator;
 
 public AppCoordinator(Router router) {
 this.router = router;
 }
 
 public void start() {
 homeCoordinator = new HomeCoordinator(router);
 homeCoordinator.setDetachListener(this);
 homeCoordinator.start();
 }
 
 
 @Override
 public void onDetached(Coordinator coordinator) {
 homeCoordinator = null;
 }
 }

  16. public class AppCoordinator implements CoordinatorDetachListener {
 
 private final Router

    router;
 private HomeCoordinator homeCoordinator;
 
 public AppCoordinator(Router router) {
 this.router = router;
 }
 
 public void start() {
 homeCoordinator = new HomeCoordinator(router);
 homeCoordinator.setDetachListener(this);
 homeCoordinator.start();
 }
 
 
 @Override
 public void onDetached(Coordinator coordinator) {
 homeCoordinator = null;
 }
 }

  17. public class AppCoordinator implements CoordinatorDetachListener {
 
 private final Router

    router;
 private HomeCoordinator homeCoordinator;
 
 public AppCoordinator(Router router) {
 this.router = router;
 }
 
 public void start() {
 homeCoordinator = new HomeCoordinator(router);
 homeCoordinator.setDetachListener(this);
 homeCoordinator.start();
 }
 
 
 @Override
 public void onDetached(Coordinator coordinator) {
 homeCoordinator = null;
 }
 }

  18. public class HomeCoordinator implements Coordinator, HomeNavListener, … {
 
 private

    final Router router;
 private CoordinatorDetachListener detachListener;
 
 public HomeCoordinator(Router router) {…}
 
 @Override
 public void start() {…}
 
 @Override
 public void setDetachListener(CoordinatorDetachListener listener) {…}
 
 @Override
 public void onNavigationClicked() {…}
 
 @Override
 public void onTransitionsClicked(Controller fromController) {…}
 
 @Override
 public void controllerPopped(Controller controller) {…}
 
 }

  19. public class HomeCoordinator implements Coordinator, HomeNavListener,… {
 
 private final

    Router router;
 private CoordinatorDetachListener detachListener;
 
 public HomeCoordinator(Router router) {…}
 
 @Override
 public void start() {
 HomeController homeController = new HomeController();
 homeController.homeListener = this;
 homeController.baseCoordinatorListener = this;
 router.setRoot(RouterTransaction.with(homeController));
 }
 
 @Override
 public void setDetachListener(CoordinatorDetachListener listener) {…} ...

  20. public class HomeCoordinator implements Coordinator, HomeNavListener,… {
 
 private final

    Router router;
 private CoordinatorDetachListener detachListener;
 
 public HomeCoordinator(Router router) {…}
 
 @Override
 public void start() {
 HomeController homeController = new HomeController();
 homeController.homeListener = this;
 homeController.baseCoordinatorListener = this;
 router.setRoot(RouterTransaction.with(homeController));
 }
 
 @Override
 public void setDetachListener(CoordinatorDetachListener listener) {…} ...

  21. public class HomeController extends SomeBaseController {
 
 HomeNavListener homeListener;
 


    public HomeController() {}
 
 @NonNull
 @Override
 protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
 return inflater.inflate(R.layout.controller_home, container, false);
 }
 
 @Override
 protected void onViewBound(@NonNull View view) {
 super.onViewBound(view);
 
 ...
 } void onModelRowClick(Model model, int position) {…}
 ...
 
 }
  22. void onModelRowClick(Model m, int position) { switch (model) {
 case

    NAVIGATION:
 homeListener.onNavigationClicked();
 break;
 case TRANSITIONS:
 homeListener.onTransitionsClicked(this);
 break; ... } }
  23. public class HomeCoordinator implements Coordinator, HomeNavListener, BaseCoordinatorListener {
 
 private

    final Router router;
 private CoordinatorDetachListener detachListener;
 
 public HomeCoordinator(Router router) {…}
 
 @Override
 public void start() {…}
 
 @Override
 public void setDetachListener(CoordinatorDetachListener listener) {…}
 
 @Override
 public void onNavigationClicked() {…}
 
 @Override
 public void onTransitionsClicked(Controller fromController) { router.pushController(TransitionDemoController .getRouterTransaction(0, fromController)); }
 
 @Override
 public void controllerPopped(Controller controller) {…}
 
 }
  24. public abstract class SomeBaseController extends Controller { ... 
 @Override


    public void onDestroy() { if (baseCoordinatorListener != null) baseCoordinatorListener.controllerPopped(this);
 super.onDestroy();
 }
 }

  25. public abstract class SomeBaseController extends Controller { ... 
 @Override


    public void onDestroy() { if (baseCoordinatorListener != null) baseCoordinatorListener.controllerPopped(this);
 super.onDestroy();
 }
 }

  26. public class HomeCoordinator implements Coordinator, HomeNavListener, BaseCoordinatorListener {
 
 private

    final Router router;
 private CoordinatorDetachListener detachListener;
 
 public HomeCoordinator(Router router) {…}
 
 @Override
 public void start() {…}
 
 @Override
 public void setDetachListener(CoordinatorDetachListener listener) {…}
 
 @Override
 public void onNavigationClicked() {…}
 
 @Override
 public void onTransitionsClicked(Controller fromController) {…}
 
 @Override
 public void controllerPopped(Controller controller) { if (controller instanceof HomeController) { // let’s pretend it might not be null
 detachListener.onDetached(this);
 } }
 }
  27. public class AppCoordinator implements CoordinatorDetachListener {
 
 private final Router

    router;
 private HomeCoordinator homeCoordinator;
 
 public AppCoordinator(Router router) {
 this.router = router;
 }
 
 public void start() {
 homeCoordinator = new HomeCoordinator(router);
 homeCoordinator.setDetachListener(this);
 homeCoordinator.start();
 }
 
 
 @Override
 public void onDetached(Coordinator coordinator) {
 homeCoordinator = null;
 }
 }

  28. public class AppCoordinator implements CoordinatorDetachListener {
 
 private final Router

    router;
 private HomeCoordinator homeCoordinator;
 
 public AppCoordinator(Router router) {
 this.router = router;
 }
 
 public void start() {
 homeCoordinator = new HomeCoordinator(router);
 homeCoordinator.setDetachListener(this);
 homeCoordinator.start();
 }
 
 
 @Override
 public void onDetached(Coordinator coordinator) {
 homeCoordinator = null;
 }
 }

  29. • Isolates navigation from view layer • Whole flows reusability

    • Makes controllers reusable in multiple flows • Multi-step forms is a breeze to implement • Can be used in only some parts of your app! • Testing is easier Overview
  30. • Could not find a way to properly survive Process

    Death (configuration change is ok) • On boarding new users (not a trivial and widely adopted pattern, especially on Android) • Memory Management: can lead to memory leaks if not handled carefully. Clean up after your coordinators. • You *might* end up with a huge Coordinator (happened with me with a flow of more than 13 screens) Problems
  31. •Use dependency injection • Don’t make your views see the

    coordinator directly, always make them pass through listeners (one method for each navigation action) • Use the coordinator for data mutation and views for fetching Recommendations