Skip to main content

WP Android App using REST and Volley with LiveData and ViewModel Part3

In last two part of our WP Android App using REST volley tutorial we have learned about how to fetch the post using REST api from wordpress website. In this part of the article we are going to implement the ViewModel and LiveData component to avoid the refresh every time when any configuration change happening in your app. When configuration change happen in any android application. A common event which trigger the configuration change is the screen rotation. So whenever any configuration change event happen then application will fetch the details from server. Which is creating unnecessary call to backend server and network.

Solution of the Configuration Change Data reset issue



As explained  above whenever any configuration change happen data is getting reset and application try to fetch the details from the server. As solution of this problem we are going to implement the ViewModel and LiveData architecture component to our application.

First of all we are explaining here what we all need to change in our earlier application. You can check the last two part of WP tutorial below.



List of change to implement the LiveData and View Model for WP App




  • We need to create a ViewModel

  • Create LiveData object with type of Post data type

  • Migrate the Vollery function from Main Activity to LiveData

  • Set the Observer on the LiveData to update the UI

  • Some other minor change we will do for refresh layout for manually triggering the refresh of the data



Create View Model for WP App



If have no idea about the ViewModel then let me explain in short. It is a library developed recently published by Google and benefit of this app is that it is life cycler aware class and it prevent the data loss of the UI even your activity are re-created due to any reason.

public class PostModel extends AndroidViewModel {

@Nullable
private JsonLiveData postsList;
private int index;

public MutableLiveData getRefresh() {
return refresh;
}

public void setRefresh(MutableLiveData refresh) {
this.refresh = refresh;
}

private MutableLiveData<Integer> refresh=new MutableLiveData<>();

public PostModel(@NonNull Application application) {
super(application);
if(postsList==null)
postsList=new JsonLiveData(application);

}

public MutableLiveData<List<Posts>> getPostsList() {
return postsList;
}

public int getChangeIndex(){
return index;
}
public void RefreshData(){
refresh.setValue(0);
postsList=new JsonLiveData(this.getApplication());
}
}


You can see that I have use the AndroidViewModel instead of ViewModel. It is because ViewModel don't allow us to use the context inside the class but Android View Model provide the application context that we can use in our Livedata to fetch the details from server over network.

Inside this view model we have an construction using which we can get the application context.

public PostModel(@NonNull Application application) {
super(application);
//Initialize the LiveData
if(postsList==null)
postsList=new JsonLiveData(application);
}


The RefershData method we are using here to manually triggering the LiveData to fetch the details from server when user use the Swipe to Refresh function and getRefresh method we are using to notify the main thread about that refresh has been done and will stop the setRefressing(false) from main activity.

However, Update we are updating the view only if change in the actual Data.

private MutableLiveData<Integer> refresh=new MutableLiveData<>();
public MutableLiveData getRefresh() {
return refresh;
}


Create LiveData for WP App



Next part is the creating the our LiveData class. In side LiveData class we will move the code from our main activity to fetch the post details from server.

We can create the LiveData class by extending the LiveData class. So here we are creating the JsonLiveData class using the below code.

public class JsonLiveData extends MutableLiveData<List<Posts>>{
public JsonLiveData(Context context){
this.context=context;
}
}


Migrate the Volley function from Main Activity to LiveData



For migrating the code of Volley function from Main Activity to LiveData class. Comment the code in main fragment for getPost or create the new method with same code inside LiveData as given below.

private void LoadData() {

final RequestQueue requestQueue = Volley.newRequestQueue(context);
JsonArrayRequest getRequest = new JsonArrayRequest(Request.Method.GET, Config.base_url+"wp/v2/posts/?page="+page, null,
new Response.Listener<JSONArray>()
{
@Override
public void onResponse(JSONArray response) {
// display response
Log.d(TAG, response.toString() + "Size: "+response.length());
for(int i=0;i<response.length();i++){
final Posts post=new Posts();
try {
Log.d(TAG,"Object at " + i+ response.get(i));
JSONObject obj=response.getJSONObject(i);
post.setId(obj.getInt("id"));
post.setCreatedAt(obj.getString("date"));
post.setPostURL(obj.getString("link"));
JSONObject titleObj=obj.getJSONObject("title");
post.setTitle(titleObj.getString("rendered"));
//Get excerpt
JSONObject exerptObj=obj.getJSONObject("excerpt");
post.setExcerpt(exerptObj.getString("rendered"));
// Get content
JSONObject contentObj=obj.getJSONObject("content");
post.setContent(exerptObj.getString("rendered"));


// getting URL of the Post fetured Image
JSONObject featureImage=obj.getJSONObject("_links");
JSONArray featureImageUrl=featureImage.getJSONArray("wp:featuredmedia");
JSONObject featureImageObj=featureImageUrl.getJSONObject(0);
String fiurl=featureImageObj.getString("href");
if(fiurl!=null) {
// post.setPostImg(fiurl);
Log.d(TAG, featureImageObj.getString("href"));

JsonObjectRequest getMedia = new JsonObjectRequest(Request.Method.GET,
fiurl, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// JSONObject obj=response.getJSONObject(0);
Log.d(TAG, response.getString("source_url"));
post.setPostImg(response.getString("source_url"));
index=mPosts.indexOf(post);
postValue(mPosts);

} catch (JSONException e) {
e.printStackTrace();
}
// post.setPostImg();


}


}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, error.toString());
}
}

);
requestQueue.add(getMedia);

}

mPosts.add(post);



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

}
setValue(mPosts);
refresh.postValue(1);

}

},
new Response.ErrorListener()
{
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, error.toString());
}
}
) ;

requestQueue.add(getRequest);

}


Set the Observer on the LiveData to update the UI



Now we have do modification in our main thread to observe the changes on LiveData. So that in case of change in the data our will get updated automatically.

Get the instance of the ViewModel under which we have implemented the LiveData.

postModel= ViewModelProviders.of(getActivity()).get(PostModel.class);


Set the observer on the post post LiveData, so that in case of change in the data our UI get updated automatically.

postModel.getPostsList().observe(this, new Observer<List<Posts>>() {
@Override
public void onChanged(@Nullable List<Posts> posts) {
postAdapter.setData(posts);
postAdapter.notifyItemChanged(postModel.getChangeIndex());
Log.d(TAG,"On Changed method called");
swipeRefreshLayout.setRefreshing(false);
}
});


Using below code we can set the observer to stop the refreshing view in case user swipe on the screen.

postModel.getRefresh().observe(this, new Observer<Integer>() {

@Override
public void onChanged(@Nullable Integer integer) {
swipeRefreshLayout.setRefreshing(false);
}
});


 

Full Source code of ViewModel



Below is the full code of the out ViewModel class.

package com.nplix.wpapp;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModel;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

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

import static com.nplix.wpapp.ListFragment.TAG;

/**
* Created by PK on 1/10/2018.
*/

public class PostModel extends AndroidViewModel {

@Nullable
private JsonLiveData postsList;
private int index;

public MutableLiveData getRefresh() {
return refresh;
}


private MutableLiveData<Integer> refresh=new MutableLiveData<>();

public PostModel(@NonNull Application application) {
super(application);
if(postsList==null)
postsList=new JsonLiveData(application);

}

public MutableLiveData<List<Posts>> getPostsList() {
return postsList;
}

public int getChangeIndex(){
return index;
}
public void RefreshData(){
refresh.setValue(0);
postsList=new JsonLiveData(this.getApplication());
}
/*
private void setPostsList(@Nullable List<Posts> postsList) {
Log.d("ViewModel", "Setting data to ViewModel");
this.postsList = postsList;


}*/


public class JsonLiveData extends MutableLiveData<List<Posts>>{
private List<Posts> mPosts=new ArrayList<Posts>();
private final Context context;
private int page=1;

public JsonLiveData(Context context){
this.context=context;
LoadData();
}

private void LoadData() {

final RequestQueue requestQueue = Volley.newRequestQueue(context);
JsonArrayRequest getRequest = new JsonArrayRequest(Request.Method.GET, Config.base_url+"wp/v2/posts/?page="+page, null,
new Response.Listener<JSONArray>()
{
@Override
public void onResponse(JSONArray response) {
// display response
Log.d(TAG, response.toString() + "Size: "+response.length());
for(int i=0;i<response.length();i++){
final Posts post=new Posts();
try {
Log.d(TAG,"Object at " + i+ response.get(i));
JSONObject obj=response.getJSONObject(i);
post.setId(obj.getInt("id"));
post.setCreatedAt(obj.getString("date"));
post.setPostURL(obj.getString("link"));
JSONObject titleObj=obj.getJSONObject("title");
post.setTitle(titleObj.getString("rendered"));
//Get excerpt
JSONObject exerptObj=obj.getJSONObject("excerpt");
post.setExcerpt(exerptObj.getString("rendered"));
// Get content
JSONObject contentObj=obj.getJSONObject("content");
post.setContent(exerptObj.getString("rendered"));
// getting URL of the Post fetured Image
JSONObject featureImage=obj.getJSONObject("_links");
JSONArray featureImageUrl=featureImage.getJSONArray("wp:featuredmedia");
JSONObject featureImageObj=featureImageUrl.getJSONObject(0);
String fiurl=featureImageObj.getString("href");
if(fiurl!=null) {
// post.setPostImg(fiurl);
Log.d(TAG, featureImageObj.getString("href"));
JsonObjectRequest getMedia = new JsonObjectRequest(Request.Method.GET,
fiurl, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// JSONObject obj=response.getJSONObject(0);
Log.d(TAG, response.getString("source_url"));
post.setPostImg(response.getString("source_url"));
index=mPosts.indexOf(post);
postValue(mPosts);
} catch (JSONException e) {
e.printStackTrace();
}
// post.setPostImg();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, error.toString());
}
}
);
requestQueue.add(getMedia);
}
mPosts.add(post);
} catch (JSONException e) {
e.printStackTrace();
}

}
setValue(mPosts);
refresh.postValue(1);
}
},
new Response.ErrorListener()
{
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, error.toString());
}
}
) ;
requestQueue.add(getRequest);
}
}
}


Collection of change inside ListFragment.java file as given below.

package com.nplix.wpapp;


import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
* A simple {@link Fragment} subclass.
*/
public class ListFragment extends BackHandledFragment {
public static String TAG="postFrag";
public List<Posts> mPosts;
public Button btnGetPost;
public RecyclerView recyclerView;
public PostAdapter postAdapter;
public int page=1;
public SwipeRefreshLayout swipeRefreshLayout;
public File file;

public ListFragment() {
// Required empty public constructor
}

private PostModel postModel;
private Observer<List<Posts>> postsObserver;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment

return inflater.inflate(R.layout.fragment_list, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
recyclerView= (RecyclerView) getActivity().findViewById(R.id.recyclerHome);
postAdapter=new PostAdapter(mPosts,getContext(),false,false);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
swipeRefreshLayout= (SwipeRefreshLayout) getActivity().findViewById(R.id.swipeRefreshLayout);
//swipeRefreshLayout.setRefreshing(true);
swipeRefreshLayout.setColorSchemeResources(R.color.accent_green,R.color.md_red_800,R.color.md_blue_500,R.color.purple);
mPosts = new ArrayList<Posts>();
recyclerView.setAdapter(postAdapter);
postModel= ViewModelProviders.of(getActivity()).get(PostModel.class);
swipeRefreshLayout.setEnabled(true);
postModel.getPostsList().observe(this, new Observer<List<Posts>>() {
@Override
public void onChanged(@Nullable List<Posts> posts) {
postAdapter.setData(posts);
postAdapter.notifyItemChanged(postModel.getChangeIndex());
Log.d(TAG,"On Changed method called");
swipeRefreshLayout.setRefreshing(false);
}
});
postModel.getRefresh().observe(this, new Observer<Integer>() {

@Override
public void onChanged(@Nullable Integer integer) {
swipeRefreshLayout.setRefreshing(false);
}
});

swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
swipeRefreshLayout.setRefreshing(true);
postModel.RefreshData();


}
});


}

public void setData(List<Posts> posts){
recyclerView.setAdapter(postAdapter);
postAdapter.setData(mPosts);
swipeRefreshLayout.setRefreshing(false);
postAdapter.notifyDataSetChanged();
}
@Override
public String getTagText() {
return TAG;
}

@Override
public boolean onBackPressed() {
return true;
}

@Override
public void RefreshLayout() {

}
}


That's all to we need to implement the LiveData and ViewModel to our WP Android App. Now you can test the app by rotating the screen and you will see that there is no network call is happening. Data will be only updated if user manually refresh the view or load the data.

WP Android App

Comments

  1. […] LiveData and ViewModel for WP Android App using REST and volley part3 […]

    ReplyDelete
  2. Source code didnot work for me.

    ReplyDelete
  3. Hi Rakha,

    you can find the source code at below link.
    https://github.com/debugandroid/WPApp

    Let me know in case of any further issue.

    Thanks,

    ReplyDelete
  4. […] LiveData and ViewModel for WP Android App using REST and volley part3 […]

    ReplyDelete
  5. Thanks a lot bro Pawan Kumar.
    Have a great day!

    ReplyDelete
  6. Hi. Are we doing the volley options on the main thread or the background thread? There is no instance of which I can make sure that the network request will be done in the backgroound thread.

    ReplyDelete
  7. Hi Partik,

    All network request performed by Volley is done in a background thread. So there is no need to perform a request on a different thread, since that's already happening in the background when you are using the volley.

    But yes if your data processing is taking much time inside the listener then you can move that part in a background thread/ async task.

    Thanks,

    ReplyDelete
  8. You've shared some excellent material. I'm grateful for this post because Android Application Template it contains a lot of useful information. Thank you for sharing this piece of writing.

    ReplyDelete

Post a Comment

Popular posts from this blog

Flutter How to Start Android Activity from Flutter View

Flutter and Dart is an excellent combination for creating the UI, but for accessing the platform-specific service we need to open platform-specific activity. So lets in this article we will explore how to start an android activity and access the service from Flutter View. Create a Project for this Android Activity Flutter View Demo Create a Project From File menu select the New Flutter Project Enter the project name Select the AndroidX support and click on next After the above, we step click on Finish We will have the following project structure created. Create the Second Activity in Android Just go to the android folder and open it in separate windows. We will have the following project structure. Create the Activity Just right-click on the Kotlin folder and create a blank activity from the menu. If you create the activity then you may be required to upgrade the Gradle and do some import. So Just click on update and wait for the project s

Kotlin Parcelable Array Objects Send To Activity

We know that IPC (Inter Process Communication) between the activity is an extremely important part of any application development. We often required that we need to send some data to other activity. For example, we may be required to send an array of data, data could be an Integer, String, Long, Double, Float or any other custom data objects. So, In this example, we are going to learn how to implement the Kotlin Parcelable Array object to send the data from one activity to second activity. What is Parcel? The parcel class is designed as a high-performance IPC transport. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC, and references to live IBinde r objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel. Create Kotlin Parcelable Array Objects Parcelable is API for placing the arbitrary objects into the Parcel. In Actual in android app development, Parcelable is an interface

Create Custom EditText View in Android

We use the EditText for taking the input from the user and use it at several places in our project. We required to do lots of customization for each time and there are lots of redundant code we write. Writing and managing these redundant codes is very difficult for example if we want to change the look and feel of the view them we need to modify it at each place where our EditText is getting used. So to avoid these kinds of the problem we can create our own Custom EditText View by just. The EditText view is just an extension of the TextView with lots of editing option and properties that required for the user input. How To Create Custom EditText View For creating the Custom EditText we need to extend the AppCompatEditText and override all three constructors of the view as given below. import android.content.Context; import android.graphics.Typeface; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatEditText; import android.util.AttributeSet; public