Middle — Senior
Code structure improvement: option 1
livecode
Task condition
It is necessary to refactor the provided code to improve its readability, testability, and separate business logic from the UI layer. Attention should be paid to the correct use of coroutines, Flow, and Rx components, as well as proper resource management in ViewModel.
class ScreenViewModel(
val configs: ConfigRepository,
val holder: UserHolder,
val cards: CardRepository,
val analytics: AnalyticsService,
val context: Context
) : ViewModel() {
lateinit var config: Config
lateinit var card: CardType
var liveData = MutableLiveData<UiModel>()
var successfulChecks = 0L
init {
GlobalScope.launch {
flowOf(configs.loadConfig())
.zip(cards.observeAvailableCard(), { it1, it2 -> Pair(it1, it2) })
.flowOn(Dispatchers.Default)
.filter {
it.second == CardType.PREMIUM && holder.getUserScore() > 42 ||
it.second != CardType.PREMIUM &&
it.first.specialUsers.contains(holder.getUserId())
}
.onEach {
config = it.first
card = it.second
}
.flatMapConcat {
flowOf(configs.checkBlackList(it.second, holder.getUserId()))
.apply { successfulChecks++ }
}
.collect {
liveData.value = UiModel(
title = when {
card == CardType.CREDIT -> config.creditTitle
card == CardType.DEBIT -> config.debitTitle
card == CardType.KID -> config.kidTitle
card == CardType.PREMIUM -> context.getString(R.string.congratulations)
else -> ""
},
description = context.getString(
R.string.description_pattern,
config.kinderSurprizeCashback
)
)
}
}
}
var disposables: CompositeDisposable? = CompositeDisposable()
override fun onCleared() {
GlobalScope.launch {
analytics.sendSuccessfulChecks(successfulChecks)
}
super.onCleared()
}
}
interface Config {
val debitTitle: String
val creditTitle: String
val kidTitle: String
val kinderSurprizeCashback: Long
val specialUsers: List<Long>
}
interface ConfigRepository {
suspend fun loadConfig(): Config
fun checkBlackList(cardType: CardType, userId: Long): BlackList
}
interface BlackList {
var successfulChecks: Long
}
interface CardRepository {
fun observeAvailableCard(): Flow<CardType>
}
interface UserHolder {
fun getUserId(): Long
fun getUserScore(): Long
}
interface AnalyticsService {
@FormUrlEncoded
@POST("/analytics")
suspend fun sendSuccessfulChecks(count: Long)
}
enum class CardType { DEBIT, CREDIT, KID, PREMIUM }
class UiModel(val title: String, val description: String)