Router


Source link: https://github.com/eyeem/router

Router 2.0

This project is based off routable-android as it seemed a good starting point. It's definitely worth checking out.

NOTE: At the time of the writing this document, the library is still in development, API is subject to changes, but already used in production.

Differences from 1.0 (routable-android)

  • drop Activity/Intent dependency
  • drop any launching Activity responsibility
  • leverage dynamic configuration (e.g. YAML file) over static Java code mappings
  • custom developer definied output of the router
  • delegate output creation to configurable developer written set of plugins
  • parametrized but logicless configuration
  • router-validator as an optional tool to aid development

Usage

Gradle

repositories {

  maven {

url 'https://oss.sonatype.org/content/repositories/snapshots/'
  
}
 
}
  dependencies {

  compile 'com.eyeem.router:router:0.0.9-SNAPSHOT' 
}

Configuration file

It’s best to start with describing what paths your app will have and what kind of output they produce respectively. In this example we’ll use YAML to do so but you could use any other format as router expects parsed java object and does not directly depend on any format.

--- 'home' :
type: 'home'
decorators :
  - TrackingDecorator
  - ViewPagerDecorator :

tabNumber : '%{
tabNumber
}
'

pages :

  - 'discover'

  - 'feed/follow'

  - 'news'

  - 'user/me/photos' 'user/:id/photos' :
request :
  path : '/v2/users/%{
id
}
/photos'
  jsonpath : 'photos.items'
type : 'userPhotos'
decorators :
  - CoordinatorLayoutInstigator
  - RecyclerViewDecorator etc...

As you can see, there are 2 paths: home and user/:id/photos. Let’s skip the explanation of inner parts of the YAML file for now and create a router instance.

Yaml yaml = new Yaml();
  // using snake yaml parser for android Map<String, Object> routerMap = (Map<String, Object>) yaml.load(Assets.loadAssetTextAsString(this, "navigation.yaml"));
  Router router = RouterLoader.prepare().load(routerMap);

Given the path, you can obtain the output now, e.g.:

// default implementation of AbstractRouter will return bundle Bundle bundle = router.outputFor("user/16/photos");

What you do with this output now it’s up to you. You can pass it along with intent somewhere, set as an argument of the fragment ...pretty much everything. This is outside of scope of this document though.

Router Plugins

Going back to configuration file, you can observe that router paths have 0 indentation level. Indentation level 1 have all the plugins. As router matches certain path, it will then delegate bundle creation to the plugins it has knowledge of. If certain plugin is missing, router will just do nothing. For now we’re generating an empty bundle, as we haven’t registered any plugins so let’s change that.

Router router = RouterLoader
.prepare()
.plugin(new RequestPlugin())
.load(routerMap);

A plugin implementation can look something like this:

public class RequestPlugin extends RouterLoader.Plugin {

  public final static String KEY_REQUEST_BUILDER = "request_builder";
  // this is how router knows where to delegate bundle creation
 public RequestPlugin() {
 super("request");
 
}

  @Override public void bundleFor(

 Router.RouteContext context, // access local & global params like :id

 Object config, // plugin node data to handle

 Bundle bundle // output bundle that will be produced by Router
 )
 {

 Map map = (Map) config;

 String path = (String) map.get("path");

  RequestBuilder requestBuilder = EyeEm.path(path);

  if (map.containsKey("jsonpath")) {

 requestBuilder.jsonpath((String)map.get("jsonpath"));

 
}

  // put request builder into output bundle

 bundle.putSerializable(KEY_REQUEST_BUILDER, requestBuilder);

 
}
 
}

Parametrization

Path params (a.k.a. local params)

Given path from our sample:

Bundle bundle = router.outputFor("user/16/photos?showNSFW=false")

Then having obtained router context

Router.RouteContext routeContext = /*...*/ ; context.getParams().get("id");

 // 16 context.getParams().get("showNSFW");
 // false

Global params

You can set up params that will be always available globally in the RouteContext, e.g.

Router router = RouterLoader
.prepare()
.plugin(new RequestPlugin())
.load(routerMap)
.globalParam("isTablet", true)
.globalParam("isPhone", false)

Then having obtained router context anywhere:

Router.RouteContext routeContext = /*...*/ ; context.getParams().get("isTablet");
 // true context.getParams().get("isPhone");
  // false

Extra param

You can pass an extra param to the path, e.g.:

Bundle extra = new Bundle();
 extra.putSerializable("something", "extra");
 Bundle bundle = router.outputFor("user/16/photos?showNSFW=false", extra);

Then having obtained router context:

Router.RouteContext routeContext = /*...*/ ; routeContext.getExtras().getString("something");
 // extra

Node parametrization

At the path resolving time, router will scan the map and replace any params with values, so, given following mapping:

'user/:id/photos' :
request :
  path : '/v2/users/%{
id
}
/photos'
  jsonpath : 'photos.items'

By the time we reach the appropriate plugin, the value of the path will be already computed:

Bundle bundle = router.outputFor("user/16/photos");
  // inside a plugin void outputFor(Router.RouteContext context, Object config, Bundle bundle) {

Map map = (Map) config;
String path = (String) map.get("path");
 // /v2/users/16/photos 
}

Validation

NOTE: This part is optional and supports only YAML format.

Moving all the router configuration outside of Java code created a peril of typos. For this very reason we’ve wrote a small validator in form of gradle plugin, that will generate java class containing statically typed paths, resources and similar. In case there is a typo, there will be an error yielded during compilation time.

Gradle setup

buildscript {

  repositories {

maven {

 url 'https://oss.sonatype.org/content/repositories/snapshots/'

}

  
}

dependencies {

classpath 'com.eyeem.router:router-validator:0.0.9-SNAPSHOT'
  
}
 
}
apply plugin: 'com.eyeem.routervalidator'  router {

  path = "src/main/assets/navigation.yaml"
  packageName = "com.eyeem.router"
  decoratorsPackageName = "com.eyeem.decorator"
  resourcePackageName = "com.baseapp.eyeem" 
}

Example Java Usage:

Bundle bundle = router.outputFor(RouterConstants.PATH_USER(id));
 

Key Advantages

Single responsibility principle

Router paths describe and define navigation points of the app. Path nodes describe what these paths are made of.

High Level Organization + Easy Learning Curve

Single and simple configuration file allows newcomers and veterans quickly grasp understanding of relations between parts of the app.

Dynamic yet Strict

Freedom of writing custom plugins is moderated by parametrized & logicless approach to configuration parsing. If you feel like you need an ‘if’ clause somewhere you are probably designing something wrong.

Complex URL handling

A bonus you get for free. If your company has a website, you can now easily route people to the equivalent parts of the Android app. If your company is sending push notifications, you can now send a path, as a place to go and never code again any push notification logic in the app.

Painless migration

Let’s say you want to migrate from volley requests to retrofit. Since your requests are defined in a YAML file anyway, all you need is to change underlying router plugin & network layer implementation.

Actually nothing stops you from migrating straight to iOS since YAML contains no java code.

A/B Testing + Live Override

Once you have established your router configuration file, you can have many permutations of it and sideload them from app’s asset folder or over the air. You can sideload parts of configuration, e.g. change mapping of a single path.

Real life example

Before: After:
'home' :
type: 'home'
decorators :
  - ViewPagerDecorator :

tabNumber : '%{
tabNumber
}
'

pages :

  - 'discover'

  - 'feed/follow'

  - 'news'

  - 'user/me/photos'
'home' :
type: 'home'
decorators :
  - ViewPagerDecorator :

tabNumber : '%{
tabNumber
}
'

pages :

  - 'discover'

  - 'feed/follow'

  - 'news'

  - 'user/me/photos'

  - 'missions' # the extra page

License

Copyright (c) 2013 Turboprop, Inc. (http://usepropeller.com/) Copyright 2016 EyeEm Mobile GmbH  Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License. You may obtain a copy of the License at
  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 

Resources

Custom Lint checks for Android development.

Automatic resource exporter plugin for android projects: generating density specific drawables from PDF files.

Mocking Client for Retrofit.

This library helps developer to read data from credit card: card number, expired date, card type.

A simple lib for zoom images on event click.

Java library used to read and extract public data from NFC EMV credit cards.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes