Основные проблемы — GlobalScope, zip с одноразовым конфигом, изменяемые промежуточные поля, неверный подсчёт проверок и отсутствие освобождения ресурсов. Результат проверки blacklist вообще не влияет на UI.
Улучшение структуры кода: вариант 1
Основные проблемы — GlobalScope, zip с одноразовым конфигом, изменяемые промежуточные поля, неверный подсчёт проверок и отсутствие освобождения ресурсов. Результат проверки blacklist вообще не влияет на UI.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо провести рефакторинг представленного кода, повысив его читаемость, тестируемость и отделив бизнес‑логику от UI‑слоя. При этом следует обратить внимание на корректное использование корутин, Flow и Rx‑компонентов, а также на правильное управление ресурсами в 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)
Ответ
Основные проблемы — GlobalScope, zip с одноразовым конфигом, изменяемые промежуточные поля, неверный подсчёт проверок и отсутствие освобождения ресурсов. Результат проверки blacklist вообще не влияет на UI.
В viewModelScope загрузите конфиг один раз, затем обрабатывайте поток карт вместе с ним через mapLatest/collectLatest, если устаревшую проверку можно отменить. zip с одним элементом конфигурации завершится после первой пары и не даст нормального потока обновлений. Данные передавайте локально по цепочке, без lateinit config/card.
Увеличивать successfulChecks нужно после завершившейся успешной проверки, а не через apply у созданного Flow. Что считать успехом, надо определить по контракту BlackList: представленный интерфейс этого не объясняет. UI публикуйте на Main, строки формируйте в UI или через отдельный ресурсный адаптер. CompositeDisposable освобождайте в onCleared, если он реально нужен. Надёжную аналитику передавайте отдельному управляемому компоненту, а не запускайте GlobalScope при уничтожении ViewModel.