Skip to content

Sample Module MvRx to Mavericks (Rx to Coroutines) #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ object Versions {
val daggerHilt = "2.28.1-alpha"

val koin = "2.1.5"
val lifecycle = "2.2.0"
val lifecycleTesting = "2.1.0"
val glide = "4.11.0"

val okhttp = "4.7.2"
Expand Down Expand Up @@ -85,28 +83,23 @@ object Deps {
val daggerHiltProcessor = "com.google.dagger:hilt-android-compiler:${Versions.daggerHilt}"

val converterMoshi = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}"
val adapterRxJava2 = "com.squareup.retrofit2:adapter-rxjava2:${Versions.retrofit}"
val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"

val glide = "com.github.bumptech.glide:glide:${Versions.glide}"
val rxJava2 = "io.reactivex.rxjava2:rxjava:2.2.19"

val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6"
val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6"

val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.7"
val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"

val leakCanaryAndroid = "com.squareup.leakcanary:leakcanary-android:${Versions.leakCanary}"

val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}"
val lifecycleTesting = "androidx.arch.core:core-testing:${Versions.lifecycleTesting}"

val lottie = "com.airbnb.android:lottie:${Versions.lottie}"

val rxpaper = "com.github.pakoito:RxPaper2:${Versions.rxpaper}"

val mvrx = "com.airbnb.android:mvrx:2.0.0-alpha2"
val mvrxTesting = "com.airbnb.android:mvrx-testing:2.0.0-alpha2"
val mvrxLauncher = "com.airbnb.android:mvrx-launcher:2.0.0-alpha2"
val mvrx = "com.airbnb.android:mvrx:2.0.0-alpha3"
val mvrxTesting = "com.airbnb.android:mvrx-testing:2.0.0-alpha3"
val mvrxLauncher = "com.airbnb.android:mvrx-launcher:2.0.0-alpha3"

val epoxy = "com.airbnb.android:epoxy:${Versions.epoxy}"
val epoxyProcessor = "com.airbnb.android:epoxy-processor:${Versions.epoxy}"
Expand Down
3 changes: 0 additions & 3 deletions common/network/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
apply from: "$rootDir/common-kotlin-library.gradle"

dependencies {
implementation Deps.rxJava2

api Deps.retrofit
implementation Deps.converterMoshi
implementation Deps.okhttpLoggingInterceptor
implementation Deps.adapterRxJava2
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.sanogueralorenzo.network

import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory

fun createNetworkClient(baseUrl: String, debug: Boolean = false): Retrofit.Builder =
Expand All @@ -25,4 +23,3 @@ private fun retrofitClient(baseUrl: String, httpClient: OkHttpClient): Retrofit.
.baseUrl(baseUrl)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MavericksView
import com.google.android.material.appbar.AppBarLayout
import com.sanogueralorenzo.views.ErrorDisplay
import com.sanogueralorenzo.views.R
Expand All @@ -19,7 +20,7 @@ import com.sanogueralorenzo.views.extensions.show
import com.sanogueralorenzo.views.extensions.visible
import kotlinx.android.synthetic.main.fragment_container.*

abstract class ContainerFragment : BaseMvRxFragment(R.layout.fragment_container) {
abstract class ContainerFragment : Fragment(R.layout.fragment_container), MavericksView {

protected val controller by lazy { controller() }

Expand Down Expand Up @@ -72,6 +73,12 @@ abstract class ContainerFragment : BaseMvRxFragment(R.layout.fragment_container)
recyclerView.requestModelBuild()
}

override fun onStart() {
super.onStart()
// required for static screens without ViewModel
postInvalidate()
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
controller.onSaveInstanceState(outState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.sanogueralorenzo.views.screen

import com.airbnb.epoxy.AsyncEpoxyController
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.withState

Expand Down Expand Up @@ -36,7 +36,7 @@ fun ContainerFragment.simpleController(
* Create a [SimpleController] that builds models with the given callback.
* When models are built the current state of the viewmodel will be provided.
*/
fun <S : MvRxState, A : BaseMvRxViewModel<S>> ContainerFragment.simpleController(
fun <S : MvRxState, A : MavericksViewModel<S>> ContainerFragment.simpleController(
viewModel: A,
buildModels: EpoxyController.(state: S) -> Unit
) = SimpleController {
Expand Down
9 changes: 3 additions & 6 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,12 @@ dependencies {
kapt Deps.assistedInjectProcessor
compileOnly Deps.assistedInject

// Viewmodels
implementation Deps.lifecycleViewModel
testImplementation Deps.lifecycleTesting

implementation Deps.mvrx
testImplementation Deps.mvrxTesting

// Rxjava
implementation Deps.rxJava2
implementation Deps.coroutines
implementation Deps.coroutinesAndroid
testImplementation Deps.coroutinesTest

implementation Deps.timber

Expand Down
4 changes: 2 additions & 2 deletions sample/src/main/java/com/sanogueralorenzo/sample/App.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.sanogueralorenzo.sample

import android.app.Application
import com.airbnb.mvrx.MavericksViewModelConfigFactory
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxViewModelConfigFactory
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber

Expand All @@ -12,6 +12,6 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
MvRx.viewModelConfigFactory = MvRxViewModelConfigFactory(applicationContext)
MvRx.viewModelConfigFactory = MavericksViewModelConfigFactory(applicationContext)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.sanogueralorenzo.sample.data

import com.sanogueralorenzo.sample.datasource.remote.GifsRemoteDataSource
import com.sanogueralorenzo.sample.domain.Gif
import io.reactivex.Single
import javax.inject.Inject

/**
Expand All @@ -14,9 +13,9 @@ class GifsRepository @Inject constructor(
private val remoteDataSource: GifsRemoteDataSource
) {

fun search(searchTerm: String, offset: Int): Single<List<Gif>> =
suspend fun search(searchTerm: String, offset: Int): List<Gif> =
remoteDataSource.search(searchTerm, offset)

fun loadTrending(offset: Int): Single<List<Gif>> =
suspend fun loadTrending(offset: Int): List<Gif> =
remoteDataSource.loadTrending(offset)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.sanogueralorenzo.sample.datasource.remote

import com.sanogueralorenzo.sample.network.NetworkModule
import io.reactivex.Single
import retrofit2.http.GET
import retrofit2.http.Query

interface GifService {
@GET("gifs/search")
fun searchGifs(
suspend fun searchGifs(
@Query("q") searchTerm: String,
@Query("offset") offset: Int = 0,
@Query("limit") limit: Int = 30,
// For security reasons, this should be moved to a C module, also investigate better way instead of query
@Query("api_key") apiKey: String = NetworkModule.GIPHY_API_KEY
): Single<GifsResponse>
): GifsResponse

@GET("gifs/trending")
fun getTrendingGifs(
suspend fun getTrendingGifs(
@Query("offset") offset: Int = 0,
@Query("limit") limit: Int = 30,
// For security reasons, this should be moved to a C module, also investigate better way instead of query
@Query("api_key") apiKey: String = NetworkModule.GIPHY_API_KEY
): Single<GifsResponse>
): GifsResponse
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.sanogueralorenzo.sample.datasource.remote

import com.sanogueralorenzo.sample.domain.Gif
import io.reactivex.Single
import javax.inject.Inject

/**
Expand All @@ -12,10 +11,11 @@ class GifsRemoteDataSource @Inject constructor(
private val service: GifService
) {

fun search(searchTerm: String, offset: Int): Single<List<Gif>> =
suspend fun search(searchTerm: String, offset: Int): List<Gif> =
service.searchGifs(searchTerm, offset)
.map { it.toDomainModel() }
.toDomainModel()

fun loadTrending(offset: Int): Single<List<Gif>> = service.getTrendingGifs(offset)
.map { it.toDomainModel() }
suspend fun loadTrending(offset: Int): List<Gif> =
service.getTrendingGifs(offset)
.toDomainModel()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.sanogueralorenzo.sample.domain

import com.sanogueralorenzo.sample.data.GifsRepository
import io.reactivex.Single
import javax.inject.Inject

/**
Expand All @@ -11,8 +10,7 @@ import javax.inject.Inject
*/
class SearchGifsUseCase @Inject constructor(
private val repository: GifsRepository
) : (String, Int) -> Single<List<Gif>> {

override fun invoke(searchTerm: String, offset: Int): Single<List<Gif>> =
) {
suspend fun run(searchTerm: String, offset: Int): List<Gif> =
repository.search(searchTerm, offset)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.sanogueralorenzo.sample.domain

import com.sanogueralorenzo.sample.data.GifsRepository
import io.reactivex.Single
import javax.inject.Inject

/**
Expand All @@ -11,7 +10,7 @@ import javax.inject.Inject
*/
class TrendingGifsUseCase @Inject constructor(
private val repository: GifsRepository
) : (Int) -> Single<List<Gif>> {
) {

override fun invoke(offset: Int): Single<List<Gif>> = repository.loadTrending(offset)
suspend fun run(offset: Int): List<Gif> = repository.loadTrending(offset)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.sanogueralorenzo.views.screen.simpleController

class GifDetailFragment : ContainerFragment() {

private val url by lazy { arguments!!.getString(URL_KEY)!! }
private val url by lazy { requireArguments().getString(URL_KEY)!! }

override fun controller() = simpleController {
imageRow {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class GifsFragment : ContainerFragment() {
controller.spanCount = GIFS_PER_ROW
layoutManager.spanSizeLookup = controller.spanSizeLookup
recyclerView.layoutManager = layoutManager
recyclerView.setController(controller)
recyclerView.setInfiniteScrolling(layoutManager) { viewModel.loadMore() }
}

Expand All @@ -56,8 +55,8 @@ class GifsFragment : ContainerFragment() {
}

private fun initListeners() {
viewModel.asyncSubscribe(GifsState::request, onFail = { showError(it) })
viewModel.selectSubscribe(GifsState::suggestions) { suggestions ->
viewModel.onAsync(GifsState::request, onFail = { showError(it) })
viewModel.onEach(GifsState::suggestions) { suggestions ->
suggestionsView!!.addSuggestions(
suggestions,
onTrendingClick = { viewModel.trending() },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.sanogueralorenzo.sample.presentation.search

import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.sanogueralorenzo.sample.domain.SearchGifsUseCase
import com.sanogueralorenzo.sample.domain.TrendingGifsUseCase
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers

class GifsViewModel @AssistedInject constructor(
@Assisted state: GifsState,
private val searchUseCase: SearchGifsUseCase,
private val trendingUseCase: TrendingGifsUseCase
) : BaseMvRxViewModel<GifsState>(state) {
) : MavericksViewModel<GifsState>(state) {

/**
* ViewModel initialization can be first time or after process recreation
Expand All @@ -28,24 +29,21 @@ class GifsViewModel @AssistedInject constructor(
is DisplayMode.Search -> search(displayMode.search, offset)
}

fun search(search: String, offset: Int = 0) {
searchUseCase(search, offset).execute {
fun search(search: String, offset: Int = 0) = suspend { searchUseCase.run(search, offset) }
.execute {
copy(
request = it,
items = combinePaginationItems(offset, DisplayMode.Search(search), it),
displayMode = DisplayMode.Search(search)
)
}
}

fun trending(offset: Int = 0) {
trendingUseCase(offset).execute {
copy(
request = it,
items = combinePaginationItems(offset, DisplayMode.Trending, it),
displayMode = DisplayMode.Trending
)
}
fun trending(offset: Int = 0) = suspend { trendingUseCase.run(offset) }.execute(Dispatchers.IO) {
copy(
request = it,
items = combinePaginationItems(offset, DisplayMode.Trending, it),
displayMode = DisplayMode.Trending
)
}

fun loadMore() = withState {
Expand Down
Loading