Skip to content

Commit

Permalink
Adds Either class for livedata handling (#14)
Browse files Browse the repository at this point in the history
* 🎉 Initial structure for the Either refactor

* ♻️ Adds Either class for livedata handling
  • Loading branch information
hernandazevedo authored Feb 9, 2019
1 parent 38e3980 commit c232199
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ object Logger {
Timber.tag(UNIQUE_TAG).e(e)
}

fun e( e: Throwable, msg: String) {
Timber.tag(UNIQUE_TAG).e(e, msg)
}

fun init() {
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.hernandazevedo.moviedb.view.base

/**
* Based on https://medium.com/@lupajz/you-either-love-it-or-you-havent-used-it-yet-a55f9b866dbe
*/
sealed class Either<out E, out V> {
data class Left<out E>(val error: E) : Either<E, Nothing>()
data class Right<out V>(val value: V) : Either<Nothing, V>()

fun <V> right(value: V): Either<Nothing, V> = Either.Right(value)
fun <E> left(value: E): Either<E, Nothing> = Either.Left(value)

fun <V> either(action: () -> V): Either<Exception, V> =
try { right(action()) } catch (e: Exception) { left(e) }
}

inline infix fun <E, V, V2> Either<E, V>
.map(f: (V) -> V2): Either<E, V2> = when (this) {
is Either.Left -> this
is Either.Right -> Either.Right(f(this.value))
}

infix fun <E, V, V2> Either<E, (V) -> V2>
.apply(f: Either<E, V>): Either<E, V2> = when (this) {
is Either.Left -> this
is Either.Right -> f.map(this.value)
}

inline infix fun <E, V, V2> Either<E, V>
.flatMap(f: (V) -> Either<E, V2>): Either<E, V2> = when (this) {
is Either.Left -> this
is Either.Right -> f(value)
}

inline infix fun <E, E2, V> Either<E, V>
.mapError(f: (E) -> E2): Either<E2, V> = when (this) {
is Either.Left -> Either.Left(f.invoke(error))
is Either.Right -> this
}

inline fun <E, V, A> Either<E, V>
.fold(e: (E) -> A, v: (V) -> A): A = when (this) {
is Either.Left -> e(this.error)
is Either.Right -> v(this.value)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import com.hernandazevedo.moviedb.R
import com.hernandazevedo.moviedb.data.Logger
import com.hernandazevedo.moviedb.getFactoryViewModel
import com.hernandazevedo.moviedb.view.base.BaseActivity
import com.hernandazevedo.moviedb.view.base.Resource
import com.hernandazevedo.moviedb.view.base.Status
import com.hernandazevedo.moviedb.view.base.fold
import kotlinx.android.synthetic.main.activity_details.*
import javax.inject.Inject

Expand All @@ -35,6 +34,7 @@ class DetailsActivity : BaseActivity() {
detailsViewModel = getFactoryViewModel { detailsViewModel }
setContentView(R.layout.activity_details)
subscribeToSearchMovie()
subscribeToFavoriteMovie()
shareAction.setOnClickListener { setupShareAction() }
populateInfo()
setupFavAction()
Expand All @@ -43,18 +43,24 @@ class DetailsActivity : BaseActivity() {

private fun subscribeToSearchMovie() {
detailsViewModel.responseGetMovieDetails.observe(this,
Observer<Resource<MovieDetail>> {
when (it?.status) {
Status.SUCCESS -> {
Logger.d("Success finding movie details")
fillMovieDetail(it.data)
}
Status.ERROR -> {
Logger.d("Error finding movie details")
showMessage("Error finding movie details")
it.throwable?.printStackTrace()
}
}
Observer {
it?.fold({ e: Throwable ->
Logger.e(e, "Left finding movie details")
showMessage("Left finding movie details")
}, {})
})
}

private fun subscribeToFavoriteMovie() {
detailsViewModel.responseFavoriteAction.observe(this,
Observer {
it?.fold(
{ e: Throwable ->
Logger.e(e, "Left finding movie details")
showMessage("Left finding movie details")
},
{}
)
})
}

Expand All @@ -81,7 +87,7 @@ class DetailsActivity : BaseActivity() {
movieTitle.text = "# ${movie.title}"
movieYear.text = year
movieVoteAverage.text = "'-'"
favoriteButton.isChecked = favored ?: false
favoriteButton.isChecked = favored

movieAdultImage.setImageDrawable(getDrawable(R.drawable.ic_clapperboard))
//TODO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import com.hernandazevedo.moviedb.domain.usecase.request.GetMovieDetailsRequest
import com.hernandazevedo.moviedb.domain.usecase.request.SaveMovieToLocalDatabaseRequest
import com.hernandazevedo.moviedb.util.UseCaseHandler
import com.hernandazevedo.moviedb.view.base.BaseViewModel
import com.hernandazevedo.moviedb.view.base.Resource
import com.hernandazevedo.moviedb.view.base.Either

class DetailsViewModel(
val getMovieDetailsUseCase: BaseUseCase<GetMovieDetailsRequest, MovieDetail>,
val saveMovieToLocalDatabaseUseCase: BaseUseCase<SaveMovieToLocalDatabaseRequest, Long>,
val deleteMovieFromLocalDatabaseUseCase: BaseUseCase<DeleteMovieFromLocalDatabaseRequest, Long>
) : BaseViewModel() {

val responseGetMovieDetails: MutableLiveData<Resource<MovieDetail>> = MutableLiveData()
val rsponseFavoriteAction: MutableLiveData<Resource<Any>> = MutableLiveData()
val responseGetMovieDetails: MutableLiveData<Either<Throwable, MovieDetail>> = MutableLiveData()
val responseFavoriteAction: MutableLiveData<Either<Throwable, Any>> = MutableLiveData()

fun getMovieDetails(imdbID: String) {
Logger.d("Starting getMovieDetails - $imdbID")
Expand All @@ -29,10 +29,10 @@ class DetailsViewModel(
useCaseExecute
.subscribe({
Logger.d("Success searching movies for imdbID $imdbID")
responseGetMovieDetails.value = Resource.success(it)
responseGetMovieDetails.value = Either.Right(it)
}, {
Logger.d("Error searching movies for imdbID $imdbID")
responseGetMovieDetails.value = Resource.error(it)
Logger.d("Left searching movies for imdbID $imdbID")
responseGetMovieDetails.value = Either.Left(it)
}))
}

Expand All @@ -51,10 +51,10 @@ class DetailsViewModel(
useCaseExecute
.subscribe({
Logger.d("Success favoriteAction $checked")
responseGetMovieDetails.value = Resource.success()
responseFavoriteAction.value = Either.Right(Any())
}, {
Logger.d("Error favoriteAction $checked")
responseGetMovieDetails.value = Resource.error(it)
Logger.d("Left favoriteAction $checked")
responseFavoriteAction.value = Either.Left(it)
}))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import com.hernandazevedo.moviedb.getFactoryViewModel
import com.hernandazevedo.moviedb.view.Navigator
import com.hernandazevedo.moviedb.view.adapter.MovieAdapter
import com.hernandazevedo.moviedb.view.base.BaseActivity
import com.hernandazevedo.moviedb.view.base.Resource
import com.hernandazevedo.moviedb.view.base.Status
import com.hernandazevedo.moviedb.view.base.fold
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_movies.*
import javax.inject.Inject
Expand Down Expand Up @@ -73,26 +72,21 @@ class MainActivity : BaseActivity() , NavigationView.OnNavigationItemSelectedLis
}

private fun subscribeToSearchMovie() {
mainViewModel.responseSearchMovie.observe(this,
Observer<Resource<List<Movie>>> {
when (it?.status) {
Status.SUCCESS -> {
Logger.d("Success finding movie")
showMovies(it.data)
}
Status.ERROR -> {
Logger.d("Error finding movie")
showMessage("Error finding movie")
it.throwable?.printStackTrace()
}
}
})
mainViewModel.responseSearchMovie.observe(this, Observer {
it?.fold(this::handleError, this::showMovies)
})
}

private fun showMovies(movieList: List<Movie>?) {
private fun showMovies(movieList: List<Movie>) {
Logger.d("Success finding movie")
movieAdapter.setMovies(movieList)
}

private fun handleError(e: Throwable) {
Logger.e(e, "Left finding movie")
showMessage("Left finding movie")
}

private fun setupRecyclersView() {
moviesRecyclerView.layoutManager = layoutManager
moviesRecyclerView.adapter = movieAdapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import com.hernandazevedo.moviedb.domain.usecase.base.BaseUseCase
import com.hernandazevedo.moviedb.domain.usecase.request.SearchMovieRequest
import com.hernandazevedo.moviedb.util.UseCaseHandler
import com.hernandazevedo.moviedb.view.base.BaseViewModel
import com.hernandazevedo.moviedb.view.base.Resource
import com.hernandazevedo.moviedb.view.base.Either

open class MainViewModel(val searchMovieUseCase: BaseUseCase<SearchMovieRequest, List<Movie>>,
val fetchMyFavoritesUseCase: BaseUseCase<BaseRequestValues, List<Movie>>) : BaseViewModel() {
//Here could be an object created on presentation layer, but to be simple we are using the domain model.
val responseSearchMovie: MutableLiveData<Resource<List<Movie>>> = MutableLiveData()
val responseSearchMovie: MutableLiveData<Either<Throwable, List<Movie>>> = MutableLiveData()

fun searchMovie(title: String) {
Logger.d("Starting searchMovie - $title")
Expand All @@ -23,10 +23,10 @@ open class MainViewModel(val searchMovieUseCase: BaseUseCase<SearchMovieRequest,
useCaseExecute
.subscribe({
Logger.d("Success searching movies for title $title")
responseSearchMovie.value = Resource.success(it)
responseSearchMovie.value = Either.Right(it)
}, {
Logger.d("Error searching movies for title $title")
responseSearchMovie.value = Resource.error(it)
Logger.d("Left searching movies for title $title")
responseSearchMovie.value = Either.Left(it)
}))
}

Expand All @@ -38,10 +38,10 @@ open class MainViewModel(val searchMovieUseCase: BaseUseCase<SearchMovieRequest,
useCaseExecute
.subscribe({
Logger.d("Success searching favorited movies")
responseSearchMovie.value = Resource.success(it)
responseSearchMovie.value = Either.Right(it)
}, {
Logger.d("Error searching favorited movies")
responseSearchMovie.value = Resource.error(it)
Logger.d("Left searching favorited movies")
responseSearchMovie.value = Either.Left(it)
}))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.hernandazevedo.moviedb.domain.usecase.base.BaseRequestValues
import com.hernandazevedo.moviedb.domain.usecase.base.BaseUseCase
import com.hernandazevedo.moviedb.domain.usecase.request.SearchMovieRequest
import com.hernandazevedo.moviedb.rx.RxImmediateSchedulerRule
import com.hernandazevedo.moviedb.view.base.Resource
import com.hernandazevedo.moviedb.view.base.Either
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.given
import com.nhaarman.mockito_kotlin.mock
Expand Down Expand Up @@ -55,8 +55,8 @@ class MainViewModelTest {

@Test
fun shouldSearchMovieTest() {
val expectedResult: MutableLiveData<com.hernandazevedo.moviedb.view.base.Resource<List<Movie>>> = MutableLiveData()
expectedResult.value = Resource.success(fakeMovieList)
val expectedResult: MutableLiveData<Either<Throwable, List<Movie>>> = MutableLiveData()
expectedResult.value = Either.Right(fakeMovieList)
mainViewModel.searchMovie(fakeTitleSearch)
Assert.assertEquals(expectedResult.value, mainViewModel.responseSearchMovie.value)
}
Expand Down

0 comments on commit c232199

Please sign in to comment.