Android Architecture Patterns Model View View Model — Part 3

ChandraSaiMohan bhupathi
5 min readJul 10, 2018

MVVM with Room DB And RetroFit

This example explains step by step procedure to fetch data from webservice using retrofit and save it in RoomDB to be displayed on view.

ViewModel fetches data from RoomDB and provides that info to view .

Step by step procedure

Step1: Create Activity that acts as container

public class RetroFitRoomActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_retro_fit_room);
getSupportFragmentManager().beginTransaction().replace(R.id.container_retro_room,new com.andr.mvvm.RetrofitRoom.Views.RetroFitPostFragment()).commit();
}
}

Layout for Activity

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".RetrofitRoom.Views.RetroFitRoomActivity"
>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="55"
android:id="@+id/container_retro_room"
></FrameLayout>

</LinearLayout>

Step2: RoomDB Creation

(1 ) POJO that represents data Table:

package com.andr.mvvm.RetrofitRoom.Models;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

@Entity(tableName = "post_info")
public class ResultModel
{
@PrimaryKey
@NonNull
@ColumnInfo(name = "id")
@SerializedName("id")
private int id;
@NonNull
@ColumnInfo(name = "body")
@SerializedName("body")
private String body;
@NonNull
@ColumnInfo(name = "title")
@SerializedName("title")
private String title;


public int getId ()
{
return id;
}

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

public String getBody ()
{
return body;
}

public void setBody (String body)
{
this.body = body;
}

public String getTitle ()
{
return title;
}

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


@Override
public String toString()
{
return "ClassPojo [id = "+id+", body = "+body+", title = "+title+"]";
}
}

(2) Create Dao with @Dao

@Dao
public interface PostInfoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(ResultModel resultModel);

@Query("SELECT * from post_info ORDER BY id ASC")
LiveData<List<ResultModel>> getAllPosts();

@Query("DELETE FROM post_info")
void deleteAll();


@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertPosts(List<ResultModel> resultModel);

}

(3) Create abstract class for Room Data Base

@Database(entities = {ResultModel.class}, version = 1)
public abstract class PostInfoRoomDataBase extends RoomDatabase {
public abstract PostInfoDao postInfoDao();

private static PostInfoRoomDataBase INSTANCE;


static PostInfoRoomDataBase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (PostInfoRoomDataBase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
PostInfoRoomDataBase.class, "postinfo_database")
.addCallback(sRoomDatabaseCallback)
.build();

}
}
}
return INSTANCE;
}

private static Callback sRoomDatabaseCallback =
new Callback(){

@Override
public void onOpen (@NonNull SupportSQLiteDatabase db){
super.onOpen(db);
new PopulateDbAsync(INSTANCE).execute();
}
};
}

(4) Create an asyncTask that clears all data when DataBase is created for the first time

public class PopulateDbAsync extends AsyncTask<Void, Void, Void> {

private final PostInfoDao mDao;

PopulateDbAsync(PostInfoRoomDataBase db) {
mDao = db.postInfoDao();
}

@Override
protected Void doInBackground(final Void... params) {
mDao.deleteAll();
return null;
}
}

Step3: RetrofitModel

(1) Create Retrofit interface:

public interface APIService {
@GET("posts")
Call<String> makeRequest();
}

(2)

public class APIUrl {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
}

(3)WebServiceRepository that returns Mutable Live data and inserts data to RoomDB

public class WebServiceRepository {

Application application;
public WebServiceRepository(Application application){
this.application = application;
}
private static OkHttpClient providesOkHttpClientBuilder(){

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
return httpClient.readTimeout(1200, TimeUnit.SECONDS)
.connectTimeout(1200, TimeUnit.SECONDS).build();

}


List<ResultModel> webserviceResponseList = new ArrayList<>();

public LiveData<List<ResultModel>> providesWebService() {

final MutableLiveData<List<ResultModel>> data = new MutableLiveData<>();

String response = "";
try {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(APIUrl.BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(providesOkHttpClientBuilder())
.build();

//Defining retrofit api service
APIService service = retrofit.create(APIService.class);
// response = service.makeRequest().execute().body();
service.makeRequest().enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
Log.d("Repository","Response::::"+response.body());
webserviceResponseList = parseJson(response.body());
PostRoomDBRepository postRoomDBRepository = new PostRoomDBRepository(application);
postRoomDBRepository.insertPosts(webserviceResponseList);
data.setValue(webserviceResponseList);


}

@Override
public void onFailure(Call<String> call, Throwable t) {
Log.d("Repository","Failed:::");
}
});
}catch (Exception e){
e.printStackTrace();
}

// return retrofit.create(ResultModel.class);
return data;

}


private List<ResultModel> parseJson(String response) {

List<ResultModel> apiResults = new ArrayList<>();

JSONObject jsonObject;

JSONArray jsonArray;

try {
jsonArray = new JSONArray(response);

for (int i = 0; i < jsonArray.length(); i++) {
JSONObject object = jsonArray.getJSONObject(i);

ResultModel mMovieModel = new ResultModel();
//mMovieModel.setId(object.getString("id"));
mMovieModel.setId(Integer.parseInt(object.getString("id")));
mMovieModel.setTitle(object.getString("title"));
mMovieModel.setBody(object.getString("body"));

apiResults.add(mMovieModel);
}


} catch (JSONException e) {
e.printStackTrace();
}

Log.i(getClass().getSimpleName(), String.valueOf(apiResults.size()));
return apiResults;

}
}

The below code highlighted in the above code will insert data to RoomDB Repository

PostRoomDBRepository postRoomDBRepository = new PostRoomDBRepository(application);
postRoomDBRepository.insertPosts(webserviceResponseList);
data.setValue(webserviceResponseList);

Step4: Create RoomDB Repository to be accessed from ViewModel

This RoomDB Repository contains methods to insert and fetch data received from WebService

public class PostRoomDBRepository {
private PostInfoDao postInfoDao;
LiveData<List<ResultModel>> mAllPosts;

public PostRoomDBRepository(Application application){
PostInfoRoomDataBase db = PostInfoRoomDataBase.getDatabase(application);
postInfoDao = db.postInfoDao();
mAllPosts = postInfoDao.getAllPosts();
}

public LiveData<List<ResultModel>> getAllPosts() {
return mAllPosts;
}
public void insertPosts (List<ResultModel> resultModel) {
new insertAsyncTask(postInfoDao).execute(resultModel);
}

private static class insertAsyncTask extends AsyncTask<List<ResultModel>, Void, Void> {

private PostInfoDao mAsyncTaskDao;

insertAsyncTask(PostInfoDao dao) {
mAsyncTaskDao = dao;
}

@Override
protected Void doInBackground(final List<ResultModel>... params) {
mAsyncTaskDao.insertPosts(params[0]);
return null;
}
}
}

Step 5: Create ViewModel

public class PostsListViewModel extends AndroidViewModel {

private PostRoomDBRepository postRoomDBRepository;
private LiveData<List<ResultModel>> mAllPosts;
WebServiceRepository webServiceRepository ;
private final LiveData<List<ResultModel>> retroObservable;
public PostsListViewModel(Application application){
super(application);
postRoomDBRepository = new PostRoomDBRepository(application);
webServiceRepository = new WebServiceRepository(application);
retroObservable = webServiceRepository.providesWebService();
//postRoomDBRepository.insertPosts(retroObservable.getValue());
mAllPosts = postRoomDBRepository.getAllPosts();
}

public LiveData<List<ResultModel>> getAllPosts() {
return mAllPosts;
}


}

The above code calls WebService repository to initiate webservice call and calls RoomDB Repository to fetch the saved Webservice data from RoomDB.

Step 6 : View — Displays list of data from RoomDB

public class RetroFitPostFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private
String mParam1;
private String mParam2;
View view = null;
PostsListViewModel retroViewModel;
ProgressDialog progressDialog;
public RetroFitPostFragment() {
// Required empty public constructor
}

/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*
@param param1 Parameter 1.
*
@param param2 Parameter 2.
*
@return A new instance of fragment RetroFitPostFragment.
*/
//
TODO: Rename and change types and number of parameters
public static
RetroFitPostFragment newInstance(String param1, String param2) {
RetroFitPostFragment fragment = new RetroFitPostFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
// progressDialog = new ProgressDialog(getActivity());
retroViewModel = ViewModelProviders.of(getActivity()).get(PostsListViewModel.class);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment_retro_fit_post, container, false);
initViews(view);
setAdapter();
progressDialog= ProgressDialog.show(getActivity(), "Loading...", "Please wait...", true);
retroViewModel.getAllPosts().observe(this, new Observer<List<com.andr.mvvm.RetrofitRoom.Models.ResultModel>>() {
@Override
public void onChanged(@Nullable List<com.andr.mvvm.RetrofitRoom.Models.ResultModel> resultModels) {
adapter.setWords(resultModels);
if(progressDialog!=null && progressDialog.isShowing()){
progressDialog.dismiss();
}
}
});


return view;
}

RecyclerView recyclerView;
private void initViews(View view){
recyclerView = (RecyclerView)view.findViewById(R.id.post_list);
}


RetroPostListAdapter adapter = null;
private void setAdapter(){
adapter = new RetroPostListAdapter(getActivity());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
}

}

The onCreate() contains code to create instance for ViewModel .

OnCreateView() observes LiveData from ViewModel

--

--