简体   繁体   中英

How to Update RecyclerView - notifyDataSetChanged()

I have an activity which displays a vertical RecyclerView list containing multiple category items. Each item in this list is a category which displays the "category title" and its corresponding "image id". The activity holding this RecyclerView is the first activity which appears after the user has launched the app on the mobile. Additionally, the item category information is being acquired via JSON where I make use of the "Volley" library to fetch the data at a URL on the web. My problem is that my RecyclerView shows unpopulated and empty even though the information is being fetched on the web perfectly. I suspect this is happening due to the following:

  • User launches the app
  • OnCreate displays the RecyclerView
  • JSON feed is fetched
  • RecyclerView appears empty since the JSON fetching process takes place in the background and never updates the RecyclerView once the data has been fetched.

How can I update the RecyclerView to display all the items once the JSON request has been completed? I heard of using the notifyDataSetChanged() in the RecyclerView adapter but I do not know how to make use of this feature and most importantly where to place it in my code.

Below is my code. Any detailed help on how to perform a RecyclerView update under these circumstances would be greatly appreciated.

Activity

package example.co.uk.vapp;

import android.app.ProgressDialog;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;

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

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

import example.co.uk.vapp.adapters.GroupAdapter;
import example.co.uk.vapp.utils.RecyclerItemClickListener;

public class CategoryListActivity extends AppCompatActivity {

    // Declare variables
    private RecyclerView mRecyclerView;
    private GridLayoutManager mGridLayoutManager;

    // Tag for logging possible errors onto the Log
    private static String TAG = CategoryListActivity.class.getSimpleName();

    // Progress dialog
    private ProgressDialog pDialog;

    // JSON Array response url - ****fictitious url****
    private static final String URL = "https://www.someurl.com";

    // JSON Array containing the response
    static JSONArray jsonArrayResponse;
    // Json Object containing the category fields such as title and image-id
    static JSONObject category;
    // ArrayList containing the category titles
    static ArrayList<String> categoryTitles;
    // ArrayList containing the category image ids
    private ArrayList<String> categoryImageId;
    static GroupAdapter groupAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set layout
        setContentView(R.layout.activity_category_list);

        // Initialize ArrayList to contain category titles
        categoryTitles = new ArrayList<String>();
        categoryImageId = new ArrayList<String>();

        // Reference variable with layout view
        mRecyclerView = (RecyclerView) findViewById(R.id.cardList);

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

        // Display under one column
        mGridLayoutManager = new GridLayoutManager(this, 1);
        mRecyclerView.setLayoutManager(mGridLayoutManager);
        // Set orientation
        mGridLayoutManager.setOrientation(GridLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(mGridLayoutManager);

        // Create list of "Group" type
        final List<Group> groups = new ArrayList<Group>();

        for (int i = 0; categoryTitles.size() < i && categoryImageId.size() < i; i++) {
            String categoryName = categoryTitles.get(i);
            int categoryImage = Integer.getInteger(categoryImageId.get(i));

            groups.add(new Group(categoryName, categoryImage));
        }

        // Set Adapter
        groupAdapter = new GroupAdapter(groups);
        mRecyclerView.setAdapter(groupAdapter);

        // Create click listener for each item on the list
        mRecyclerView.addOnItemTouchListener(
                new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, int position) {

                        Intent intent = new Intent(CategoryListActivity.this, groups.get(position).getClass());
                        startActivity(intent);
                    }
                })
        );

        // Shows message to user while makeJsonObjectRequest() is still running
        pDialog = new ProgressDialog(this);
        pDialog.setMessage("Getting exchange rates...");
        pDialog.setCancelable(false);

        // Retrieves JSON format exchange rates from Yahoo Finance
        makeJsonArrayRequest();
    }

    private void makeJsonArrayRequest() {

        // Show dialog while the request is made
        showpDialog();

        final JsonArrayRequest jsonArrayReq = new JsonArrayRequest(URL,
                new Response.Listener<JSONArray>() {

                    @Override
                    public void onResponse(JSONArray response) {

                        // Log response
                        Log.d(TAG, response.toString());

                        // Set "response" to jsonArrayResponse global variable so that we can use it on other methods in this class
                        jsonArrayResponse = response;

                        try {
                            // Get array with all categories (raw)

                            for (int i = 0; i < jsonArrayResponse.length(); i++) {
                                category = jsonArrayResponse.getJSONObject(i);

                                String title = category.getString("category-localized-title");
                                categoryTitles.add(title);

                                String imageId = category.getString("product-category-image-id");
                                categoryImageId.add(imageId);
                            }

                        } catch (JSONException e) {
                            e.printStackTrace();
                            Toast.makeText(gevapplicationContext(),
                                    "Error: " + e.getMessage(), 
                                    Toast.LENGTH_LONG).show();
                        }

                        for (int i = 0; i < categoryTitles.size(); i++) {
                            Log.i("categoryTitles", categoryTitles.get(i).toString());
                            Log.i("categoryImageIds", categoryImageId.get(i).toString());
                        }

                        // Hide dialog after information has been requested
                        hidepDialog();
                        }


                    }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {

                // Warn user
                Toast.makeText(gevapplicationContext(),
                        "No internet connection", Toast.LENGTH_LONG).show();

                // Log error
                Log.e("test", error.getMessage());

                // hide the progress dialog
                hidepDialog();
            }
        });

        // Adding request to request queue
        AppController.getInstance().addToRequestQueue(jsonArrayReq);
    }

    /**
     * Method for showing dialog
     */
    private void showpDialog() {
        if (!pDialog.isShowing())
            pDialog.show();
    }

    /**
     * Method for hiding dialog
     */
    private void hidepDialog() {
        if (pDialog.isShowing())
            pDialog.dismiss();
    }
}

RecyclerView Adapter

package example.co.uk.vapp.adapters;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

import example.co.uk.vapp.Group;
import example.co.uk.vapp.R;

public class GroupAdapter extends RecyclerView.Adapter<GroupAdapter.GroupViewHolder> {

    private List<Group> groupList;

    public GroupAdapter(List<Group> groupList) {
        this.groupList = groupList;
    }

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

    @Override
    public void onBindViewHolder(GroupViewHolder holder, int position) {

        Group group = groupList.get(position);
        holder.vGroupTitle.setText(group.getGroupTitle());
        holder.vGroupImage.setImageResource(group.getGroupImage());
    }

    @Override
    public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.group_list_card_layout, parent, false);
        return new GroupViewHolder(itemView);
    }

    // Create the ViewHolder class "pattern"
    public static class GroupViewHolder extends RecyclerView.ViewHolder {
        protected TextView vGroupTitle;
        protected ImageView vGroupImage;

        public GroupViewHolder(View v) {
            super(v);

            vGroupTitle = (TextView) v.findViewById(R.id.title);
            vGroupImage = (ImageView) v.findViewById(R.id.image);
        }
    }
}

In your adapter, declare a method with which you will update the adapter once you fetched the new or initial data set. It should look something like this:

public void swapDataSet(list<Group> newData){

 this.groupList = newData;

 //now, tell the adapter about the update
 notifyDataSetChanged();

}

The above method will work, but it won't necessary be the most efficient way of doing it for subsequent changes, because the adapter will update all the items assuming the entire dataset has changed. Once your adapter is set for the first time, it is better, and more efficient, to use more specific change notifications like notifyItemInsered(int pos) , notifyItemDeleted(int pos) , etc if possible. This way you will also get nice animations with no extra effort on your part.

More specifically, in your case, you are updating the groups before there are any titles or categories. Why not do this:

for (int i = 0; i < jsonArrayResponse.length(); i++) {
    category = jsonArrayResponse.getJSONObject(i);

    String title = category.getString("category-localized-title");

    String imageId = category.getString("product-category-image-id");


    categories.add(new Group(title, imageId));
}

//now that you have fresh groups
GroupAdapter.swapDataSet(groups);

I think your suspicion is correct. The JSON request is completing after the Recyclerview is initialized, so the data is not available to the RecyclerView upon instantiation. Also, some of your lists are not being initialized correctly.

Let's go through the order of some of the key elements of your code:

1. categoryTitles = new ArrayList<String>();

Right now, categoryTitles is empty with categoryTitles.size() = 0 and categoryImageId.size() = 0.

2. List item mRecyclerView = (RecyclerView) findViewById(R.id.cardList);

This is good.

3. mGridLayoutManager = new GridLayoutManager(this, 1); etc. etc.

This looks good.

4. for (int i = 0; categoryTitles.size() < i && categoryImageId.size() < i; i++) {...}

Remember from 1. categoryTitles.size() = 0 as and categoryImageId.size() = 0, so this loop won't execute. The lists won't be populated at all.

Also, you probably have your conditionals backwards. Change them to i < categoryTitles.size() and i < categoryImageId.size()

5. final List<Group> groups = new ArrayList<Group>();

groups is empty.

6. groupAdapter = new GroupAdapter(groups);

groups is currently empty, so is GroupAdapter .

7. mRecyclerView.setAdapter(groupAdapter);

You are passing an empty list to the adapter.

8. makeJsonArrayRequest();

This should happen before the adapter is set.


To fix things, groups should be populated before it is passed to the adapter, and even if you set the order of this code correctly, it won't work because most likely the JSON fetch will happen after the adapter is set to the RecyclerView.

I would suggest using an AsyncTask in a separate class to populate the groups list and an interface to inform the main activity that the JSON fetch is complete. Upon completion of the JSON fetch in your AsyncTask , use the interface to populate the lists and subsequently set the Adapter. Good luck and if you need more help, please let us know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM