Micro
Micro

Reputation: 10891

Refresh RecyclerView with latest data dynamically

I have a SQLite database in my app for which I made a ContentProvider class.

I also have a RecyclerView into which I load an ArrayList of objects into its adapter to populate the RecyclerView.

Currently, when the activity starts I get a Cursor via my ContentProvider, loop through the Cursor to create an ArrayList of objects that I then set as part of my RecyclerView.Adapter.

All that works, but what I really want is for my RecyclerView to dynamically update as new data is loaded into the SQLite database via the content provider.

I have seen posts listing this library CursorRecyclerAdapter but I do not want to use it because you do not get the nice RecyclerView animations on insert/delete.

I was trying to somehow use the LoaderManager.LoaderCallbacks<Cursor> call back methods to get a cursor, convert to arraylist, then swap that in my RecyclerView adapter but couldn't figure it out.

Could someone please show me some example code on how to set it up in my Activity so that the RecyclerView will refresh when new data is written into the local database via a local content provider?

Here is what my RecyclerView.Adapter looks like:

public class MyAdapter extends RecyclerView.Adapter<AdapterTodoList.Holder> {

    private List<TodoItem> itemList;
    private Context mContext;

    //data
    String message;
    Long datetime;

    //this class takes a context and a list of the items you want to populate into the recycler view
    public AdapterTodoList(Context context, List<TodoItem> itemList) {
        this.itemList = itemList;
        this.mContext = context;
    }

    @Override
    public Holder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        //our xml showing how one row looks
        View row = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_todo_item, viewGroup, false);
        Holder holder = new Holder(row);
        return holder;
    }

    @Override
    public void onBindViewHolder(Holder holder, final int position) {
        holder.recyclerLinearLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(mContext, "Recycle Click" + position, Toast.LENGTH_SHORT).show();
            }
        });

        //get one item
        TodoItem data = itemList.get(position);
        Log.d("Test", "onBindViewHolder position " + position);

        message = data.getMessage();
        datetime = data.getDatetime();

        //convert long to date
        String dateString = new SimpleDateFormat("MM/dd/yyyy").format(new Date(datetime));

        //set the holder
        holder.messageTextView.setText(message); 
    }

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

    public class Holder extends RecyclerView.ViewHolder {
        protected ImageView checkBoxImageView;
        protected TextView messageTextView;
        protected LinearLayout recyclerLinearLayout;

        public Holder(View view) {
            super(view);
            //checkBoxImageView = (ImageView) view.findViewById(R.id.checkBoxImageView);
            messageTextView = (TextView) view.findViewById(R.id.messageTextView);
            //the whole view
            recyclerLinearLayout = (LinearLayout) view.findViewById(R.id.recyclerItemLinearLayout);
        }
    }
}

Here is what my Activity looks like so far:

public class HomeRec extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{

    private Toolbar mToolbar;

    //recyclerview and adapter
    private RecyclerView mRecyclerView;
    private MyAdapter adapter;

    //the swipe refresh layout that wraps the recyclerview
    private SwipeRefreshLayout mSwipeRefreshLayout;

    //this will hold all of our results from our query.
    List<TodoItem> itemList = new ArrayList<TodoItem>();

    private Cursor mCursor;
    //resources from layout
    EditText toDoEditText;
    Button cancelButton;
    Button addButton;

    //variables
    private String message;
    private long datetime;

    //loader
    private SimpleCursorAdapter mTodoAdapter;
    private static final int TODO_LOADER = 0;

    // These indices are tied to Projection.  If Projection changes, these
    // must change.
    public static final int COL_ID = 0;
    public static final int COL_MESSAGE = 1;
    public static final int COL_DATETIME = 2;
    public static final int COL_CHECKED = 3;

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

        mToolbar = (Toolbar) findViewById(R.id.app_bar);
        //set the Toolbar as ActionBar
        setSupportActionBar(mToolbar);

        // Initialize recycler view //
        mRecyclerView = (RecyclerView) findViewById(R.id.todoRecyclerView);
        mRecyclerView.hasFixedSize();
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        //set a grey line divider for each item in recycler view
        mRecyclerView.addItemDecoration(
                new DividerItemDecoration(this, null, false, true));
        // END Initialize recycler view //

        //initiate the swipe to refresh layout
        mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                // Refresh items
                refreshItems();
            }

            void refreshItems() {
                // Load items
                // ...
                // Load complete
                onItemsLoadComplete();
            }

            void onItemsLoadComplete() {
                // Update the adapter and notify data set changed
                // ...

                // Stop refresh animation
                mSwipeRefreshLayout.setRefreshing(false);
            }
        });
        //set colors for swipe to refresh
        mSwipeRefreshLayout.setColorSchemeResources(
                R.color.refresh_progress_2,
                R.color.refresh_progress_3);

        //fire my asynctask to get data for the first time
        new MessagesAsyncTask().execute();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_home_rec, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }


        return super.onOptionsItemSelected(item);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        //Not sure what to do here or how to make this work.
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        //Not sure what to do here or how to make this work.    
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        //Not sure what to do here or how to make this work.
    }

    public class MessagesAsyncTask extends AsyncTask<Void, Void, List<TodoItem>> {

        //the cursor for the query to content provider
        private Cursor mCursor;

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected List<TodoItem> doInBackground(Void... params) {
            // A "projection" defines the columns that will be returned for each row
            String[] projection =
                    {
                            DataProvider.COL_ID,    // Contract class constant for the COL_ID column name
                            DataProvider.COL_MESSAGE,   // Contract class constant for the COL_MESSAGE column name
                            DataProvider.COL_DATETIME,  // Contract class constant for the COL_DATETIME column name
                            DataProvider.COL_CHECKED  // Contract class constant for the COL_CHECKED column name
                    };

            // Defines a string to contain the selection clause
            String selectionClause = null;

            // An array to contain selection arguments
            String[] selectionArgs = null;

            // An ORDER BY clause, or null to get results in the default sort order
            String sortOrder = DataProvider.COL_DATETIME + " DESC";

            // Does a query against the table and returns a Cursor object
            mCursor = getContentResolver().query(
                    DataProvider.CONTENT_URI_TODO,  // The content URI of the Todo table
                    projection,                       // The columns to return for each row
                    selectionClause,                   // Either null, or the word the user entered
                    selectionArgs,                    // Either empty, or the string the user entered
                    sortOrder);                       // The sort order for the returned rows

            // Some providers return null if an error occurs, others throw an exception
            if (null == mCursor) {
                // Insert code here to handle the error.
            } else if (mCursor.getCount() < 1) {
                // If the Cursor is empty, the provider found no matches
            } else {
                // Insert code here to do something with the results
            }

            //convert cursor to arraylist of objects
            while (mCursor.moveToNext()) {
                itemList.add(new TodoItem(mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_ID)),
                        mCursor.getString(mCursor.getColumnIndex(DataProvider.COL_MESSAGE)),
                        mCursor.getLong(mCursor.getColumnIndex(DataProvider.COL_DATETIME)),
                        mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_CHECKED))
                ));
            }
            mCursor.close();
            return itemList;
        }


        @Override
        protected void onPostExecute(List<TodoItem> itemList) {
            if (!itemList.isEmpty()) {
                adapter = new MyAdapter(HomeRec.this, itemList);
                mRecyclerView.setAdapter(adapter);
            } else {
                Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();
            }
        }
    }
}

Upvotes: 5

Views: 44457

Answers (5)

Rangana Fernando
Rangana Fernando

Reputation: 11

Refresh cursor every second

final Handler handler = new Handler();
final int delay = 1000; //milliseconds

handler.postDelayed(new Runnable(){
    public void run(){
       
//Call cursor loader to refresh cursor
    getSupportLoaderManager().restartLoader(LOADER_ID, null, MainActivity.this);

        handler.postDelayed(this, delay);
    }
}, delay);

Upvotes: 0

WinHtaikAung
WinHtaikAung

Reputation: 414

I m not sure what you need but I think you should add this method To adapter and call once your data was pulled

public void swapItems(List< TodoItem > todolist){
    this.mTodoList = todolist;
    notifyDataSetChanged();    
}

Hope this would help :D

Upvotes: 6

Akash Jain
Akash Jain

Reputation: 437

Instead of making new adapter each time in onPostExecute and set it to recyclerview again you can notify adapter after modifying list elements.

OR

If you want to make adapter using arraylist instead of cursoradapter using loader i have made sample for you with data provided by you. You can use this as a reference:

   public class DataBaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {

private List itemList;
private MyAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_data_base);
    RecyclerView recycle=(RecyclerView)findViewById(R.id.rv_data);
    SwipeRefreshLayout swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl_data);
    recycle.setLayoutManager(new LinearLayoutManager(this));
    itemList=new ArrayList();
    mAdapter= new MyAdapter(this, itemList);
    recycle.setAdapter(mAdapter);
    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            getContentResolver().notifyChange(DataProvider.CONTENT_URI_TODO, null);    //if you are using content provider
    //getSupportLoaderManager().restartLoader(100, null, DataBaseActivity.this);   // if you are using support lib
//getLoaderManager().restartLoader(100, null, DataBaseActivity.this);   //if you are not using support lib
        }
    });

    // getLoaderManager().initLoader(100, null, this);   //if you are not using support lib
    getSupportLoaderManager().initLoader(100, null, this);
}

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    String[] projection =
            {
                    DataProvider.COL_ID,    // Contract class constant for the COL_ID column name
                    DataProvider.COL_MESSAGE,   // Contract class constant for the COL_MESSAGE column name
                    DataProvider.COL_DATETIME,  // Contract class constant for the COL_DATETIME column name
                    DataProvider.COL_CHECKED  // Contract class constant for the COL_CHECKED column name
            };

    // Defines a string to contain the selection clause
    String selectionClause = null;

    // An array to contain selection arguments
    String[] selectionArgs = null;

    // An ORDER BY clause, or null to get results in the default sort order
    String sortOrder = DataProvider.COL_DATETIME + " DESC";
    return new CursorLoader(this,DataProvider.CONTENT_URI_TODO,  // The content URI of the Todo table
            projection,                       // The columns to return for each row
            selectionClause,                   // Either null, or the word the user entered
            selectionArgs,                    // Either empty, or the string the user entered
            sortOrder);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    if(data!=null && data.getCount()>0)
    {
        itemList.clear();
        while (data.moveToNext()) {
            itemList.add(new TodoItem(data.getInt(data.getColumnIndex(DataProvider.COL_ID)),
                    data.getString(data.getColumnIndex(DataProvider.COL_MESSAGE)),
                    data.getLong(data.getColumnIndex(DataProvider.COL_DATETIME)),
                    data.getInt(data.getColumnIndex(DataProvider.COL_CHECKED))
            ));
        }
    }
    else
        Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();

    if(data!=null)
    data.close();
    mAdapter.notifyDataSetChanged();
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
 }

Upvotes: 1

Deepak Baliga
Deepak Baliga

Reputation: 245

from your question I assume that you are loading the data from the database and somewhere there is a code that is updating the database. And on every update you want to update your RecyclerView, If this is the case continue reading. I am not going to explain this completely but there are a lot of source that will explain you this.

Use BroadcastReciever : In the place where you are updating your database sendBroadcast(). And in the activity use the BroadcastReceiver example and in the onReceive() function call load the data in your ArrayList and call the adapter.notifyDataSetChanged()

Upvotes: 1

deubaka
deubaka

Reputation: 1447

For "listening" to your ContentProvider changes, you'll could try to integrate ContentObserver into your ContentProvider, so it can trigger the necessary events when a transaction is done on your ContentProvider. After which, you'll declare an ContentObserver to your CONTENT_URI, then you can trigger an update to your RecyclerView.

More info on implementing ContentObserver here.

A sample code for updating an item in your RecyclerView would be,

public void update(T data){
    synchronized (mLock){
        if(data != null && mData.contains(data)){
            int index = mData.indexOf(data);
            mData.set(index, data);
            notifyItemChanged(index);
        }
    }
}

Wherein T is the type of object if your row returns, mLock is just an instance object to acquire a lock, mData the list of items you've provided to your RecyclerView. You get the gist. :D

Hope it helps.

Upvotes: 0

Related Questions