From e3f613c6c58d2d4b19c891bd83d07c4cd738bbd0 Mon Sep 17 00:00:00 2001
From: Serchinastico <54cymru@gmail.com>
Date: Thu, 31 Jan 2019 14:37:40 +0100
Subject: [PATCH] Add LiveData and ViewModels and implement detail screen with
 it

Migrate edit super hero screen

Migrate main screen to view model

WIP
---
 .../superheroes/data/singleValueLiveData.kt   |  7 ++
 .../ui/view/EditSuperHeroActivityTest.kt      |  3 +-
 .../superheroes/ui/view/MainActivityTest.kt   |  5 +-
 .../ui/view/SuperHeroDetailActivityTest.kt    |  3 +-
 .../ui/view/SuperHeroViewHolderTest.kt        | 16 ++--
 .../superheroes/common/ViewModelFactory.kt    | 37 +++++++++
 .../repository/LocalSuperHeroDataSource.kt    | 13 +--
 .../repository/RemoteSuperHeroDataSource.kt   | 29 ++++---
 .../data/repository/SuperHeroRepository.kt    | 34 ++++++--
 .../data/repository/room/SuperHeroDao.kt      |  5 +-
 .../domain/usecase/GetSuperHeroById.kt        |  3 +-
 .../domain/usecase/GetSuperHeroes.kt          |  3 +-
 .../ui/presenter/EditSuperHeroPresenter.kt    | 83 -------------------
 .../ui/presenter/SuperHeroDetailPresenter.kt  | 57 -------------
 .../ui/presenter/SuperHeroesPresenter.kt      | 55 ------------
 .../superheroes/ui/view/BaseActivity.kt       | 23 +++--
 .../ui/view/EditSuperHeroActivity.kt          | 49 ++++-------
 .../superheroes/ui/view/MainActivity.kt       | 37 ++++-----
 .../ui/view/SuperHeroDetailActivity.kt        | 41 +++------
 .../ui/view/adapter/SuperHeroViewHolder.kt    |  8 +-
 .../ui/view/adapter/SuperHeroesAdapter.kt     |  9 +-
 .../ui/viewmodel/EditSuperHeroViewModel.kt    | 60 ++++++++++++++
 .../ui/viewmodel/SuperHeroDetailViewModel.kt  | 35 ++++++++
 .../ui/viewmodel/SuperHeroesViewModel.kt      | 37 +++++++++
 .../res/layout/edit_super_hero_activity.xml   | 34 +++-----
 app/src/main/res/layout/main_activity.xml     | 12 +--
 .../res/layout/super_hero_detail_activity.xml | 28 +++----
 app/src/main/res/layout/super_hero_row.xml    |  5 +-
 28 files changed, 342 insertions(+), 389 deletions(-)
 create mode 100644 app/src/androidTest/java/com/karumi/jetpack/superheroes/data/singleValueLiveData.kt
 create mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/common/ViewModelFactory.kt
 delete mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/EditSuperHeroPresenter.kt
 delete mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroDetailPresenter.kt
 delete mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroesPresenter.kt
 create mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/EditSuperHeroViewModel.kt
 create mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroDetailViewModel.kt
 create mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroesViewModel.kt

diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/singleValueLiveData.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/singleValueLiveData.kt
new file mode 100644
index 0000000..340b1b9
--- /dev/null
+++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/singleValueLiveData.kt
@@ -0,0 +1,7 @@
+package com.karumi.jetpack.superheroes.data
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+fun <T> singleValueLiveData(value: T): LiveData<T> =
+    MutableLiveData<T>().apply { postValue(value) }
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivityTest.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivityTest.kt
index a0bf0b8..9fc82c1 100644
--- a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivityTest.kt
+++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivityTest.kt
@@ -2,6 +2,7 @@ package com.karumi.jetpack.superheroes.ui.view
 
 import android.os.Bundle
 import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
+import com.karumi.jetpack.superheroes.data.singleValueLiveData
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.nhaarman.mockitokotlin2.whenever
 import org.junit.Test
@@ -37,7 +38,7 @@ class EditSuperHeroActivityTest :
             true,
             ""
         )
-        whenever(repository.get(ANY_ID)).thenReturn(superHero)
+        whenever(repository.get(ANY_ID)).thenReturn(singleValueLiveData(superHero))
         return superHero
     }
 
diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/MainActivityTest.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/MainActivityTest.kt
index 8e5f195..414c938 100644
--- a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/MainActivityTest.kt
+++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/MainActivityTest.kt
@@ -1,6 +1,7 @@
 package com.karumi.jetpack.superheroes.ui.view
 
 import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
+import com.karumi.jetpack.superheroes.data.singleValueLiveData
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.nhaarman.mockitokotlin2.whenever
 import org.junit.Test
@@ -83,12 +84,12 @@ class MainActivityTest : AcceptanceTest<MainActivity>(MainActivity::class.java)
             )
         }
 
-        whenever(repository.getAllSuperHeroes()).thenReturn(superHeroes)
+        whenever(repository.getAllSuperHeroes()).thenReturn(singleValueLiveData(superHeroes))
         return superHeroes
     }
 
     private fun givenThereAreNoSuperHeroes() {
-        whenever(repository.getAllSuperHeroes()).thenReturn(emptyList())
+        whenever(repository.getAllSuperHeroes()).thenReturn(singleValueLiveData(emptyList()))
     }
 
     override val testDependencies = Kodein.Module("Test dependencies", allowSilentOverride = true) {
diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivityTest.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivityTest.kt
index f596dc4..e960984 100644
--- a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivityTest.kt
+++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivityTest.kt
@@ -2,6 +2,7 @@ package com.karumi.jetpack.superheroes.ui.view
 
 import android.os.Bundle
 import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
+import com.karumi.jetpack.superheroes.data.singleValueLiveData
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.nhaarman.mockitokotlin2.whenever
 import org.junit.Test
@@ -40,7 +41,7 @@ class SuperHeroDetailActivityTest : AcceptanceTest<SuperHeroDetailActivity>(
         val superHeroName = "SuperHero"
         val superHeroDescription = "Super Hero Description"
         val superHero = SuperHero(superHeroId, superHeroName, null, isAvenger, superHeroDescription)
-        whenever(repository.get(superHeroId)).thenReturn(superHero)
+        whenever(repository.get(superHeroId)).thenReturn(singleValueLiveData(superHero))
         return superHero
     }
 
diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroViewHolderTest.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroViewHolderTest.kt
index 3f70a31..7a13aa7 100644
--- a/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroViewHolderTest.kt
+++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroViewHolderTest.kt
@@ -6,10 +6,9 @@ import androidx.test.platform.app.InstrumentationRegistry
 import com.karumi.jetpack.superheroes.R
 import com.karumi.jetpack.superheroes.databinding.SuperHeroRowBinding
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesPresenter
 import com.karumi.jetpack.superheroes.ui.view.adapter.SuperHeroViewHolder
+import com.nhaarman.mockitokotlin2.mock
 import org.junit.Test
-import org.mockito.Mockito.mock
 
 class SuperHeroViewHolderTest : ScreenshotTest {
 
@@ -18,7 +17,7 @@ class SuperHeroViewHolderTest : ScreenshotTest {
         val superHero = givenASuperHero()
         val holder = givenASuperHeroViewHolder()
 
-        holder.render(superHero)
+        holder.render(superHero, mock())
 
         compareScreenshot(holder, R.dimen.super_hero_row_height)
     }
@@ -28,7 +27,7 @@ class SuperHeroViewHolderTest : ScreenshotTest {
         val superHero = givenASuperHeroWithALongName()
         val holder = givenASuperHeroViewHolder()
 
-        holder.render(superHero)
+        holder.render(superHero, mock())
 
         compareScreenshot(holder, R.dimen.super_hero_row_height)
     }
@@ -38,7 +37,7 @@ class SuperHeroViewHolderTest : ScreenshotTest {
         val superHero = givenASuperHeroWithALongDescription()
         val holder = givenASuperHeroViewHolder()
 
-        holder.render(superHero)
+        holder.render(superHero, mock())
 
         compareScreenshot(holder, R.dimen.super_hero_row_height)
     }
@@ -48,7 +47,7 @@ class SuperHeroViewHolderTest : ScreenshotTest {
         val superHero = givenASuperHero(isAvenger = true)
         val holder = givenASuperHeroViewHolder()
 
-        holder.render(superHero)
+        holder.render(superHero, mock())
 
         compareScreenshot(holder, R.dimen.super_hero_row_height)
     }
@@ -59,10 +58,7 @@ class SuperHeroViewHolderTest : ScreenshotTest {
             val inflater = LayoutInflater.from(context)
             val binding: SuperHeroRowBinding =
                 DataBindingUtil.inflate(inflater, R.layout.super_hero_row, null, false)
-            SuperHeroViewHolder(
-                binding,
-                mock<SuperHeroesPresenter>(SuperHeroesPresenter::class.java)
-            )
+            SuperHeroViewHolder(binding)
         }
 
     private fun givenASuperHeroWithALongDescription(): SuperHero {
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/common/ViewModelFactory.kt b/app/src/main/java/com/karumi/jetpack/superheroes/common/ViewModelFactory.kt
new file mode 100644
index 0000000..175acd4
--- /dev/null
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/common/ViewModelFactory.kt
@@ -0,0 +1,37 @@
+package com.karumi.jetpack.superheroes.common
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProviders
+import com.karumi.jetpack.superheroes.ui.view.BaseActivity
+import org.kodein.di.DKodein
+import org.kodein.di.Kodein
+import org.kodein.di.KodeinAware
+import org.kodein.di.direct
+import org.kodein.di.erased.bind
+import org.kodein.di.erased.instance
+import org.kodein.di.erased.instanceOrNull
+
+class ViewModelFactory(
+    private val injector: DKodein,
+    private val application: Application
+) : ViewModelProvider.Factory {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return injector.instanceOrNull<ViewModel>(tag = modelClass.simpleName) as T?
+            ?: modelClass.getConstructor(Application::class.java).newInstance(application)
+    }
+}
+
+inline fun <reified VM : ViewModel, T> T.viewModel(): Lazy<VM>
+        where T : KodeinAware,
+              T : BaseActivity<*> {
+    return lazy { ViewModelProviders.of(this, direct.instance()).get(VM::class.java) }
+}
+
+inline fun <reified T : ViewModel> Kodein.Builder.bindViewModel(
+    overrides: Boolean? = null
+): Kodein.Builder.TypeBinder<T> {
+    return bind<T>(T::class.java.simpleName, overrides)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt
index 038e093..44ca70c 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt
@@ -1,5 +1,7 @@
 package com.karumi.jetpack.superheroes.data.repository
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
 import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroDao
 import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroEntity
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
@@ -9,12 +11,11 @@ class LocalSuperHeroDataSource(
     private val dao: SuperHeroDao,
     private val executor: ExecutorService
 ) {
-    fun getAllSuperHeroes(): List<SuperHero> =
-        dao.getAll()
-            .map { it.toSuperHero() }
+    fun getAllSuperHeroes(): LiveData<List<SuperHero>> =
+        Transformations.map(dao.getAll()) { it.toSuperHeroes() }
 
-    fun get(id: String): SuperHero? =
-        dao.getById(id)?.toSuperHero()
+    fun get(id: String): LiveData<SuperHero?> =
+        Transformations.map(dao.getById(id)) { it?.toSuperHero() }
 
     fun saveAll(all: List<SuperHero>) = executor.execute {
         dao.deleteAll()
@@ -26,7 +27,7 @@ class LocalSuperHeroDataSource(
         return superHero
     }
 
+    private fun List<SuperHeroEntity>.toSuperHeroes(): List<SuperHero> = map { it.toSuperHero() }
     private fun SuperHeroEntity.toSuperHero(): SuperHero = superHero
-
     private fun SuperHero.toEntity(): SuperHeroEntity = SuperHeroEntity(this)
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/RemoteSuperHeroDataSource.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/RemoteSuperHeroDataSource.kt
index e28ac2a..3e30cdd 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/RemoteSuperHeroDataSource.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/RemoteSuperHeroDataSource.kt
@@ -1,5 +1,7 @@
 package com.karumi.jetpack.superheroes.data.repository
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import java.util.concurrent.ExecutorService
 
@@ -10,20 +12,25 @@ class RemoteSuperHeroDataSource(
         private const val BIT_TIME = 1500L
     }
 
-    private val superHeroes: MutableMap<String, SuperHero>
+    private val superHeroes: MutableMap<String, SuperHero> =
+        fakeData().associateBy { it.id }.toMutableMap()
 
-    init {
-        superHeroes = fakeData().associateBy { it.id }.toMutableMap()
-    }
-
-    fun getAllSuperHeroes(): List<SuperHero> {
-        waitABit()
-        return superHeroes.values.toList().sortedBy { it.id }
+    fun getAllSuperHeroes(): LiveData<List<SuperHero>> {
+        val allSuperHeroes = MutableLiveData<List<SuperHero>>()
+        executor.execute {
+            waitABit()
+            allSuperHeroes.postValue(superHeroes.values.toList().sortedBy { it.id })
+        }
+        return allSuperHeroes
     }
 
-    fun get(id: String): SuperHero? {
-        waitABit()
-        return superHeroes[id]
+    fun get(id: String): LiveData<SuperHero?> {
+        val superHero = MutableLiveData<SuperHero?>()
+        executor.execute {
+            waitABit()
+            superHero.postValue(superHeroes[id])
+        }
+        return superHero
     }
 
     fun save(superHero: SuperHero): SuperHero {
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/SuperHeroRepository.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/SuperHeroRepository.kt
index a821e65..27cb8a7 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/SuperHeroRepository.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/SuperHeroRepository.kt
@@ -1,24 +1,44 @@
 package com.karumi.jetpack.superheroes.data.repository
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 
 class SuperHeroRepository(
     private val local: LocalSuperHeroDataSource,
     private val remote: RemoteSuperHeroDataSource
 ) {
-    fun getAllSuperHeroes(): List<SuperHero> =
-        local.getAllSuperHeroes().ifEmpty {
-            remote.getAllSuperHeroes()
-                .also { local.saveAll(it) }
+    fun getAllSuperHeroes(): LiveData<List<SuperHero>> = MediatorLiveData<List<SuperHero>>().apply {
+        val localSource = local.getAllSuperHeroes()
+        val remoteSource = remote.getAllSuperHeroes()
+
+        addSource(remoteSource) { superHeroes ->
+            removeSource(remoteSource)
+            addSource(localSource) { postValue(it) }
+            local.saveAll(superHeroes)
         }
+    }
 
-    fun get(id: String): SuperHero? =
-        local.get(id)
-            ?: remote.get(id)?.also { local.save(it) }
+    fun get(id: String): LiveData<SuperHero?> = MediatorLiveData<SuperHero?>().apply {
+        addSource(local.get(id)) {
+            if (it == null) {
+                syncSuperHeroFromRemote(id)
+            } else {
+                postValue(it)
+            }
+        }
+    }
 
     fun save(superHero: SuperHero): SuperHero {
         local.save(superHero)
         remote.save(superHero)
         return superHero
     }
+
+    private fun MediatorLiveData<SuperHero?>.syncSuperHeroFromRemote(id: String) {
+        addSource(remote.get(id)) { superHero ->
+            superHero ?: return@addSource
+            local.save(superHero)
+        }
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt
index 03dc2c1..b9e87aa 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt
@@ -1,5 +1,6 @@
 package com.karumi.jetpack.superheroes.data.repository.room
 
+import androidx.lifecycle.LiveData
 import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
@@ -9,10 +10,10 @@ import androidx.room.Update
 @Dao
 interface SuperHeroDao {
     @Query("SELECT * FROM superheroes ORDER BY superhero_id ASC")
-    fun getAll(): List<SuperHeroEntity>
+    fun getAll(): LiveData<List<SuperHeroEntity>>
 
     @Query("SELECT * FROM superheroes WHERE superhero_id = :id")
-    fun getById(id: String): SuperHeroEntity?
+    fun getById(id: String): LiveData<SuperHeroEntity?>
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun insertAll(superHeroes: List<SuperHeroEntity>)
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroById.kt b/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroById.kt
index 4f446b3..ce48baf 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroById.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroById.kt
@@ -1,8 +1,9 @@
 package com.karumi.jetpack.superheroes.domain.usecase
 
+import androidx.lifecycle.LiveData
 import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 
 class GetSuperHeroById(private val superHeroesRepository: SuperHeroRepository) {
-    operator fun invoke(id: String): SuperHero? = superHeroesRepository.get(id)
+    operator fun invoke(id: String): LiveData<SuperHero?> = superHeroesRepository.get(id)
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroes.kt b/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroes.kt
index c260f03..fa97ecb 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroes.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/domain/usecase/GetSuperHeroes.kt
@@ -1,8 +1,9 @@
 package com.karumi.jetpack.superheroes.domain.usecase
 
+import androidx.lifecycle.LiveData
 import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 
 class GetSuperHeroes(private val superHeroesRepository: SuperHeroRepository) {
-    operator fun invoke(): List<SuperHero> = superHeroesRepository.getAllSuperHeroes()
+    operator fun invoke(): LiveData<List<SuperHero>> = superHeroesRepository.getAllSuperHeroes()
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/EditSuperHeroPresenter.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/EditSuperHeroPresenter.kt
deleted file mode 100644
index 4d23688..0000000
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/EditSuperHeroPresenter.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.karumi.jetpack.superheroes.ui.presenter
-
-import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
-import androidx.lifecycle.Lifecycle.Event.ON_RESUME
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
-import com.karumi.jetpack.superheroes.common.weak
-import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
-import com.karumi.jetpack.superheroes.domain.usecase.SaveSuperHero
-import java.util.concurrent.ExecutorService
-
-class EditSuperHeroPresenter(
-    view: View,
-    private val getSuperHeroById: GetSuperHeroById,
-    private val saveSuperHero: SaveSuperHero,
-    private val executor: ExecutorService
-) : EditSuperHeroListener, LifecycleObserver {
-
-    private val view: View? by weak(view)
-    private lateinit var id: String
-    private var superHero: SuperHero? = null
-
-    fun preparePresenter(id: String) {
-        this.id = id
-    }
-
-    @OnLifecycleEvent(ON_RESUME)
-    fun onResume() {
-        view?.showLoading()
-        refreshSuperHero()
-    }
-
-    @OnLifecycleEvent(ON_DESTROY)
-    fun onDestroy() {
-        executor.shutdownNow()
-    }
-
-    override fun onSaveSuperHeroSelected(
-        editableSuperHero: EditSuperHeroPresenter.EditableSuperHero
-    ) {
-        saveSuperHero(editableSuperHero)
-    }
-
-    private fun saveSuperHero(
-        editableSuperHero: EditSuperHeroPresenter.EditableSuperHero
-    ) = executor.submit {
-        view?.showLoading()
-        val superHero = superHero ?: return@submit
-        saveSuperHero(
-            superHero.copy(
-                name = editableSuperHero.name,
-                description = editableSuperHero.description,
-                isAvenger = editableSuperHero.isAvenger
-            )
-        )
-        view?.close()
-    }
-
-    private fun refreshSuperHero() = executor.submit {
-        val superHero = getSuperHeroById(id) ?: return@submit
-        view?.hideLoading()
-        view?.showSuperHero(superHero)
-        this@EditSuperHeroPresenter.superHero = superHero
-    }
-
-    data class EditableSuperHero(
-        var isAvenger: Boolean,
-        var name: String,
-        var description: String
-    )
-
-    interface View {
-        fun close()
-        fun hideLoading()
-        fun showLoading()
-        fun showSuperHero(superHero: SuperHero)
-    }
-}
-
-interface EditSuperHeroListener {
-    fun onSaveSuperHeroSelected(editableSuperHero: EditSuperHeroPresenter.EditableSuperHero)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroDetailPresenter.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroDetailPresenter.kt
deleted file mode 100644
index 5699165..0000000
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroDetailPresenter.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.karumi.jetpack.superheroes.ui.presenter
-
-import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
-import androidx.lifecycle.Lifecycle.Event.ON_RESUME
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
-import com.karumi.jetpack.superheroes.common.weak
-import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
-import java.util.concurrent.ExecutorService
-
-class SuperHeroDetailPresenter(
-    view: View,
-    private val getSuperHeroById: GetSuperHeroById,
-    private val executor: ExecutorService
-) : SuperHeroDetailListener, LifecycleObserver {
-
-    private val view: View? by weak(view)
-
-    private lateinit var id: String
-
-    fun preparePresenter(id: String) {
-        this.id = id
-    }
-
-    @OnLifecycleEvent(ON_RESUME)
-    fun onResume() {
-        view?.showLoading()
-        refreshSuperHero()
-    }
-
-    @OnLifecycleEvent(ON_DESTROY)
-    fun onDestroy() {
-        executor.shutdownNow()
-    }
-
-    override fun onEditSelected() {
-        view?.openEditSuperHero(id)
-    }
-
-    private fun refreshSuperHero() = executor.submit {
-        val superHero = getSuperHeroById(id) ?: return@submit
-        view?.hideLoading()
-        view?.showSuperHero(superHero)
-    }
-
-    interface View {
-        fun hideLoading()
-        fun showLoading()
-        fun showSuperHero(superHero: SuperHero)
-        fun openEditSuperHero(superHeroId: String)
-    }
-}
-
-interface SuperHeroDetailListener {
-    fun onEditSelected()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroesPresenter.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroesPresenter.kt
deleted file mode 100644
index 534ad7a..0000000
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/presenter/SuperHeroesPresenter.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.karumi.jetpack.superheroes.ui.presenter
-
-import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
-import androidx.lifecycle.Lifecycle.Event.ON_RESUME
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
-import com.karumi.jetpack.superheroes.common.weak
-import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroes
-import java.util.concurrent.ExecutorService
-
-class SuperHeroesPresenter(
-    view: View,
-    private val getSuperHeroes: GetSuperHeroes,
-    private val executor: ExecutorService
-) : SuperHeroesListener, LifecycleObserver {
-
-    private val view: View? by weak(view)
-
-    @OnLifecycleEvent(ON_RESUME)
-    fun onResume() {
-        view?.showLoading()
-        refreshSuperHeroes()
-    }
-
-    @OnLifecycleEvent(ON_DESTROY)
-    fun onDestroy() {
-        executor.shutdownNow()
-    }
-
-    private fun refreshSuperHeroes() = executor.submit {
-        val result = getSuperHeroes()
-        view?.hideLoading()
-        when {
-            result.isEmpty() -> view?.showEmptyCase()
-            else -> view?.showSuperHeroes(result)
-        }
-    }
-
-    override fun onSuperHeroClicked(superHero: SuperHero) {
-        view?.openDetail(superHero.id)
-    }
-
-    interface View {
-        fun hideLoading()
-        fun showLoading()
-        fun showEmptyCase()
-        fun showSuperHeroes(superHeroes: List<SuperHero>)
-        fun openDetail(id: String)
-    }
-}
-
-interface SuperHeroesListener {
-    fun onSuperHeroClicked(superHero: SuperHero)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/BaseActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/BaseActivity.kt
index a692ec3..cebf2df 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/BaseActivity.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/BaseActivity.kt
@@ -1,43 +1,48 @@
 package com.karumi.jetpack.superheroes.ui.view
 
-import android.content.Intent
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.Toolbar
 import androidx.databinding.DataBindingUtil
 import androidx.databinding.ViewDataBinding
-import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.karumi.jetpack.superheroes.common.ViewModelFactory
 import org.kodein.di.Kodein
 import org.kodein.di.KodeinAware
 import org.kodein.di.android.closestKodein
+import org.kodein.di.erased.bind
+import org.kodein.di.erased.instance
+import org.kodein.di.erased.singleton
 
 abstract class BaseActivity<T : ViewDataBinding> : AppCompatActivity(), KodeinAware {
 
     private val appKodein by closestKodein()
     override val kodein: Kodein = Kodein.lazy {
         extend(appKodein)
+        includeViewModelFactory()
         import(activityModules)
     }
-    abstract val presenter: LifecycleObserver
+
     abstract val layoutId: Int
     abstract val toolbarView: Toolbar
     abstract val activityModules: Kodein.Module
+    abstract val viewModel: AndroidViewModel
     protected lateinit var binding: T
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        lifecycle.addObserver(presenter)
         binding = DataBindingUtil.setContentView(this, layoutId)
+        binding.lifecycleOwner = this
         configureBinding(binding)
         setSupportActionBar(toolbarView)
-        prepare(intent)
     }
 
-    override fun onDestroy() {
-        super.onDestroy()
-        lifecycle.removeObserver(presenter)
+    private fun Kodein.MainBuilder.includeViewModelFactory() {
+        bind<ViewModelProvider.Factory>() with singleton {
+            ViewModelFactory(instance(), instance())
+        }
     }
 
     abstract fun configureBinding(binding: T)
-    open fun prepare(intent: Intent?) {}
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivity.kt
index 96c9726..8ef4b68 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivity.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivity.kt
@@ -2,22 +2,23 @@ package com.karumi.jetpack.superheroes.ui.view
 
 import android.app.Activity
 import android.content.Intent
+import android.os.Bundle
 import androidx.appcompat.widget.Toolbar
+import androidx.lifecycle.Observer
 import com.karumi.jetpack.superheroes.R
+import com.karumi.jetpack.superheroes.common.bindViewModel
 import com.karumi.jetpack.superheroes.common.module
+import com.karumi.jetpack.superheroes.common.viewModel
 import com.karumi.jetpack.superheroes.databinding.EditSuperHeroActivityBinding
-import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
 import com.karumi.jetpack.superheroes.domain.usecase.SaveSuperHero
-import com.karumi.jetpack.superheroes.ui.presenter.EditSuperHeroPresenter
+import com.karumi.jetpack.superheroes.ui.viewmodel.EditSuperHeroViewModel
 import kotlinx.android.synthetic.main.edit_super_hero_activity.*
 import org.kodein.di.erased.bind
 import org.kodein.di.erased.instance
 import org.kodein.di.erased.provider
 
-class EditSuperHeroActivity :
-    BaseActivity<EditSuperHeroActivityBinding>(),
-    EditSuperHeroPresenter.View {
+class EditSuperHeroActivity : BaseActivity<EditSuperHeroActivityBinding>() {
 
     companion object {
         private const val SUPER_HERO_ID_KEY = "super_hero_id_key"
@@ -29,48 +30,28 @@ class EditSuperHeroActivity :
         }
     }
 
-    override val presenter: EditSuperHeroPresenter by instance()
+    override val viewModel: EditSuperHeroViewModel by viewModel()
     override val layoutId = R.layout.edit_super_hero_activity
     override val toolbarView: Toolbar
         get() = toolbar
     private val superHeroId: String by lazy { intent?.extras?.getString(SUPER_HERO_ID_KEY) ?: "" }
 
-    override fun configureBinding(binding: EditSuperHeroActivityBinding) {
-        binding.listener = presenter
-        binding.isLoading = false
-    }
-
-    override fun prepare(intent: Intent?) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
         title = superHeroId
-        presenter.preparePresenter(superHeroId)
     }
 
-    override fun close() = runOnUiThread {
-        finish()
-    }
-
-    override fun showLoading() = runOnUiThread {
-        binding.isLoading = true
-    }
-
-    override fun hideLoading() = runOnUiThread {
-        binding.isLoading = false
-    }
-
-    override fun showSuperHero(superHero: SuperHero) = runOnUiThread {
-        binding.listener = presenter
-        binding.superHero = superHero
-        binding.editableSuperHero = superHero.toEditable()
+    override fun configureBinding(binding: EditSuperHeroActivityBinding) {
+        binding.viewModel = viewModel
+        viewModel.isClosing.observe(this, Observer { finish() })
+        viewModel.prepare(superHeroId)
     }
 
     override val activityModules = module {
-        bind<EditSuperHeroPresenter>() with provider {
-            EditSuperHeroPresenter(this@EditSuperHeroActivity, instance(), instance(), instance())
+        bindViewModel<EditSuperHeroViewModel>() with provider {
+            EditSuperHeroViewModel(instance(), instance(), instance())
         }
         bind<GetSuperHeroById>() with provider { GetSuperHeroById(instance()) }
         bind<SaveSuperHero>() with provider { SaveSuperHero(instance()) }
     }
-
-    private fun SuperHero.toEditable() =
-        EditSuperHeroPresenter.EditableSuperHero(isAvenger, name, description)
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/MainActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/MainActivity.kt
index 4521ade..1cbb49f 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/MainActivity.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/MainActivity.kt
@@ -2,22 +2,25 @@ package com.karumi.jetpack.superheroes.ui.view
 
 import android.os.Bundle
 import androidx.appcompat.widget.Toolbar
+import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.karumi.jetpack.superheroes.R
+import com.karumi.jetpack.superheroes.common.bindViewModel
 import com.karumi.jetpack.superheroes.common.module
+import com.karumi.jetpack.superheroes.common.viewModel
 import com.karumi.jetpack.superheroes.databinding.MainActivityBinding
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroes
-import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesPresenter
 import com.karumi.jetpack.superheroes.ui.view.adapter.SuperHeroesAdapter
+import com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroesViewModel
 import kotlinx.android.synthetic.main.main_activity.*
 import org.kodein.di.erased.bind
 import org.kodein.di.erased.instance
 import org.kodein.di.erased.provider
 
-class MainActivity : BaseActivity<MainActivityBinding>(), SuperHeroesPresenter.View {
+class MainActivity : BaseActivity<MainActivityBinding>() {
 
-    override val presenter: SuperHeroesPresenter by instance()
+    override val viewModel: SuperHeroesViewModel by viewModel()
     private lateinit var adapter: SuperHeroesAdapter
     override val layoutId: Int = R.layout.main_activity
     override val toolbarView: Toolbar
@@ -27,15 +30,17 @@ class MainActivity : BaseActivity<MainActivityBinding>(), SuperHeroesPresenter.V
         super.onCreate(savedInstanceState)
         initializeAdapter()
         initializeRecyclerView()
+        viewModel.prepare()
+        viewModel.idOfSuperHeroToOpen.observe(this, Observer { openDetail(it) })
+        viewModel.superHeroes.observe(this, Observer { showSuperHeroes(it) })
     }
 
     override fun configureBinding(binding: MainActivityBinding) {
-        binding.isLoading = false
-        binding.isShowingEmptyCase = false
+        binding.viewModel = viewModel
     }
 
     private fun initializeAdapter() {
-        adapter = SuperHeroesAdapter(presenter)
+        adapter = SuperHeroesAdapter(viewModel)
     }
 
     private fun initializeRecyclerView() {
@@ -44,25 +49,13 @@ class MainActivity : BaseActivity<MainActivityBinding>(), SuperHeroesPresenter.V
         recycler_view.adapter = adapter
     }
 
-    override fun showLoading() = runOnUiThread {
-        binding.isLoading = true
-    }
-
-    override fun hideLoading() = runOnUiThread {
-        binding.isLoading = false
-    }
-
-    override fun showEmptyCase() = runOnUiThread {
-        binding.isShowingEmptyCase = true
-    }
-
-    override fun showSuperHeroes(superHeroes: List<SuperHero>) = runOnUiThread {
+    private fun showSuperHeroes(superHeroes: List<SuperHero>) {
         adapter.clear()
         adapter.addAll(superHeroes)
         adapter.notifyDataSetChanged()
     }
 
-    override fun openDetail(id: String) = runOnUiThread {
+    private fun openDetail(id: String) {
         SuperHeroDetailActivity.open(
             activity = this,
             superHeroId = id
@@ -70,8 +63,8 @@ class MainActivity : BaseActivity<MainActivityBinding>(), SuperHeroesPresenter.V
     }
 
     override val activityModules = module {
-        bind<SuperHeroesPresenter>() with provider {
-            SuperHeroesPresenter(this@MainActivity, instance(), instance())
+        bindViewModel<SuperHeroesViewModel>() with provider {
+            SuperHeroesViewModel(instance(), instance())
         }
         bind<GetSuperHeroes>() with provider { GetSuperHeroes(instance()) }
     }
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivity.kt
index b336878..a1b47b2 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivity.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivity.kt
@@ -3,20 +3,21 @@ package com.karumi.jetpack.superheroes.ui.view
 import android.app.Activity
 import android.content.Intent
 import androidx.appcompat.widget.Toolbar
+import androidx.lifecycle.Observer
 import com.karumi.jetpack.superheroes.R
+import com.karumi.jetpack.superheroes.common.bindViewModel
 import com.karumi.jetpack.superheroes.common.module
+import com.karumi.jetpack.superheroes.common.viewModel
 import com.karumi.jetpack.superheroes.databinding.SuperHeroDetailActivityBinding
-import com.karumi.jetpack.superheroes.domain.model.SuperHero
 import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
-import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroDetailPresenter
+import com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroDetailViewModel
 import kotlinx.android.synthetic.main.super_hero_detail_activity.*
 import org.kodein.di.erased.bind
 import org.kodein.di.erased.instance
 import org.kodein.di.erased.provider
 
 class SuperHeroDetailActivity :
-    BaseActivity<SuperHeroDetailActivityBinding>(),
-    SuperHeroDetailPresenter.View {
+    BaseActivity<SuperHeroDetailActivityBinding>() {
     companion object {
         private const val SUPER_HERO_ID_KEY = "super_hero_id_key"
 
@@ -27,42 +28,26 @@ class SuperHeroDetailActivity :
         }
     }
 
-    override val presenter: SuperHeroDetailPresenter by instance()
+    override val viewModel: SuperHeroDetailViewModel by viewModel()
     override val layoutId: Int = R.layout.super_hero_detail_activity
     override val toolbarView: Toolbar
         get() = toolbar
     private val superHeroId: String by lazy { intent?.extras?.getString(SUPER_HERO_ID_KEY) ?: "" }
 
     override fun configureBinding(binding: SuperHeroDetailActivityBinding) {
-        binding.listener = presenter
-        binding.isLoading = false
+        binding.viewModel = viewModel
+        viewModel.idOfSuperHeroToEdit.observe(this, Observer { openEditSuperHero(it) })
+        viewModel.superHero.observe(this, Observer { title = it?.name })
+        viewModel.prepare(superHeroId)
     }
 
-    override fun prepare(intent: Intent?) {
-        title = superHeroId
-        presenter.preparePresenter(superHeroId)
-    }
-
-    override fun showLoading() = runOnUiThread {
-        binding.isLoading = true
-    }
-
-    override fun hideLoading() = runOnUiThread {
-        binding.isLoading = false
-    }
-
-    override fun showSuperHero(superHero: SuperHero) = runOnUiThread {
-        title = superHero.name
-        binding.superHero = superHero
-    }
-
-    override fun openEditSuperHero(superHeroId: String) = runOnUiThread {
+    private fun openEditSuperHero(superHeroId: String) {
         EditSuperHeroActivity.open(this, superHeroId)
     }
 
     override val activityModules = module {
-        bind<SuperHeroDetailPresenter>() with provider {
-            SuperHeroDetailPresenter(this@SuperHeroDetailActivity, instance(), instance())
+        bindViewModel<SuperHeroDetailViewModel>() with provider {
+            SuperHeroDetailViewModel(instance(), instance())
         }
         bind<GetSuperHeroById>() with provider { GetSuperHeroById(instance()) }
     }
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroViewHolder.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroViewHolder.kt
index 13e329a..f769f87 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroViewHolder.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroViewHolder.kt
@@ -3,14 +3,12 @@ package com.karumi.jetpack.superheroes.ui.view.adapter
 import androidx.recyclerview.widget.RecyclerView
 import com.karumi.jetpack.superheroes.databinding.SuperHeroRowBinding
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener
+import com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroesListener
 
 class SuperHeroViewHolder(
-    private val binding: SuperHeroRowBinding,
-    private val listener: SuperHeroesListener
+    private val binding: SuperHeroRowBinding
 ) : RecyclerView.ViewHolder(binding.root) {
-
-    fun render(superHero: SuperHero) {
+    fun render(superHero: SuperHero, listener: SuperHeroesListener) {
         binding.superHero = superHero
         binding.listener = listener
         binding.executePendingBindings()
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroesAdapter.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroesAdapter.kt
index fabd7f7..af8ad63 100644
--- a/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroesAdapter.kt
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/adapter/SuperHeroesAdapter.kt
@@ -7,10 +7,10 @@ import androidx.recyclerview.widget.RecyclerView
 import com.karumi.jetpack.superheroes.R
 import com.karumi.jetpack.superheroes.databinding.SuperHeroRowBinding
 import com.karumi.jetpack.superheroes.domain.model.SuperHero
-import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener
+import com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroesViewModel
 
 internal class SuperHeroesAdapter(
-    private val listener: SuperHeroesListener
+    private val viewModel: SuperHeroesViewModel
 ) : RecyclerView.Adapter<SuperHeroViewHolder>() {
     private val superHeroes: MutableList<SuperHero> = ArrayList()
 
@@ -26,12 +26,11 @@ internal class SuperHeroesAdapter(
             false
         )
 
-        return SuperHeroViewHolder(binding, listener)
+        return SuperHeroViewHolder(binding)
     }
 
     override fun onBindViewHolder(holder: SuperHeroViewHolder, position: Int) {
-        val superHero = superHeroes[position]
-        holder.render(superHero)
+        holder.render(superHeroes[position], viewModel)
     }
 
     override fun getItemCount(): Int {
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/EditSuperHeroViewModel.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/EditSuperHeroViewModel.kt
new file mode 100644
index 0000000..829bdf4
--- /dev/null
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/EditSuperHeroViewModel.kt
@@ -0,0 +1,60 @@
+package com.karumi.jetpack.superheroes.ui.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.karumi.jetpack.superheroes.domain.model.SuperHero
+import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
+import com.karumi.jetpack.superheroes.domain.usecase.SaveSuperHero
+
+class EditSuperHeroViewModel(
+    application: Application,
+    private val getSuperHeroById: GetSuperHeroById,
+    private val saveSuperHero: SaveSuperHero
+) : AndroidViewModel(application) {
+
+    val isLoading = MutableLiveData<Boolean>()
+    val superHero = MediatorLiveData<SuperHero?>()
+    val editableSuperHero = MutableLiveData<EditableSuperHero>()
+    val isClosing = MutableLiveData<Boolean>()
+
+    fun prepare(id: String) {
+        loadSuperHero(id)
+    }
+
+    private fun loadSuperHero(id: String) {
+        isLoading.value = true
+        val superHeroSource = getSuperHeroById(id)
+        superHero.addSource(superHeroSource) {
+            superHero.removeSource(superHeroSource)
+
+            superHero.postValue(it)
+            editableSuperHero.postValue(it?.toEditable())
+            isLoading.postValue(false)
+        }
+    }
+
+    fun onSaveSuperHeroSelected() {
+        val updatedSuperHero = getUpdatedSuperHero() ?: return
+
+        isLoading.value = true
+        saveSuperHero(updatedSuperHero)
+        isClosing.value = true
+    }
+
+    private fun getUpdatedSuperHero(): SuperHero? {
+        val superHero = superHero.value ?: return null
+        return editableSuperHero.value?.applyTo(superHero)
+    }
+
+    data class EditableSuperHero(
+        var isAvenger: Boolean,
+        var name: String,
+        var description: String
+    )
+
+    private fun SuperHero.toEditable() = EditableSuperHero(isAvenger, name, description)
+    private fun EditableSuperHero.applyTo(superHero: SuperHero) =
+        superHero.copy(isAvenger = isAvenger, name = name, description = description)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroDetailViewModel.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroDetailViewModel.kt
new file mode 100644
index 0000000..cf0369d
--- /dev/null
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroDetailViewModel.kt
@@ -0,0 +1,35 @@
+package com.karumi.jetpack.superheroes.ui.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.karumi.jetpack.superheroes.domain.model.SuperHero
+import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroById
+import com.karumi.jetpack.superheroes.ui.view.SingleLiveEvent
+
+class SuperHeroDetailViewModel(
+    application: Application,
+    private val getSuperHeroById: GetSuperHeroById
+) : AndroidViewModel(application) {
+
+    val isLoading = MutableLiveData<Boolean>()
+    val superHero = MediatorLiveData<SuperHero?>()
+    val idOfSuperHeroToEdit = SingleLiveEvent<String>()
+
+    fun prepare(id: String) {
+        loadSuperHero(id)
+    }
+
+    fun onEditSelected() {
+        idOfSuperHeroToEdit.value = superHero.value?.id
+    }
+
+    private fun loadSuperHero(id: String) {
+        isLoading.value = true
+        superHero.addSource(getSuperHeroById(id)) {
+            superHero.postValue(it)
+            isLoading.postValue(false)
+        }
+    }
+}
diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroesViewModel.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroesViewModel.kt
new file mode 100644
index 0000000..d467cfb
--- /dev/null
+++ b/app/src/main/java/com/karumi/jetpack/superheroes/ui/viewmodel/SuperHeroesViewModel.kt
@@ -0,0 +1,37 @@
+package com.karumi.jetpack.superheroes.ui.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.karumi.jetpack.superheroes.domain.model.SuperHero
+import com.karumi.jetpack.superheroes.domain.usecase.GetSuperHeroes
+import com.karumi.jetpack.superheroes.ui.view.SingleLiveEvent
+
+class SuperHeroesViewModel(
+    application: Application,
+    private val getSuperHeroes: GetSuperHeroes
+) : SuperHeroesListener, AndroidViewModel(application) {
+
+    val isLoading = MutableLiveData<Boolean>()
+    val isShowingEmptyCase = MutableLiveData<Boolean>()
+    val superHeroes = MediatorLiveData<List<SuperHero>>()
+    val idOfSuperHeroToOpen = SingleLiveEvent<String>()
+
+    fun prepare() {
+        isLoading.value = true
+        superHeroes.addSource(getSuperHeroes()) {
+            isShowingEmptyCase.postValue(it.isEmpty())
+            superHeroes.postValue(it)
+            isLoading.postValue(false)
+        }
+    }
+
+    override fun onSuperHeroClicked(id: String) {
+        idOfSuperHeroToOpen.value = id
+    }
+}
+
+interface SuperHeroesListener {
+    fun onSuperHeroClicked(id: String)
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/edit_super_hero_activity.xml b/app/src/main/res/layout/edit_super_hero_activity.xml
index 8b033f4..7a3aa82 100644
--- a/app/src/main/res/layout/edit_super_hero_activity.xml
+++ b/app/src/main/res/layout/edit_super_hero_activity.xml
@@ -8,16 +8,8 @@
     <import type="android.view.View" />
 
     <variable
-      name="listener"
-      type="com.karumi.jetpack.superheroes.ui.presenter.EditSuperHeroListener" />
-
-    <variable
-      name="superHero"
-      type="com.karumi.jetpack.superheroes.domain.model.SuperHero" />
-
-    <variable
-      name="editableSuperHero"
-      type="com.karumi.jetpack.superheroes.ui.presenter.EditSuperHeroPresenter.EditableSuperHero" />
+      name="viewModel"
+      type="com.karumi.jetpack.superheroes.ui.viewmodel.EditSuperHeroViewModel" />
 
     <variable
       name="isLoading"
@@ -50,7 +42,7 @@
           android:id="@+id/iv_super_hero_photo"
           android:layout_width="match_parent"
           android:layout_height="@dimen/super_hero_detail_header_height"
-          app:imageUrl="@{ superHero.photo }"
+          app:imageUrl="@{ viewModel.superHero.photo }"
           app:layout_constraintTop_toTopOf="parent"
           tools:background="@color/color_primary_dark"
           tools:ignore="ContentDescription" />
@@ -60,7 +52,7 @@
           android:layout_width="0dp"
           android:layout_height="0dp"
           android:background="@drawable/super_hero_gradient"
-          android:visibility="@{ superHero == null ? View.GONE : View.VISIBLE }"
+          android:visibility="@{ viewModel.superHero == null ? View.GONE : View.VISIBLE }"
           app:layout_constraintBottom_toBottomOf="@id/iv_super_hero_photo"
           app:layout_constraintEnd_toEndOf="@id/iv_super_hero_photo"
           app:layout_constraintStart_toStartOf="@id/iv_super_hero_photo"
@@ -73,8 +65,8 @@
           android:layout_height="wrap_content"
           android:layout_margin="8dp"
           android:buttonTint="@color/white"
-          android:checked="@={ editableSuperHero.avenger, default = false }"
-          android:enabled="@{ superHero != null }"
+          android:checked="@={ viewModel.editableSuperHero.avenger, default = false }"
+          android:enabled="@{ viewModel.superHero != null }"
           android:text="@string/is_super_hero_an_avenger_checkbox"
           android:textColor="@color/white"
           app:layout_constraintTop_toBottomOf="@id/iv_super_hero_photo"
@@ -93,11 +85,11 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:backgroundTint="@color/white"
-            android:enabled="@{ superHero != null }"
+            android:enabled="@{ viewModel.superHero != null }"
             android:hint="@string/edit_super_hero_name_hint"
             android:importantForAutofill="no"
             android:inputType="text"
-            android:text="@={ editableSuperHero.name }"
+            android:text="@={ viewModel.editableSuperHero.name }"
             android:textColor="@color/white"
             tools:ignore="UnusedAttribute" />
 
@@ -116,12 +108,12 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:backgroundTint="@color/white"
-            android:enabled="@{ superHero != null }"
+            android:enabled="@{ viewModel.superHero != null }"
             android:hint="@string/edit_super_hero_description_hint"
             android:importantForAutofill="no"
             android:inputType="textMultiLine"
             android:scrollbars="vertical"
-            android:text="@={ editableSuperHero.description }"
+            android:text="@={ viewModel.editableSuperHero.description }"
             android:textColor="@color/white"
             tools:ignore="UnusedAttribute" />
 
@@ -143,7 +135,7 @@
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_centerInParent="true"
-      android:visibility="@{ isLoading ? View.VISIBLE : View.GONE }"
+      android:visibility="@{ viewModel.isLoading ? View.VISIBLE : View.GONE }"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
@@ -154,8 +146,8 @@
       android:layout_width="0dp"
       android:layout_height="56dp"
       android:background="@color/royal_blue"
-      android:enabled="@{ superHero != null }"
-      android:onClick="@{ () -> listener.onSaveSuperHeroSelected(editableSuperHero) }"
+      android:enabled="@{ viewModel.superHero != null }"
+      android:onClick="@{ () -> viewModel.onSaveSuperHeroSelected() }"
       android:text="@string/save_edited_super_hero_button"
       android:textColor="@color/white"
       app:layout_constraintBottom_toBottomOf="parent"
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index c75c59c..298539f 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -8,12 +8,8 @@
     <import type="android.view.View" />
 
     <variable
-      name="isLoading"
-      type="boolean" />
-
-    <variable
-      name="isShowingEmptyCase"
-      type="boolean" />
+      name="viewModel"
+      type="com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroesViewModel" />
   </data>
 
   <androidx.constraintlayout.widget.ConstraintLayout
@@ -46,7 +42,7 @@
       style="?android:attr/progressBarStyleLarge"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:visibility="@{ isLoading ? View.VISIBLE : View.GONE }"
+      android:visibility="@{ viewModel.isLoading ? View.VISIBLE : View.GONE }"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
@@ -59,7 +55,7 @@
       android:layout_centerInParent="true"
       android:text="¯\\_(ツ)_/¯"
       android:textColor="@android:color/white"
-      android:visibility="@{ isShowingEmptyCase ? View.VISIBLE : View.GONE }"
+      android:visibility="@{ viewModel.isShowingEmptyCase ? View.VISIBLE : View.GONE }"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/super_hero_detail_activity.xml b/app/src/main/res/layout/super_hero_detail_activity.xml
index 256e817..ed43b0f 100644
--- a/app/src/main/res/layout/super_hero_detail_activity.xml
+++ b/app/src/main/res/layout/super_hero_detail_activity.xml
@@ -8,16 +8,8 @@
     <import type="android.view.View" />
 
     <variable
-      name="listener"
-      type="com.karumi.jetpack.superheroes.ui.presenter.SuperHeroDetailListener" />
-
-    <variable
-      name="superHero"
-      type="com.karumi.jetpack.superheroes.domain.model.SuperHero" />
-
-    <variable
-      name="isLoading"
-      type="boolean" />
+      name="viewModel"
+      type="com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroDetailViewModel" />
 
   </data>
 
@@ -43,7 +35,7 @@
         android:id="@+id/iv_super_hero_photo"
         android:layout_width="match_parent"
         android:layout_height="@dimen/super_hero_detail_header_height"
-        app:imageUrl="@{ superHero.photo }"
+        app:imageUrl="@{ viewModel.superHero.photo }"
         app:layout_constraintTop_toBottomOf="@id/toolbar"
         tools:background="@color/color_primary_dark"
         tools:ignore="ContentDescription" />
@@ -53,7 +45,7 @@
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:background="@drawable/super_hero_gradient"
-        android:visibility="@{ superHero == null ? View.GONE : View.VISIBLE }"
+        android:visibility="@{ viewModel.superHero == null ? View.GONE : View.VISIBLE }"
         app:layout_constraintBottom_toBottomOf="@id/iv_super_hero_photo"
         app:layout_constraintEnd_toEndOf="@id/iv_super_hero_photo"
         app:layout_constraintStart_toStartOf="@id/iv_super_hero_photo"
@@ -66,7 +58,7 @@
         android:layout_height="wrap_content"
         android:layout_margin="@dimen/default_margin"
         android:src="@mipmap/ic_avengers"
-        android:visibility="@{ superHero.avenger ? View.VISIBLE : View.GONE }"
+        android:visibility="@{ viewModel.superHero.avenger ? View.VISIBLE : View.GONE }"
         app:layout_constraintBottom_toBottomOf="@id/iv_super_hero_photo"
         app:layout_constraintStart_toStartOf="@id/iv_super_hero_photo"
         tools:ignore="ContentDescription"
@@ -84,9 +76,9 @@
         android:elevation="2dp"
         android:focusable="true"
         android:foreground="?attr/selectableItemBackgroundBorderless"
-        android:onClick="@{ () -> listener.onEditSelected() }"
+        android:onClick="@{ () -> viewModel.onEditSelected() }"
         android:src="@drawable/ic_edit"
-        android:visibility="@{ superHero == null ? View.GONE : View.VISIBLE }"
+        android:visibility="@{ viewModel.superHero == null ? View.GONE : View.VISIBLE }"
         app:layout_constraintEnd_toEndOf="@id/iv_super_hero_photo"
         app:layout_constraintTop_toTopOf="@id/iv_super_hero_photo"
         tools:ignore="UnusedAttribute"
@@ -99,7 +91,7 @@
         android:layout_marginStart="@dimen/default_margin"
         android:layout_marginLeft="@dimen/default_margin"
         android:layout_marginTop="@dimen/default_margin"
-        android:text="@{ superHero.name }"
+        android:text="@{ viewModel.superHero.name }"
         android:textColor="@android:color/white"
         android:textSize="@dimen/title_text_size"
         app:layout_constraintEnd_toEndOf="parent"
@@ -112,7 +104,7 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_margin="@dimen/default_margin"
-        android:text="@{ superHero.description }"
+        android:text="@{ viewModel.superHero.description }"
         android:textColor="@android:color/white"
         android:textSize="@dimen/body_text_size"
         app:layout_constraintEnd_toEndOf="parent"
@@ -126,7 +118,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerInParent="true"
-        android:visibility="@{ isLoading ? View.VISIBLE : View.GONE }"
+        android:visibility="@{ viewModel.isLoading ? View.VISIBLE : View.GONE }"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/super_hero_row.xml b/app/src/main/res/layout/super_hero_row.xml
index 3bec5ff..f2f266a 100644
--- a/app/src/main/res/layout/super_hero_row.xml
+++ b/app/src/main/res/layout/super_hero_row.xml
@@ -13,13 +13,14 @@
 
     <variable
       name="listener"
-      type="com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener" />
+      type="com.karumi.jetpack.superheroes.ui.viewmodel.SuperHeroesListener" />
+
   </data>
 
   <androidx.constraintlayout.widget.ConstraintLayout
     android:layout_width="match_parent"
     android:layout_height="@dimen/super_hero_row_height"
-    android:onClick="@{ () -> listener.onSuperHeroClicked(superHero) }">
+    android:onClick="@{ () -> listener.onSuperHeroClicked(superHero.id) }">
 
     <ImageView
       android:id="@+id/iv_super_hero_photo"