Как восстановить состояние модели в MVP?

Описание приложения

Я пытаюсь впервые реализовать приложение для Android с MVP, где отображается сообщение (взятое из пула сообщений), и оно изменяется, когда пользователь нажимает на экран. Как только все сообщения будут отображены, процесс начнется заново (следуя тому же порядку сообщений). Требование состоит в том, чтобы отображать одно и то же сообщение, если приложение закрыто/повторно открыто. Итак, мы должны реализовать некоторый механизм сохранения/восстановления состояния в модели MVP.

Вот базовая демонстрация приложения:

Как восстановить состояние модели в MVP?

Лучший дизайн

Я реализовал этот MVP для этого приложения следующим образом:

  1. Модель заботится о какие, это будет следующее сообщение (оно реализует состояние приложения).
  2. Ведущий решает когда запросить следующее сообщение (для обновления состояния) в зависимости от полученных событий от пользователя (через представление).
  3. Вид решает, что как показывает фактическое сообщение, и передает события от пользователя (щелчки на экране) ведущему. Кроме того, поскольку View также является MainActivity, он заботится о создавать экземпляр реализациях Presenter и Model. Наконец, это экономит Состояние модели (как Parcelable) с onSaveInstanceState (а также восстанавливает его).

Некоторый код

(Частично) Посмотреть реализацию:

class MainActivity : AppCompatActivity(), ViewMVC {

    private lateinit var presenter: Presenter
    private var model: Model? = CircularModel(LinkedList<State>(Arrays.asList(
            State("First"),
            State("Second"),
            State("Third")

    )))

    override fun onCreate(savedInstanceState: Bundle?) {
        if (savedInstanceState != null) {
            model = savedInstanceState.getParcelable("model")
        }

        presenter = PresenterImpl(this, model!!)
    }

    override fun onSaveInstanceState(outState: Bundle?) {
        outState?.putParcelable("model", model!!)
        super.onSaveInstanceState(outState)
    }

(Частичная) реализация модели:

@Parcelize
class CircularModel constructor(var states: @RawValue Deque<State>?) : Model, Parcelable {

    override fun getModelState(): State {
        return states!!.peekFirst()
    }

    override fun getModelNextState(): State {
        // Black magic happening here!
        return getModelState()
    }
}

Проблема / Мой вопрос

Поскольку Presenter и Model должны быть «независимыми от Android», представление сохраняет состояние приложения (т. е. объект Model). Однако это нарушает принцип, согласно которому представление не знает модели. Мой вопрос: как сохранить объект модели, если представление не знает о его фактической реализации? Каков наилучший способ справиться с состоянием модели в этом сценарии?

Фактическим решением может быть написание кода для сериализации модели в самой модели и сохранения его для каждого getNextState(), но это будет означать использование вызовов Android в модели (и снижение ее тестируемости).

0
0
412
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы должны использовать другой механизм сохраняемости. OnSaveInstanceState() действительно используется в ситуациях, когда ОС необходимо восстановить состояние пользовательского интерфейса из-за таких вещей, как изменения конфигурации/ориентации. Это не универсальный механизм хранения.

Модель — это правильное место для хранения данных, и правильно, что вы должны стараться, чтобы модель была как можно более независимой от Android. Что вы можете сделать, так это определить интерфейс, который представляет ваши требования к сохраняемости:

interface SampleRepo{ 
   fun saveData(...)
   fun getData(...)
}

Затем ваш предпочтительный механизм сохранения (например, SharedPreferences, SQlite и т. д.) в классе реализует этот интерфейс. Здесь будут скрыты ваши специфические вещи для Android.

class SharedPrefRepo : SampleRepo{
   override fun saveData(...)
   override fun getData(...)
}

В идеале вам понадобится некоторый механизм внедрения, чтобы вы могли внедрить экземпляр вышеперечисленного в класс вашей модели (например, Dagger). Для этого требуется дополнительный код сантехники, но это цена слабой связи. Для более простого приложения, такого как то, что вы делаете, все это излишне. Но если вы пытаетесь изучить правильную архитектуру приложений для Android и слабую связанность, стоит изучить, как это сделать правильно.

Спасибо, Евгений, за ваш ответ, я нахожу это очень хорошим решением. У меня все еще есть несколько вопросов: 1. Каждый вызов SampleRepo вызова будет выполняться в CircularModel модели (реализация модели), верно? 2. Когда звонить saveData? На каждом getModelNextState()? Или только при выходе из приложения?

justHelloWorld 07.03.2019 09:06

1) - да, верно. Класс Repo будет внутри класса Model. 2) - В этом конкретном случае я бы, вероятно, сохранил его только тогда, когда приложение выходит или переходит в фоновый режим. Но в реальной реализации может потребоваться постоянная синхронизация таких данных, чтобы важные изменения состояния сохранялись, как только они происходят. Если ваше приложение разбилось, например, когда оно делало что-то еще, и вы сохраняете только тогда, когда приложение переходит в фоновый режим, тогда приложение может быть восстановлено с устаревшими данными.

Ryujin 07.03.2019 09:39

Другие вопросы по теме