Skip to content

Commit

Permalink
Converted dogs module from Epoxy to data binding (#272)
Browse files Browse the repository at this point in the history
I wanted to give data binding a shot for my Droidcon NYC talk for a few reasons:

I was curious.
As I talk to people, a lot of people think that using MvRx requires Epoxy. It doesn't, it just happens to work really well with it.
I think it adds to much overhead for the talk and scares/overwhelms people.
It turns out that it's really awesome and is also super compatible with MvRx.
P.S. I still ❤Epoxy
  • Loading branch information
gpeal authored Aug 30, 2019
1 parent e24b385 commit c0d9db3
Show file tree
Hide file tree
Showing 40 changed files with 505 additions and 468 deletions.
2 changes: 0 additions & 2 deletions counter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ dependencies {
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"

testImplementation 'junit:junit:4.12'
testImplementation project(':testing')
Expand Down
12 changes: 0 additions & 12 deletions counter/src/main/java/com/airbnb/mvrx/counter/CounterFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.transition.Fade
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.PersistState
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import kotlinx.android.synthetic.main.fragment_counter.counterText
import kotlinx.android.synthetic.main.fragment_counter.nextButton

data class CounterState(@PersistState val count: Int = 0) : MvRxState

Expand All @@ -25,20 +22,11 @@ class CounterFragment : BaseMvRxFragment() {

private val viewModel: CounterViewModel by activityViewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = Fade()
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_counter, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
nextButton.setOnClickListener {
findNavController().navigate(R.id.counterFragment)
}

counterText.setOnClickListener {
viewModel.incrementCount()
}
Expand Down
16 changes: 6 additions & 10 deletions counter/src/main/res/layout/activity_counter.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout 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="match_parent"
android:orientation="vertical"
tools:context=".CounterActivity">

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:name="com.airbnb.mvrx.counter.CounterFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
android:layout_weight="1" />

</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
8 changes: 0 additions & 8 deletions counter/src/main/res/layout/fragment_counter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,4 @@
android:textSize="48dp"
tools:text="12" />

<Button
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="16dp"
android:text="Next Counter" />

</FrameLayout>
23 changes: 0 additions & 23 deletions counter/src/main/res/navigation/nav_graph.xml

This file was deleted.

5 changes: 4 additions & 1 deletion dogs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ android {
versionCode 1
versionName "1.0"
}

dataBinding {
enabled = true
}
}

dependencies {
Expand All @@ -25,7 +29,6 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
implementation("com.airbnb.android:epoxy:$epoxyVersion") { exclude group: 'com.android.support' }
kapt "com.airbnb.android:epoxy-processor:$epoxyVersion"
implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
implementation 'com.squareup.picasso:picasso:2.5.2'
Expand Down
4 changes: 2 additions & 2 deletions dogs/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

<application
android:allowBackup="true"
android:name=".app.DogApplication"
android:name=".DogApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".app.DogsActivity">
<activity android:name=".DogsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
22 changes: 5 additions & 17 deletions dogs/src/main/java/com/airbnb/mvrx/dogs/AdoptionFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,19 @@ import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.dogs.app.withModels
import com.airbnb.mvrx.dogs.views.dogRow
import com.airbnb.mvrx.dogs.views.titleRow
import com.airbnb.mvrx.dogs.databinding.AdoptionFragmentBinding
import com.airbnb.mvrx.withState
import kotlinx.android.synthetic.main.fragment_adoption.dogsRecyclerView

class AdoptionFragment : BaseMvRxFragment() {
private val viewModel: DogViewModel by activityViewModel()
private lateinit var binding: AdoptionFragmentBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_adoption, container, false)
binding = AdoptionFragmentBinding.inflate(inflater, container, false)
return binding.root
}

override fun invalidate() = withState(viewModel) { state ->
dogsRecyclerView.withModels {
titleRow {
id("title")
title(R.string.meet_your_dog)
}
state.adoptionRequest()?.let { dog ->
dogRow {
id(dog.id)
dog(dog)
}
}
}
binding.dog = state.adoptionRequest()
}
}
25 changes: 25 additions & 0 deletions dogs/src/main/java/com/airbnb/mvrx/dogs/DogAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.airbnb.mvrx.dogs

import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import com.airbnb.mvrx.dogs.data.Dog
import com.airbnb.mvrx.dogs.databinding.DogRowBinding
import com.airbnb.mvrx.dogs.utils.LayoutViewHolder
import com.airbnb.mvrx.dogs.views.DogsFragmentHandler
import com.airbnb.mvrx.dogs.views.HashItemCallback

class DogViewHolder(parent: ViewGroup) : LayoutViewHolder(parent, R.layout.dog_row) {
val binding = DogRowBinding.bind(itemView)
}

class DogAdapter(private val fragment: DogsFragmentHandler) : ListAdapter<Dog, DogViewHolder>(HashItemCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DogViewHolder {
return DogViewHolder(parent).apply {
binding.handler = fragment
}
}

override fun onBindViewHolder(holder: DogViewHolder, position: Int) {
holder.binding.dog = getItem(position)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.airbnb.mvrx.dogs.app
package com.airbnb.mvrx.dogs

import android.app.Application
import com.airbnb.mvrx.dogs.data.DogRepository
Expand Down
38 changes: 14 additions & 24 deletions dogs/src/main/java/com/airbnb/mvrx/dogs/DogDetailFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,35 @@ import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args
import com.airbnb.mvrx.dogs.databinding.DogDetailFragmentBinding
import com.airbnb.mvrx.dogs.views.DogDetailFragmentHandler
import com.airbnb.mvrx.withState
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.fragment_dog_detail.breedsView
import kotlinx.android.synthetic.main.fragment_dog_detail.descriptionView
import kotlinx.android.synthetic.main.fragment_dog_detail.image
import kotlinx.android.synthetic.main.fragment_dog_detail.loveButton
import kotlinx.android.synthetic.main.fragment_dog_detail.nameView

class DogDetailFragment : BaseMvRxFragment() {
class DogDetailFragment : BaseMvRxFragment(), DogDetailFragmentHandler {

private val viewModel: DogViewModel by activityViewModel()
private val dogId: Long by args()

private lateinit var binding: DogDetailFragmentBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_dog_detail, container, false)
binding = DogDetailFragmentBinding.inflate(inflater, container, false)
binding.handler = this
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
loveButton.setOnClickListener {
viewModel.loveDog(dogId)
findNavController().popBackStack()
}
override fun onLoveClicked() {
viewModel.loveDog(dogId)
findNavController().popBackStack()
}

override fun invalidate() = withState(viewModel) { state ->
val dog = state.dog(dogId) ?: throw IllegalStateException("Cannot find dog with id $dogId")
Picasso.with(requireContext())
.load(dog.imageUrl)
.into(image)
nameView.text = dog.name
breedsView.text = dog.breeds
descriptionView.text = dog.description
binding.dog = state.dog(dogId) ?: error("Cannot find dog with id $dogId")
}

companion object {
fun arg(dogId: Long): Bundle {
val args = Bundle()
args.putLong(MvRx.KEY_ARG, dogId)
return args
fun arg(dogId: Long) = Bundle().apply {
putLong(MvRx.KEY_ARG, dogId)
}
}
}
11 changes: 4 additions & 7 deletions dogs/src/main/java/com/airbnb/mvrx/dogs/DogViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ package com.airbnb.mvrx.dogs

import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.airbnb.mvrx.dogs.app.DogApplication
import com.airbnb.mvrx.dogs.app.MvRxViewModel
import com.airbnb.mvrx.dogs.data.DogRepository
import com.airbnb.mvrx.dogs.utils.MvRxViewModel
import io.reactivex.schedulers.Schedulers

class DogViewModel(
state: DogState,
initialState: DogState,
private val dogRepository: DogRepository
) : MvRxViewModel<DogState>(state) {
) : MvRxViewModel<DogState>(initialState) {

init {
dogRepository.getDogs()
.subscribeOn(Schedulers.io())
.execute { copy(dogs = it) }
dogRepository.getDogs().execute { copy(dogs = it) }
}

fun loveDog(dogId: Long) = setState { copy(lovedDogId = dogId) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.airbnb.mvrx.dogs.app
package com.airbnb.mvrx.dogs

import android.os.Bundle
import com.airbnb.mvrx.BaseMvRxActivity
import com.airbnb.mvrx.dogs.R

class DogsActivity : BaseMvRxActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dogs)
setContentView(R.layout.dogs_activity)
}
}
53 changes: 17 additions & 36 deletions dogs/src/main/java/com/airbnb/mvrx/dogs/DogsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,37 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.navigation.fragment.findNavController
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.dogs.app.withModels
import com.airbnb.mvrx.dogs.views.dogRow
import com.airbnb.mvrx.dogs.data.Dog
import com.airbnb.mvrx.dogs.databinding.DogsFragmentBinding
import com.airbnb.mvrx.dogs.views.DogsFragmentHandler
import com.airbnb.mvrx.withState
import kotlinx.android.synthetic.main.fragment_dogs.adoptButton
import kotlinx.android.synthetic.main.fragment_dogs.dogsRecyclerView
import kotlinx.android.synthetic.main.fragment_dogs.loadingAnimation

class DogsFragment : BaseMvRxFragment() {
class DogsFragment : BaseMvRxFragment(), DogsFragmentHandler {

private val viewModel: DogViewModel by activityViewModel()
private lateinit var bindings: DogsFragmentBinding
private val adapter = DogAdapter(this)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.asyncSubscribe(DogState::adoptionRequest, onSuccess = {
findNavController().navigate(R.id.action_dogs_to_adoption)
})
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
bindings = DogsFragmentBinding.inflate(inflater, container, false)
bindings.dogsRecyclerView.adapter = adapter
bindings.handler = this
return bindings.root
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_dogs, container, false)
override fun onDogClicked(dog: Dog) {
findNavController().navigate(R.id.action_dogs_to_dogDetail, DogDetailFragment.arg(dog.id))
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adoptButton.setOnClickListener {
viewModel.adoptLovedDog()
}
override fun adoptLovedDog() {
viewModel.adoptLovedDog()
findNavController().navigate(R.id.action_dogs_to_adoption)
}

override fun invalidate() = withState(viewModel) { state ->
loadingAnimation.isVisible = state.dogs is Loading || state.adoptionRequest is Loading

adoptButton.isVisible = state.lovedDog != null
adoptButton.text = getString(R.string.adopt_dog, state.lovedDog?.name)

dogsRecyclerView.isVisible = state.adoptionRequest is Uninitialized || state.adoptionRequest is Fail
dogsRecyclerView.withModels {
state.dogs()?.forEach { dog ->
dogRow {
id(dog.id)
dog(dog)
clickListener { _ -> findNavController().navigate(R.id.action_dogs_to_dogDetail, DogDetailFragment.arg(dog.id)) }
}
}
}
bindings.state = state
}
}
Loading

0 comments on commit c0d9db3

Please sign in to comment.