From 5c25ce3b5e2204c51604814deed1a961eed09276 Mon Sep 17 00:00:00 2001 From: Ashwini Kumar Date: Thu, 31 Mar 2022 16:29:34 +0530 Subject: [PATCH] More towards clean code architecture by Robert Martin --- .../com/android/tvflix/TvFlixApplication.kt | 2 + .../com/android/tvflix/db/TvFlixDatabase.kt | 2 +- .../tvflix/db/favouriteshow/FavoriteShow.kt | 3 +- .../tvflix/domain/AddToFavoritesUseCase.kt | 17 ++++++ .../tvflix/domain/GetFavoriteShowsUseCase.kt | 21 ++++++++ .../tvflix/domain/GetSchedulesUseCase.kt | 34 ++++++++++++ .../domain/RemoveFromFavoritesUseCase.kt | 19 +++++++ .../android/tvflix/domain/SuspendUseCase.kt | 35 +++++++++++++ .../tvflix/favorite/FavoriteShowsActivity.kt | 8 +-- .../tvflix/favorite/FavoriteShowsAdapter.kt | 31 +---------- .../favorite/FavoriteShowsRepository.kt | 6 +-- .../tvflix/favorite/FavoriteShowsViewModel.kt | 12 ----- .../com/android/tvflix/home/HomeActivity.kt | 11 ++-- .../com/android/tvflix/home/HomeViewData.kt | 2 +- .../com/android/tvflix/home/HomeViewModel.kt | 50 ++++++++++-------- .../model/respositores/SchedulesRepository.kt | 13 +++++ .../com/android/tvflix/utils/Extensions.kt | 16 ++++++ app/src/main/res/layout/activity_home.xml | 4 +- .../android/tvflix/home/HomeViewModelTest.kt | 52 +++++++++++++------ .../java/com/android/tvflix/utils/TestUtil.kt | 11 +--- buildSrc/src/main/kotlin/Dependencies.kt | 4 +- release-notes.txt | 8 +-- 22 files changed, 242 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/com/android/tvflix/domain/AddToFavoritesUseCase.kt create mode 100644 app/src/main/java/com/android/tvflix/domain/GetFavoriteShowsUseCase.kt create mode 100644 app/src/main/java/com/android/tvflix/domain/GetSchedulesUseCase.kt create mode 100644 app/src/main/java/com/android/tvflix/domain/RemoveFromFavoritesUseCase.kt create mode 100644 app/src/main/java/com/android/tvflix/domain/SuspendUseCase.kt create mode 100644 app/src/main/java/com/android/tvflix/model/respositores/SchedulesRepository.kt create mode 100644 app/src/main/java/com/android/tvflix/utils/Extensions.kt diff --git a/app/src/main/java/com/android/tvflix/TvFlixApplication.kt b/app/src/main/java/com/android/tvflix/TvFlixApplication.kt index dab4385..c9ad9ce 100644 --- a/app/src/main/java/com/android/tvflix/TvFlixApplication.kt +++ b/app/src/main/java/com/android/tvflix/TvFlixApplication.kt @@ -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()) } diff --git a/app/src/main/java/com/android/tvflix/db/TvFlixDatabase.kt b/app/src/main/java/com/android/tvflix/db/TvFlixDatabase.kt index cf6cfa2..70fe32a 100644 --- a/app/src/main/java/com/android/tvflix/db/TvFlixDatabase.kt +++ b/app/src/main/java/com/android/tvflix/db/TvFlixDatabase.kt @@ -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 diff --git a/app/src/main/java/com/android/tvflix/db/favouriteshow/FavoriteShow.kt b/app/src/main/java/com/android/tvflix/db/favouriteshow/FavoriteShow.kt index e5e9267..ba4ff31 100644 --- a/app/src/main/java/com/android/tvflix/db/favouriteshow/FavoriteShow.kt +++ b/app/src/main/java/com/android/tvflix/db/favouriteshow/FavoriteShow.kt @@ -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? ) diff --git a/app/src/main/java/com/android/tvflix/domain/AddToFavoritesUseCase.kt b/app/src/main/java/com/android/tvflix/domain/AddToFavoritesUseCase.kt new file mode 100644 index 0000000..15461e4 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/domain/AddToFavoritesUseCase.kt @@ -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(ioDispatcher) { + override suspend fun execute(parameters: FavoriteShow) { + favoriteShowsRepository.insertIntoFavorites(parameters) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/tvflix/domain/GetFavoriteShowsUseCase.kt b/app/src/main/java/com/android/tvflix/domain/GetFavoriteShowsUseCase.kt new file mode 100644 index 0000000..7a79491 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/domain/GetFavoriteShowsUseCase.kt @@ -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>(ioDispatcher) { + override suspend fun execute(parameters: Unit): List { + return favoriteShowsRepository.allFavoriteShowIds() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/tvflix/domain/GetSchedulesUseCase.kt b/app/src/main/java/com/android/tvflix/domain/GetSchedulesUseCase.kt new file mode 100644 index 0000000..2203771 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/domain/GetSchedulesUseCase.kt @@ -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>(ioDispatcher) { + override suspend fun execute(parameters: Unit): List { + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/android/tvflix/domain/RemoveFromFavoritesUseCase.kt b/app/src/main/java/com/android/tvflix/domain/RemoveFromFavoritesUseCase.kt new file mode 100644 index 0000000..fd15bba --- /dev/null +++ b/app/src/main/java/com/android/tvflix/domain/RemoveFromFavoritesUseCase.kt @@ -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(ioDispatcher) { + override suspend fun execute(parameters: FavoriteShow) { + favoriteShowsRepository.removeFromFavorites(parameters) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/tvflix/domain/SuspendUseCase.kt b/app/src/main/java/com/android/tvflix/domain/SuspendUseCase.kt new file mode 100644 index 0000000..3437bf7 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/domain/SuspendUseCase.kt @@ -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(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 +} diff --git a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsActivity.kt b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsActivity.kt index 9579923..d9c0967 100644 --- a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsActivity.kt +++ b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsActivity.kt @@ -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 diff --git a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsAdapter.kt b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsAdapter.kt index 6cd24c9..34bdfa5 100644 --- a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsAdapter.kt +++ b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsAdapter.kt @@ -13,48 +13,23 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions class FavoriteShowsAdapter( - private val favoriteShows: MutableList, - private val callback: Callback + private val favoriteShows: MutableList ) : RecyclerView.Adapter() { 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) - } } diff --git a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsRepository.kt b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsRepository.kt index a46bd72..ad0ecb1 100644 --- a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsRepository.kt +++ b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsRepository.kt @@ -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) } diff --git a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsViewModel.kt b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsViewModel.kt index 2fcb578..35813f6 100644 --- a/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsViewModel.kt +++ b/app/src/main/java/com/android/tvflix/favorite/FavoriteShowsViewModel.kt @@ -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)) - } - } - } } diff --git a/app/src/main/java/com/android/tvflix/home/HomeActivity.kt b/app/src/main/java/com/android/tvflix/home/HomeActivity.kt index 18a9a8f..2b0fa4e 100644 --- a/app/src/main/java/com/android/tvflix/home/HomeActivity.kt +++ b/app/src/main/java/com/android/tvflix/home/HomeActivity.kt @@ -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 -> diff --git a/app/src/main/java/com/android/tvflix/home/HomeViewData.kt b/app/src/main/java/com/android/tvflix/home/HomeViewData.kt index 645d283..b7c0f45 100644 --- a/app/src/main/java/com/android/tvflix/home/HomeViewData.kt +++ b/app/src/main/java/com/android/tvflix/home/HomeViewData.kt @@ -2,7 +2,7 @@ package com.android.tvflix.home import com.android.tvflix.network.home.Show -data class HomeViewData(val episodes: List) { +data class HomeViewData(val heading: String, val episodes: List) { data class EpisodeViewData( val id: Long, val showViewData: ShowViewData, diff --git a/app/src/main/java/com/android/tvflix/home/HomeViewModel.kt b/app/src/main/java/com/android/tvflix/home/HomeViewModel.kt index 03f9de0..db97fe0 100644 --- a/app/src/main/java/com/android/tvflix/home/HomeViewModel.kt +++ b/app/src/main/java/com/android/tvflix/home/HomeViewModel.kt @@ -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, favoriteShowIds: List @@ -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) - } - } } diff --git a/app/src/main/java/com/android/tvflix/model/respositores/SchedulesRepository.kt b/app/src/main/java/com/android/tvflix/model/respositores/SchedulesRepository.kt new file mode 100644 index 0000000..eb24220 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/model/respositores/SchedulesRepository.kt @@ -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 { + return tvFlixApi.getCurrentSchedule(country, currentDate) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/tvflix/utils/Extensions.kt b/app/src/main/java/com/android/tvflix/utils/Extensions.kt new file mode 100644 index 0000000..fd54f18 --- /dev/null +++ b/app/src/main/java/com/android/tvflix/utils/Extensions.kt @@ -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!! + ) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index bea2838..daee5bd 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -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" /> () + // 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 addToFavoritesUseCase: AddToFavoritesUseCase + + @Mock + private lateinit var removeFromFavoritesUseCase: RemoveFromFavoritesUseCase @Mock - private lateinit var favoriteShowsRepository: FavoriteShowsRepository + 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() diff --git a/app/src/test/java/com/android/tvflix/utils/TestUtil.kt b/app/src/test/java/com/android/tvflix/utils/TestUtil.kt index c9fe21d..784f4e5 100644 --- a/app/src/test/java/com/android/tvflix/utils/TestUtil.kt +++ b/app/src/test/java/com/android/tvflix/utils/TestUtil.kt @@ -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 { val episodeList = ArrayList(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 ) } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5b43cd1..cc686bf 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -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" diff --git a/release-notes.txt b/release-notes.txt index 6869c90..9b51119 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -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