Recently, I developed an Android application. The application was developed focusing on simplicity. The application was small thus skipped writing unit tests. I know skipping the unit tests is not a good practice.

In past, I was working on backend projects where tons of unit tests exists for API’s. I am new to application development and spent some time to learn about MVC and MVVM design patterns.

For next project, the preference is MVVM. At end of this post I will share the reason to choose MVVM.

The example code for this post is written for Android application.

MVC (Model-View-Controller)

Model: Model is data layer. Model call services or database to get data from external systems.
View: View is user interface layer.
Controller: Controller is triggered first. Controller has reference to Model as well as View. Controller get data from Model and send to View.

I developed a sample application to demonstrate MVC. This application populates ListView on Button click.


Folder Structure
Folder Structure
User.java

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

UserModel.java

public class UserModel{
    private final ArrayList<User> userList;

    public UserModel(){
        userList = new ArrayList<User>();

        // get users from database
        userList.add(new User("Ram"));
        userList.add(new User("Devin"));
        userList.add(new User("Sham"));
        userList.add(new User("Inder"));
        userList.add(new User("David"));
        userList.add(new User("Kaur"));
    }

    public ArrayList<User> getUsers() {
        return userList;
    }
}

UserController.java
Controller has instances of both Model & View. Controller get data from Model & update view.

public class UserController {
    private final UserModel userModel;
    private final UserView userView;

    public UserController(UserView userView) {
        this.userModel = new UserModel();
        this.userView = userView;
    }

    public void getUsers() {
        userView.displayUsers(userModel.getUsers());
    }
}

UserView.java
UserView create instance of Controller. This class handles button clicks from UI.

public class UserView {
    View userView;
    UserController userController;
    Button getUsersButton;
    ListView usersListView;

    public UserView(Context context, ViewGroup container) {
        userView = LayoutInflater.from(context).inflate(R.layout.activity_main, container, false);
        userController = new UserController(this);
    }

    public void initView() {
        getUsersButton = userView.findViewById(R.id.button);
        getUsersButton.setOnClickListener(l -> {
            userController.getUsers();
        });
        usersListView = userView.findViewById(R.id.user_list_view);
    }

    public void displayUsers(ArrayList<User> users) {
        UsersAdapter adapter = new UsersAdapter(userView.getContext(), users);

        usersListView.setAdapter(adapter);
    }

    public View getRootView() {
        return userView;
    }
}

class UsersAdapter extends ArrayAdapter<User> {
    public UsersAdapter(Context context, ArrayList<User> users) {
        super(context, 0, users);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        User user = getItem(position);
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_view_item, parent, false);
        }
        TextView tvName = (TextView) convertView.findViewById(R.id.user_name);
        tvName.setText(user.getName());
        return convertView;
    }
}

MainActivity.java
MainActivity creates UserView instance.

public class MainActivity extends AppCompatActivity {

    UserView userView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userView = new UserView(MainActivity.this, null);
        setContentView(userView.getRootView());
        userView.initView();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/user_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="90dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >

    </ListView>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="151dp"
        android:layout_marginTop="5dp"
        android:layout_marginEnd="146dp"
        android:layout_marginBottom="24dp"
        android:text="Get Users"
        app:layout_constraintBottom_toTopOf="@+id/user_list_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

list_view_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/user_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

In above example, controller is not loosely coupled with view. It has both View & Model. View also has controller reference to send receive.

MVVM (Model-View-ViewModel)

Model: Model is data layer. Model call services or database to get data from external systems.
View: View is user interface layer. View is triggered first to request controller to get data from Model.
View Model: View Model has reference to Model only. ViewModel is loosely coupled with View through livedata or databinding.

I developed a sample application to demonstrate MVVM. This application populates ListView on Button click.

Folder Structure
Folder Structure

User.java

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

UserModel.java

public class UserModel{
    private final ArrayList<User> userList;

    public UserModel(){
        userList = new ArrayList<User>();

        // get users from database
        userList.add(new User("Ram"));
        userList.add(new User("Devin"));
        userList.add(new User("Sham"));
        userList.add(new User("Inder"));
        userList.add(new User("David"));
        userList.add(new User("Kaur"));
    }

    public ArrayList<User> getUsers() {
        return userList;
    }
}

UserViewModel.java
UserViewModel has instances of Model. Controller get data from Model and notify live data subscribers.

public class UserViewModel extends ViewModel {

    private MutableLiveData<ArrayList<User>> userList;

    public UserViewModel() {
        userList = new MutableLiveData<>();
    }

    public MutableLiveData<ArrayList<User>> getObserverUserList() {
        return userList;
    }

    public void getUsers() {
        // notify subscribers
        userList.postValue((new UserModel()).getUsers());
    }
}

MainActivity.java
MainActivity creates UserViewModel. UserViewModel triggers live data.

public class MainActivity extends AppCompatActivity {
    UserViewModel userViewModel;
    Button getUsersButton;
    ListView usersListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // get view model
        userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
        getUsersButton = findViewById(R.id.button);
        getUsersButton.setOnClickListener(l -> {
            userViewModel.getUsers();
        });
        usersListView = findViewById(R.id.user_list_view);
        // subscribe to view model
        userViewModel.getObserverUserList().observe(this, l -> {
            UsersAdapter adapter = new UsersAdapter(this, l);

            usersListView.setAdapter(adapter);
        });
    }
}

class UsersAdapter extends ArrayAdapter<User> {
    public UsersAdapter(Context context, ArrayList<User> users) {
        super(context, 0, users);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        User user = getItem(position);
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_view_item, parent, false);
        }
        TextView tvName = (TextView) convertView.findViewById(R.id.user_name);
        tvName.setText(user.getName());
        return convertView;
    }
}

list_view_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/user_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

In above example, controller is loosely coupled with view. It has only Model. Controller has no information of view.

Note: The data bindings can be used for loose coupling.

Finally

My purpose to use design patterns is to write unit testable and maintainable code. As MVVM ViewModel is loosely coupled with View. I can write unit tests for Model & ViewModel without referencing View. In future, many new modules will be integrated to project. Considering all these requirement, I will consider MVVM.