Реализация чистой логики после onFailure в MVVM — android mvvm архитектура

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд
Загрузка...

Вопрос:


Обучаю себя чистому коду и MVVM. Возник вопрос…

И так, есть BaseViewModel, в котором ничего толком не выполняется, только хранятся данные в виде MutableLiveData. Тут же я храню результаты запросов. Сами запросы аккуратно лежат в ScheduleRepository. Его объект создается при создании BaseViewModel.

В MainActivity инициализирую несколько obserListener-ов, например:

viewModel?.isLoading?.observe(this, Observer<Boolean> { isLoading ->
    waitProgressbar.visibility = if (isLoading!!)
        View.VISIBLE
    else
        View.GONE
})

viewModel?.message?.observe(this, Observer<String> { message ->
    Snackbar.make(fragmentFrame, message!!, Snackbar.LENGTH_SHORT).setDuration(2000).show()
})

Соответственно в BaseViewModel существуют эти поля:

var isLoading: MutableLiveData<Boolean> = MutableLiveData()
var message: MutableLiveData<String> = MutableLiveData()

Поля меняются здесь же. Отправили запрос, установили isLoading = true. Запрос выполнился, установили false, изменение переменной перехватилось обсервером, анимация скрылась. Вроде бы все хорошо, но…

В случае, если запрос выполнился с ошибкой, необходимо показать сообщение со стандартной ошибкой. Все запросы в ScheduleRepository. Согласно гугл апи это единственный экземляр при инициализации ViewModel-и. Однако как я могу установить поле message из этого презентера, если ссылки на ViewModel внутри нет? В итоге, я добавляю отдельный метод, прокидывающий внутрь ScheduleRepository модель и контекст. Я не считаю, что это верно, но я не могу придумать, как это сделать чисто.

Приведу кода ScheduleRepository:

class ScheduleRepository {
    private var viewModel: BaseViewModel? = null
    private var context: Context? = null

    fun setViewModel(context: Context, viewModel: BaseViewModel) {
        this.viewModel = viewModel
        this.context = context
    }

    fun getWeek(groupId: String, date: String): MutableLiveData<RequestResultWeek> {
        val data = MutableLiveData<RequestResultWeek>()

        getAPIService().getWeek(groupId, date).enqueue(object: Callback<RequestResultWeek> {
            override fun onFailure(call: Call<RequestResultWeek>?, t: Throwable?) {
                viewModel?.isLoading?.value = false
                viewModel?.title?.value = context?.getString(R.string.request_error)
            }

            override fun onResponse(call: Call<RequestResultWeek>?, response: Response<RequestResultWeek>?) {
                data.postValue(response?.body())
            }
        })

        return data
    }
}

как обыграть это чисто? Жду ваши идеи и опыт!

Автор вопроса: Георгий Чеботарев

Георгий Чеботарев

Мне удалось решить проблему. Не идеально, конечно, потому как я нарушаю некоторые принципы абстракции, но думаю решение получиось более менее красивое. И так. Поля, относящиеся к косметической отрисовке, вынесены в отдельную модель: CosmeticView и оформлены в виде MutableData:

class CosmeticView {
    var showBackBtn: MutableLiveData<Boolean> = MutableLiveData()
    var isLoading: MutableLiveData<Boolean> = MutableLiveData()
    var message: MutableLiveData<String> = MutableLiveData()
    var title: MutableLiveData<String> = MutableLiveData()
}

Объект CosmeticView иницилизуерся внутри BaseViewModel:

var cosmeticView: CosmeticView = CosmeticView()

Он передается в качестве параметров запросов. Например, запрос с неделями.

Код из BaseViewModel:

fun getWeek(groupId: String, period: LocalDate): MutableLiveData<RequestResultWeek>? {

    if (this.groupId != groupId || this.period != period) {
        this.groupId = groupId
        this.period = period

        week = repository.getWeek(groupId, getStringFromDate(period), cosmeticView)
    }

    return week
}

Часть кода из Репозитория:

fun getWeek(groupId: String, date: String, cosmeticView: CosmeticView): MutableLiveData<RequestResultWeek> {
    val data = MutableLiveData<RequestResultWeek>()
    cosmeticView.isLoading.value = true

    getAPIService().getWeek(groupId, date).enqueue(object: Callback<RequestResultWeek> {
        override fun onFailure(call: Call<RequestResultWeek>?, t: Throwable?) {
            cosmeticView.isLoading.value = false
            cosmeticView.message.value = t?.message
        }

        override fun onResponse(call: Call<RequestResultWeek>?, response: Response<RequestResultWeek>?) {
            cosmeticView.isLoading.value = false
            data.postValue(response?.body())
        }
    })

    return data
}

Перед запросом включается анимация загрузки. По завершению загрузки, анимация выключается, а в случае возникновения ошибки устанавливается текст, который будет выведен. В MainActivity обработка все так же через observe.

private fun initObservers() {
    viewModel?.cosmeticView?.isLoading?.observe(this, Observer<Boolean> { isLoading ->
        waitProgressbar.visibility = if (isLoading!!)
            View.VISIBLE
        else
            View.GONE
    })

    viewModel?.cosmeticView?.showBackBtn?.observe(this, Observer<Boolean> { showBackBtn ->
        supportActionBar?.setDisplayHomeAsUpEnabled(showBackBtn!!)
    })

    viewModel?.cosmeticView?.title?.observe(this, Observer<String> { title ->
        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        if (title != null) {
            supportActionBar?.show()
            supportActionBar?.title = title
        } else
            supportActionBar?.hide()
    })

    viewModel?.cosmeticView?.message?.observe(this, Observer<String> { message ->
        Snackbar.make(fragmentFrame, message!!, Snackbar.LENGTH_SHORT).setDuration(2000).show()
    })
}

Я попробовал сделать 1 обработчик для CosmeticView, но тогда нужно разбираться с ложными срабатываниями, например: изменилась анимация, а я вместе с анимацией случайно показываю сообщение. Для простоты не стал стягивать все в одно место, обработчик.

Итог

Скорее всего есть решения более высокого качества, но пока оставляю этот вариант. Передача объекта CosmeticView в репозиторий, выглядит аккуратнее нежеле передача все модели и контекста в придачу. К абсолютной абстракции я не стремлюсь, т.к. считаю, что она нужна только для крупных и очень крупных проектах, с большими командами. Мой проект крошечный (что говорить, всего 3 вида запросов). Если же у кто-то есть идеи с другими вариантами, или вы можете улучшить этот — обязательно пишите. В споре рождается истина.

Источник

Вам также может быть интересно:

Drag and Drop UWP — c# xaml mvvm
Вопрос: Нужно иметь возможность добавлять изображения в приложение путем перетаскивания из файловой системы У Grid включил AllowDrop. Но как добавить перетаскиваемые изображения в коллекцию? Т.к. те ...
Растягивание Высоты , Резина — html css html5
Вопрос: Здравствуйте! Реализовал резиновый дизайн. Растягивается ширина, но длина по % не растягивается. Возможно ли это реализовать? Или так и задумано, растягивание только на ширину ...
Jquery position().left Как быть на мобильниках? — javascript jquery css
Вопрос: На мобильных устройствах, если зумишь экран, position всегда разный. $('g').position().left Как сделать так, чтобы он не менял свои значения? Автор вопроса: ...
Jquery position().left Как быть на мобильниках? — javascript jquery css
Вопрос: На мобильных устройствах, если зумишь экран, position всегда разный. $('g').position().left Как сделать так, чтобы он не менял свои значения? Автор вопроса: ...
requestAnimation и очередность отрисовки — javascript canvas
Вопрос: Здравствуйте! Реализовал 2D игру и в Edge работает все плавно и круто, но в Firefox'e подвисает, сказали, что нужно сделать, чтобы раз-два момент и все ...
Контекстное/всплывающее меню в Android — java android popup
Вопрос: Есть ли техническая возможность сделать приложение, добавляющее свое контекстное или всплывающее меню в любом месте системы? Хочу, чтобы при выделении любого текста появлялась дополнительная кнопка ...
Контекстное/всплывающее меню в Android — java android popup
Вопрос: Есть ли техническая возможность сделать приложение, добавляющее свое контекстное или всплывающее меню в любом месте системы? Хочу, чтобы при выделении любого текста появлялась дополнительная кнопка ...
Завершить службы циклом — c# windows-service
Вопрос: Можно ли остановить службы циклом? У меня есть список служб, занёс их в List List<string> name = new List<string> { "AdobeARMservice", "RemoteRegistry", "TermService", "Messenger", "SSDPSRV", ...
Завершить службы циклом — c# windows-service
Вопрос: Можно ли остановить службы циклом? У меня есть список служб, занёс их в List List<string> name = new List<string> { "AdobeARMservice", "RemoteRegistry", "TermService", "Messenger", "SSDPSRV", ...
RecyclerView — разная разметка — android recyclerview
Вопрос: Смотрел, я смотрел в сторону RecyclerView и наконец решил кинуть ListView и на тебе! В "плохом прошлом" мой ListView использовался для двух разметок. Сейчас я ...
RecyclerView — разная разметка — android recyclerview
Вопрос: Смотрел, я смотрел в сторону RecyclerView и наконец решил кинуть ListView и на тебе! В "плохом прошлом" мой ListView использовался для двух разметок. Сейчас я ...
Как правильно передать массив аргументом для пользовательской функции — php
Вопрос: Если не брать в функцию все это, тогда результат работает. А если вот так в функции все выполнять, тогда PHP ругается: «Invalid ...
Как правильно передать массив аргументом для пользовательской функции — php
Вопрос: Если не брать в функцию все это, тогда результат работает. А если вот так в функции все выполнять, тогда PHP ругается: «Invalid ...
ViewPager внутри ViewPager — такие матрешки работают? — android viewpager
Вопрос: Доброго времени суток. Назрела новая задача. Есть каталог мастеров. При выборе конкретного мастера открывается его страница (активити с ViewPager), и теперь мастеров можно перелистывать свайпом. ...
Почему id всегда 1 Yii2 — yii авторизация
Вопрос: Использую все как по документации. Для получения информации о пользователе использую $identity = $model->findOne(]); И каким бы не был email, id пользователя всегда ...

Оставьте ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *