Tuesday, 19 January 2021

MVVM Architecture example using Retrofit Java

Model-View-VieModel (MVVM)

Model-View-ViewModel (MVVM) is a structural design pattern that separates objects into three distinct groups: Models hold application data. They're usually structs or simple classes. Views display visual elements and controls on the screen. They're typically subclasses of UIView.


build.gradle

implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

AppConstant.java

package com.mvvm_retrofit_java.constants;

/**
* get a api key from https://newsapi.org/
* just register an account and request for an api key
* when you will get an api key please replace with YOUR_API_KEY
*/

public class AppConstant {
public static final String BASE_URL = "https://newsapi.org/v2/";
public static final String API_KEY = "YOUR_API_KEY";
}

Article.java

package com.mvvm_retrofit_java.model;

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

public class Article {

@SerializedName("urlToImage")
@Expose
private String urlToImage;
@SerializedName("description")
@Expose
private String description;
@SerializedName("title")
@Expose
private String title;

public String getUrlToImage() {
return urlToImage;
}

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

public String getDescription() {
return description;
}

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

public String getTitle() {
return title;
}

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

@Override
public String toString() {
return "BashboardNews{" +
"urlToImage='" + urlToImage + '\'' +
", description='" + description + '\'' +
", title='" + title + '\'' +
'}';
}

}

ArticleResponse.java

package com.mvvm_retrofit_java.response;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.mvvm_retrofit_java.model.Article;

import java.util.List;

public class ArticleResponse {
@SerializedName("articles")
@Expose
private List<Article> articles;

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

public void setArticles(List<Article> articles) {
this.articles = articles;
}

@Override
public String toString() {
return "BashboardNewsResponse{" +
"articles=" + articles +
'}';
}
}

RetrofitRequest.java

package com.mvvm_retrofit_java.retrofit;

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

import static com.mvvm_retrofit_java.constants.AppConstant.BASE_URL;

public class RetrofitRequest {

private static Retrofit retrofit;


public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

ApiRequest.java

package com.mvvm_retrofit_java.retrofit;

import com.mvvm_retrofit_java.response.ArticleResponse;

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

import static com.mvvm_retrofit_java.constants.AppConstant.API_KEY;

public interface ApiRequest {

@GET("top-headlines?country=de&category=business&apiKey="+API_KEY)
Call<ArticleResponse> getTopHeadlines();

}

ArticleRepository.java

package com.mvvm_retrofit_java.repository;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import com.mvvm_retrofit_java.response.ArticleResponse;
import com.mvvm_retrofit_java.retrofit.ApiRequest;
import com.mvvm_retrofit_java.retrofit.RetrofitRequest;

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

public class ArticleRepository {
private static final String TAG = ArticleRepository.class.getSimpleName();
private final ApiRequest apiRequest;

public ArticleRepository() {
apiRequest = RetrofitRequest.getRetrofitInstance().create(ApiRequest.class);
}

public LiveData<ArticleResponse> getDashBoardNews() {
final MutableLiveData<ArticleResponse> data = new MutableLiveData<>();
apiRequest.getTopHeadlines()
.enqueue(new Callback<ArticleResponse>() {


@Override
public void onResponse(@NonNull Call<ArticleResponse> call,
@NonNull Response<ArticleResponse> response) {
Log.d(TAG, "onResponse response:: " + response);



if (response.body() != null) {
data.setValue(response.body());

Log.d(TAG, "articles total result:: " + response.body());
Log.d(TAG, "size:: " + response.body().getArticles().size());
// Log.d(TAG, "articles title pos 0:: " + response.body().getArticles().get(0).getTitle());
}
}

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

ArticleViewModel.java

package com.mvvm_retrofit_java.view_model;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import com.mvvm_retrofit_java.repository.ArticleRepository;
import com.mvvm_retrofit_java.response.ArticleResponse;

public class ArticleViewModel extends AndroidViewModel {

private ArticleRepository articleResponse;
private LiveData<ArticleResponse> articleResponseLiveData;

public ArticleViewModel(@NonNull Application application) {
super(application);

articleResponse = new ArticleRepository();
this.articleResponseLiveData = articleResponse.getDashBoardNews();
}

public LiveData<ArticleResponse> getBashboardNewsResponseLiveData() {
return articleResponseLiveData;
}
}

MovieArticleAdapter.java

package com.mvvm_retrofit_java.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.mvvm_retrofit_java.R;
import com.mvvm_retrofit_java.model.Article;

import java.util.ArrayList;

public class MovieArticleAdapter extends RecyclerView.Adapter<MovieArticleAdapter.ViewHolder> {

private final Context context;
ArrayList<Article> articleArrayList;

public MovieArticleAdapter(Context context, ArrayList<Article> articleArrayList) {
this.context = context;
this.articleArrayList = articleArrayList;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view= LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_each_article,viewGroup,false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
Article article = articleArrayList.get(i);
viewHolder.tvTitle.setText(article.getTitle());
Glide.with(context)
.load(article.getUrlToImage())
.into(viewHolder.imgViewCover);
}

@Override
public int getItemCount() {
return articleArrayList.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView imgViewCover;
private final TextView tvTitle;

public ViewHolder(@NonNull View itemView) {
super(itemView);

imgViewCover = itemView.findViewById(R.id.imgViewCover);
tvTitle = itemView.findViewById(R.id.tvTitle);
}
}
}

MainActivity.java

package com.mvvm_retrofit_java.view;

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

import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;

import com.mvvm_retrofit_java.R;
import com.mvvm_retrofit_java.adapter.MovieArticleAdapter;
import com.mvvm_retrofit_java.model.Article;
import com.mvvm_retrofit_java.view_model.ArticleViewModel;

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

public class MainActivity extends AppCompatActivity {

private static final String TAG = MainActivity.class.getSimpleName();

private RecyclerView recycler_view;
private ProgressBar progress_bar;

private LinearLayoutManager layoutManager;
private ArrayList<Article> articleArrayList = new ArrayList<>();
ArticleViewModel articleViewModel;
private MovieArticleAdapter adapter;

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

init();

getArticles();
}

private void init() {
progress_bar = findViewById(R.id.progress_bar);
recycler_view = findViewById(R.id.recycler_view);

// use a linear layout manager
layoutManager = new LinearLayoutManager(MainActivity.this);
recycler_view.setLayoutManager(layoutManager);

// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recycler_view.setHasFixedSize(true);

// adapter
adapter = new MovieArticleAdapter(MainActivity.this, articleArrayList);
recycler_view.setAdapter(adapter);

// View Model
articleViewModel = ViewModelProviders.of(this).get(ArticleViewModel.class);
}

private void getArticles() {
articleViewModel.getBashboardNewsResponseLiveData().observe(this, articleResponse -> {
if (articleResponse != null && articleResponse.getArticles() != null
&& !articleResponse.getArticles().isEmpty()) {
progress_bar.setVisibility(View.GONE);
List<Article> articleList = articleResponse.getArticles();
articleArrayList.addAll(articleList);
adapter.notifyDataSetChanged();
}
});
}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:background="@android:color/black"
tools:context=".view.MainActivity">

<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>


</RelativeLayout>

list_each_article.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/imgViewCover"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="5dp"
android:scaleType="centerCrop" />


<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/transparent">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textStyle="bold"
android:padding="16dp"/>
</RelativeLayout>

</RelativeLayout>


You can work on to enhance and customize to your requirements. You can also get the project here.