I'm trying to implement for my first time an Android app with MVP, where a message (taken from a pool of messages) is displayed and it's changed when the user click on the screen. Once all message have been displayed, the process will start over (following the same order of messages). A requirement is to display the same message if the app is closed/reopened. So, we have to implement some implement some store/restore state mechanism in the MVP model.
Here's a basic demo of the app:
I've implemented this MVP for this app as follow:
MainActivity
, it takes care to instantiate the Presenter and Model implementations. Finally, it saves the Model state (as a Parcelable
) with onSaveInstanceState
(and also restores it).(Partial) View implementation:
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)
}
(Partial) Model implementation:
@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()
}
}
Since Presenter and Model should be "Android agnostic", saving the app state (i.e., the Model object) is taken care by the View. However, this breaks the principle where the View doesn't know the Model. My question is: how to save the Model object, without the View knowing the actual implementation of it? What is the best way to deal with the Model state in this scenario?
An actual solution could be to write the code to serialize the Model in the Model itself and save it for each getNextState()
, but this would mean use Android calls in the Model (and reduce its testability).
You should use a different persistence mechanism. The onSaveInstanceState() is really used for situations where the OS needs to restore UI state because of things like configuration / orientation changes. It's not a general purpose storage mechanism.
The model is the correct place to persist data and it is correct that you should try to keep the model as Android agnostic as possible. What you can do is define an interface that represents your persistence requirements:
interface SampleRepo{
fun saveData(...)
fun getData(...)
}
Then your preferred persistence mechanism (e.g. SharedPreferences, SQlite etc.) in a class implements that interface. This is where your Android specific stuff will be hidden.
class SharedPrefRepo : SampleRepo{
override fun saveData(...)
override fun getData(...)
}
Ideally you'll want some injection mechanism so you can inject an instance of the above to your model class (e.g. Dagger). It requires some more plumbing code, but that is the price of loose coupling. For a simpler app, like what you're doing, all of this is overkill. But if you're trying to study proper Android app architecture and loose coupling then it's worth exploring how to do it properly.