Building Modular and Reactive Android Applications

Building Modular and Reactive Android Applications

D1d94e8756f378c5021b09978699793f?s=128

Mouna Cheikhna

April 28, 2016
Tweet

Transcript

  1. Building Modular and Reactive Android Applications Mouna Cheikhna @monac1

  2. None
  3. None
  4. Structure

  5. None
  6. None
  7. + AsyncTask =

  8. + AsyncTask =

  9. Modularity Dagger 2 & MVP

  10. Modularity: Dagger 2

  11. None
  12. Dagger 2

  13. Dagger 2 • A compile-time dependency injection framework for Java

    and Android.
  14. Dagger 2 • A compile-time dependency injection framework for Java

    and Android. • An adaptation of Square’s version and now maintained by Google.
  15. Dagger 2 • A compile-time dependency injection framework for Java

    and Android. • An adaptation of Square’s version and now maintained by Google. • Dagger 2 aims to address many of the development and performance issues that have plagued reflection-based solutions.
  16. None
  17. Dagger API

  18. Dagger API public @interface Component {
 Class<?>[] modules() default {};


    Class<?>[] dependencies() default {};
 }
 public @interface Subcomponent {
 Class<?>[] modules() default {};
 }
 
 public @interface Module {
 }
  19. Dagger API

  20. Dagger API public @interface Provides {
 }
 
 public @interface

    MapKey {
 boolean unwrapValue() default true;
 }
 
 public interface Lazy<T> {
 T get();
 }
  21. None
  22. Component dependencies

  23. Component dependencies @Singleton @Component(modules = { AppModule.class, ApiModule.class }) 


    public interface AppComponent {
 
 void injectApplication(MyApp app);
 Application application();
 
 @ApplicationContext
 Context provideApplicationContext();
 }
  24. Component dependencies

  25. Component dependencies public class SearchView extends LinearLayout { 
 @ScopeSingleton(SearchComponent.class)

    
 @Component(dependencies = AppComponent.class) 
 public interface SearchComponent extends BaseComponent {
 void inject(SearchView searchView);
 @ApplicationContext
 Context provideApplicationContext();
 }
 }
  26. None
  27. Singleton

  28. Singleton @Module
 Class DripCoffeeModule {
 @Provides @Singleton
 CoffeeMaker provideCoffeeMaker ()

    {
 return new CoffeeMaker();
 }
 }
 
 //or
 @Singleton
 class CoffeeMaker {
 //...
 }
 
 @Component(modules = DripCoffeeModule.class) 
 @Singleton 
 interface CoffeeShopComponent {
 CoffeeMaker maker();
 }
  29. None
  30. Scoping

  31. Scoping @Scope
 @Retention(RUNTIME)
 public @interface ActivityScope {
 } @Module
 public

    Module ActivityModule { 
 @Provides @ActivityScope 
 public Activity provideActivity () {
 return mainActivity;
 }
 // Provide other activity dependencies
 }
 
 @ActivityScope
 @Component(dependencies = AppComponent.class, modules = {ActivityModule.class}) 
 public interface MainActivityComponent extends AppGraph {
 void inject(MainActivity mainActivity);
 }

  32. Scoping

  33. Scoping public class MainActivity {
 @Override 
 protected void onCreate(Bundle

    savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 DaggerMainActivityComponent.builder()
 .appComponent(MyApp.get(this).getComponent())
 .activityModule(new ActivityModule(this))
 .build().inject(this);
 }
 }

  34. None
  35. MapKey

  36. MapKey @MapKey(unwrapValue = true) 
 public @interface TaskIdentifier {
 TaskKey

    value();
 }
 
 @Provides(type = MAP)
 @TaskIdentifier(TAKE_PHOTO)
 BaseTask provideTakePhotoTask(@ApplicationContext Context context) {
 return new TakePhotoTask(context);
 }
 
 @Provides(type = MAP)
 @TaskIdentifier(PREVIEW)
 BaseTask providePreviewTask(@ApplicationContext Context context) { 
 return new PreviewTask(context);
 }

  37. MapKey

  38. MapKey @Provides @Singleton 
 public TasksMap provideTasksMap(Map<TaskKey, Provider<Task>> tasks) {


    return new TasksMap(tasks);
 } public class TasksRunner { 
 @Inject TasksMap tasksStore;
 }
  39. None
  40. Lazy

  41. Lazy //Example: Shared Preferences
 @Inject Lazy<SharedPreferences> lazySharedPrefs;
 void onSaveBtnClicked() {


    lazySharedPrefs.get()
 .edit().putString("status", "lazy...")
 .apply();
 } 
 //Example: Picasso
 @Inject Lazy<Picasso> lazyPicasso;
 private Bitmap loadBitmap(Uri uri) {
 if (uri == null) return null;
 return lazyPicasso.get()
 .load(uri).get();
 }
  42. Modularity: MVP

  43. None
  44. MVP

  45. MVP

  46. None
  47. MVP: Decouple UI from dependencies

  48. public class SearchView extends LinearLayout implements SearchScreen {
 @ScopeSingleton(SearchComponent.class)
 @Component(dependencies

    = AppComponent.class) 
 public interface SearchComponent extends BaseComponent {
 void inject(SearchView searchView);
 }
 @Inject SearchPresenter searchPresenter; 
 private void init(Context context) {
 DaggerSearchView_SearchComponent.builder()
 .appComponent(MyApp.get(getContext()).getComponent())
 .build().inject(this);
 
 }
 
 @OnClick(R.id.search_btn) public void search() { 
 searchPresenter.search(searchEditText.getText().toString())
 .subscribe(adapter);
 }
 } MVP: Decouple UI from dependencies
  49. None
  50. MVP: Decouple UI from dependencies

  51. MVP: Decouple UI from dependencies public interface SearchScreen extends PresenterScreen

    {
 //main methods used in this screen
 void search();
 }
 
 @ScopeSingleton(SearchComponent.class) 
 public class SearchPresenter extends BasePresenter<SearchScreen> {
 private SearchApi api;
 
 @Inject 
 public SearchPresenter(SearchApi api) {
 this.api = api;
 }
 
 public Observable<List<Result>> search(String query) {
 return api.search(query);
 }
 }
  52. None
  53. Modularity: Tips

  54. None
  55. Tip 1: Separate Debug behavior from Release Behavior.

  56. Tip 1: Separate debug behavior from release

  57. Tip 1: Separate debug behavior from release //in Main folder

    @Module
 public class ApiModule {
 @Provides @Singleton @ApiClient
 OkHttpClient provideApiClient(OkHttpClient client, List<Interceptor> networkInterceptors) {
 OkHttpClient okClient = client.clone();
 okClient.networkInterceptors().addAll(networkInterceptors);
 return okClient;
 }
 } }
 //In debug flavor 
 @Module 
 public class DebugApiModule { 
 @Provides @Singleton List<Interceptor> provideNetworkInterceptors() {
 return Arrays.asList(new StethoInterceptor(), new LoggingInterceptor());
 }
 }
 
 @Component(modules = { ApiModule.class, DebugApiModule.class,... })
 public interface AppComponent {
 //...
 }
 

  58. Tip 1: Separate debug behavior from release

  59. Tip 1: Separate debug behavior from release //In release flavor


    @Module 
 public class ReleaseApiModule {
 @Provides @Singleton
 List<Interceptor> provideNetworkInterceptors() { 
 return Arrays.asList(new AnalyticsInterceptor());
 }
 }
 
 @Component(modules = {ApiModule.class, ReleaseApiModule.class,...}) 
 public interface AppComponent {
 //...
 } and each app in each flavor use its component and is set in the manifest.
  60. Tip 2: Decouple Intent results from Activity/Fragment

  61. Tip 2: Decouple Intent results from Activity/Fragment

  62. Tip 2: Decouple Intent results from Activity/Fragment public final class

    ActivityResults {
 private final List<ActivityListener> activityListeners = new CopyOnWriteArrayList<>();
 
 public void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (!activityListeners.isEmpty()) {
 for (ActivityListener activityListener : activityListeners) {
 activityListener.onActivityResult(requestCode, resultCode, data);
 }
 } } 
 
 public void addListener(ActivityListener activityListener) {
 activityListeners.add(activityListener);
 }
 
 public void removeListener(ActivityListener activityListener) {
 activityListeners.remove(activityListener);
 }
 }
 
 public interface ActivityListener {
 void onActivityResult(int requestCode, int resultCode, Intent data);
 }
  63. Tip 2: Decouple Intent results from Activity/Fragment

  64. Tip 2: Decouple Intent results from Activity/Fragment @Module
 public class

    MyAppModule {
 @Provides @Singleton
 ActivityResults provideActivityResults() {
 return new ActivityResults();
 }
 }
 
 public class MyActivity { @Inject ActivityResults activityResults;
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 super.onActivityResult(requestCode, resultCode, data);
 activityResults.onActivityResult(requestCode, resultCode, data); }
 }
 

  65. Tip 2: Decouple Intent results from Activity/Fragment

  66. Tip 2: Decouple Intent results from Activity/Fragment public final class

    MyImageView extends ImageView implements ActivityResults.Listener {
 @Inject ActivityResults activityResults;
 public MyImageView(Context context) {
 super(context);
 }
 
 protected void onAttachedToWindow() {
 super.onAttachedToWindow();
 this.activityResults.addListener(this);
 }
 
 protected void onDetachedFromWindow() {
 super.onDetachedFromWindow();.
 this.activityResults.removeListener(this);
 }
 
 public void onActivityResult(int req, int res, Intent intent) {
 /* react to the intent result inside the component */ 
 } }
  67. Tip 3: Scope object only while they are needed

  68. Tip 3: Scope object only while they are needed @ScopeSingleton(MyView.MyComponent.class)

    
 public class MyPresenter extends BasePresenter<MyScreen> {
 @Inject HeavyCache heavyCache; 
 //... 
 }
 
 public class MyView extends LinearLayout implements MyScreen {
 //...
 @ScopeSingleton(MyComponent.class) 
 @Component(modules = { MyHeavyCacheModule.class })
 public interface MyComponent {
 void inject(MyView view);
 }
 }
  69. None
  70. Reactive

  71. Reactive RxJava & Rx Libraries

  72. RxJava

  73. RxJava • A library for composing asynchronous and event- based

    programs using observable sequences.
  74. RxJava API

  75. RxJava API

  76. None
  77. Subjects

  78. Subjects • A bridge or proxy that acts both as

    an observer and as an Observable. • A subject is an observer so it can subscribe to one or more observables, and an observable so it can pass through the items it observes by reemitting them, and it can also emit new items. • Multiple Types: PublishSubject, BehaviorSubject, ReplaySubject.
  79. None
  80. Reactive: Tips

  81. Tip 1: Use Retrolambda

  82. Tip 1: Use Retrolambda

  83. Tip 1: Use Retrolambda new Func1<String, Integer>() {
 @Override public

    Integer call(String s) {
 return s.length();
 }
 }
 
 
 //equivalent to 
 s -> s.length()
  84. Tip 2: Decouple dependencies using subjects

  85. Tip 2: Decouple dependencies using subjects

  86. Tip 2: Decouple dependencies using subjects Example: When a component

    depends on an activity state public enum ActivityState {CREATE, START, RESUME, STOP, DESTROY}
 
 @Module
 public final class MyActivityModule {
 @Provides @Singleton 
 BehaviorSubject<ActivityState> provideActivityStateSubject() { return BehaviorSubject.create();
 }
 
 @Provides @Singleton 
 Observable<ActivityState> provideActivityStateObservable( BehaviorSubject<ActivityState> subject) {
 return subject;
 }
 }

  87. Tip 2: Decouple dependencies using subjects

  88. Tip 2: Decouple dependencies using subjects public class MyActivity {


    @Inject BehaviorSubject<ActivityState> activityStateSubject;
 protected void onCreate(Bundle bundle) {
 activityStateSubject.onNext(ActivityState.CREATE);
 }
 
 protected void onDestroy() {
 super.onDestroy();
 this.activityStateSubject.onNext(ActivityState.DESTROY);
 }
 }
 
 public class MyView extends LinearLayout {
 @Inject Observable<ActivityState> activityStateObservable;
 void load() {
 this.activityStateObservable
 .filter(state -> state == ActivityState.STOP)
 .subscribe( /* do only when activity stops */);
 }
 }
  89. Tip 3: Use SqlBrite to interact with the database

  90. Tip 3: Use SqlBrite to interact with the database

  91. Tip 3: Use SqlBrite to interact with the database Sqlbrite

    : wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations : @Override 
 protected void onResume() {
 super.onResume(); 
 sqlBrite = SqlBrite.create();
 BriteContentResolver resolver = sqlBrite.wrapContentProvider(getContentResolver());
 subscription = resolver.createQuery(DB_URI, Users.COLUMNS, null, null, null, true) 
 .mapToList(MainActivity::cursorToUser) 
 .subscribe(adapter::setUsers); }
  92. Tip 4: Use takeUntil to watch terminal situations

  93. Tip 4: Use takeUntil to watch terminal situations

  94. Tip 4: Use takeUntil to watch terminal situations Example: Stop

    fetching sensible data when the user has signed out : @Retention(RetentionPolicy.RUNTIME)
 @Qualifier
 public @interface SignOut {}
 @SignOut @Provides @Singleton 
 PublishSubject<Void> provideSignOutSubject() {
 return PublishSubject.create();
 }
 
 @SignOut @Provides @Singleton 
 Observable<Void> provideSignOutObservable(@SignOut PublishSubject<Void> subject){
 return subject;
 }
 
 public MyDrawerView extends DrawerView { @Inject @SignOut PublishSubject<Void> signOutSubject;
 void onSignoutClick() {
 this.signOut.onNext(null); 
 }
 }

  95. Tip 4: Use takeUntil to watch terminal situations

  96. Tip 4: Use takeUntil to watch terminal situations public PaymentView

    extends LinearLayout {
 @Inject @SignOut Observable<Void> signOut;
 void confirmPayment () { 
 api.confirmPayment(request)
 .takeUntil(this.signOut)
 .subscribe(/*display confirmation*/) 
 }
 } public MessageView extends LinearLayout {
 @Inject @SignOut Observable<Void> signOut;
 void displayMessages() {
 messagesObservable .flatMap(/*...*/)
 .takeUntil(this.signOut) 
 .subscribe(/* display messages */);
 }
 }

  97. Tip 5: Reactive Sync

  98. Tip 5: Reactive Sync

  99. Tip 5: Reactive Sync Example using RxBinding: sync changes of

    an input text and a recyclerview :
  100. Tip 5: Reactive Sync Example using RxBinding: sync changes of

    an input text and a recyclerview :
  101. Tip 5: Reactive Sync Example using RxBinding: sync changes of

    an input text and a recyclerview : Observable<CharSequence> descriptionChanges = RxTextView.textChanges(descriptionField); 
 Observable<MyAdapter> adapterObservable = RxRecyclerViewAdapter.dataChanges(adapter);
 Observable.combineLatest(
 descriptionChanges, adapterObservable)
 (charSequence,tasksAdapter) -> true)
 .subscribe(b -> {
 startSync(getCurrentContent());
 });
  102. Other tips

  103. Other tips • Don’t Break the Rx chain (use compose).

    • Remember to always unsubscribe (use CompositeSubscription or RxLifecycle)
  104. Resources • Rx Resources list: https://github.com/chemouna/Rx-Resources • Cyclejs: http://cycle.js.org/ •

    HarryPotterBooks (MVP): https://github.com/chemouna/ HarryPotterBooks • U2020: https://github.com/JakeWharton/u2020 • Foursquare client (Kotlin & MVP): https://github.com/chemouna/Nearby • RxBinding: https://github.com/JakeWharton/RxBinding • RxPreference: https://github.com/f2prateek/rx-preferences • SqlBrite: https://github.com/square/sqlbrite • RxRelay: https://github.com/JakeWharton/RxRelay
  105. Thank you Questions ?