SlideShare a Scribd company logo
1 of 59
Download to read offline
WORKING EFFECTIVELY WITH VIEWMODELS AND TDD
ANDRIY MATKIVSKIY
Senior Mobile Engineer at Valtech
Topics
●
●
●
●
HOW MANY ARCHITECTURES DO YOU KNOW?
● MVC
● HMVC (Hierarchical model–view–controller)
● MVA (MODEL–VIEW–ADAPTER)
● MVP
● MVVM
● MVI
They all give us the ability to decouple
development process into smaller pieces
which can be distributed between team
members
They all lack integration with Android lifecycle system
How it works ?
● Lifecycle components
● LiveData<T>
● ViewModel
How it works ?
Lifecycle components
interface LifeCycle (Observer pattern)
+ addObserver()
+ removeObserver()
+ getCurrentState()
interface LifecycleObserver
interface LifecycleOwner
+ getLifecycle()
class LifecycleRegistry : LifeCycle
+ Does all the magic required to handle this
How it works ?
Lifecycle components
How it works ?
val lifecycleOwner = object : LifecycleOwner {
val lifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle() = lifecycleRegistry
}
How it works ?
val lifecycleOwner = object : LifecycleOwner {
val lifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle() = lifecycleRegistry
}
lifecycleOwner.lifecycle.addObserver(
object : LifecycleObserver {
@OnLifecycleEvent(Event.ON_ANY)
fun onAny(source: LifecycleOwner, event: Event) {
// Handle incoming events
}
}
)
How it works ?
val lifecycleOwner = object : LifecycleOwner {
val lifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle() = lifecycleRegistry
}
lifecycleOwner.lifecycle.addObserver(
object : LifecycleObserver {
@OnLifecycleEvent(Event.ON_ANY)
fun onAny(source: LifecycleOwner, event: Event) {
// Handle incoming events
}
}
)
lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME)
How it works ?
val lifecycleOwner = object : LifecycleOwner {
val lifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle() = lifecycleRegistry
}
lifecycleOwner.lifecycle.addObserver(
object : LifecycleObserver {
@OnLifecycleEvent(Event.ON_ANY)
fun onAny(source: LifecycleOwner, event: Event) {
// Handle incoming events
}
}
)
lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME)
How it works ?
LiveData<T>
class LiveData<T> (Observer pattern) on steroids (Lifecycle aware)
● Handles Observer state for us (via listening to LifecycleOwner)
liveData.observe(LifecycleOwner(), Observer {})
- Ensures Observer is not called when related LifecycleOwner is at least in
STARTED state
- Remove Observer if when related LifecycleOwner reaches DESTROYED
state
- Ensures that Observer receives last value when it is active again
(LifecycleOwner is back in STARTED state)
How it works ?
LiveData<T>
class LiveData<T> (Observer pattern) on steroids (Lifecycle aware)
● Handles threading for us
How it works ?
ViewModel
public abstract class ViewModel {
protected void onCleared() {}
}
That’s all :)
Tips and tricks of working with ViewModel and LiveData
Single emitted events through LiveData
- Show one time message (toast, snackbar,
dialog)
- Send one time actions to the view (close
activity, navigation events for fragments)
- Open other activities (by intent)
- Any type of activity that should be triggered
once and not re-triggered after rotation
Show me the code
data class Event<out T>(val content: T) {
private var consumed = false
fun consume(consumer: (T) -> Unit) {
if (not(consumed)) {
consumer(content)
}
consumed = true
}
fun not(condition: Boolean) = !condition
}
Show me the code TODO: Remove
data class Event<out T>(val content: T) {
private var consumed = false
fun consume(consumer: (T) -> Unit) {
if (not(consumed)) {
consumer(content)
}
consumed = true
}
fun not(condition: Boolean) = !condition
}
Show me the code TODO: Remove
data class Event<out T>(val content: T) {
private var consumed = false
fun consume(consumer: (T) -> Unit) {
if (not(consumed)) {
consumer(content)
}
consumed = true
}
fun not(condition: Boolean) = !condition
}
Show me the code TODO: Remove
data class Event<out T>(val content: T) {
private var consumed = false
fun consume(consumer: (T) -> Unit) {
if (not(consumed)) {
consumer(content)
}
consumed = true
}
fun not(condition: Boolean) = !condition
}
Usage (from ViewModel)
class MainViewModelExample : ViewModel() {
val events = MutableLiveData<Event<ViewModelEvent>>()
fun sendEvent() {
events.value = Event(ViewModelEvent.ShowToast("Hello"))
}
open class ViewModelEvent {
data class ShowToast(val message: String) : ViewModelEvent()
}
}
Usage (from ViewModel)
class MainViewModelExample : ViewModel() {
val events = MutableLiveData<Event<ViewModelEvent>>()
fun sendEvent() {
events.value = Event(ViewModelEvent.ShowToast("Hello"))
}
open class ViewModelEvent {
data class ShowToast(val message: String) : ViewModelEvent()
}
}
Usage (from ViewModel)
class MainViewModelExample : ViewModel() {
val events = MutableLiveData<Event<ViewModelEvent>>()
fun sendEvent() {
events.value = Event(ViewModelEvent.ShowToast("Hello"))
}
open class ViewModelEvent {
data class ShowToast(val message: String) : ViewModelEvent()
}
}
Usage (from ViewModel)
class MainViewModelExample : ViewModel() {
val events = MutableLiveData<Event<ViewModelEvent>>()
fun sendEvent() {
events.value = Event(ViewModelEvent.ShowToast("Hello"))
}
open class ViewModelEvent {
data class ShowToast(val message: String) : ViewModelEvent()
}
}
Usage (from View)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this)
.get(MainViewModelExample::class.java)
observeEvents(model.events) {
when (it) {
is ViewModelEvent.ShowToast -> showToast(it.message)
}
}
Sharing data between fragments (or views)
Imagine we have to fragments that need to
communicate somehow?
Sharing data between fragments (or views)
class SpeakersFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val model = ViewModelProviders.of(requireActivity())
.get(SharedViewModel::class.java)
// For example, on button click
model.onSpeakerSelected(speaker)
}
}
Sharing data between fragments (or views)
class SpeakerDetailsFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Get the same instance of ViewModel as in SpeakersFragment
val model = ViewModelProviders.of(requireActivity())
.get(SharedViewModel::class.java)
observe(model.selectedSpeaker) {
// Render speaker details on screen
}
}
}
Sharing data between fragments (or views)
class SharedViewModel : ViewModel() {
val selectedSpeaker = MutableLiveData<Speaker>()
fun onSpeakerSelected(speaker: Speaker) {
selectedSpeaker.value = speaker
}
}
Be careful with fragments (as usual)
class AnotherUserFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val model = ..
model.users.observe(this, Observer {
// Handle users
// This can be triggered multiple times during the switch of
fragments in activity
})
}
}
Be careful with fragments (as usual)
class AnotherUserFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val model = ..
model.users.observe(viewLifecycleOwner, Observer {
// Handle users
})
}
}
Using ViewModels in Views
class SpeakerFragment : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val model = viewModel<SpeakerViewModel>(viewModelFactory) {
observe(speakers, ::handlerSpeakers)
observeEvents(events, ::handleEvent)
}
}
}
- Android-CleanArchitecture-Kotlin with Dagger way
Using ViewModels in Views
class SpeakerFragment : Fragment() {
private val model: SpeakerViewModel by sharedViewModel()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
observe(model.speakers, ::handlerSpeakers)
observeEvents(model.events, ::handleEvent)
}
}
- With Koin DI framework
Rx Support in ViewModels
class SpeakerViewModel(private val useCase: FetchSpeakerUseCase)
:ViewModel() {
private val compositeDisposable = CompositeDisposable()
fun loadSpeaker() {
compositeDisposable.add(
useCase.call("speaker_id").subscribe {
// Handle result
}
)
}
override fun onCleared() {
compositeDisposable.clear()
}
}
Problem
Rx Support in ViewModels
abstract class BaseViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
fun addDisposable(block: () -> Disposable) {
compositeDisposable.add(block())
}
public override fun onCleared() {
compositeDisposable.clear()
}
}
How we can fix this?
Rx Support in ViewModels
class SpeakerViewModel(private val useCase: FetchSpeakerUseCase) :
BaseViewModel() {
fun loadSpeaker() {
addDisposable {
useCase.call("speaker_id").subscribe {
// Handle result
}
}
}
}
TDD with ViewModels
General architecture
General architecture
General architecture
This gives a independent development tasks for separate feature
Testing ViewModels
Handling LiveData values with TestObserver
class TestObserver<T> : Observer<T> {
override fun onChanged(value: T) {
// Handle values coming from LiveData
}
}
Testing ViewModels
Handling LiveData values with TestObserver
class TestObserver<T> : Observer<T> {
val observedValues = mutableListOf<T>()
override fun onChanged(value: T) {
observedValues.add(value)
}
}
Testing ViewModels
Handling LiveData values with TestObserver
class TestObserver<T> : Observer<T> {
val observedValues = mutableListOf<T>()
override fun onChanged(value: T) {
observedValues.add(value)
}
}
fun <T> LiveData<T>.testObserver() = TestObserver<T>().also {
observeForever(it)
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
}
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct speaker`() {
}
}
Testing ViewModels
Sample test
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct data`() {
val speaker = Speaker("Andriy")
val speakerObserver = viewModel.selectedSpeaker.testObserver()
}
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct data`() {
val speaker = Speaker("Andriy")
val speakerObserver = viewModel.selectedSpeaker.testObserver()
viewModel.onSpeakerSelected(speaker)
}
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct data`() {
val speaker = Speaker("Andriy")
val speakerObserver = viewModel.selectedSpeaker.testObserver()
viewModel.onSpeakerSelected(speaker)
speakerObserver.observedValues.shouldContainSame(speaker)
}
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct data`() {
val speaker = Speaker("Andriy")
val speakerEventsObserver = viewModel.speakerEvents.testObserver()
viewModel.onSpeakerSelected(speaker)
speakerEventsObserver.observedValues.shouldContainSame(
Event(ViewModelEvent.OpenSpeakerUrl("https://andriy))
)
}
}
Testing ViewModels
Let’s add Kotlin magic
class TestObserver<T> : Observer<T> {
val observedValues = mutableListOf<T>()
fun <Event> shouldContainEvents(vararg events: Event) {
val wrapped = events.map { Event(it) }
observedValues.shouldContainSame(wrapped)
}
}
Testing ViewModels
Let’s add Kotlin magic
class TestObserver<T> : Observer<T> {
val observedValues = mutableListOf<T>()
fun <Event> shouldContainEvents(vararg events: Event) {
val wrapped = events.map { Event(it) }
observedValues.shouldContainSame(wrapped)
}
fun <T> shouldContainValues(vararg values: T) {
observedValues.shouldContainSame(values.asList())
}
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val viewModel = SharedViewModel()
@Test fun `test view model send correct data`() {
val speaker = Speaker("Andriy")
val speakerEventsObserver = viewModel.speakerEvents.testObserver()
viewModel.onSpeakerSelected(speaker)
speakerEventsObserver.shouldContainEvents(
ViewModelEvent.OpenSpeakerUrl("https://andriy")
)
}
}
Testing ViewModels
Sample test
class SharedViewModelTest {
@get:Rule val rule = InstantTaskExecutorRule()
val useCase = mockk<FetchSpeakerUseCase>()
val viewModel = SharedViewModel(useCase)
@Test fun `test view model send correct data`() {
val speakerName = "Andriy"
val speakerObserver = viewModel.speaker.testObserver()
every { useCase.call(speakerName) }
.returns(Speaker("Andriy"))
viewModel.onSpeakerSelected(speakerName)
speakerObserver.shouldContainValues(Speaker("Andriy"))
}
}

More Related Content

What's hot

Android Architecture Component in Real Life
Android Architecture Component in Real LifeAndroid Architecture Component in Real Life
Android Architecture Component in Real LifeSomkiat Khitwongwattana
 
Architecture Components In Real Life Season 2
Architecture Components In Real Life Season 2Architecture Components In Real Life Season 2
Architecture Components In Real Life Season 2Somkiat Khitwongwattana
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKFabio Collini
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackNelson Glauber Leal
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose worldFabio Collini
 
Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"Fwdays
 
"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил Анохин"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил АнохинFwdays
 
안드로이드 데이터 바인딩
안드로이드 데이터 바인딩안드로이드 데이터 바인딩
안드로이드 데이터 바인딩GDG Korea
 
Dinheiro em Java: Joda-Money, Money API e além
Dinheiro em Java: Joda-Money, Money API e alémDinheiro em Java: Joda-Money, Money API e além
Dinheiro em Java: Joda-Money, Money API e alémOtávio Santana
 
JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New EvolutionAllan Huang
 
Petcube epic battle: architecture vs product. UA Mobile 2017.
Petcube epic battle: architecture vs product. UA Mobile 2017.Petcube epic battle: architecture vs product. UA Mobile 2017.
Petcube epic battle: architecture vs product. UA Mobile 2017.UA Mobile
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applicationsGabor Varadi
 
YUI3 Modules
YUI3 ModulesYUI3 Modules
YUI3 Modulesa_pipkin
 
Designing for Windows Phone 8
Designing for Windows Phone 8Designing for Windows Phone 8
Designing for Windows Phone 8David Isbitski
 
Code to DI For - Dependency Injection for Modern Applications
Code to DI For - Dependency Injection for Modern ApplicationsCode to DI For - Dependency Injection for Modern Applications
Code to DI For - Dependency Injection for Modern ApplicationsCaleb Jenkins
 
ProTips DroidCon Paris 2013
ProTips DroidCon Paris 2013ProTips DroidCon Paris 2013
ProTips DroidCon Paris 2013Mathias Seguy
 
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Codemotion
 

What's hot (20)

Android Architecture Component in Real Life
Android Architecture Component in Real LifeAndroid Architecture Component in Real Life
Android Architecture Component in Real Life
 
Architecture Components In Real Life Season 2
Architecture Components In Real Life Season 2Architecture Components In Real Life Season 2
Architecture Components In Real Life Season 2
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
 
Dynamic Elements
Dynamic ElementsDynamic Elements
Dynamic Elements
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com Jetpack
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
 
Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"
 
"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил Анохин"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил Анохин
 
안드로이드 데이터 바인딩
안드로이드 데이터 바인딩안드로이드 데이터 바인딩
안드로이드 데이터 바인딩
 
Dinheiro em Java: Joda-Money, Money API e além
Dinheiro em Java: Joda-Money, Money API e alémDinheiro em Java: Joda-Money, Money API e além
Dinheiro em Java: Joda-Money, Money API e além
 
JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New Evolution
 
Petcube epic battle: architecture vs product. UA Mobile 2017.
Petcube epic battle: architecture vs product. UA Mobile 2017.Petcube epic battle: architecture vs product. UA Mobile 2017.
Petcube epic battle: architecture vs product. UA Mobile 2017.
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applications
 
YUI3 Modules
YUI3 ModulesYUI3 Modules
YUI3 Modules
 
Designing for Windows Phone 8
Designing for Windows Phone 8Designing for Windows Phone 8
Designing for Windows Phone 8
 
Code to DI For - Dependency Injection for Modern Applications
Code to DI For - Dependency Injection for Modern ApplicationsCode to DI For - Dependency Injection for Modern Applications
Code to DI For - Dependency Injection for Modern Applications
 
読むと怖くないDagger2
読むと怖くないDagger2読むと怖くないDagger2
読むと怖くないDagger2
 
Popup view on Mortar
Popup view on MortarPopup view on Mortar
Popup view on Mortar
 
ProTips DroidCon Paris 2013
ProTips DroidCon Paris 2013ProTips DroidCon Paris 2013
ProTips DroidCon Paris 2013
 
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
 

Similar to Working effectively with ViewModels and TDD - UA Mobile 2019

Reactive Model-View-ViewModel Architecture
Reactive Model-View-ViewModel ArchitectureReactive Model-View-ViewModel Architecture
Reactive Model-View-ViewModel ArchitectureGyuwon Yi
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Mahmoud Hamed Mahmoud
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture ComponentsBurhanuddinRashid
 
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...DicodingEvent
 
Say bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinSay bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinMiquel Beltran Febrer
 
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdf
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdfJEDI Slides-Intro2-Chapter20-GUI Event Handling.pdf
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdfMarlouFelixIIICunana
 
Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...DroidConTLV
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0Jeado Ko
 
Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)Eyal Vardi
 
SE2016 Android Dmytro Zaitsev "Viper make your MVP cleaner"
SE2016 Android Dmytro Zaitsev "Viper  make your MVP cleaner"SE2016 Android Dmytro Zaitsev "Viper  make your MVP cleaner"
SE2016 Android Dmytro Zaitsev "Viper make your MVP cleaner"Inhacking
 
Sexy Architecting. VIPER: MVP on steroids
Sexy Architecting. VIPER: MVP on steroidsSexy Architecting. VIPER: MVP on steroids
Sexy Architecting. VIPER: MVP on steroidsDmytro Zaitsev
 
Android architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaAndroid architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaPratama Nur Wijaya
 
Android development with Scala and SBT
Android development with Scala and SBTAndroid development with Scala and SBT
Android development with Scala and SBTAnton Yalyshev
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT TalkConstantine Mars
 
Architecture Components
Architecture Components Architecture Components
Architecture Components DataArt
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeMacoscope
 
Optimize CollectionView Scrolling
Optimize CollectionView ScrollingOptimize CollectionView Scrolling
Optimize CollectionView ScrollingAndrea Prearo
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsHassan Abid
 
Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)Vladislav Ermolin
 

Similar to Working effectively with ViewModels and TDD - UA Mobile 2019 (20)

Reactive Model-View-ViewModel Architecture
Reactive Model-View-ViewModel ArchitectureReactive Model-View-ViewModel Architecture
Reactive Model-View-ViewModel Architecture
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture Components
 
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...
Dicoding Developer Coaching #30: Android | Mengenal Macam-Macam Software Desi...
 
Say bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinSay bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & Kotlin
 
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdf
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdfJEDI Slides-Intro2-Chapter20-GUI Event Handling.pdf
JEDI Slides-Intro2-Chapter20-GUI Event Handling.pdf
 
Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0
 
Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)
 
SE2016 Android Dmytro Zaitsev "Viper make your MVP cleaner"
SE2016 Android Dmytro Zaitsev "Viper  make your MVP cleaner"SE2016 Android Dmytro Zaitsev "Viper  make your MVP cleaner"
SE2016 Android Dmytro Zaitsev "Viper make your MVP cleaner"
 
Sexy Architecting. VIPER: MVP on steroids
Sexy Architecting. VIPER: MVP on steroidsSexy Architecting. VIPER: MVP on steroids
Sexy Architecting. VIPER: MVP on steroids
 
Android architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaAndroid architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta Indonesia
 
Android development with Scala and SBT
Android development with Scala and SBTAndroid development with Scala and SBT
Android development with Scala and SBT
 
GWT MVP Case Study
GWT MVP Case StudyGWT MVP Case Study
GWT MVP Case Study
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT Talk
 
Architecture Components
Architecture Components Architecture Components
Architecture Components
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, Macoscope
 
Optimize CollectionView Scrolling
Optimize CollectionView ScrollingOptimize CollectionView Scrolling
Optimize CollectionView Scrolling
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
 
Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)
 

More from UA Mobile

Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...UA Mobile
 
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...UA Mobile
 
Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019UA Mobile
 
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019UA Mobile
 
Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019UA Mobile
 
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019UA Mobile
 
Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019UA Mobile
 
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019UA Mobile
 
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019UA Mobile
 
До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019UA Mobile
 
Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019UA Mobile
 
Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019UA Mobile
 
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...UA Mobile
 
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019UA Mobile
 
Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019UA Mobile
 
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019UA Mobile
 
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019UA Mobile
 
Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019UA Mobile
 
Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.UA Mobile
 
Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.UA Mobile
 

More from UA Mobile (20)

Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
 
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
 
Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019
 
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
 
Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019
 
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
 
Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019
 
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
 
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
 
До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019
 
Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019
 
Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019
 
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
 
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
 
Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019
 
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
 
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
 
Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019
 
Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.
 
Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.
 

Working effectively with ViewModels and TDD - UA Mobile 2019

  • 1. WORKING EFFECTIVELY WITH VIEWMODELS AND TDD ANDRIY MATKIVSKIY Senior Mobile Engineer at Valtech
  • 3. HOW MANY ARCHITECTURES DO YOU KNOW? ● MVC ● HMVC (Hierarchical model–view–controller) ● MVA (MODEL–VIEW–ADAPTER) ● MVP ● MVVM ● MVI
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9. They all give us the ability to decouple development process into smaller pieces which can be distributed between team members
  • 10. They all lack integration with Android lifecycle system
  • 11. How it works ? ● Lifecycle components ● LiveData<T> ● ViewModel
  • 12. How it works ? Lifecycle components interface LifeCycle (Observer pattern) + addObserver() + removeObserver() + getCurrentState() interface LifecycleObserver interface LifecycleOwner + getLifecycle() class LifecycleRegistry : LifeCycle + Does all the magic required to handle this
  • 13. How it works ? Lifecycle components
  • 14. How it works ? val lifecycleOwner = object : LifecycleOwner { val lifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle() = lifecycleRegistry }
  • 15. How it works ? val lifecycleOwner = object : LifecycleOwner { val lifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle() = lifecycleRegistry } lifecycleOwner.lifecycle.addObserver( object : LifecycleObserver { @OnLifecycleEvent(Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Event) { // Handle incoming events } } )
  • 16. How it works ? val lifecycleOwner = object : LifecycleOwner { val lifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle() = lifecycleRegistry } lifecycleOwner.lifecycle.addObserver( object : LifecycleObserver { @OnLifecycleEvent(Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Event) { // Handle incoming events } } ) lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME)
  • 17. How it works ? val lifecycleOwner = object : LifecycleOwner { val lifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle() = lifecycleRegistry } lifecycleOwner.lifecycle.addObserver( object : LifecycleObserver { @OnLifecycleEvent(Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Event) { // Handle incoming events } } ) lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME)
  • 18. How it works ? LiveData<T> class LiveData<T> (Observer pattern) on steroids (Lifecycle aware) ● Handles Observer state for us (via listening to LifecycleOwner) liveData.observe(LifecycleOwner(), Observer {}) - Ensures Observer is not called when related LifecycleOwner is at least in STARTED state - Remove Observer if when related LifecycleOwner reaches DESTROYED state - Ensures that Observer receives last value when it is active again (LifecycleOwner is back in STARTED state)
  • 19. How it works ? LiveData<T> class LiveData<T> (Observer pattern) on steroids (Lifecycle aware) ● Handles threading for us
  • 20. How it works ? ViewModel public abstract class ViewModel { protected void onCleared() {} } That’s all :)
  • 21. Tips and tricks of working with ViewModel and LiveData
  • 22. Single emitted events through LiveData - Show one time message (toast, snackbar, dialog) - Send one time actions to the view (close activity, navigation events for fragments) - Open other activities (by intent) - Any type of activity that should be triggered once and not re-triggered after rotation
  • 23. Show me the code data class Event<out T>(val content: T) { private var consumed = false fun consume(consumer: (T) -> Unit) { if (not(consumed)) { consumer(content) } consumed = true } fun not(condition: Boolean) = !condition }
  • 24. Show me the code TODO: Remove data class Event<out T>(val content: T) { private var consumed = false fun consume(consumer: (T) -> Unit) { if (not(consumed)) { consumer(content) } consumed = true } fun not(condition: Boolean) = !condition }
  • 25. Show me the code TODO: Remove data class Event<out T>(val content: T) { private var consumed = false fun consume(consumer: (T) -> Unit) { if (not(consumed)) { consumer(content) } consumed = true } fun not(condition: Boolean) = !condition }
  • 26. Show me the code TODO: Remove data class Event<out T>(val content: T) { private var consumed = false fun consume(consumer: (T) -> Unit) { if (not(consumed)) { consumer(content) } consumed = true } fun not(condition: Boolean) = !condition }
  • 27. Usage (from ViewModel) class MainViewModelExample : ViewModel() { val events = MutableLiveData<Event<ViewModelEvent>>() fun sendEvent() { events.value = Event(ViewModelEvent.ShowToast("Hello")) } open class ViewModelEvent { data class ShowToast(val message: String) : ViewModelEvent() } }
  • 28. Usage (from ViewModel) class MainViewModelExample : ViewModel() { val events = MutableLiveData<Event<ViewModelEvent>>() fun sendEvent() { events.value = Event(ViewModelEvent.ShowToast("Hello")) } open class ViewModelEvent { data class ShowToast(val message: String) : ViewModelEvent() } }
  • 29. Usage (from ViewModel) class MainViewModelExample : ViewModel() { val events = MutableLiveData<Event<ViewModelEvent>>() fun sendEvent() { events.value = Event(ViewModelEvent.ShowToast("Hello")) } open class ViewModelEvent { data class ShowToast(val message: String) : ViewModelEvent() } }
  • 30. Usage (from ViewModel) class MainViewModelExample : ViewModel() { val events = MutableLiveData<Event<ViewModelEvent>>() fun sendEvent() { events.value = Event(ViewModelEvent.ShowToast("Hello")) } open class ViewModelEvent { data class ShowToast(val message: String) : ViewModelEvent() } }
  • 31. Usage (from View) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val model = ViewModelProviders.of(this) .get(MainViewModelExample::class.java) observeEvents(model.events) { when (it) { is ViewModelEvent.ShowToast -> showToast(it.message) } }
  • 32. Sharing data between fragments (or views) Imagine we have to fragments that need to communicate somehow?
  • 33. Sharing data between fragments (or views) class SpeakersFragment : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val model = ViewModelProviders.of(requireActivity()) .get(SharedViewModel::class.java) // For example, on button click model.onSpeakerSelected(speaker) } }
  • 34. Sharing data between fragments (or views) class SpeakerDetailsFragment : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // Get the same instance of ViewModel as in SpeakersFragment val model = ViewModelProviders.of(requireActivity()) .get(SharedViewModel::class.java) observe(model.selectedSpeaker) { // Render speaker details on screen } } }
  • 35. Sharing data between fragments (or views) class SharedViewModel : ViewModel() { val selectedSpeaker = MutableLiveData<Speaker>() fun onSpeakerSelected(speaker: Speaker) { selectedSpeaker.value = speaker } }
  • 36. Be careful with fragments (as usual) class AnotherUserFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val model = .. model.users.observe(this, Observer { // Handle users // This can be triggered multiple times during the switch of fragments in activity }) } }
  • 37. Be careful with fragments (as usual) class AnotherUserFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val model = .. model.users.observe(viewLifecycleOwner, Observer { // Handle users }) } }
  • 38. Using ViewModels in Views class SpeakerFragment : Fragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val model = viewModel<SpeakerViewModel>(viewModelFactory) { observe(speakers, ::handlerSpeakers) observeEvents(events, ::handleEvent) } } } - Android-CleanArchitecture-Kotlin with Dagger way
  • 39. Using ViewModels in Views class SpeakerFragment : Fragment() { private val model: SpeakerViewModel by sharedViewModel() override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) observe(model.speakers, ::handlerSpeakers) observeEvents(model.events, ::handleEvent) } } - With Koin DI framework
  • 40. Rx Support in ViewModels class SpeakerViewModel(private val useCase: FetchSpeakerUseCase) :ViewModel() { private val compositeDisposable = CompositeDisposable() fun loadSpeaker() { compositeDisposable.add( useCase.call("speaker_id").subscribe { // Handle result } ) } override fun onCleared() { compositeDisposable.clear() } } Problem
  • 41. Rx Support in ViewModels abstract class BaseViewModel : ViewModel() { private val compositeDisposable = CompositeDisposable() fun addDisposable(block: () -> Disposable) { compositeDisposable.add(block()) } public override fun onCleared() { compositeDisposable.clear() } } How we can fix this?
  • 42. Rx Support in ViewModels class SpeakerViewModel(private val useCase: FetchSpeakerUseCase) : BaseViewModel() { fun loadSpeaker() { addDisposable { useCase.call("speaker_id").subscribe { // Handle result } } } }
  • 46. General architecture This gives a independent development tasks for separate feature
  • 47. Testing ViewModels Handling LiveData values with TestObserver class TestObserver<T> : Observer<T> { override fun onChanged(value: T) { // Handle values coming from LiveData } }
  • 48. Testing ViewModels Handling LiveData values with TestObserver class TestObserver<T> : Observer<T> { val observedValues = mutableListOf<T>() override fun onChanged(value: T) { observedValues.add(value) } }
  • 49. Testing ViewModels Handling LiveData values with TestObserver class TestObserver<T> : Observer<T> { val observedValues = mutableListOf<T>() override fun onChanged(value: T) { observedValues.add(value) } } fun <T> LiveData<T>.testObserver() = TestObserver<T>().also { observeForever(it) }
  • 50. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() }
  • 51. class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct speaker`() { } } Testing ViewModels Sample test
  • 52. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct data`() { val speaker = Speaker("Andriy") val speakerObserver = viewModel.selectedSpeaker.testObserver() } }
  • 53. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct data`() { val speaker = Speaker("Andriy") val speakerObserver = viewModel.selectedSpeaker.testObserver() viewModel.onSpeakerSelected(speaker) } }
  • 54. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct data`() { val speaker = Speaker("Andriy") val speakerObserver = viewModel.selectedSpeaker.testObserver() viewModel.onSpeakerSelected(speaker) speakerObserver.observedValues.shouldContainSame(speaker) } }
  • 55. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct data`() { val speaker = Speaker("Andriy") val speakerEventsObserver = viewModel.speakerEvents.testObserver() viewModel.onSpeakerSelected(speaker) speakerEventsObserver.observedValues.shouldContainSame( Event(ViewModelEvent.OpenSpeakerUrl("https://andriy)) ) } }
  • 56. Testing ViewModels Let’s add Kotlin magic class TestObserver<T> : Observer<T> { val observedValues = mutableListOf<T>() fun <Event> shouldContainEvents(vararg events: Event) { val wrapped = events.map { Event(it) } observedValues.shouldContainSame(wrapped) } }
  • 57. Testing ViewModels Let’s add Kotlin magic class TestObserver<T> : Observer<T> { val observedValues = mutableListOf<T>() fun <Event> shouldContainEvents(vararg events: Event) { val wrapped = events.map { Event(it) } observedValues.shouldContainSame(wrapped) } fun <T> shouldContainValues(vararg values: T) { observedValues.shouldContainSame(values.asList()) } }
  • 58. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val viewModel = SharedViewModel() @Test fun `test view model send correct data`() { val speaker = Speaker("Andriy") val speakerEventsObserver = viewModel.speakerEvents.testObserver() viewModel.onSpeakerSelected(speaker) speakerEventsObserver.shouldContainEvents( ViewModelEvent.OpenSpeakerUrl("https://andriy") ) } }
  • 59. Testing ViewModels Sample test class SharedViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() val useCase = mockk<FetchSpeakerUseCase>() val viewModel = SharedViewModel(useCase) @Test fun `test view model send correct data`() { val speakerName = "Andriy" val speakerObserver = viewModel.speaker.testObserver() every { useCase.call(speakerName) } .returns(Speaker("Andriy")) viewModel.onSpeakerSelected(speakerName) speakerObserver.shouldContainValues(Speaker("Andriy")) } }