developing apps for emerging markets
TRANSCRIPT
ONLINE-OFFLINEDeveloping Apps for Emerging Markets
OFF GRID ELECTRIC
AGENDA
ARCHITECTURELIBRARIES
CHALLENGES
ARCHITECTURE
BE USEFUL
My App ‣Store data locally ‣Separate UI and network ‣Queue requests
MVP(Model View Presenter)
EVENT BUS
DATABASE
REPOSITORY
REPOSITORY
ACTIVITY
FRAGMENT
JOBS
PRESENTER
PRESENTER
SERVICE
DISPLAY A VIEW
PRESENTER
SERVICE
ACTIVITY
EVENT BUS
DATABASEREPOSITORY
TAKE AN ACTION
PRESENTER
SERVICE
ACTIVITY
EVENT BUS
DATABASEREPOSITORY
JOB NETWORK
APP SERVER
AGREE ON
‣Conflict Resolution ‣Adding Timestamps to Requests ‣Bundling Requests ‣Use of Status Fields
EVENT BUS
DATABASE
REPOSITORY
REPOSITORY
ACTIVITY
FRAGMENT
JOBS
PRESENTER
PRESENTER
SERVICE
LIBRARIES
WHAT WE USE
‣Realm ‣EventBus ‣Android Job
STORE DATA LOCALLY
REALM
▸Easy to Set Up
▸Faster than ORMs
▸Has a Fluent API
REALM - MODEL CLASS
@RealmClasspublic class Place implements RealmModel{
}
REALM - MODEL CLASS
@RealmClasspublic class Place implements RealmModel{ @PrimaryKey private String localId;
@Index private Long remoteId;private Gps location; …
}
REALM - MODEL CLASS
localId remoteId location
19444498-2a40… 1458260
65031f36-bde9…
e85c9757-f546…
REALM - ADDING A RECORD
public class PlaceRepository implements Repository<Place> { @Override public void add (final Place item) {
}
…
REALM - ADDING A RECORD
public class PlaceRepository implements Repository<Place> { @Override public void add (final Place item) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction( (realm) -> { realm.copyToRealmOrUpdate( item ); } ); realm.close(); }
…
REALM - QUERY RESULT
private static final String PLACE_ID = "id";
@Overridepublic Place toResult (Realm realm){ return realm.where( Place.class )
}
REALM - QUERY RESULT
private static final String PLACE_ID = "id";
@Overridepublic Place toResult (Realm realm){ return realm.where( Place.class ) .equalTo( PLACE_ID, placeId ) .findFirst();}
SEPARATE UI AND NETWORK
… WHEN YOU HAVE LOTS OF OBJECTS THAT ARE POTENTIAL EVENT SOURCES.
Martin Fowler
EVENTBUS
EVENTBUS - SETUP
@Provides @Singletonpublic Bus provideBus (){ return new Bus( EventBus.builder() .logNoSubscriberMessages( BuildConfig.DEBUG ) .throwSubscriberException( false ) .build() );}
EVENTBUS - REGISTER
public class LeadsMapPresenter { private final Bus bus; private LeadsMapView leadsMapView; @Inject LeadsMapPresenter (Bus bus) { this.bus = bus; } …
EVENTBUS - REGISTER
public class LeadsMapPresenter { public void attachView (LeadsMapView view) { this.leadsMapView = view; bus.register( this ); }
EVENTBUS - REGISTER
public class LeadsMapPresenter { public void attachView (LeadsMapView view) { this.leadsMapView = view; bus.register( this ); }
public void detachView () { this.leadsMapView = null; bus.unregister( this ); }
TAKE AN ACTION
PRESENTER
SERVICE
ACTIVITY
EVENT BUS
DATABASEREPOSITORY
JOB NETWORK
EVENTBUS - POST EVENT
api.submitPlace( place ).enqueue( new Callback<Place>(){
…JOB
EVENTBUS - POST EVENT
api.submitPlace( place ).enqueue( new Callback<Place>() { @Override public void onResponse (Call<Place> call, Response<Place> response) { if ( response.isSuccessful() ) { }
…JOB
EVENTBUS - POST EVENT
api.submitPlace( place ).enqueue( new Callback<Place>() { @Override public void onResponse (Call<Place> call, Response<Place> response) { if ( response.isSuccessful() ) { Place updatedPlace = response.body(); bus.post( new PlaceSubmissionSuccessEvent( updatedPlace ) ); }
…JOB
EVENTBUS - RETRIEVE EVENT
public class LeadsMapPresenter { @Subscribe public void onPlaceSubmissionSuccess (PlaceSubmissionSuccessEvent event) { leadsMapView.displaySuccessMessage( PLACE_SUCCESS ); }
…
PRESENTER
QUEUE REQUESTS
ANDROID JOB
ANDROID JOB
‣Alarm Manager
‣Job Scheduler
‣GCM Network Manager
ANDROID JOB
JOB MANAGER
JOB CREATOR
ANDROID JOB
JOB MANAGERPROVIDER
JOB
JOB CREATOR
PROVIDER
JOB
PROVIDER
JOB
ANDROID JOB
JOB MANAGERPROVIDER
JOB
JOB CREATOR
PROVIDER
JOB
PROVIDER
JOB
JOB REQUEST
ANDROID JOB
JOB MANAGERPROVIDER
JOB
JOB CREATOR
PROVIDER
JOB
PROVIDER
JOB
JOB REQUEST
JOB
ANDROID JOB - CREATOR
@Singletonpublic class ConfettiJobCreator implements JobCreator{ @Inject Map<String, Provider<Job>> jobs; @Override public Job create (String tag) {
}}
ANDROID JOB - CREATOR
@Singletonpublic class ConfettiJobCreator implements JobCreator { @Inject Map<String, Provider<Job>> jobs; @Override public Job create (String tag) { Provider<Job> jobProvider = jobs.get( tag ); return jobProvider.get(); }}
ANDROID JOB - JOB EXECUTION
@Overrideprotected Result onRunJob (final Params params){
} JOB
ANDROID JOB - JOB EXECUTION
@Overrideprotected Result onRunJob (final Params params) { PersistableBundleCompat extras = params.getExtras(); String placeId = extras.getString( PARAM_PLACE_ID );
} JOB
ANDROID JOB - JOB EXECUTION
@Overrideprotected Result onRunJob (final Params params) { PersistableBundleCompat extras = params.getExtras(); String placeId = extras.getString( PARAM_PLACE_ID ); if ( submitRequest( placeId ) ) { return Result.SUCCESS; } return Result.FAILURE;} JOB
ANDROID JOB
JOB MANAGERPROVIDER
JOB
JOB CREATOR
JOB REQUEST
JOB
ANDROID JOB - SCHEDULING
public static JobRequest buildJobRequest (String placeId) { PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putString( PARAM_PLACE_ID, placeId );
}
ANDROID JOB - SCHEDULING
public static JobRequest buildJobRequest (String placeId) { PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putString( PARAM_PLACE_ID, placeId ); return new JobRequest.Builder( SendPlaceRequestJob.JOB_TAG )
}
ANDROID JOB - SCHEDULING
public static JobRequest buildJobRequest (String placeId) { PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putString( PARAM_PLACE_ID, placeId ); return new JobRequest.Builder( SendPlaceRequestJob.JOB_TAG ) .setExecutionWindow( 10_000L, 20_000L ) .setRequiredNetworkType( JobRequest.NetworkType.CONNECTED )
}
ANDROID JOB - SCHEDULING
public static JobRequest buildJobRequest (String placeId) { PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putString( PARAM_PLACE_ID, placeId ); return new JobRequest.Builder( SendPlaceRequestJob.JOB_TAG ) .setExecutionWindow( 10_000L, 20_000L ) .setRequiredNetworkType( JobRequest.NetworkType.CONNECTED ) .setExtras( extras ) }
ANDROID JOB - SCHEDULING
public static JobRequest buildJobRequest (String placeId) { PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putString( PARAM_PLACE_ID, placeId ); return new JobRequest.Builder( SendPlaceRequestJob.JOB_TAG ) .setExecutionWindow( 10_000L, 20_000L ) .setRequiredNetworkType( JobRequest.NetworkType.CONNECTED ) .setExtras( extras ) .setRequirementsEnforced( true ) .build(); }
ANDROID JOB - SCHEDULING
jobManager.schedule( );
ANDROID JOB - SCHEDULING
jobManager.schedule( SendPlaceRequestJob.buildJobRequest( id ) );
JOB REQUEST
JOB QUEUE
WHAT WE USE
‣Realm ‣EventBus ‣Android Job
CHALLENGES
WHAT’S NEXT?
‣RxJava ‣Mapbox ‣Push Notifications