Sobes.tech
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)