Skip to content

Commit

Permalink
Add LayoutContainer for SuperHeroViewHolder
Browse files Browse the repository at this point in the history
Add binding adapter to load images

Add data binding to recycler view adapter

Fix tests
  • Loading branch information
Serchinastico committed Jan 30, 2019
1 parent b6e050c commit 07a1e0b
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,53 +16,64 @@ class SuperHeroViewHolderTest : ScreenshotTest {
@Test
fun showsAnySuperHero() {
val superHero = givenASuperHero()
val holder = givenASuperHeroViewHolder()
val (holder, binding) = givenASuperHeroViewHolder()

holder.render(superHero)
renderViewHolder(superHero, holder, binding)

compareScreenshot(holder, R.dimen.super_hero_row_height)
}

@Test
fun showsSuperHeroesWithLongNames() {
val superHero = givenASuperHeroWithALongName()
val holder = givenASuperHeroViewHolder()
val (holder, binding) = givenASuperHeroViewHolder()

holder.render(superHero)
renderViewHolder(superHero, holder, binding)

compareScreenshot(holder, R.dimen.super_hero_row_height)
}

@Test
fun showsSuperHeroesWithLongDescriptions() {
val superHero = givenASuperHeroWithALongDescription()
val holder = givenASuperHeroViewHolder()
val (holder, binding) = givenASuperHeroViewHolder()

holder.render(superHero)
renderViewHolder(superHero, holder, binding)

compareScreenshot(holder, R.dimen.super_hero_row_height)
}

@Test
fun showsAvengersBadge() {
val superHero = givenASuperHero(isAvenger = true)
val holder = givenASuperHeroViewHolder()
val (holder, binding) = givenASuperHeroViewHolder()

holder.render(superHero)
renderViewHolder(superHero, holder, binding)

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>(SuperHeroesPresenter::class.java)
)
private fun renderViewHolder(
superHero: SuperHero,
holder: SuperHeroViewHolder,
binding: SuperHeroRowBinding
) {
holder.render(superHero)
binding.executePendingBindings()
}

private fun givenASuperHeroViewHolder(): Pair<SuperHeroViewHolder, SuperHeroRowBinding> =
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>(SuperHeroesPresenter::class.java)
) to binding
}

private fun givenASuperHeroWithALongDescription(): SuperHero {
val superHeroName = "Super Hero Name"
val superHeroDescription = """
Expand Down Expand Up @@ -94,4 +107,10 @@ class SuperHeroViewHolderTest : ScreenshotTest {
superHeroDescription: String = "Super Hero Description",
isAvenger: Boolean = false
): SuperHero = SuperHero(superHeroId, superHeroName, null, isAvenger, superHeroDescription)
}

private fun <T> runOnUi(block: () -> T): T {
var response: T? = null
InstrumentationRegistry.getInstrumentation().runOnMainSync { response = block() }
return response!!
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,7 +42,7 @@ class SuperHeroesPresenter(
}
}

fun onSuperHeroClicked(superHero: SuperHero) {
override fun onSuperHeroClicked(superHero: SuperHero) {
view?.openDetail(superHero.id)
}

Expand All @@ -53,4 +53,8 @@ class SuperHeroesPresenter(
fun showSuperHeroes(superHeroes: List<SuperHero>)
fun openDetail(id: String)
}
}

interface SuperHeroesListener {
fun onSuperHeroClicked(superHero: SuperHero)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,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
Expand Down Expand Up @@ -73,7 +72,6 @@ class EditSuperHeroActivity :

override fun showSuperHero(superHero: SuperHero) {
binding.superHero = superHero
iv_super_hero_photo.setImageBackground(superHero.photo)
}

override val activityModules =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,17 @@
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ 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.SuperHeroesListener
import com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesPresenter

internal class SuperHeroesAdapter(
private val presenter: SuperHeroesPresenter
private val listener: SuperHeroesListener
) : RecyclerView.Adapter<SuperHeroViewHolder>() {
private val superHeroes: MutableList<SuperHero> = ArrayList()

Expand All @@ -17,11 +20,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) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/layout/edit_super_hero_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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" />
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/layout/super_hero_detail_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
92 changes: 56 additions & 36 deletions app/src/main/res/layout/super_hero_row.xml
Original file line number Diff line number Diff line change
@@ -1,41 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/super_hero_row_height">
xmlns:tools="http://schemas.android.com/tools">

<ImageView
android:id="@+id/iv_super_hero_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<data>

<import type="android.view.View" />

<variable
name="superHero"
type="com.karumi.jetpack.superheroes.domain.model.SuperHero" />

<View
<variable
name="listener"
type="com.karumi.jetpack.superheroes.ui.presenter.SuperHeroesListener" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/super_hero_row_gradient_height"
android:background="@drawable/super_hero_gradient"
app:layout_constraintBottom_toBottomOf="parent" />

<TextView
android:id="@+id/tv_super_hero_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_margin"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Iron Man" />

<ImageView
android:id="@+id/iv_avengers_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_margin"
android:src="@mipmap/ic_avengers"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/is_avenger_badge_content_description" />

</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="@dimen/super_hero_row_height"
android:onClick="@{ () -> listener.onSuperHeroClicked(superHero) }">

<ImageView
android:id="@+id/iv_super_hero_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:imageUrl="@{ superHero.photo }"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />

<View
android:layout_width="match_parent"
android:layout_height="@dimen/super_hero_row_gradient_height"
android:background="@drawable/super_hero_gradient"
app:layout_constraintBottom_toBottomOf="parent" />

<TextView
android:id="@+id/tv_super_hero_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_margin"
android:text="@{ superHero.name }"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Iron Man" />

<ImageView
android:id="@+id/iv_avengers_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_margin"
android:contentDescription="@string/is_avenger_badge_content_description"
android:src="@mipmap/ic_avengers"
android:visibility="@{ superHero.avenger ? View.VISIBLE : View.GONE, default=gone }"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

0 comments on commit 07a1e0b

Please sign in to comment.