Understanding MVVM Architecture in Android

Priyank Kumar
The Startup
Published in
5 min readApr 11, 2020

--

You know the importance of architecture and design pattern if you have worked on any intermediate and high-level projects. It is essential to keep our project loosely coupled; which means keeping all the components in our project separate, meaning each component has little or no knowledge about the others. This is especially important in big projects because things get messy quite quickly, and the end-result is unmaintainable. These spaghetti codes (Tightly coupled) is also difficult to test because many different parts depend on each other. That’s why we have architecture patterns to make our project modular, where each component have a specific responsibility and modifications are possible without modifying other modules.

There are several different architectural patterns to choose from, here are some of the popular ones you may have heard about,

  • MVC
  • MVP
  • MVVM

In android, we can use any one of the above as each has their own advantages and disadvantages (which we are not going to discuss here). But it is highly recommended by google and android developers team to use MVVM architecture.

MVVM stands for Model-View-ViewModel architecture. There are several advantages of using MVVM in your projects, such as:

  • Makes the project loosely coupled.
  • Easier to maintain.
  • Simple to add a new feature or remove existing.
  • Very testable code.
  • It gives great structure to your project and makes it easier to navigate and understand our code.

Now, there are quite a few frameworks that can help us implement MVVM. Which one should you use in case you don’t want to make your own?

Source: DevExpress interpretation of XKCD Comics

Google provides its framework comprised of several different components that you can use and build upon to make your job simpler.

This is how Google’s implementation of MVVM architecture looks like:

View

This part of our architecture help us build our user interface and the only part our users can interact directly. I consist of Fragment object defined in “src” and a layout resource. There is a two-way binding between them, which allows easy data sharing.

Here’s an example, you don’t need to understand each line, only go through their implementations.

Fragment :
view/MovieFragment.java
public class MovieFragment extends Fragment {
OnPosterClickListener mCallback;
GridView moviesLayout;
List<Movie> movies;

public MovieFragment() {
}

public MovieFragment(List<Movie> movies) {
this.movies = movies;
}

public interface OnPosterClickListener {
void onImageClick(Movie movie);
}

@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
mCallback = (OnPosterClickListener) context;
} catch (ClassCastException e) {
throw new ClassCastException();
}
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final View rootView = inflater.inflate(
//Inflating fragment with the layout
R.layout.fragment_movie_list, container, false);
moviesLayout = rootView.findViewById(R.id.images_grid_view);
MovieRecyclerViewAdapter mMovieAdapter = new
MovieRecyclerViewAdapter(getContext(), movies);

moviesLayout.setAdapter(mMovieAdapter);
moviesLayout.setOnItemClickListener((
(parent, view, position, id) ->
mCallback.onImageClick(movies.get(position))
));
return rootView;
}
}
Layout:
res/fragment_movie_list.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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/movie_recycler_view"
android:name="com.example.moviereviewer.MovieFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />

</androidx.constraintlayout.widget.ConstraintLayout>

ViewModel

ViewModel object acts as an intermediate between View and the Model, meaning it provides data for the UI components like fragments or activities. It also includes an observable data holder called LiveData that allows ViewModel to inform or update the View whenever the data get updated. It is very crucial, mainly to keep our app from reloading on orientation changes. Which ultimately provides a great user experience.

Here’s an example,

// viewmodel/MovieModelView.javapublic class MovieViewModel extends AndroidViewModel {

private static final String DEFAULT_FILTER = "popular";
private MovieRepository movieRepository;
private LiveData<TMDB_Response> movieList =
new MutableLiveData<>();
private MutableLiveData<TrailerResponse> trailerList =
new MutableLiveData<>();
private MutableLiveData<ReviewResponse> reviewList =
new MutableLiveData<>();
private MutableLiveData<String> filter =
new MutableLiveData<>();

public MovieViewModel(@NonNull Application application) {
super(application);
filter.setValue(DEFAULT_FILTER);
movieRepository = new MovieRepository(application);
}
public void setFilter(String value) {
filter.setValue(value);
}

public String getFilter() {
return filter.getValue();
}

public LiveData<MovieResponse> getMovieList(int pageNo) {
movieList = Transformations.switchMap(filter,
(v) -> movieRepository.getMovieList(v, pageNo));
return movieList;
}

public LiveData<TrailerResponse> getTrailerList(int movieId) {
trailerList = movieRepository.getTrailerList(movieId);
return trailerList;
}

public LiveData<ReviewResponse> getReviewList(int movieId) {
reviewList = movieRepository.getReviewList(movieId);
return reviewList;
}
}

Model

Model is responsible for fetching the data either from the local SQLite database or from a web service. So it is further divided into various components.

  • Repository — It is responsible for handling the data information that includes where to get the data from either a web service or the persisted data models.
  • Room — It is an ORM provided by Google, which provides an abstraction layer between the SQLite database and our data in the form of objects. It gives us errors in compile-time, which is much better than run-time error which difficult to track and debug.
    In order to use the room, it’s very important to define our schema. We do that by creating a data model class and add an @entity annotation. We also must add a @PrimaryKey annotation to our entity’s id.

After that, we must create our database class extending RoomDatabase, and we must define it as abstract so that room can take care of its implementations.

@Database(entities = Movie.class, version = 1)
public abstract class MovieDatabase extends RoomDatabase {
private static MovieDatabase instance;

public abstract MovieDao movieDao();

public static synchronized MovieDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
MovieDatabase.class, "movie_database"
).fallbackToDestructiveMigration()
.build();
}
return instance;
}
}

Finally, define all the necessary operation we require from the database. For that, we create an interface to access database, also known as “data access object (DAO)”.

@Dao
public interface MovieDao {
@Insert
void insert(Movie movie);

@Query("SELECT * FROM favorites")
public LiveData<List<Movie>> getFavMovies();

@Query("SELECT * FROM favorites WHERE id=:movieId")
public LiveData<List<Movie>> getCurrentMovie(int movieId);

@Delete
void delete(Movie movie);
}
  • Web Service — Now if we wish to access data from a REST API, we have a library called “Retrofit” at our disposal. And it helps us make our network calls. In short, it feeds us the JSON string; we convert that JSON response to java object and add as an entity to our app.

Conclusion

One an easily make android apps without following this architecture, but if we want to make apps that are robust, testable, maintainable and easy to read, then we must use this to our advantage. I will be writing more on MVVM architecture and how to follow the best practices.

--

--

Priyank Kumar
The Startup

Full-Stack/Android Developer (Language Agnostic).