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.
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.
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.
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.
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.
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.
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.
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.
Set the observer on the post post LiveData, so that in case of change in the data our UI get updated automatically.
Using below code we can set the observer to stop the refreshing view in case user swipe on the screen.
Below is the full code of the out ViewModel class.
Collection of change inside ListFragment.java file as given below.
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.
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.
[…] LiveData and ViewModel for WP Android App using REST and volley part3 […]
ReplyDeleteSource code didnot work for me.
ReplyDeleteHi Rakha,
ReplyDeleteyou can find the source code at below link.
https://github.com/debugandroid/WPApp
Let me know in case of any further issue.
Thanks,
[…] LiveData and ViewModel for WP Android App using REST and volley part3 […]
ReplyDeleteThanks a lot bro Pawan Kumar.
ReplyDeleteHave a great day!
You are most Welcome!
ReplyDeleteHi. 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.
ReplyDeleteHi Partik,
ReplyDeleteAll 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,
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