More towards clean code architecture by Robert Martin

This commit is contained in:
Ashwini Kumar
2022-03-31 16:29:34 +05:30
parent 671f82cc08
commit 5c25ce3b5e
22 changed files with 242 additions and 119 deletions

View File

@ -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())
}

View File

@ -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

View File

@ -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?
)

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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))
}
}
}
}

View File

@ -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 ->

View File

@ -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,

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View 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!!
)
}

View File

@ -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"

View File

@ -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()

View File

@ -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
)
}
}

View File

@ -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"

View File

@ -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