From 46b7c13e7e4a75429b11cc4aaa47c78eedf504ff Mon Sep 17 00:00:00 2001 From: Serchinastico <54cymru@gmail.com> Date: Wed, 30 Jan 2019 17:49:41 +0100 Subject: [PATCH] Add binding adapter to load images. Add data binding to recycler view adapter. Add 2-way data binding to superHero edition. --- .../ui/view/SuperHeroViewHolderTest.kt | 28 ++++-- .../superheroes/common/BindingAdapters.kt | 12 +++ .../ui/presenter/EditSuperHeroPresenter.kt | 24 +++-- .../ui/presenter/SuperHeroesPresenter.kt | 8 +- .../ui/view/EditSuperHeroActivity.kt | 19 +--- .../ui/view/SuperHeroDetailActivity.kt | 2 - .../ui/view/adapter/SuperHeroViewHolder.kt | 41 ++------- .../ui/view/adapter/SuperHeroesAdapter.kt | 15 ++- .../res/layout/edit_super_hero_activity.xml | 12 ++- .../res/layout/super_hero_detail_activity.xml | 1 + app/src/main/res/layout/super_hero_row.xml | 92 +++++++++++-------- 11 files changed, 137 insertions(+), 117 deletions(-) create mode 100644 app/src/main/java/com/karumi/jetpack/superheroes/common/BindingAdapters.kt 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 8204503..3f70a31 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 @@ -1,8 +1,10 @@ package com.karumi.jetpack.superheroes.ui.view import android.view.LayoutInflater +import androidx.databinding.DataBindingUtil 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 @@ -51,15 +53,17 @@ class SuperHeroViewHolderTest : ScreenshotTest { compareScreenshot(holder, R.dimen.super_hero_row_height) } - private fun givenASuperHeroViewHolder(): SuperHeroViewHolder { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val inflater = LayoutInflater.from(context) - val view = inflater.inflate(R.layout.super_hero_row, null, false) - return SuperHeroViewHolder( - view, - mock(SuperHeroesPresenter::class.java) - ) - } + private fun givenASuperHeroViewHolder(): SuperHeroViewHolder = + runOnUi { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val inflater = LayoutInflater.from(context) + val binding: SuperHeroRowBinding = + DataBindingUtil.inflate(inflater, R.layout.super_hero_row, null, false) + SuperHeroViewHolder( + binding, + mock(SuperHeroesPresenter::class.java) + ) + } private fun givenASuperHeroWithALongDescription(): SuperHero { val superHeroName = "Super Hero Name" @@ -94,4 +98,10 @@ class SuperHeroViewHolderTest : ScreenshotTest { superHeroDescription: String = "Super Hero Description", isAvenger: Boolean = false ): SuperHero = SuperHero(superHeroId, superHeroName, null, isAvenger, superHeroDescription) +} + +private fun runOnUi(block: () -> T): T { + var response: T? = null + InstrumentationRegistry.getInstrumentation().runOnMainSync { response = block() } + return response!! } \ No newline at end of file diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/common/BindingAdapters.kt b/app/src/main/java/com/karumi/jetpack/superheroes/common/BindingAdapters.kt new file mode 100644 index 0000000..3a78ab6 --- /dev/null +++ b/app/src/main/java/com/karumi/jetpack/superheroes/common/BindingAdapters.kt @@ -0,0 +1,12 @@ +package com.karumi.jetpack.superheroes.common + +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import com.karumi.jetpack.superheroes.ui.utils.picasso + +@BindingAdapter("imageUrl") +fun setImageUrl(view: ImageView, url: String?) { + if (url != null) { + view.context.picasso.load(url).fit().centerCrop().fit().into(view) + } +} \ 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 index af9dcd6..76fd9aa 100644 --- 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 @@ -43,20 +43,16 @@ class EditSuperHeroPresenter( cancel() } - override fun onSaveSuperHeroSelected( - name: String, - description: String, - isAvenger: Boolean - ) { + override fun onSaveSuperHeroSelected(editableSuperHero: EditSuperHeroPresenter.EditableSuperHero) { launch { view?.showLoading() val superHero = superHero ?: return@launch async { saveSuperHero( superHero.copy( - name = name, - description = description, - isAvenger = isAvenger + name = editableSuperHero.name, + description = editableSuperHero.description, + isAvenger = editableSuperHero.isAvenger ) ) } @@ -71,6 +67,12 @@ class EditSuperHeroPresenter( this@EditSuperHeroPresenter.superHero = superHero } + data class EditableSuperHero( + var isAvenger: Boolean, + var name: String, + var description: String + ) + interface View { fun close() fun hideLoading() @@ -80,9 +82,5 @@ class EditSuperHeroPresenter( } interface EditSuperHeroListener { - fun onSaveSuperHeroSelected( - name: String, - description: String, - isAvenger: Boolean - ) + fun onSaveSuperHeroSelected(editableSuperHero: EditSuperHeroPresenter.EditableSuperHero) } \ 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 index 8b5ba08..f6eb968 100644 --- 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 @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch class SuperHeroesPresenter( view: View, private val getSuperHeroes: GetSuperHeroes -) : LifecycleObserver, CoroutineScope by MainScope() { +) : SuperHeroesListener, LifecycleObserver, CoroutineScope by MainScope() { private val view: View? by weak( view @@ -42,7 +42,7 @@ class SuperHeroesPresenter( } } - fun onSuperHeroClicked(superHero: SuperHero) { + override fun onSuperHeroClicked(superHero: SuperHero) { view?.openDetail(superHero.id) } @@ -53,4 +53,8 @@ class SuperHeroesPresenter( fun showSuperHeroes(superHeroes: List) 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/EditSuperHeroActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/EditSuperHeroActivity.kt index d4efc5c..7f14227 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,7 +2,6 @@ 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 com.karumi.jetpack.superheroes.R import com.karumi.jetpack.superheroes.databinding.EditSuperHeroActivityBinding @@ -10,7 +9,6 @@ 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.utils.setImageBackground import kotlinx.android.synthetic.main.edit_super_hero_activity.* import org.kodein.di.Kodein import org.kodein.di.erased.bind @@ -38,17 +36,6 @@ class EditSuperHeroActivity : private val superHeroId: String get() = intent?.extras?.getString(SUPER_HERO_ID_KEY) ?: "" - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - bt_save_edition.setOnClickListener { - presenter.onSaveSuperHeroSelected( - name = et_super_hero_name.text?.toString() ?: "", - description = et_super_hero_description.text?.toString() ?: "", - isAvenger = cb_is_avenger.isChecked - ) - } - } - override fun configureBinding(binding: EditSuperHeroActivityBinding) { binding.listener = presenter binding.isLoading = false @@ -72,8 +59,9 @@ class EditSuperHeroActivity : } override fun showSuperHero(superHero: SuperHero) { + binding.listener = presenter binding.superHero = superHero - iv_super_hero_photo.setImageBackground(superHero.photo) + binding.editableSuperHero = superHero.toEditable() } override val activityModules = @@ -84,4 +72,7 @@ class EditSuperHeroActivity : bind() with provider { GetSuperHeroById(instance()) } bind() 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/SuperHeroDetailActivity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/ui/view/SuperHeroDetailActivity.kt index e48c670..0555888 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 @@ -8,7 +8,6 @@ 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.utils.setImageBackground import kotlinx.android.synthetic.main.super_hero_detail_activity.* import org.kodein.di.Kodein import org.kodein.di.erased.bind @@ -58,7 +57,6 @@ class SuperHeroDetailActivity : override fun showSuperHero(superHero: SuperHero) { title = superHero.name binding.superHero = superHero - iv_super_hero_photo.setImageBackground(superHero.photo) } override fun openEditSuperHero(superHeroId: String) { 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 04ef259..13e329a 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 @@ -1,43 +1,18 @@ package com.karumi.jetpack.superheroes.ui.view.adapter -import android.view.View -import android.widget.ImageView -import android.widget.TextView 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.SuperHeroesPresenter -import com.karumi.jetpack.superheroes.ui.utils.setImageBackground +import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener class SuperHeroViewHolder( - itemView: View, - private val presenter: SuperHeroesPresenter -) : RecyclerView.ViewHolder(itemView) { - - private val photoImageView: ImageView = itemView.findViewById(R.id.iv_super_hero_photo) - private val nameTextView: TextView = itemView.findViewById(R.id.tv_super_hero_name) - private val avengersBadgeView: View = itemView.findViewById(R.id.iv_avengers_badge) + private val binding: SuperHeroRowBinding, + private val listener: SuperHeroesListener +) : RecyclerView.ViewHolder(binding.root) { fun render(superHero: SuperHero) { - hookListeners(superHero) - renderSuperHeroPhoto(superHero.photo) - renderSuperHeroName(superHero.name) - renderAvengersBadge(superHero.isAvenger) - } - - private fun hookListeners(superHero: SuperHero) { - itemView.setOnClickListener { presenter.onSuperHeroClicked(superHero) } - } - - private fun renderSuperHeroPhoto(photo: String?) { - photoImageView.setImageBackground(photo) - } - - private fun renderSuperHeroName(name: String) { - nameTextView.text = name - } - - private fun renderAvengersBadge(isAvenger: Boolean) { - avengersBadgeView.visibility = if (isAvenger) View.VISIBLE else View.GONE + binding.superHero = superHero + binding.listener = listener + binding.executePendingBindings() } } \ No newline at end of file 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 a3988b4..fabd7f7 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 @@ -2,13 +2,15 @@ package com.karumi.jetpack.superheroes.ui.view.adapter import android.view.LayoutInflater import android.view.ViewGroup +import androidx.databinding.DataBindingUtil 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.SuperHeroesPresenter +import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener internal class SuperHeroesAdapter( - private val presenter: SuperHeroesPresenter + private val listener: SuperHeroesListener ) : RecyclerView.Adapter() { private val superHeroes: MutableList = ArrayList() @@ -17,11 +19,14 @@ internal class SuperHeroesAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuperHeroViewHolder { - val view = LayoutInflater.from(parent.context).inflate( - R.layout.super_hero_row, parent, + val binding: SuperHeroRowBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + R.layout.super_hero_row, + parent, false ) - return SuperHeroViewHolder(view, presenter) + + return SuperHeroViewHolder(binding, listener) } override fun onBindViewHolder(holder: SuperHeroViewHolder, position: Int) { 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 b73e69f..8b033f4 100644 --- a/app/src/main/res/layout/edit_super_hero_activity.xml +++ b/app/src/main/res/layout/edit_super_hero_activity.xml @@ -15,6 +15,10 @@ name="superHero" type="com.karumi.jetpack.superheroes.domain.model.SuperHero" /> + + @@ -46,6 +50,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:layout_constraintTop_toTopOf="parent" tools:background="@color/color_primary_dark" tools:ignore="ContentDescription" /> @@ -68,7 +73,7 @@ android:layout_height="wrap_content" android:layout_margin="8dp" android:buttonTint="@color/white" - android:checked="@{ superHero.avenger }" + android:checked="@={ editableSuperHero.avenger, default = false }" android:enabled="@{ superHero != null }" android:text="@string/is_super_hero_an_avenger_checkbox" android:textColor="@color/white" @@ -92,7 +97,7 @@ android:hint="@string/edit_super_hero_name_hint" android:importantForAutofill="no" android:inputType="text" - android:text="@{ superHero.name }" + android:text="@={ editableSuperHero.name }" android:textColor="@color/white" tools:ignore="UnusedAttribute" /> @@ -116,7 +121,7 @@ android:importantForAutofill="no" android:inputType="textMultiLine" android:scrollbars="vertical" - android:text="@{ superHero.description }" + android:text="@={ editableSuperHero.description }" android:textColor="@color/white" tools:ignore="UnusedAttribute" /> @@ -150,6 +155,7 @@ android:layout_height="56dp" android:background="@color/royal_blue" android:enabled="@{ superHero != null }" + android:onClick="@{ () -> listener.onSaveSuperHeroSelected(editableSuperHero) }" 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/super_hero_detail_activity.xml b/app/src/main/res/layout/super_hero_detail_activity.xml index 1dbffa8..256e817 100644 --- a/app/src/main/res/layout/super_hero_detail_activity.xml +++ b/app/src/main/res/layout/super_hero_detail_activity.xml @@ -43,6 +43,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:layout_constraintTop_toBottomOf="@id/toolbar" tools:background="@color/color_primary_dark" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/super_hero_row.xml b/app/src/main/res/layout/super_hero_row.xml index 9e2399d..3bec5ff 100644 --- a/app/src/main/res/layout/super_hero_row.xml +++ b/app/src/main/res/layout/super_hero_row.xml @@ -1,41 +1,61 @@ - + xmlns:tools="http://schemas.android.com/tools"> - + + + + + - + + + - - - - - - \ No newline at end of file + android:layout_height="@dimen/super_hero_row_height" + android:onClick="@{ () -> listener.onSuperHeroClicked(superHero) }"> + + + + + + + + + + +