This session takes a deep dive into several real-world rewrites of large features in a popular app using exclusively reactive streams, from the view all the way to the network!
inline Filters by genre Do not display songs already on soundtrack 9 List of songs Set of selected songs Play information: total and currently played List of filters Currently selected filter List of soundtrack songs
Play information: total and currently played List of filters Currently selected filter List of soundtrack songs typealias Genre = String data class Song(val genre: Genre, val url: String, ...) data class Beat(val current: Int, val total: Int) data class SoundtrackElement(val song: Song, ...)
inline Filters by genre Do not display songs already on soundtrack val songList: BehaviorSubject<List<Song>> = BehaviorSubject.create(emptyList()) val songSelected: BehaviorSubject<Set<Song>> = BehaviorSubject.create(emptySet()) val filterList: BehaviorSubject<List<Genre>> = BehaviorSubject.create(emptyList()) val filterSelected: BehaviorSubject<Set<Song>> = BehaviorSubject.create(emptySet()) val soundtrack: BehaviorSubject<List<Song>> = BehaviorSubject.create(emptyList()) Say bye bye to songs on soundtrack for now Notice there is no Beat state. Beats are transient data.
inline Filters by genre 14 interface MusicViewDisplay { fun showSongs(songs: List<Song>) fun showSelected(selected: Set<Song>) fun showBeat(beat: Tuple2<Beat, Song>) fun clearBeat() fun showGenres(genres: List<Genre>) fun showVisible(visible: Set<Song>) }
inline Filters by genre 16 val bindSimple = ::bind.apply( lifecycle, AndroidSchedulers.mainThread()) bindSimple(songList, ::showSongs) bindSimple(songSelected, ::showSelected) bindSimple(filterList, ::showGenres) bindSimple(filterSelected, ::showVisible) Beats have no permanent state -> no binding!
inline Filters by genre 17 class MusicAdapter: RecyclerView.Adapter<VH> { val songs: List<Song> val selected: Set<Song> val visible: Set<Song> val beat: AtomicReference<Tuple2<Beat, Song>> /*...*/ } class MusicFilterSpinner: BaseAdapter { val genres: List<Genre> /*...*/ }
inline Filters by genre 18 class MusicAdapter: RecyclerView.Adapter<VH> { val selected: Set<Song> val visible: Set<Song> val beat: AtomicReference<Tuple2<Beat, Song>> fun getItemViewType(pos: Int, element: Song): Int = if visible.contains(element) SHOW else HIDDEN fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = if (viewType == HIDDEN) HiddenSongViewHolder(parent.context) else SongViewHolder(/*...*/) fun onBindViewHolder(holder: VH, pos: Int) { val position = songs.get(pos); if (selected.contains(position)) { /*...*/ } val beatC = beat.get() if (beatC != null && beatC.b == position) { /*...*/ } } }
inline Filters by genre 25 interface UserInteractionProvider { fun clickSong(): Observable<Position<Song>> fun clickPlay(): Observable<Position<Song>> fun clickStop(): Observable<Position<Song>> fun clickFilter(): Observable<Genre> fun clickSave(): Observable<Unit> } Two separate buttons for play/stop
Service fun clickSave() : Observable<Unit> // State val songSelected : BehaviorSubject<Set<Song>> val soundtrack : BehaviorSubject<List<Song>> ObservableKW.monadErrorSwitch().binding { val selectedSongs = songSelected.bind().toList() clickSave().bind() val currentSoundtrack = soundtrack.bind() yields(currentSoundtrack + selectedSongs) }.subscribe(soundtrack)
modeling with sealed classes: http://www.pacoworks.com/2016/10/03/ new-talk-a-domain-driven-approach-to- kotlins-new-types-at-mobilization-2016/ Sample project: https://github.com/pakoito/ FunctionalAndroidReference Copyright slides 4 & 5 - CC BY 4.0 Carmen Martín Herrero pacoworks.com @pacoworks github.com/pakoito Slides: http://tinyurl.com/RxUseCaseMobi17 This presentation will be soon available on the Mobiconf website at the following link: https://2017.mobiconf.org/ 38