•moderne app-architektur mit dagger2 und rxjava · dependencyinjection- android now what about...
TRANSCRIPT
•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!