2. Agenda
▪ MVP & MVVM
▪ Constructing a simple
Music App
▪ Intro Journey into RxJava
3. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
International Code of Signals
4. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
MVP
Presenter ModelView
User events
*Lifecycle Events
View updates Notifications
Updates/Requests
5. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
MVP
Presenter
<<interface>>
View
<<interface>>
MyPresenter MyModelMyView
User events
*Lifecycle Events
View updates Notifications
Updates/Requests
Model
<<interface>>
6. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
MVVM
ViewModel ModelView
Stream of data Notifications
Updates/Requests
Data bindings
Commands
Model
<<interface>>
7. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Data LayerBusiness Logic /
Domain Layer
Presentation Layer
Layered Architecture
Presenter /
ViewModel
View
Model
Use Case
Repository
DB
Rest API
Shared
Prefs
8. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Data LayerBusiness Logic /
Domain Layer
Presentation Layer
Layered Architecture
Presenter /
ViewModel
View
Model
Use Case
Repository
DB
Rest API
Shared
Prefs
Device Specific
(Location,
Notification)
9. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
What we want to do
Search on letter entered Loading Show results
10. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
What we want to do
Play Track Change icon when new track is playing
11. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
What we use
MVVM Architectural Pattern
RxJava
Retrofit
Realm
12. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
ViewModelView
Stream of data
Data bindings
Commands
13. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
SearchViewModelSearchActivity
ViewModelView
Stream of data
Data bindings
Commands
14. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
SearchViewModelSearchActivity
searchChangedAction
ViewModelView
Stream of data
Data bindings
Commands
15. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - building blocks
public interface Observer<T> {
void onCompleted();
void onError(Throwable e);
void onNext(T t);
}
16. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - building blocks
public interface Observer<T> {
void onCompleted();
void onError(Throwable e);
void onNext(T t);
}
public interface Subscription {
void unsubscribe();
boolean isUnsubscribed();
}
17. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - building blocks
public interface Observer<T> {
void onCompleted();
void onError(Throwable e);
void onNext(T t);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription {
…
}
public interface Subscription {
void unsubscribe();
boolean isUnsubscribed();
}
18. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - building blocks
public interface Observer<T> {
void onCompleted();
void onError(Throwable e);
void onNext(T t);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription {
…
}
public class Observable<T> {
....
public final Subscription subscribe() {
Action1<T> onNext = Actions.empty();
Action1<Throwable> onError = InternalObservableUtils.ERROR_NOT_IMPLEMENTED;
Action0 onCompleted = Actions.empty();
return subscribe(new ActionSubscriber<T>(onNext, onError, onCompleted));
}
}
public interface Subscription {
void unsubscribe();
boolean isUnsubscribed();
}
19. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Observable & Subscriber
Observable<String> editTextObservable =
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Entered text");
subscriber.onCompleted();
}
});
20. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Observable & Subscriber
Observable<String> editTextObservable =
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Entered text");
subscriber.onCompleted();
}
});
private Subscriber<String> searchTriggered =
new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
}
};
21. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Observable & Subscriber
Observable<String> editTextObservable =
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Entered text");
subscriber.onCompleted();
}
});
private Subscriber<String> searchTriggered = new
Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
}
};
editTextObservable.subscribe(searchTriggered);
22. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Action
public interface Action1<T> extends Action {
void call(T t);
}
private Action1<String> searchTriggered = new Action1<String>() {
@Override
public void call(String searchedText) {
searchTracks(searchedText)
}
};
23. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
SearchViewModelSearchActivity
searchChangedAction
ViewModelView
Stream of data
Data bindings
Commands
24. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Transform TextWatcher into an Observable
public static Observable<String> editTextObservable(EditText editText) {
return Observable. create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (subscriber .isUnsubscribed()) {
editText .removeTextChangedListener( this);
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (subscriber .isUnsubscribed()) {
editText .removeTextChangedListener( this);
}
subscriber .onNext(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
if (subscriber .isUnsubscribed()) {
editText .removeTextChangedListener( this);
}
}
};
editText .addTextChangedListener(textWatcher);
}
});
}
25. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchActivity (View)
private CompositeSubscription subscriptions;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
subscriptions = new CompositeSubscription();
}
@Override
protected void onDestroy() {
super.onDestroy();
subscriptions.unsubscribe();
}
26. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchActivity (View)
private CompositeSubscription subscriptions;
private SearchViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
subscriptions = new CompositeSubscription();
viewModel = new SearchViewModel();
}
@Override
protected void onDestroy() {
super.onDestroy();
subscriptions.unsubscribe();
}
27. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchActivity (View)
private CompositeSubscription subscriptions;
private TracksViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
subscriptions = new CompositeSubscription();
viewModel = new TracksViewModel();
}
subscriptions.addAll(
CustomViewBindings.editTextObservable(searchEditText)
.subscribe(viewModel.searchChangedAction())
);
@Override
protected void onDestroy() {
super.onDestroy();
subscriptions.unsubscribe();
}
28. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchViewModel (ViewModel)
private Action1<String> searchTriggered = new Action1<String>() {
@Override
public void call(String searchedText) {
searchTracks(searchedText)
.debounce(50, TimeUnit.MILLISECONDS)
.subscribe();
}
};
public Action1<String> searchChangedAction() {
return searchTriggered;
}
29. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Debounce
30. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Timeline
SearchActivity SearchViewModel TracksRepoUser
Open app
Subscribe for
TextWatcher events
31. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
SearchViewModelSearchActivity
searchChangedAction
isLoadingObservable
ViewModelView
Stream of data
Data bindings
Commands
32. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchViewModel (ViewModel)
private BehaviorSubject<Boolean> isLoadingTracks = BehaviorSubject.create(false);
public Observable<Boolean> isLoadingObservable() {
return isLoadingTracks.asObservable();
}
33. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchActivity (View)
subscriptions.addAll(
CustomViewBindings.editTextObservable(searchEditText)
.subscribe(viewModel.searchChangedAction()),
viewModel.isLoadingObservable()
.observeOn(AndroidSchedulers. mainThread())
.subscribe( new Action1<Boolean>() {
@Override
public void call(Boolean show) {
showLoading(show);
}
})
);
34. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Subject
A Subject is a bridge or proxy that acts both as an Observer and as an Observable.
public abstract class Subject<T, R> extends Observable<R> implements Observer<T>
Because it is an observer, it can subscribe to one or more Observables, and because it is an
Observable, it can pass through the items it observes by re-emitting them, and it can also emit
new items.
Types:
AsyncSubject
BehaviorSubject
PublishSubject
ReplaySubject
35. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - PublishSubject
36. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - BehaviorSubject
37. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Timeline
SearchActivity SearchViewModel TracksRepoUser
Subscribe for loading updates
Open app
Subscribe for
TextWatcher events
isLoadingObservable emits “false”
38. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Search Screen
SearchViewModelSearchActivity
searchChangedAction
isLoadingObservable
tracksObservable
ViewModelView
Stream of data
Data bindings
Commands
39. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchViewModel (ViewModel)
private BehaviorSubject<List<TrackViewModel>> searchedTracks =
BehaviorSubject.create(new ArrayList<TrackViewModel>());
public Observable<List<TrackViewModel>> tracksObservable() {
return searchedTracks.asObservable();
}
40. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchActivity (View)
subscriptions.addAll(
CustomViewBindings.editTextObservable(searchEditText).subscribe(viewModel.searchChangedAction()),
viewModel.isLoadingObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(show -> showLoading(show)),
viewModel.tracksObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<TrackViewModel>>() {
@Override
public void call(List<TrackViewModel> newTracks) {
updateAdapter(newTracks);
}
})
);
41. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Timeline
SearchActivity SearchViewModel TracksRepoUser
Subscribe for loading updates
Open app
Subscribe for
TextWatcher events
Subscribe for track list updates
isLoadingObservable emits “false”
searchedTracksObservable emits
“empty array”
42. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
SearchViewModel (ViewModel)
private Observable<List<TrackViewModel>> searchTracks(String text) {
isLoadingTracks.onNext(true);
return TracksRepo.getInstance().searchTracks(text, searchedTracks.getValue().size())
.flatMap(new Func1<List<Track>, Observable<Track>>() {
@Override
public Observable<Track> call(List<Track> tracks) {
return Observable.from(tracks);
}
})
.map(new Func1<Track, TrackViewModel>() {
@Override
public TrackViewModel call(Track track) {
return getNewTrackViewModel(track);
}
})
.toList()
.doOnNext(tracks1 -> {
searchedTracks.onNext(tracks1);
}).onErrorResumeNext(throwable -> {
return Observable.empty();
}).doOnTerminate(() -> isLoadingTracks.onNext(false));
}
43. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Map
44. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - Map Example
.map(new Func1<Track, TrackViewModel>() {
@Override
public TrackViewModel call(Track track) {
return getNewTrackViewModel(track);
}
})
45. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - FlatMap
46. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
RxJava - FlatMap Example
.flatMap(new Func1<List<Track>, Observable<Track>>() {
@Override
public Observable<Track> call(List<Track> tracks) {
return Observable.from(tracks);
}
})
47. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Timeline
SearchActivity SearchViewModel TracksRepoUser
Subscribe for loading updates
Open app
Press “r”
Subscribe for
TextWatcher events
Subscribe for track list updates
Subscribe for searchTracks
Search tracks
Notify of Tracks Result
isLoadingObservable emits “true”
isLoadingObservable emits “false”
searchedTracksObservable emits
“empty array”
isLoadingObservable emits “false”
searchedTracksObservable emits
“List<TracksViewModel>”
48. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
List Item
TrackViewModelTrackView
isPlayingObservable
onPlayClicked
ViewModelView
Stream of data
Data bindings
Commands
49. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TrackViewModel
private BehaviorSubject<Boolean> isPlaying = BehaviorSubject.create(false);
public Observable<Boolean> isPlaying() {
return isPlaying.asObservable();
}
50. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TrackView
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (this.viewModel != null) {
subscription = this.viewModel.isPlaying().subscribe(playing -> setPlayIcon(playing));
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (subscription != null) {
subscription.unsubscribe();
}
}
51. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TrackView
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (this.viewModel != null) {
subscription = this.viewModel.isPlaying().subscribe(playing -> setPlayIcon(playing));
}
}
public void bindViewModel(TrackViewModel viewModel) {
if (this.viewModel != null && subscription != null) {
subscription.unsubscribe();
}
this.viewModel = viewModel;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (subscription != null) {
subscription.unsubscribe();
}
}
52. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TrackViewModel
private BehaviorSubject<Boolean> isPlaying = BehaviorSubject.create(false);
private Action0 onPlayClicked;
public Observable<Boolean> isPlaying() {
return isPlaying.asObservable();
}
public void changePlayState() {
isPlaying.onNext(!isPlaying.getValue());
}
public void onPlayClicked() {
onPlayClicked.call();
}
53. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TrackView
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (this.viewModel != null) {
subscription = this.viewModel.isPlaying().subscribe(playing -> setPlayIcon(playing));
}
}
public void bindViewModel(TrackViewModel viewModel) {
if (this.viewModel != null && subscription != null) {
subscription.unsubscribe();
}
this.viewModel = viewModel;
trackImage .setOnClickListener(v -> viewModel.onPlayClicked());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (subscription != null) {
subscription.unsubscribe();
}
}
54. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
TracksAdapter
private List<TrackViewModel> trackViewModels = new ArrayList<>();
@Override
public void onBindViewHolder(TracksViewHolder holder, int position) {
holder.bind(trackViewModels.get(position));
}
class TracksViewHolder extends RecyclerView.ViewHolder {
TrackView trackView;
private TracksViewHolder(View view) {
super(view);
trackView = (TrackView) view;
}
public void bind(TrackViewModel viewModel) {
trackView.bindViewModel(viewModel);
}
}
55. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
Timeline
TrackView TrackViewModelTracksAdapter
Subscribe for play state updates
isPlayingObservable emits “true or false”
isPlayingObservable emits “false or true”
TracksViewHolder
View Inflated
bindViewModel
attachedToWindow
user
play clicked
Unsubscribe from previous viewmodel
SearchViewModel
onPlayClicked action
changePlayState
dettachedFromWindow
user
Scroll item out of screen
Unsubscribe from viewmodel
56. ATLANTA | AUSTIN | PHILADELPHIA | BENTONVILLE | ROMANIA | INDIA | AUSTRALIA | BRAZIL | NEPAL | CANADA
What to explore next?
▪ Talk by Jacke Warthon ->
http://jakewharton.com/demystifying-rxjava-subscribers/
▪ RxBindings -> https://github.com/JakeWharton/RxBinding
▪ Android Data Binding Library ->
https://developer.android.com/topic/libraries/data-binding/index.ht
ml
▪ Debugging Rx -> Frodo -> https://github.com/android10/frodo
▪ Clean Architecture
▪ Books: Clean Code & Clean Coder - Robert C. Martin aka “Uncle Bob”
▪ Nice talk Simple made Esay:
https://www.infoq.com/presentations/Simple-Made-Easy