Moderne App-Architektur mit Dagger2 und RxJava

81
Moderne App-Architektur mit Dagger2 und RxJava code.talks 2015 Martin Breuer http://www.gedankensuppe.de/hamburg

Transcript of Moderne App-Architektur mit Dagger2 und RxJava

•Moderne App-Architektur mitDagger2 und RxJava

code.talks 2015

Martin Breuerhttp://www.gedankensuppe.de/hamburg

Dominik Helleberg

+DominikHelleberg

Mobile Application Development

Tools

Android / Embedded

Angelo Rüggeberg

+AngeloRüggeberg

Google Developer Group, Munich | Lead

Mobile Application Development

Android Enthusiast

Since Cupcake (Version 1.5)

Why?

App Architektur

App Architektur

App ArchitekturThemen

• Dependency Injection

• MV(i)P

• RxJava

Dependency Injection

• Your class get’s what it needs (Dependencies come

to you)

• You have no idea where they come from

• A well known pattern

• Everyone uses it (more or less)

App ArchitekturDependency Injection

Dependency Injection

• It’s common sense on the server side

• There are already frameworks out there:

–Spring DI

–Google Guice

–Pico Container

–...

App ArchitekturDependency Injection

Dependency Injection - Android

Now what about android?

• Mobile (limited ressources)

• App Startup time does really matter

• Reflection is slow

• App Size + method count does really matter

App ArchitekturDependency Injection

Dagger 2

Why another Framework?

• no reflection at all

• Based on Dagger1

• Proposed + Implemented by the Java Core Team (Google)

• Code generation (As if you would write it)

App ArchitekturDependency Injection

Dagger 2 - Basics

@Modules / @Provides

• Provide Dependencies to

@Inject

• Requests for Dependencies

@Components

• Public API, the Bridge between Modules and Injects

and some more...

App ArchitekturDependency Injection

Dagger2

TwitterTimeline(interface)

TwitterTimeline RestAdapter

MockTwitterTimeline

HTTPClient

App ArchitekturDependency Injection

Dagger2

TwitterTimeline

RestAdapter

MockTwitterTimeline

MockNetworkModule

NetworkModule

ApplicationComponent

MockApplicationComponent

App ArchitekturDependency Injection

@Singleton@Component(modules = ApplicationModule.class, NetworkingModule.class)public interface ApplicationComponent

MVPComponent plus();;

App ArchitekturDependency Injection

@Modulepublic class NetworkingModule

@Provides@SingletonTwitterTimeline provideTwitterTimeline(RestAdapter restAdapter) return restAdapter.create(TwitterTimeline.class);;

@Provides@Singletonpublic RestAdapter provideRestAdapter(SigningOkClient client) return TwitterApiAdapter.getInstance(client);;

App ArchitekturDependency Injection

@InjectTwitterTimeline timeline;;

App ArchitekturDependency Injection

@Modulepublic class MockNetworkingModule extends NetworkingModule

@Provides@SingletonTwitterTimeline provideTwitterTimeline() return new MockTwitterTimeline();;

App ArchitekturDependency Injection

Dagger2• DI is a good thing to have

• steep learning curve

• Sometimes hard to get hold on componentreferences

• Still under active development

• Enforces structured code

• Code generation is a good thing

App ArchitekturDependency Injection

Model View Presenter (MViP)

• Seperate Business Logic, Application Logic, Display Logic

• Change independent parts without Issues

• Easier to Test

–Mock Network Modules etc.

App ArchitekturMViP

MViP: Components• Model

–Data that is represented

–for example Tweets

• View

–The View Displaying the Data

–can be Activity, Fragment, CustomView

• Presenter

–Logic

–for example fetching Tweets from Network

App ArchitekturMViP

MVP: Components

https://blog.8thlight.com/uncle-­bob/2012/08/13/the-­clean-­architecture.html

App ArchitekturMViP

Example

App ArchitekturMViP

App ArchitekturMViP

Activity

fetchFeed()

updateFeed(List Feed)

updateListitem(item)

onTextChanged()

MViP: Model

• The Data that is displayed to the User

• Data used to do Interactions with the Presenter

• Can be the same as the Models for Network Requests

–Retrofit

App ArchitekturMViP

public class Tweet @JsonProperty("created_at")private String DateCreated;;@JsonProperty("id")private long Id;;@JsonProperty("text")private String Text;;@JsonProperty("user")private TwitterUser User;;

App ArchitekturMViP -­‐ Model

• Interface

• Actual Display Unit Implements Interface

–Fragment, Activity, Custom View, whatever…

• Views should be dumb!

–Only react to Actions

• No Business Logic in here

MVP: View

App ArchitekturMViP

public interface TwitterTimelineView void showLoading();;void hideLoading();;void showFeed(List<Tweet> messages);;void showError(String errorMessage);;

App ArchitekturMViP -­‐ View

class HashtagFeedActivity extends BaseActivity implementsTwitterTimelineView @Overridepublic void showLoading() …

@Overridepublic void hideLoading() …

@Overridepublic void showFeed(List<Tweet> messages) …

@Overridepublic void showError(String errorMessage) …

App ArchitekturMViP -­‐ View

class HashtagFeedActivity extends BaseActivity implementsTwitterTimelineView @Overridepublic void showLoading() …

@Overridepublic void hideLoading() …

@Overridepublic void showFeed(List<Tweet> messages) …

@Overridepublic void showError(String errorMessage) …

App ArchitekturMViP -­‐ View

class HashtagFeedActivity extends BaseActivity implementsTwitterTimelineView @Overridepublic void showLoading() …

@Overridepublic void hideLoading() …

@Overridepublic void showFeed(List<Tweet> messages) …

@Overridepublic void showError(String errorMessage) …

App ArchitekturMViP -­‐ View

Presenter

• Interface

• Binds to the View

• Business Logic in here

App ArchitekturMViP

public interface Presenter<T> void onDestroy();;void bindView(T view);;void unbindView();;

App ArchitekturMViP -­‐ Presenter

public interface TwitterHashtagTimelinePresenterextends Presenter<TwitterTimelineView>

void bindSearchView(EditText textView);;void loadHashtagTimeline(String hashtag);;void goToDetails(Activity context, Tweet t);;

App ArchitekturMViP -­‐ Presenter

public interface TwitterHashtagTimelinePresenterextends Presenter<TwitterTimelineView>

void bindSearchView(EditText textView);;void loadHashtagTimeline(String hashtag);;void goToDetails(Activity context, Tweet t);;

App ArchitekturMViP -­‐ Presenter

public class TwitterHashtagTimelinePresenterImplimplements TwitterHashtagTimelinePresenter private TwitterTimelineView view;;

@Overridepublic void bindView(TwitterTimelineView view) this.view = view;;

@Overridepublic void loadHashtagTimeline(String hashtag)

… Do Network Request ...

App ArchitekturMViP -­‐ Presenter

public class TwitterHashtagTimelinePresenterImplimplements TwitterHashtagTimelinePresenter private TwitterTimelineView view;;

@Overridepublic void bindView(TwitterTimelineView view) this.view = view;;

@Overridepublic void loadHashtagTimeline(String hashtag)

… Do Network Request ...

App ArchitekturMViP -­‐ Presenter

public class TwitterHashtagTimelinePresenterImplimplements TwitterHashtagTimelinePresenter private TwitterTimelineView view;;

@Overridepublic void bindView(TwitterTimelineView view) this.view = view;;

@Overridepublic void loadHashtagTimeline(String hashtag)

… Do Network Request ...

App ArchitekturMViP -­‐ Presenter

public class TwitterHashtagTimelinePresenterImplimplements TwitterHashtagTimelinePresenter private TwitterTimelineView view;;

@Overridepublic void bindView(TwitterTimelineView view) this.view = view;;

@Overridepublic void loadHashtagTimeline(String hashtag)

… Do Network Request ...

App ArchitekturMViP -­‐ Presenter

@Overridepublic void loadHashtagTimeline(String hashtag) view.showLoading();;List<Tweet> tweets = interactor.loadHashtagTweets(hashtag);;view.showTweets(tweets);;view.hideLoading();;

App ArchitekturMViP -­‐ Presenter

@Overridepublic void loadHashtagTimeline(String hashtag) view.showLoading();;List<Tweet> tweets = interactor.loadHashtagTweets(hashtag);;view.showTweets(tweets);;view.hideLoading();;

App ArchitekturMViP -­‐ Presenter

@Overridepublic void loadHashtagTimeline(String hashtag) view.showLoading();;List<Tweet> tweets = interactor.loadHashtagTweets(hashtag);;view.showTweets(tweets);;view.hideLoading();;

App ArchitekturMViP -­‐ Presenter

Interactor?

• Do not bloat up Presenters!

• Interactors are used for doing things

–Fetching Stuff from the network

–Fetching Stuff from Disk

• Interface again!

App ArchitekturMViP -­‐ interactor

public interface TwitterTimelineInteractor void loadUserTweets(Observer<? super List<Tweet>>

observer, String username);;

void loadHashtagTweets(Observer<? super List<Tweet>> observer, String hastag);;

Observable<List<Tweet>> loadHashtagTweets(String hashtag);;

void pollHashtagTweets(Observer<? super List<Tweet>> observer, String hashtag);;

App ArchitekturMViP -­‐ interactor

public class TwitterTimelineInteractorImplimplements TwitterTimelineInteractor TwitterTimeline twitterTimeline;;

@Overridepublic void loadUserTweets(Observer<? super List<Tweet>>

observer, String username) twitterTimeline

.getUserTimeline(username)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(observer);;

App ArchitekturMViP -­‐ interactor

App ArchitekturSummary

Activity

textChangeObservable

Presenter

bindView()

Adapter

showFeed()

setTweets()

loadHashtagTimelineFeedshowFeed()

Interactor

loadHashtagTimeline

Why?• Push don’t Poll!

–do not stress the UI

–do not stress the Application

• Avoid Lifecycle Problems

• Avoid the Callback Hell

• Death to:

–Boilerplate Code

–AsyncTasks, Timertasks, Loaders, etc...

App ArchitekturRXJava

RX: What?

• Programming Paradigm based on asynchronous Data Flow

• Idea from the late 90s

• Erik Meijer (Microsoft) developed first RX extension for .NET

• Adapted by many Programming languages

App ArchitekturRXJava

RX: What?

• Ported to JVM by Ben Christensen and Jafar Husain (Netflix)

–Java, Closure, Groovy, Scala, etc etc….

• RXJava -> RXAndroid

• The Observer pattern done right.

App ArchitekturRXJava

http://reactivex.io/

RX: What?

App ArchitekturRXJava

RXAndroid: How?

• Producing Entities:

–Observables

–Subjects

• Consuming Entities:

–Observers

–Subscribers

• Consuming Entities subscribe to Producing Entities

App ArchitekturRXJava

RXAndroid: Setup

compile 'io.reactivex:rxandroid:1.0.1'//optionalcompile 'io.reactivex:rxjava:1.0.14'

App ArchitekturRXJava

ObserversLifecycle:

–onNext(T):

• gets called when new Data is emited with theData

–onCompleted():

• gets called when the Sequence is done

–onError(Throwable):

• gets called when an Error occured

App ArchitekturRXJava

Observables

• Sequence that emits data

–can emit more than once

• Lives as long as there is work to do

• Observers subscribe to an Observable

• When there is no Subscriber, the Observable emitsthe data nowhere

App ArchitekturRXJava

Sequence Example

Observable

Emits Something every Second

App ArchitekturRXJava

Sequence Example

Observable

Observer

Subscribe Unsubscribe

onNext(T);;

App ArchitekturRXJava

Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS);;

App ArchitekturRXJava

interval.subscribe(new Observer<Long>() @Overridepublic void onCompleted() Timber.d("onCompleted");;

@Overridepublic void onError(Throwable e) Timber.e(e, e.getMessage());;

@Overridepublic void onNext(Long aLong) Timber.d("Tick: " + aLong.toString());;

App ArchitekturRXJava

Transforming the Data

• We do not want a Long as result, we want a String

• map()

–gets the Long value

–returns a String value

• Chainable!

–map().map().map().map().............map()

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(new Func1<Long, String>() @Overridepublic String call(Long aLong) return "Tick: " + aLong.toString();;

);;

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(new Func1<Long, String>() @Overridepublic String call(Long aLong) return "Tick: " + aLong.toString();;

);;

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(new Func1<Long, String>() @Overridepublic String call(Long aLong) return "Tick: " + aLong.toString();;

);;

Returns Long

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(new Func1<Long, String>() @Overridepublic String call(Long aLong) return "Tick: " + aLong.toString();;

);;

Takes the Long

Transforms the Data and returns a String

App ArchitekturRXJava

Hello Again Boilerplatecode

• new Func1<T, T>() public T call(t t2) … for every mapping will turn the code really unreadable and bloated

• Lambda to the Rescue!

–retrolambda for android & gradle

–https://github.com/evant/gradle-retrolambda

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(new Func1<Long, String>() @Overridepublic String call(Long aLong) return "Tick: " + aLong.toString();;

);;

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(aLong -­> "Tick: " + aLong.toString());;

App ArchitekturRXJava

Observable<String> interval = Observable.interval(1, TimeUnit.SECONDS).map(aLong -­> aLong.toString()).map(string -­> "Tick: " + string);;

App ArchitekturRXJava

Combining Observables

• Prevent Callback hell

• Easy to Synchronize for async Operations

• Running 2 Network Calls and combine theResults

• Combine onTextChanged events from two fields

• etc.

• .zip(), combineLatest()

App ArchitekturRXJava

Observable.combineLatest(WidgetObservable.text(txt1, false), WidgetObservable.text(txt2, false),(event1, event2) -­> event1.text().toString() +

event2.text().toString()).subscribe(combined -­> Timber.d(combined);;

);;

App ArchitekturRXJava

Observable.combineLatest(WidgetObservable.text(txt1, false), WidgetObservable.text(txt2, false),(event1, event2) -­> event1.text().toString() +

event2.text().toString()).subscribe(combined -­> Timber.d(combined);;

);;

App ArchitekturRXJava

Observable.combineLatest(WidgetObservable.text(txt1, false), WidgetObservable.text(txt2, false),(event1, event2) -­> event1.text().toString() +

event2.text().toString()).subscribe(combined -­> Timber.d(combined);;

);;

App ArchitekturRXJava

Observable.combineLatest(WidgetObservable.text(txt1, false), WidgetObservable.text(txt2, false),(event1, event2) -­> event1.text().toString() +

event2.text().toString()).subscribe(combined -­> Timber.d(combined);;

);;

App ArchitekturRXJava

Observable.combineLatest(WidgetObservable.text(txt1, false), WidgetObservable.text(txt2, false),(event1, event2) -­> event1.text().toString() +

event2.text().toString()).subscribe(combined -­> Timber.d(combined);;

);;

App ArchitekturRXJava

Example: Livesearch

• Retrieve the user Input from a EditText when text changes

–only after nothing changes for 600 ms

• Add # infront of the input to do a hastag search

• Call the network (via Retrofit)

–returns StatusesResponse Object

• Transform to List<Tweets>

• Show Tweets in view

App ArchitekturRXJava

textChangeObservable

.observeOn(AndroidSchedulers.mainThread())

textChangeObservable

.observeOn(AndroidSchedulers.mainThread())

.map(onTextChangeEvent -­> onTextChangeEvent.text().toString())

textChangeObservable

.observeOn(AndroidSchedulers.mainThread())

.map(onTextChangeEvent -­> onTextChangeEvent.text().toString())

.map(searchText -­> "#" + searchText)

textChangeObservable

.observeOn(AndroidSchedulers.mainThread())

.map(onTextChangeEvent -­> onTextChangeEvent.text().toString())

.map(searchText -­> "#" + searchText)

.subscribe(tag -­> Timber.d(tag);;loadHashtagTimeline(tag);;

textChangeObservable

.observeOn(AndroidSchedulers.mainThread())

.map(onTextChangeEvent -­> onTextChangeEvent.text().toString())

.map(searchText -­> "#" + searchText)

.subscribe(tag -­> Timber.d(tag);;loadHashtagTimeline(tag);;

,e -­> Timber.e(e, e.getMessage());;view.showError(e.getMessage());;

);;

textChangeObservable.debounce(600, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).map(onTextChangeEvent -­> onTextChangeEvent.text().toString()).map(searchText -­> "#" + searchText).subscribe(

tag -­> Timber.d(tag);;loadHashtagTimeline(tag);;

,e -­> Timber.e(e, e.getMessage());;view.showError(e.getMessage());;

);;

Demo

Thank You!