Reputation: 8495
I have read in the recently released 'Android Best Practices' book that a good design pattern to use for android programming is MVVM. Having tried it myself on my latest project it does seem to be beneficial in separating code into more manageable sections.
The View only handles creation of view items and an interface to a ViewModel. The ViewModel implements the interface and handlss operations on the view and interaction with the Model. Sample code below:
Model
public class MyModel{
public String myString;
public MyModel(String myString){
this.myString = myString;
}
}
View
public class MyActivity{
public ViewManager delegate;
public interface ViewManager{
void registerTextView(TextView tvText);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
delegate = new ViewController(this);
TextView tvText = (TextView) view.findViewById(R.id.tvText);
delegate.registerTextView(tvText);
}
}
ViewModel
public class ViewController implements MyActivity.ViewManager{
Context activity;
TextView tvText;
MyModel myModel;
public ViewController(Context app_context){
activity = app_context;
myModel = new MyModel("Hello World");
}
@Override
public registerTextView(TextView tvText){
this.tvText = tvText;
tvText.setText(myModel.myString);
}
}
However, I have not seen this approach anywhere else online and am unable to find much information that supports it being a good design pattern for android. I also have a few questions such as :
Should you have a separate ViewModel for every fragment or just Activities?
Does this approach perform well on configuration change and Activity recreation with the extra overhead of another class? Can you cast the context to your activity to enable use of the fragmentManager?
How does this scale as code gets more complex?
Does anyone have experience using this design pattern with android or could anyone point me in the direction of some good study material before i start converting all my projects to MVVM???
Upvotes: 7
Views: 4362
Reputation: 316
I refactored available code a bit to get some points such as how to bind.
package com.example.mvvm.model;
public class User {
private String email;
private String myPassword;
public User(String email, String password) {
this.email = email;
this.myPassword = password;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setMyPassword(String myPassword) {
this.myPassword = myPassword;
}
public String getMyPassword() {
return myPassword;
}
}
package com.example.mvvm.viewmodels;
import android.text.TextUtils;
import android.util.Patterns;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.example.mvvm.BR;
import com.example.mvvm.model.User;
public class LoginViewModel extends BaseObservable {
@Bindable
private User user = new User("","");
private String successMessage = "Login was successful";
private String errorMessage = "Email or Password not valid";
@Bindable
private String toastMessage = null;
public String getToastMessage() {
return toastMessage;
}
private void setToastMessage(String toastMessage) {
this.toastMessage = toastMessage;
notifyPropertyChanged(BR.toastMessage);
notifyPropertyChanged(BR.user);
}
public void setUserEmail(String email) {
user.setEmail(email);
notifyPropertyChanged(BR.user);
}
public void setUserPassword(String password) {
user.setMyPassword(password);
notifyPropertyChanged(BR.user);
}
@Bindable
public User getUser() {
return user;
}
public void onLoginClicked() {
if (isInputDataValid())
setToastMessage(successMessage);
else
setToastMessage(errorMessage);
}
public boolean isInputDataValid() {
return !TextUtils.isEmpty(getUser().getEmail()) &&
Patterns.EMAIL_ADDRESS.matcher(getUser().getEmail()).matches() &&
getUser().getMyPassword().length() > 5;
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.mvvm.viewmodels.LoginViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:orientation="vertical">
<EditText
android:id="@+id/inEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"
android:padding="8dp"
android:text="@={viewModel.user.email}" />
<EditText
android:id="@+id/inPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:padding="8dp"
android:text="@={viewModel.user.myPassword}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="@{()-> viewModel.onLoginClicked()}"
android:text="LOGIN"
bind:user="@{viewModel.user}" />
</LinearLayout>
</ScrollView>
</layout>
package com.example.mvvm.views;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.example.mvvm.R;
import com.example.mvvm.databinding.ActivityMainBinding;
import com.example.mvvm.model.User;
import com.example.mvvm.viewmodels.LoginViewModel;
import java.sql.Time;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setViewModel(new LoginViewModel());
activityMainBinding.executePendingBindings();
activityMainBinding.setViewModel(new LoginViewModel());
activityMainBinding.inEmail.setText("[email protected]");
activityMainBinding.inPassword.setText("password");
}
@BindingAdapter({"user"})
public static void runtest(View view, User user) {
if (user.getEmail() != null)
Toast.makeText(view.getContext(), user.getEmail(), Toast.LENGTH_SHORT).show();
}
}
Upvotes: 0
Reputation: 276
I have been working on a library for building Android apps in MVVM pattern. You should find examples there.
https://github.com/manas-chaudhari/android-mvvm
Core ideas:
Related Blog Post for architecture: https://manaschaudhari.com/blog/2016/08/19/rxjava-meets-data-binding-part-3
Upvotes: 1
Reputation: 1850
Android MVVM Design Pattern
The Data Binding Library offers both flexibility and broad compatibility — it's a support library, so you can use it with all Android platform versions back to Android 2.1
Build Environment
android {
....
dataBinding {
enabled = true
}
}
You can follow this link step by step and apply databinding in your android projects.
Advance Guide go to developer page Link
Upvotes: 5
Reputation: 572
I will try to give my opinion. I think the sample code you gave didn't follow the core value of applying MVVM(or presentation model. MVVM is originated from presentation model) pattern. One of the major motive of the pattern is to make ViewModel(or Presentaion Model) pure POJO so that ViewModels allow maxmium testability. I have not read the book, but i recommend you to read Martin Fowler's original article about the pattern. I created some examples to demonstrate how to apply the pattern in Android development. If you are interested, you can have a look here - Album Sample, which is an android translation of Martin Fowler's original album example, and AndroidMVVM, a minimal demo app.
One way to apply the pattern is: View(Activity or fragment+layout), ViewModel, Model(business model: persistence layer, networking etc..). With this approach, to answer your question, i think one fragment maps to one ViewModel.
The pattern is to improve the design. When applied correctly, it will reduce the complexity not the other way around. Hope this helps.
Upvotes: 5