App Descripion
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:
MVP Design
I've implemented this MVP for this app as follow:
The Model takes care of what it will be the next message (it implements the application state).
The Presenter decides when to ask for the next message (to update the state), depending on the received events from the user (through the View).
The View decides how to show the actual message and communicates events from the user (clicks on the screen) to the presenter. In addition, since the View is also the 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).
Some Code
(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()
}
}
The Problem / My question
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.
Related
I have a BaseViewModel that basically has the function to get the user data like so:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun getUserData() = viewModelScope.launch {
_userResponse.value = Resource.Loading
_userResponse.value = repository.getLoggedInUserData()
}
}
In my Fragment, I access this data by just calling viewModel.getUserData(). This works. However, I'd like to now be able to edit the data. For example, the data class of UserResponse looks like this:
data class UserResponse(
var id: Int,
var username: String,
var email: String
)
In other fragments, I'd like to edit username and email for example. How do I do access the UserResponse object and edit it? Is this a good way of doing things? The getUserData should be accessed everywhere and that is why I'm including it in the abstract BaseViewModel. Whenever the UserResponse is null, I do the following check:
if (viewModel.userResponse.value == null) {
viewModel.getUserData()
}
If you want to be able to edit the data in userResponse, really what you're talking about is changing the value it holds, right? The best way to do that is through the ViewModel itself:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun setUserResponse(response: UserResponse) {
_userResponse.value = response
}
...
}
This has a few advantages - first, the view model is responsible for holding and managing the data, and provides an interface for reading, observing, and updating it. Rather than having lots of places where the data is manipulated, those places just call this one function instead. That makes it a lot easier to change things later, if you need to - the code that calls the function might not need to change at all!
This also means that you can expand the update logic more easily, since it's all centralised in the VM. Need to write the new value to a SavedStateHandle, so it's not lost if the app goes to the background? Just throw that in the update function. Maybe persist it to a database? Throw that in. None of the callers need to know what's happening in there
The other advantage is you're actually setting a new value on the LiveData, which means your update behaviour is consistent and predictable. If the user response changes (either a whole new one, or a change to the current one) then everything observeing that LiveData sees the update, and can decide what to do with it. It's less brittle than this idea that one change to the current response is "new" and another change is "an update" and observers will only care about one of those and don't need to be notified of the other. Consistency in how changes are handled will avoid bugs being introduced later, and just make it easier to reason about what's going on
There's nothing stopping you from updating the properties of the object held in userResponse, just like there's nothing stopping you from holding a List in a LiveData, and adding elements to that list. Everything with a reference to that object will see the new data, but only if they look at it. The point of LiveData and the observer pattern is to push updates to observers, so they can react to changes (like, say, updating text displayed in a UI). If you change one of the vars in that data class, how are you going to make sure everything that needs to see those changes definitely sees them? How can you ensure that will always happen, as the app gets developed, possibly by other people? The observer pattern is about simplifying that logic - update happens, observers are notified, the end
If you are going to do things this way, then I'd still recommend putting an update function in your VM, and let that update the vars. You get the same benefits - centralising the logic, enabling things like persistence if it ever becomes necessary, etc. It could be as simple as
fun setUserResponse(response: UserResponse) {
_userResponse.value?.run {
id = response.id
username = response.username
email = response.email
}
}
and if you do decide to go with the full observer pattern for all changes later, everything is already calling the function the right way, no need for changes there. Or you could just make separate updateEmail(email: String) etc functions, whatever you want to do. But putting all that logic in the VM is a good idea, it's kinda what it's there for
Oh and you access that object through userResponse.value if you want to poke at it - but like I said, better to do that inside a function in the VM, keep that implementation detail, null-safety etc in one place, so callers don't need to mess with it
The ideal way to update userResponse you should change/edit _userResponse so that your userResponse we'll give you the updated data.
it should be something like this
_userResponse.value = Resource<UserResponse>()
so in MVVM architecture even in google samples we can see things like this:
class CharacterListActivity :BaseActivity() {
val ViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getData() // Bad!!!
...
viewModel.state.observe(this) { state ->
when(state) { // handling state is not views job
Success -> { navigatetoNextPage() } // navigating is not views job
Progress -> { showProgress() }
NetworkError -> { ShowSnackbar(viewModel.error) } // I,m not sure about this one either
Error -> { showErrorDialog(viewModel.error)
}
}
We know that any architecture has its own rules that makes the code testable, maintainable, and scalable over time.
in MVVM pattern according to both Wikipedia and Microsoft docs this is the View:
the view is the structure, layout, and appearance of what a user sees on the screen.[6] It displays a representation of the model and receives the user's interaction with the view (clicks, keyboard, gestures, etc.), and it forwards the handling of these to the view model via the data binding (properties, event callbacks, etc.) that is defined to link the view and view model.
each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior such as animations.
XAML is a Xamarin thing, so now let's get back to our code:
here, since activity decides what to do with the state, the activity works as Controller like in MVC but, activity supposed to be the View ,view is just supposed to do the UI logic.
the activity even tells the ViewModel to get data. this is again not the View's job.
please note that telling what to do to the other modules in the code is not the View's job. this is making the view act as controller. view is supposed to handle its state via callbacks from the ViewModel.
the View is supposed to just tell the ViewModel about events like onClick().
since ViewModel doesn't have access to View, it can't show a dialog or navigate through the app directly!
so what is an alternative approach to do this without violation of architecture rules? should I have a function for any lif cycle event in ViewModel, like viewModel.onCreate? or viewModel.onStart? what about navigation or showing dialogs?
For The Record I'm not mixing Up mvc and mvvm, I'm saying that this pattern does which is recommended buy google.
This is not opinion-based, surely anyone can have their own implementation of any architecture but the rules must always be followed to achieve overtime maintainability.
I can name the violations in this code one by one for you:
1) UI is not responsible for getting data, UI just needs to tell ViewModel about events.
2) UI is not responsible for handling state which is exactly what it does here. more general, UI shouldn't contain any non-UI logic.
3) UI is not responsible for navigating between screens
the activity even tells the ViewModel to get data. this is again not the View's job.
Correct. The data fetch should be triggered either by ViewModel.init, or more accurately the activation of a reactive data source (modeled by LiveData, wrapping said reactive source with onActive/onInactive).
If the fetch MUST happen as a result of create, which is unlikely, then it could be done using the DefaultLifecycleObserver using the Jetpack Lifecycle API to create a custom lifecycle-aware component.
Refer to https://stackoverflow.com/a/59109512/2413303
since ViewModel doesn't have access to View, it can't show a dialog or navigate through the app directly!
You can use a custom lifecycle aware component such as EventEmitter (or here) to send one-off events from the ViewModel to the View.
You can also refer to a slightly more advanced technique where rather than just an event, an actual command is sent down in the form of a lambda expression sent as an event, which will be handled by the Activity when it becomes available.
Refer to https://medium.com/#Zhuinden/simplifying-jetpack-navigation-between-top-level-destinations-using-dagger-hilt-3d918721d91e
typealias NavigationCommand = NavController.() -> Unit
#ActivityRetainedScoped
class NavigationDispatcher #Inject constructor() {
private val navigationEmitter: EventEmitter<NavigationCommand> = EventEmitter()
val navigationCommands: EventSource<NavigationCommand> = navigationEmitter
fun emit(navigationCommand: NavigationCommand) {
navigationEmitter.emit(navigationCommand)
}
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject
lateinit var navigationDispatcher: NavigationDispatcher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigationDispatcher.navigationCommands.observe(this) { command ->
command.invoke(Navigation.findNavController(this, R.id.nav_host))
}
}
}
class LoginViewModel #ViewModelInject constructor(
private val navigationDispatcher: NavigationDispatcher,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
fun onRegisterClicked() {
navigationDispatcher.emit {
navigate(R.id.logged_out_to_registration)
}
}
}
If Hilt is not used, the equivalent can be done using Activity-scoped ViewModel and custom AbstractSavedStateViewModelFactory subclasses.
I am using an Observable field in a ViewModel. When the Observable field gets updated, I change the UI visibility.
This can be done either done by
object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
}
}
remove the callback in ondestroy.
or
directly mapping in XML like #{} using two-way binding.
Now the question is how do I remove the listener if using two-way binding? I know the Livedata can be a replacement for this.
I am not sure regarding which memory leak you are talking.
Memory leak in Java occur when one object exists long period of time and it contains strong references to other objects that should not be used anymore, thus should be destroyed by GC, but still persist because of that strong reference.
In Android specifically memory leaks usually occur when some long lasting object stores strong reference to an Activity (or in some cases Fragment). All the other memory leaks in android are not so impactful(except the ones with bitmaps - but it is a completely different topic)
So let us return to the data binding with an ObservableField and its callbacks inside the ViewModel or two way data binding via #={}. In most of the cases there will be no memory leak in both of those cases. To understand why - you will need to understand how does Android framework operates with UI and also understand now does view data binding works. So what happens when you are creating a callback via either ObservableField and callback or with #={}
When you write
val someField: ObservabaleField = ObservableFiled<String>("someText")
val someCallback = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
}
}
someField.addOnPropertyChangedCallback(someCallback)
// and in the layout
android:text="#={viewModel.someField}"
In the generated file it does something like this
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, viewModelSomeFieldGet);
#Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
//...
return onChangeViewModelSomeOtherStuff(object, fieldId);
case 1 :
return onChangeViewModelSomeField((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
}
return false;
}
As you can see there is no context neither activity or fragment leaks since there is no strong reference to them stored anywhere. There is no references to context, activity or fragment in your ViewModel either(I hope!). Moreover it works the other way around - ui stores link to the ViewModel in the binding implementation thus our ViewModel may be leaking. It is rear case since the UI of an Activity or a Fragment usually gets destroyed along with its ActivityBindingImpl or FragmentBindingImpl bindings but...
To be sure you have manual way to clear references: in either Activity' onDestroy or Fragment' onDestroyView call
clearFindViewByIdCache()
binding.unbind()
binding = null
// if you store view link in your viewModel(which is bad and may cause leaks) this is the perfect place to nullify it
viewModel.view = null
Also to handle binding auto clearing you may use AutoClearedValue
the actual usage may look like(if you don't care about its type)
override var binding: ViewDataBinding? by autoCleared()// that is all - no need of onDestroy or onDestroyView
Edit
If you want to manually unregister all the callbacks from your ObservableFields you can do it. The best way to do it is in onCleared() method of ViewModel. You should call observableField.removeOnPropertyChangedCallback(callback) to handle the stuff. It will look like this considering ObservableField and callback declarations above:
class MyViewModel: ViewModel{
//ObservableField and callback declarations
...
override void onCleared(){
someField.removeOnPropertyChangedCallback(someCallback)
}
}
Edit end
This all things I've just described ensures absence of memory leaks while using ObservableFields and view data bindings. It is all about a correct implementation. Of course you can implement it with leaks, but you can implement it without ones.
Comment if something is still unclear - I will try to expand the answer.
A bit more info about Fragment dependent leaks here
Hope it helps.
You can do that using removeOnPropertyChangedCallback function in ViewModel class. Here is how your ViewModel would look like:
abstract class ObservableViewModel(app: Application): AndroidViewModel(app), Observable {
#delegate:Transient
private val mCallBacks: PropertyChangeRegistry by lazy { PropertyChangeRegistry() }
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
mCallBacks.add(callback)
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
mCallBacks.remove(callback)
}
fun notifyChange() {
mCallBacks.notifyChange(this, 0)
}
fun notifyChange(viewId:Int){
mCallBacks.notifyChange(this, viewId)
}
}
removeOnPropertyChangedCallback never gets called?
This actually does
get called, eventually and periodically, by the Data Binding framework
to clean listeners that have been collected. It’s likely however, that
your ViewModel will still have some callbacks registered when it is
destroyed, and this is okay. The Data Binding framework uses weak
references for the observers and it’s not required that they be
unregistered before the ViewModel is destroyed. This won’t cause any
memory leaks.
With that said, if you rotate the phone rapidly, several times in a
row, while on the same screen. You’ll notice
ObservableViewModel.addOnPropertyChangedCallBack is called several
times and if you look inside the source for
android.databinding.ViewDataBinding, you’ll see the observer count
does rise each time.
This is where the periodic removal comes in. If you use the app long
enough, rotate a few times, and have a breakpoint set on
ObservableViewModel.removeOnPropertyChangedCallback. You’ll see that
it is called periodically to clean up old observers and if you look up
the call stack you can find more detail about where that comes from,
how it’s triggered, etc.
You can track more at: https://caster.io/lessons/android-mvvm-pattern-with-architecture-component-viewmodels.
Hope this help you!!
I am developing an Android application using Kotlin programming language. I am starting to use the Model-View-Presenter pattern for my application. To be honest, this is my first time using the MVP pattern even though I have been doing Android Development quite a little while. Now, I am a little bit struggling following the best practices and standards of the pattern.
This is the feature now I am currently developing. A very simple and basic feature.
The application will update a text view. When the text view is updated, a string (message) will be passed to a function as a parameter. Inside the function, if the string is empty, it will hide the text view. Otherwise, it will show the text view again. My struggle is that I am not quite sure how to implement my code that fits the MVP pattern. Especially, the logic to toggle the visibility state of the text view.
This is the current signature of my presenter class called, LoginActivityPresenter
class LoginActivityPresenter(viewArg: View)
{
private var view:View = viewArg
interface View
{
fun updateErrorMessageTextView(message: String)
}
}
What I am thinking is that the activity class will implement the LoginActivityPresenter.View and provide the implementation of the updateErrorMessageTextView method. So in the activity, inside one single function, it will do the following tasks.
It will check if the string is empty. If yes, it will hide the view. Otherwise, it will show the view (back). Then it will set the text view with the message. All in the activity class because of the above presenter class signature.
The other way I am thinking of implementing LoginActivityPresenter class is as follows.
class LoginActivityPresenter(viewArg: View)
{
private var view:View = viewArg
fun updateErrorMessage(message: String) {
if (message.isNullOrEmpty()) {
this.view.hideErrorTextView()
return
}
this.view.showErrorTextView()
this.view.updateErrorMessageTextView(message)
}
interface View
{
fun updateErrorMessageTextView(message: String)
fun showErrorTextView()
fun hideErrorTextView()
}
}
So in the second implementation, the presenter class will handle the logic of showing or hiding the view based on the string value rather than logic is being handled by the activity class.
My question is that which approach is more suitable and standard way for the MVP pattern and why?
I am having a DB populated with weather data, in both imperial and metric units. Now, I have made two different classes which act as a model to get data from the DB. CurrentWeatherMetric has only metric columns, and CurrentWeatherImperial has only imperial fields.
Since I am using MVVM architecture pattern, the ViewModel provides me this data, by calling a function in ViewModel getData(Unit.METRIC) where Unit is an enum class I've made to distinguish the data.
The problem arises here.
My viewModel looks like:
class WeatherViewModel(
private val weatherRepository: WeatherRepositoryImpl
) : ViewModel() {
lateinit var currentWeather: LiveData<CurrentWeather>
lateinit var forecastWeather: LiveData<List<ForecastWeather>>
fun getValuesOfUnit(unit: Unit) {
currentWeather = when (unit) {
Unit.IMPERIAL->weatherRepository.getCurrentWeatherImperial()
Unit.METRIC->weatherRepository.getCurrentWeatherMetric()
}
getWeather()
}
private fun getWeather() {
viewModelScope.launch {
try {
weatherRepository.getWeather()
} catch (e: IOException) {
}
}
}
}
As you can see, lateinit var currentWeather: LiveData<CurrentWeather>,
I had to make another class which store the data of the query with units. I made this so that I could easily implement databinding with it. But I feel this is a really wrong way to do things and hence I have asked this question. How can I get rid of that lateinit variable and implement databinding to adapt to any of the data.
In my current data binding layout, I have data field as:
<data>
<variable
name="viewModel"
type="com.mythio.weather.ui.WeatherViewModel" />
</data>
And I bind to views by:
app:someattribute="#{viewModel.currentWeather.temperature}"
If the question title makes a little sense about what I am asking, or seems misleading, please feel free to edit this to make it a better question.
When using MVVM architecture pattern, Google's recommended way is to make ViewModel that handles connection between your data and view, so it contains UI logic as well as some portion of business logic bound to your UI.
Moreover, implementation of ViewModel in recommended way helps you handle UI lifecycle (Activity/Fragments) in better and hassle-free way.
When using data-binding with MVVM, it's good practice to bind ViewModel directly to xml so that, when data changes you can directly reflect it to UI using LiveData without wiring it manually.
Hence, LiveData can be used as Data-Value holder as it's also Lifecycle-aware component.
On the other hand, Repositories are good way to manage business logic and providing "single source of truth" for data driving through app. So, all data sources like local-db, API calls, shared-preferences etc. should be accessed via repository.
So, yes!! Things you're doing are good & you're on the right track while following MVVM Architecture Pattern.
Note: You can refer here for more info and some improvements in your code.