Live data, ViewModel, Retrofit Android Architecture Component

MVVM is one of the architectural patterns which enhances separation of concerns, and it allows separating the user interface logic from the business logic. Its target is to achieve Keeping UI code free and straightforward of app logic to make it easier to manage.

AMIT KUMAR TIWARI
May 18, 2018 · 5 min read

MVVM has mainly divided into three parts:

Model —
A model represents the data and business logic of the app. One of the recommended implementations of this is to expose its data through observable. Unlike a regular observable, LiveData respects the lifecycle of other app components, such as activities and fragments. We are using LiveData in Repository class that are listed below.

Since LiveData respects Android Lifecycle, this means it will not invoke its observer callback unless activity or fragment is in received onStart() but did not accept onStop() Adding to this, LiveData will also automatically remove the observer when its host receives onDestroy()

ViewModel —
ViewModel interacts with model and also prepares observable that can be observed by a View. One of the essential implementation strategies of this layer is to decouple it from the View. ViewModel should not be aware of the view which is interacting. ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations. We are using ViewModel in HeadLineViewModel class that are listed below.

This is ViewModel LifeCycle scope for data observable

View —
The view role in this pattern is to observe a ViewModel observable to get data to update UI elements accordingly. This part is implemented in our MainActivity class where we are observing data from ViewModel and set observed data on the adapter.

Here I am tried to explain about android architecture component LiveData, ViewModel with Retrofit2. In this post, I am simply hit an API using Retrofit and show data on activity using RecyclerView.

1- Add Dependency on the build. gradle file these are the required dependency for using LiveData, ViewModel, and Retrofit

apply plugin: 'com.android.application'android {
compileSdkVersion 28
defaultConfig {
applicationId "com.amit.mvvmnews"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'android.arch.lifecycle:viewmodel:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'
testImplementation 'junit:junit:4.13-beta-3'
androidTestImplementation 'androidx.test:runner:1.2.0-beta01'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-beta01'
}

2- Model class for the API response

package com.amit.mvvmnews.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class NewsResponse {

@SerializedName("status")
@Expose
private String status;
@SerializedName("totalResults")
@Expose
private Integer totalResults;
@SerializedName("articles")
@Expose
private List<NewsArticle> articles = null;

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public Integer getTotalResults() {
return totalResults;
}

public void setTotalResults(Integer totalResults) {
this.totalResults = totalResults;
}

public List<NewsArticle> getArticles() {
return articles;
}

public void setArticles(List<NewsArticle> articles) {
this.articles = articles;
}
}
package com.amit.mvvmnews.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class NewsArticle {

@SerializedName("source")
@Expose
private NewsSource source;
@SerializedName("author")
@Expose
private String author;
@SerializedName("title")
@Expose
private String title;
@SerializedName("description")
@Expose
private String description;
@SerializedName("url")
@Expose
private String url;
@SerializedName("urlToImage")
@Expose
private String urlToImage;
@SerializedName("publishedAt")
@Expose
private String publishedAt;
@SerializedName("content")
@Expose
private String content;

public NewsSource getSource() {
return source;
}

public void setSource(NewsSource source) {
this.source = source;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getUrlToImage() {
return urlToImage;
}

public void setUrlToImage(String urlToImage) {
this.urlToImage = urlToImage;
}

public String getPublishedAt() {
return publishedAt;
}

public void setPublishedAt(String publishedAt) {
this.publishedAt = publishedAt;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
}
package com.amit.mvvmnews.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class NewsSource {
@Expose
private String id;
@SerializedName("name")
@Expose
private String name;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

3- Implement Retrofit part

package com.amit.mvvmnews.networking;

import com.amit.mvvmnews.model.NewsResponse;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface NewsApi {
@GET("top-headlines")
Call<NewsResponse> getNewsList(@Query("sources") String newsSource,
@Query("apiKey") String apiKey);
}

— Retrofit Service class for creating service

package com.amit.mvvmnews.networking;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitService {

private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build();


public static <S> S cteateService(Class<S> serviceClass) {
return retrofit.create(serviceClass);
}

}

4- Make a Repository class- This class provides Singleton network request for hitting API and using LiveData for observing API response. LiveData is an observable data holder class. Unlike a regular observable, LiveData respects the lifecycle of other app components, such as activities and fragments.

package com.amit.mvvmnews.networking;


import androidx.lifecycle.MutableLiveData;

import com.amit.mvvmnews.model.NewsResponse;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class NewsRepository {

private static NewsRepository newsRepository;

public static NewsRepository getInstance(){
if (newsRepository == null){
newsRepository = new NewsRepository();
}
return newsRepository;
}

private NewsApi newsApi;

public NewsRepository(){
newsApi = RetrofitService.cteateService(NewsApi.class);
}

public MutableLiveData<NewsResponse> getNews(String source, String key){
MutableLiveData<NewsResponse> newsData = new MutableLiveData<>();
newsApi.getNewsList(source, key).enqueue(new Callback<NewsResponse>() {
@Override
public void onResponse(Call<NewsResponse> call,
Response<NewsResponse> response) {
if (response.isSuccessful()){
newsData.setValue(response.body());
}
}

@Override
public void onFailure(Call<NewsResponse> call, Throwable t) {
newsData.setValue(null);
}
});
return newsData;
}
}

5- Make a ViewModel class — The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

package com.amit.mvvmnews.viewmodels;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.amit.mvvmnews.model.NewsResponse;
import com.amit.mvvmnews.networking.NewsRepository;


public class NewsViewModel extends ViewModel {

private MutableLiveData<NewsResponse> mutableLiveData;
private NewsRepository newsRepository;

public void init(){
if (mutableLiveData != null){
return;
}
newsRepository = NewsRepository.getInstance();
mutableLiveData = newsRepository.getNews("google-news", "API_KEY");

}

public LiveData<NewsResponse> getNewsRepository() {
return mutableLiveData;
}

}

6- MainActivity class where we use ViewModel for observing data

package com.amit.mvvmnews;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import com.amit.mvvmnews.adapters.NewsAdapter;
import com.amit.mvvmnews.model.NewsArticle;
import com.amit.mvvmnews.model.NewsResponse;
import com.amit.mvvmnews.viewmodels.NewsViewModel;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

ArrayList<NewsArticle> articleArrayList = new ArrayList<>();
NewsAdapter newsAdapter;
RecyclerView rvHeadline;
NewsViewModel newsViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rvHeadline = findViewById(R.id.rvNews);

newsViewModel = ViewModelProviders.of(this).get(NewsViewModel.class);
newsViewModel.init();
newsViewModel.getNewsRepository().observe(this, newsResponse -> {
List<NewsArticle> newsArticles = newsResponse.getArticles();
articleArrayList.addAll(newsArticles);
newsAdapter.notifyDataSetChanged();
});

setupRecyclerView();
}

private void setupRecyclerView() {
if (newsAdapter == null) {
newsAdapter = new NewsAdapter(MainActivity.this, articleArrayList);
rvHeadline.setLayoutManager(new LinearLayoutManager(this));
rvHeadline.setAdapter(newsAdapter);
rvHeadline.setItemAnimator(new DefaultItemAnimator());
rvHeadline.setNestedScrollingEnabled(true);
} else {
newsAdapter.notifyDataSetChanged();
}
}

}

I have got lots of comment regarding the GitHub repository for this project, so I have created a repo for this project. The repo link is here

https://github.com/Amit00a/MVVMNews

AMIT KUMAR TIWARI

Written by

Android Developer @Envent Digital Technologies

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade