mirror of
https://github.com/reactivedroid/TvFlix.git
synced 2025-05-17 13:25:55 +08:00
More towards clean code architecture by Robert Martin
This commit is contained in:
@ -2,6 +2,7 @@ package com.android.tvflix
|
||||
|
||||
import android.app.Application
|
||||
import com.android.tvflix.config.AppConfig
|
||||
import com.google.firebase.FirebaseApp
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -13,6 +14,7 @@ class TvFlixApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FirebaseApp.initializeApp(this)
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import androidx.room.RoomDatabase
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.db.favouriteshow.ShowDao
|
||||
|
||||
@Database(entities = [FavoriteShow::class], version = 2, exportSchema = false)
|
||||
@Database(entities = [FavoriteShow::class], version = 3, exportSchema = false)
|
||||
abstract class TvFlixDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun showDao(): ShowDao
|
||||
|
@ -12,7 +12,6 @@ data class FavoriteShow(
|
||||
val imageUrl: String?,
|
||||
var summary: String?,
|
||||
var rating: String?,
|
||||
var runtime: Int?,
|
||||
val isFavorite: Boolean
|
||||
var runtime: Int?
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.android.tvflix.domain
|
||||
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.di.IoDispatcher
|
||||
import com.android.tvflix.favorite.FavoriteShowsRepository
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import javax.inject.Inject
|
||||
|
||||
class AddToFavoritesUseCase @Inject
|
||||
constructor(
|
||||
private val favoriteShowsRepository: FavoriteShowsRepository,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||
) : SuspendUseCase<FavoriteShow, Unit>(ioDispatcher) {
|
||||
override suspend fun execute(parameters: FavoriteShow) {
|
||||
favoriteShowsRepository.insertIntoFavorites(parameters)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.android.tvflix.domain
|
||||
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.di.IoDispatcher
|
||||
import com.android.tvflix.favorite.FavoriteShowsRepository
|
||||
import com.android.tvflix.home.HomeViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetFavoriteShowsUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
private val favoriteShowsRepository: FavoriteShowsRepository,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||
) : SuspendUseCase<Unit, List<Long>>(ioDispatcher) {
|
||||
override suspend fun execute(parameters: Unit): List<Long> {
|
||||
return favoriteShowsRepository.allFavoriteShowIds()
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.android.tvflix.domain
|
||||
|
||||
import com.android.tvflix.di.IoDispatcher
|
||||
import com.android.tvflix.model.respositores.SchedulesRepository
|
||||
import com.android.tvflix.network.home.Episode
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class GetSchedulesUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
private val schedulesRepository: SchedulesRepository,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||
) :
|
||||
SuspendUseCase<Unit, List<Episode>>(ioDispatcher) {
|
||||
override suspend fun execute(parameters: Unit): List<Episode> {
|
||||
return schedulesRepository.getSchedule(country = COUNTRY, currentDate = currentDate)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val QUERY_DATE_FORMAT = "yyyy-MM-dd"
|
||||
const val COUNTRY = "US"
|
||||
private val currentDate: String
|
||||
get() {
|
||||
val simpleDateFormat = SimpleDateFormat(QUERY_DATE_FORMAT, Locale.US)
|
||||
val calendar = Calendar.getInstance()
|
||||
return simpleDateFormat.format(calendar.time)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.android.tvflix.domain
|
||||
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.di.IoDispatcher
|
||||
import com.android.tvflix.favorite.FavoriteShowsRepository
|
||||
import com.android.tvflix.network.home.Show
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoveFromFavoritesUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
private val favoriteShowsRepository: FavoriteShowsRepository,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||
) : SuspendUseCase<FavoriteShow, Unit>(ioDispatcher) {
|
||||
override suspend fun execute(parameters: FavoriteShow) {
|
||||
favoriteShowsRepository.removeFromFavorites(parameters)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.android.tvflix.domain
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Executes business logic synchronously or asynchronously using Coroutines.
|
||||
*
|
||||
* The [execute] method of [SuspendUseCase] is a suspend function
|
||||
*/
|
||||
abstract class SuspendUseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
|
||||
|
||||
/** Executes the use case asynchronously and returns a [Result].
|
||||
*
|
||||
* @return a [Result].
|
||||
*
|
||||
* @param parameters the input parameters to run the use case with
|
||||
*/
|
||||
val tag: String = this.javaClass.simpleName
|
||||
|
||||
suspend operator fun invoke(parameters: P): R {
|
||||
// Moving all use case's executions to the injected dispatcher
|
||||
// In production code, this is usually the Default dispatcher (background thread)
|
||||
// In tests, this becomes a TestCoroutineDispatcher
|
||||
return withContext(coroutineDispatcher) {
|
||||
execute(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to set the code to be executed.
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
protected abstract suspend fun execute(parameters: P): R
|
||||
}
|
@ -22,7 +22,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FavoriteShowsActivity : AppCompatActivity(), FavoriteShowsAdapter.Callback {
|
||||
class FavoriteShowsActivity : AppCompatActivity() {
|
||||
private val favoriteShowsViewModel: FavoriteShowsViewModel by viewModels()
|
||||
private val binding by lazy { ActivityFavoriteShowsBinding.inflate(layoutInflater) }
|
||||
|
||||
@ -62,7 +62,7 @@ class FavoriteShowsActivity : AppCompatActivity(), FavoriteShowsAdapter.Callback
|
||||
binding.progress.isVisible = false
|
||||
val layoutManager = GridLayoutManager(this, COLUMNS_COUNT)
|
||||
binding.shows.layoutManager = layoutManager
|
||||
val favoriteShowsAdapter = FavoriteShowsAdapter(favoriteShows.toMutableList(), this)
|
||||
val favoriteShowsAdapter = FavoriteShowsAdapter(favoriteShows.toMutableList())
|
||||
binding.shows.adapter = favoriteShowsAdapter
|
||||
val spacing = resources.getDimensionPixelSize(R.dimen.show_grid_spacing)
|
||||
binding.shows.addItemDecoration(GridItemDecoration(spacing, COLUMNS_COUNT))
|
||||
@ -81,10 +81,6 @@ class FavoriteShowsActivity : AppCompatActivity(), FavoriteShowsAdapter.Callback
|
||||
binding.favoriteHint.isVisible = true
|
||||
}
|
||||
|
||||
override fun onFavoriteClicked(show: FavoriteShow) {
|
||||
favoriteShowsViewModel.onFavoriteClick(show)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FAVORITE_ICON_START_OFFSET = 13
|
||||
private const val FAVORITE_ICON_END_OFFSET = 14
|
||||
|
@ -13,48 +13,23 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
class FavoriteShowsAdapter(
|
||||
private val favoriteShows: MutableList<FavoriteShow>,
|
||||
private val callback: Callback
|
||||
private val favoriteShows: MutableList<FavoriteShow>
|
||||
) : RecyclerView.Adapter<FavoriteShowsAdapter.FavoriteShowHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteShowHolder {
|
||||
val layoutInflater = LayoutInflater.from(parent.context)
|
||||
val showListItemBinding = ShowListItemBinding.inflate(layoutInflater, parent, false)
|
||||
val holder = FavoriteShowHolder(showListItemBinding)
|
||||
holder.binding.favorite.setOnClickListener { onFavouriteIconClicked(holder.absoluteAdapterPosition) }
|
||||
holder.binding.showFavoriteIcon = false
|
||||
return holder
|
||||
}
|
||||
|
||||
private fun onFavouriteIconClicked(position: Int) {
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
val show = favoriteShows[position]
|
||||
val updatedShow = show.copy(isFavorite = !show.isFavorite)
|
||||
favoriteShows[position] = updatedShow
|
||||
notifyItemChanged(position)
|
||||
callback.onFavoriteClicked(show)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FavoriteShowHolder, position: Int) {
|
||||
val favoriteShow = favoriteShows[position]
|
||||
Glide.with(holder.itemView.context).load(favoriteShow.imageUrl)
|
||||
.apply(RequestOptions.placeholderOf(R.color.grey))
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(holder.binding.showImage)
|
||||
configureFavoriteIcon(holder.binding.favorite, favoriteShow.isFavorite)
|
||||
}
|
||||
|
||||
private fun configureFavoriteIcon(favoriteIcon: ImageView, favorite: Boolean) {
|
||||
if (favorite) {
|
||||
val favoriteDrawable = AppCompatResources
|
||||
.getDrawable(favoriteIcon.context, R.drawable.favorite)
|
||||
favoriteIcon.setImageDrawable(favoriteDrawable)
|
||||
} else {
|
||||
val unFavoriteDrawable = AppCompatResources
|
||||
.getDrawable(favoriteIcon.context, R.drawable.favorite_border)
|
||||
favoriteIcon.setImageDrawable(unFavoriteDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -63,8 +38,4 @@ class FavoriteShowsAdapter(
|
||||
|
||||
class FavoriteShowHolder(val binding: ShowListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
interface Callback {
|
||||
fun onFavoriteClicked(show: FavoriteShow)
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,7 @@ constructor(private val showDao: ShowDao) {
|
||||
imageUrl = show.image!!["original"],
|
||||
summary = show.summary,
|
||||
rating = show.rating!!["average"],
|
||||
runtime = show.runtime!!,
|
||||
isFavorite = true
|
||||
runtime = show.runtime!!
|
||||
)
|
||||
showDao.insert(favoriteShow)
|
||||
}
|
||||
@ -36,8 +35,7 @@ constructor(private val showDao: ShowDao) {
|
||||
imageUrl = show.image!!["original"],
|
||||
summary = show.summary,
|
||||
rating = show.rating!!["average"],
|
||||
runtime = show.runtime!!,
|
||||
isFavorite = false
|
||||
runtime = show.runtime!!
|
||||
)
|
||||
showDao.remove(favoriteShow)
|
||||
}
|
||||
|
@ -53,16 +53,4 @@ constructor(
|
||||
)
|
||||
Timber.d(throwable)
|
||||
}
|
||||
|
||||
fun onFavoriteClick(show: FavoriteShow) {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
if (!show.isFavorite) {
|
||||
favoriteShowsRepository.insertIntoFavorites(show)
|
||||
_favoriteShowsStateFlow.emit(FavoriteShowState.AddedToFavorites(show))
|
||||
} else {
|
||||
favoriteShowsRepository.removeFromFavorites(show)
|
||||
_favoriteShowsStateFlow.emit(FavoriteShowState.RemovedFromFavorites(show))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.android.tvflix.R
|
||||
import com.android.tvflix.config.FavoritesFeatureFlag
|
||||
import com.android.tvflix.databinding.ActivityHomeBinding
|
||||
import com.android.tvflix.domain.GetSchedulesUseCase
|
||||
import com.android.tvflix.favorite.FavoriteShowsActivity
|
||||
import com.android.tvflix.shows.AllShowsActivity
|
||||
import com.android.tvflix.utils.GridItemDecoration
|
||||
@ -57,10 +58,6 @@ class HomeActivity : AppCompatActivity(), ShowsAdapter.Callback {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
homeViewModel.homeViewStateFlow.collect { setViewState(it) }
|
||||
}
|
||||
binding.popularShowHeader.text = String.format(
|
||||
getString(R.string.popular_shows_airing_today),
|
||||
homeViewModel.country
|
||||
)
|
||||
}
|
||||
|
||||
private fun setViewState(homeViewState: HomeViewState) {
|
||||
@ -71,7 +68,11 @@ class HomeActivity : AppCompatActivity(), ShowsAdapter.Callback {
|
||||
showError(homeViewState.message!!)
|
||||
}
|
||||
is HomeViewState.Success -> {
|
||||
binding.progress.isVisible = false
|
||||
with(binding) {
|
||||
progress.isVisible = false
|
||||
popularShowHeader.text = homeViewState.homeViewData.heading
|
||||
popularShowHeader.isVisible = true
|
||||
}
|
||||
showPopularShows(homeViewState.homeViewData)
|
||||
}
|
||||
is HomeViewState.AddedToFavorites ->
|
||||
|
@ -2,7 +2,7 @@ package com.android.tvflix.home
|
||||
|
||||
import com.android.tvflix.network.home.Show
|
||||
|
||||
data class HomeViewData(val episodes: List<EpisodeViewData>) {
|
||||
data class HomeViewData(val heading: String, val episodes: List<EpisodeViewData>) {
|
||||
data class EpisodeViewData(
|
||||
val id: Long,
|
||||
val showViewData: ShowViewData,
|
||||
|
@ -1,31 +1,41 @@
|
||||
package com.android.tvflix.home
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.tvflix.R
|
||||
import com.android.tvflix.analytics.Analytics
|
||||
import com.android.tvflix.analytics.Event
|
||||
import com.android.tvflix.analytics.EventNames
|
||||
import com.android.tvflix.analytics.EventParams
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.di.IoDispatcher
|
||||
import com.android.tvflix.favorite.FavoriteShowsRepository
|
||||
import com.android.tvflix.network.TvFlixApi
|
||||
import com.android.tvflix.domain.AddToFavoritesUseCase
|
||||
import com.android.tvflix.domain.GetFavoriteShowsUseCase
|
||||
import com.android.tvflix.domain.GetSchedulesUseCase
|
||||
import com.android.tvflix.domain.RemoveFromFavoritesUseCase
|
||||
import com.android.tvflix.network.home.Episode
|
||||
import com.android.tvflix.utils.toFavoriteShow
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
private val tvFlixApi: TvFlixApi,
|
||||
private val favoriteShowsRepository: FavoriteShowsRepository,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val getSchedulesUseCase: GetSchedulesUseCase,
|
||||
private val getFavoriteShowsUseCase: GetFavoriteShowsUseCase,
|
||||
private val addToFavoritesUseCase: AddToFavoritesUseCase,
|
||||
private val removeFromFavoritesUseCase: RemoveFromFavoritesUseCase,
|
||||
// Inject coroutineDispatcher to facilitate Unit Testing
|
||||
@IoDispatcher private val dispatcher: CoroutineDispatcher,
|
||||
private val analytics: Analytics
|
||||
@ -34,19 +44,18 @@ class HomeViewModel @Inject constructor(
|
||||
|
||||
// Represents _homeViewStateFlow mutable state flow as a read-only state flow.
|
||||
val homeViewStateFlow = _homeViewStateFlow.asStateFlow()
|
||||
val country: String
|
||||
get() = COUNTRY_US
|
||||
|
||||
fun onScreenCreated() {
|
||||
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
|
||||
onError(exception)
|
||||
}
|
||||
viewModelScope.launch(dispatcher + coroutineExceptionHandler) {
|
||||
val favoriteShowIds = favoriteShowsRepository.allFavoriteShowIds()
|
||||
val episodes = tvFlixApi.getCurrentSchedule(country, currentDate)
|
||||
val favoriteShowIds = getFavoriteShowsUseCase.invoke(Unit)
|
||||
val episodes = getSchedulesUseCase.invoke(Unit)
|
||||
_homeViewStateFlow.emit(
|
||||
HomeViewState.Success(
|
||||
HomeViewData(
|
||||
heading(),
|
||||
getShowsWithFavorites(
|
||||
episodes,
|
||||
favoriteShowIds
|
||||
@ -57,6 +66,13 @@ class HomeViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun heading(): String {
|
||||
return String.format(
|
||||
context.getString(R.string.popular_shows_airing_today),
|
||||
GetSchedulesUseCase.COUNTRY
|
||||
)
|
||||
}
|
||||
|
||||
private fun getShowsWithFavorites(
|
||||
episodes: List<Episode>,
|
||||
favoriteShowIds: List<Long>
|
||||
@ -99,7 +115,7 @@ class HomeViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
)
|
||||
favoriteShowsRepository.insertShowIntoFavorites(showViewData.show)
|
||||
addToFavoritesUseCase.invoke(showViewData.show.toFavoriteShow())
|
||||
_homeViewStateFlow.emit(HomeViewState.AddedToFavorites(showViewData.show))
|
||||
} else {
|
||||
analytics.sendEvent(
|
||||
@ -113,21 +129,9 @@ class HomeViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
)
|
||||
favoriteShowsRepository.removeShowFromFavorites(showViewData.show)
|
||||
removeFromFavoritesUseCase.invoke(showViewData.show.toFavoriteShow())
|
||||
_homeViewStateFlow.emit(HomeViewState.RemovedFromFavorites(showViewData.show))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val COUNTRY_US = "US"
|
||||
private const val QUERY_DATE_FORMAT = "yyyy-MM-dd"
|
||||
|
||||
private val currentDate: String
|
||||
get() {
|
||||
val simpleDateFormat = SimpleDateFormat(QUERY_DATE_FORMAT, Locale.US)
|
||||
val calendar = Calendar.getInstance()
|
||||
return simpleDateFormat.format(calendar.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.android.tvflix.model.respositores
|
||||
|
||||
import com.android.tvflix.network.TvFlixApi
|
||||
import com.android.tvflix.network.home.Episode
|
||||
import javax.inject.Inject
|
||||
|
||||
class SchedulesRepository
|
||||
@Inject
|
||||
constructor(private val tvFlixApi: TvFlixApi) {
|
||||
suspend fun getSchedule(country: String, currentDate: String): List<Episode> {
|
||||
return tvFlixApi.getCurrentSchedule(country, currentDate)
|
||||
}
|
||||
}
|
16
app/src/main/java/com/android/tvflix/utils/Extensions.kt
Normal file
16
app/src/main/java/com/android/tvflix/utils/Extensions.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.android.tvflix.utils
|
||||
|
||||
import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.network.home.Show
|
||||
|
||||
fun Show.toFavoriteShow(): FavoriteShow {
|
||||
return FavoriteShow(
|
||||
id = id,
|
||||
name = name,
|
||||
premiered = premiered,
|
||||
imageUrl = image!!["original"],
|
||||
summary = summary,
|
||||
rating = rating!!["average"],
|
||||
runtime = runtime!!
|
||||
)
|
||||
}
|
@ -21,8 +21,10 @@
|
||||
android:text="@string/popular_shows_airing_today"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/popular_shows"
|
||||
|
@ -1,9 +1,13 @@
|
||||
package com.android.tvflix.home
|
||||
|
||||
import android.content.Context
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.tvflix.analytics.Analytics
|
||||
import com.android.tvflix.favorite.FavoriteShowsRepository
|
||||
import com.android.tvflix.network.TvFlixApi
|
||||
import com.android.tvflix.domain.AddToFavoritesUseCase
|
||||
import com.android.tvflix.domain.GetFavoriteShowsUseCase
|
||||
import com.android.tvflix.domain.GetSchedulesUseCase
|
||||
import com.android.tvflix.domain.RemoveFromFavoritesUseCase
|
||||
import com.android.tvflix.utils.MainCoroutineRule
|
||||
import com.android.tvflix.utils.TestUtil
|
||||
import com.android.tvflix.utils.runBlockingTest
|
||||
@ -14,13 +18,20 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
@ExperimentalCoroutinesApi
|
||||
class HomeViewModelTest {
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
|
||||
// Executes tasks in the Architecture Components in the same thread
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
@ -30,10 +41,16 @@ class HomeViewModelTest {
|
||||
var coroutineRule = MainCoroutineRule()
|
||||
|
||||
@Mock
|
||||
private lateinit var tvFlixApi: TvFlixApi
|
||||
private lateinit var getSchedulesUseCase: GetSchedulesUseCase
|
||||
|
||||
@Mock
|
||||
private lateinit var favoriteShowsRepository: FavoriteShowsRepository
|
||||
private lateinit var addToFavoritesUseCase: AddToFavoritesUseCase
|
||||
|
||||
@Mock
|
||||
private lateinit var removeFromFavoritesUseCase: RemoveFromFavoritesUseCase
|
||||
|
||||
@Mock
|
||||
private lateinit var getFavoriteShowsUseCase: GetFavoriteShowsUseCase
|
||||
|
||||
private val testDispatcher = coroutineRule.testDispatcher
|
||||
|
||||
@ -49,10 +66,10 @@ class HomeViewModelTest {
|
||||
fun `test if home is loaded with shows and without favorites`() {
|
||||
coroutineRule.runBlockingTest {
|
||||
// Stubbing network calls with fake episode list
|
||||
whenever(tvFlixApi.getCurrentSchedule("US", TestUtil.currentDate))
|
||||
whenever(getSchedulesUseCase.invoke(Unit))
|
||||
.thenReturn(TestUtil.getFakeEpisodeList())
|
||||
// Stub repository with empty list
|
||||
whenever(favoriteShowsRepository.allFavoriteShowIds())
|
||||
whenever(getFavoriteShowsUseCase.invoke(Unit))
|
||||
.thenReturn(emptyList())
|
||||
|
||||
val homeViewModel = createHomeViewModel()
|
||||
@ -62,7 +79,7 @@ class HomeViewModelTest {
|
||||
val homeState = homeViewModel.homeViewStateFlow.first() as HomeViewState.Success
|
||||
assertThat(homeState).isNotNull()
|
||||
val episodes = homeState.homeViewData.episodes
|
||||
assertThat(episodes.isNotEmpty())
|
||||
assertThat(episodes.isNotEmpty()).isTrue()
|
||||
// compare the response with fake list
|
||||
assertThat(episodes).hasSize(TestUtil.getFakeEpisodeList().size)
|
||||
// compare the data and also order
|
||||
@ -76,10 +93,13 @@ class HomeViewModelTest {
|
||||
|
||||
private fun createHomeViewModel(): HomeViewModel {
|
||||
return HomeViewModel(
|
||||
tvFlixApi = tvFlixApi,
|
||||
favoriteShowsRepository = favoriteShowsRepository,
|
||||
dispatcher = testDispatcher,
|
||||
analytics = analytics
|
||||
context,
|
||||
getSchedulesUseCase,
|
||||
getFavoriteShowsUseCase,
|
||||
addToFavoritesUseCase,
|
||||
removeFromFavoritesUseCase,
|
||||
testDispatcher,
|
||||
analytics
|
||||
)
|
||||
}
|
||||
|
||||
@ -87,10 +107,10 @@ class HomeViewModelTest {
|
||||
fun `test if home is loaded with shows and favorites`() {
|
||||
coroutineRule.runBlockingTest {
|
||||
// Stubbing network calls with fake episode list
|
||||
whenever(tvFlixApi.getCurrentSchedule("US", TestUtil.currentDate))
|
||||
whenever(getSchedulesUseCase.invoke(Unit))
|
||||
.thenReturn(TestUtil.getFakeEpisodeList())
|
||||
// Stub repository with fake favorites
|
||||
whenever(favoriteShowsRepository.allFavoriteShowIds())
|
||||
whenever(getFavoriteShowsUseCase.invoke(Unit))
|
||||
.thenReturn(arrayListOf(1, 2))
|
||||
val homeViewModel = createHomeViewModel()
|
||||
homeViewModel.onScreenCreated()
|
||||
@ -98,7 +118,7 @@ class HomeViewModelTest {
|
||||
val homeState = homeViewModel.homeViewStateFlow.first() as HomeViewState.Success
|
||||
assertThat(homeState).isNotNull()
|
||||
val episodes = homeState.homeViewData.episodes
|
||||
assertThat(episodes.isNotEmpty())
|
||||
assertThat(episodes.isNotEmpty()).isTrue()
|
||||
// compare the response with fake list
|
||||
assertThat(episodes).hasSize(TestUtil.getFakeEpisodeList().size)
|
||||
// compare the data and also order
|
||||
@ -115,10 +135,10 @@ class HomeViewModelTest {
|
||||
coroutineRule.runBlockingTest {
|
||||
val homeViewModel = createHomeViewModel()
|
||||
// Stubbing network calls with fake episode list
|
||||
whenever(tvFlixApi.getCurrentSchedule("US", TestUtil.currentDate))
|
||||
whenever(getSchedulesUseCase.invoke(Unit))
|
||||
.thenThrow(RuntimeException("Error occurred"))
|
||||
// Stub repository with fake favorites
|
||||
whenever(favoriteShowsRepository.allFavoriteShowIds())
|
||||
whenever(getFavoriteShowsUseCase.invoke(Unit))
|
||||
.thenReturn(arrayListOf(1, 2))
|
||||
|
||||
homeViewModel.onScreenCreated()
|
||||
|
@ -4,8 +4,6 @@ import com.android.tvflix.db.favouriteshow.FavoriteShow
|
||||
import com.android.tvflix.home.HomeViewData
|
||||
import com.android.tvflix.network.home.Episode
|
||||
import com.android.tvflix.network.home.Show
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
object TestUtil {
|
||||
@ -30,13 +28,6 @@ object TestUtil {
|
||||
return episodeViewDataList
|
||||
}
|
||||
|
||||
val currentDate: String
|
||||
get() {
|
||||
val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
val calendar = Calendar.getInstance()
|
||||
return simpleDateFormat.format(calendar.time)
|
||||
}
|
||||
|
||||
fun getFakeEpisodeList(): List<Episode> {
|
||||
val episodeList = ArrayList<Episode>(2)
|
||||
val show1 = Show(
|
||||
@ -69,7 +60,7 @@ object TestUtil {
|
||||
id = 222, name = "Friends",
|
||||
premiered = "Aug 2002", imageUrl = null,
|
||||
summary = "Friends for life!", rating = "10 stars",
|
||||
runtime = 132000, isFavorite = true
|
||||
runtime = 132000
|
||||
)
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ object Deps {
|
||||
const val compile_sdk = 31
|
||||
const val min_sdk = 23
|
||||
const val target_sdk = 30
|
||||
const val app_version_code = 106
|
||||
const val app_version_name = "2.2.1"
|
||||
const val app_version_code = 107
|
||||
const val app_version_name = "2.2.2"
|
||||
const val android_plugin = "7.0.4"
|
||||
const val constraint_layout = "2.1.2"
|
||||
const val lifecycle = "2.4.0"
|
||||
|
@ -1,6 +1,2 @@
|
||||
1. Add firebase crashlytics and performance monitoring
|
||||
2. Upgrade AGP to 7.0.4
|
||||
3. Rename package and files to remove `TvMaze` reference
|
||||
4. Add Firebase analytics
|
||||
5. Update dependencies
|
||||
6. Integrate Firebase Remote Config
|
||||
v2.2.2
|
||||
Refactoring to move towards clean code architecture
|
||||
|
Reference in New Issue
Block a user