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

Building Modular and Reactive Android Applications

Building Modular and Reactive Android Applications

Mouna Cheikhna

April 28, 2016
Tweet

More Decks by Mouna Cheikhna

Other Decks in Programming

Transcript

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

    and Android. • An adaptation of Square’s version and now maintained by Google.
  2. 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.
  3. Dagger API public @interface Component {
 Class<?>[] modules() default {};


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

    MapKey {
 boolean unwrapValue() default true;
 }
 
 public interface Lazy<T> {
 T get();
 }
  5. Component dependencies @Singleton @Component(modules = { AppModule.class, ApiModule.class }) 


    public interface AppComponent {
 
 void injectApplication(MyApp app);
 Application application();
 
 @ApplicationContext
 Context provideApplicationContext();
 }
  6. 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();
 }
 }
  7. Singleton @Module
 Class DripCoffeeModule {
 @Provides @Singleton
 CoffeeMaker provideCoffeeMaker ()

    {
 return new CoffeeMaker();
 }
 }
 
 //or
 @Singleton
 class CoffeeMaker {
 //...
 }
 
 @Component(modules = DripCoffeeModule.class) 
 @Singleton 
 interface CoffeeShopComponent {
 CoffeeMaker maker();
 }
  8. 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);
 }

  9. 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);
 }
 }

  10. 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);
 }

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


    return new TasksMap(tasks);
 } public class TasksRunner { 
 @Inject TasksMap tasksStore;
 }
  12. 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();
 }
  13. MVP

  14. MVP

  15. 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
  16. 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);
 }
 }
  17. 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 {
 //...
 }
 

  18. 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.
  19. 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);
 }
  20. 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); }
 }
 

  21. 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 */ 
 } }
  22. 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);
 }
 }
  23. 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.
  24. Tip 1: Use Retrolambda new Func1<String, Integer>() {
 @Override public

    Integer call(String s) {
 return s.length();
 }
 }
 
 
 //equivalent to 
 s -> s.length()
  25. 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;
 }
 }

  26. 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 */);
 }
 }
  27. 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); }
  28. 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); 
 }
 }

  29. 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 */);
 }
 }

  30. 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());
 });
  31. Other tips • Don’t Break the Rx chain (use compose).

    • Remember to always unsubscribe (use CompositeSubscription or RxLifecycle)
  32. 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