Refreshing the Cursor from SQLiteCursorLoader - android

My project uses the SQLiteCursorLoader library from commonsguy to load data from a database into a ListView. Among that data is a simple boolean (as so far as SQLite supports booleans... that is, a number that only ever is 0 or 1) that tells the state of a checkbox. If I change the state of a checkbox in a list and then scroll the item off the list, the list item returns to the state it has when the cursor was passed in, despite the fact that the underlying database has changed. If I change the state of a bunch of checkboxes and then activate the list's MultiChoiceMode, all the items displayed will revert back to the state they were in when the cursor was originally passed in, despite the fact that the underlying database has changed.
Is there a way to refresh the cursor? Cursor.requery() is deprecated, and I don't want to have to create a new Cursor each time a checkbox is checked, which happens a lot. I'm also unsure of how calling restartLoader() several times would work, performance-wise, especially since I use onLoadFinish() to perform some animations.

Is there a way to refresh the cursor?
Call restartLoader() to have it reload the data.
I don't want to have to create a new Cursor each time a checkbox is checked, which happens a lot
Used properly, a ListView maintains the checked state for you. You would do this via android:choiceMode="multipleChoice" and row Views that implement Checkable. The objective is to not modify the database until the user has indicated they are done messing with the checklist by one means or another (e.g., "Save" action bar item).
I'm also unsure of how calling restartLoader() several times would work, performance-wise, especially since I use onLoadFinish() to perform some animations.
Don't call it several times. Ideally, call it zero times, by not updating the database until the user is done, at which point you are probably transitioning to some other portion of your UI. Or, call it just once per requested database I/O (e.g., at the point of clicking "Save"). Or, switch from using a CursorAdapter to something else, such as an ArrayAdapter on a set of POJOs you populate from the Cursor, so that the in-memory representation is modifiable in addition to being separately persistable.

Related

Android ViewModel inside RecyclerView Adapter for lazy database downloads

I have a question that is more related to proper design and architecture of MVVM than to coding itself. In my project I have a situation that on ViewModel is suplying data, that are later used in RecyclerView.Adapter to create a proper view.
However I wonder if this would be also correct (from proper 'way of doing things' POV) if some of the data would be supplied in form of id's to be further fetched from Room or external server? For instance during onBindViewHolder use some LiveData with observe() to update certain fields on succesfull load.
Doing data fetch in the views is a no-go. It defeats the very purpose of MVVM and in particular the Android Jetpack efforts. One of the big reason is the data needs to survive configurations. Putting "data fetching" mechanism in the view defeats that as the view can be destroyed and recreated anytime when need be.
So I would suggest you make sure all calls to the network or any other source of data for that matter revolve around the ViewModel and never a view. Let the VM feed the data to the View through observer.
Exception to what I have just said is such use case as loading images with Picasso or Glide where by you feed them URL and they load image. But that's a different thing as they are designed to handle that.
UPDATE with followup Questions
it's ok to put observe() still inside Adapter, during the binding process?
No! Data sent to the adapter must be complete in the purpose it is supposed to serve. For example, if you have to do list app and your Top-Level Activity displays all Todos, then you must feed adapter will complete data (Title, Created time, et al).
The data that are not going to be displayed (like descriptions or sub-to-do-lists) and aren't necessary to identify specific to do should not be fetched (unless you want to store them for sole purpose of avoiding a second network call and pass them to the next activity as serialized data).
When user clicks specific To-Do, then launch new activity with its own ViewModel that will fetch details for that activity (assuming you passed some ID with intent).
If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?
Observe is a way to post data to the view when either it have changed or the old view was destroyed and so the new view is being given the same old data that survived the "view death". So it is in the same method where updating data of the Adapter should be done and hence call to notifyDataSetChanged() or similar methods.
I hope that makes it clear.
I think it's best to keep the ViewModel separate from the Adapter. From what I'm gathering you want to basically have each list item load it's own data from Room by having each list item observe on a particular ID for that item. This sounds rather inefficient as you're basically having each item execute a query on the database (or network call) to retrieve just one item for all items that are going to be displayed, think about how it will scale if you were displaying a 100 items. I think it's best that you just execute one query to get the list of data and set the data to the list items in the adapter, keep it simple. Note that onBindViewHolder() doesn't just get called once but multiple times when you're scrolling the screen, so it could get quite ugly if you're trying to lazily load every list item.

Android ListView.getChildCount() greater than getCount()

I have an Activity with a Fragment that displays a ListView containing simple TextViews. A menu item can trigger another Activity via an Intent. That new Activity clears the ArrayList underlying the ArrayAdapter for the ListView using ArrayList.clear().
When I backup from the new Activity to my original one with the ListView, and get control in onResume(), I find that my ListView.getChildCount() is the same as when it was left due to the Intent, but the ListView.getCount() is now properly zero!
I have tried using the adapter's clear() method, I have tried Adapter.notifyDataSetChanged() (although I should not have to).
If I modify the underlying ArrayList from within the fragment itself, all seems fine. For example, clicking on an element gives you an option to remove it, move it up or down, etc... That works OK.
Also, If I then leave the List Activity and return to it again, all is well. So clearly the ArrayList is the same list. I never create a new list, only .clear() it.
Any idea how the Child Count can possibly be more than the underlying element count? Perhaps some kind of observer for the ArrayList does not trigger because the Activity is suspended? In which case how could I sync them up again? I have tried invalidate() for example.
This is under API 23.
I seem to have found a workaround. I noticed another post here discussing thread safety. It seemed to have implications if you do not modify your list from the original UI. So, in my onResume(), I just did a setAdapter() again. With the same adapter and list, and viola! All seems well again.

How to pass new data to onCreateLoader

I have an Activity that implements LoaderManager.LoaderCallbacks<Cursor>. The CursorLoader feeds into a RecyclerView (think ListView). So inside onLoadFinished I swap the data inside my adapter.
Imagine the data has to do with stock performance for a day. Now I have a Spinner that allows the user to choose a day. And when the user changes day I want the data to change. So I am thinking that the Loader needs to be able to listen to the parameter. How do I do that? Per the documentation of getSupportLoaderManager().initLoader I am not sure if calling it inside my spinner’s onItemSelected will do that trick. Thanks for any help.
And when the user changes day I want the data to change. So I am
thinking that the Loader needs to be able to listen to the parameter
to me, it sounds like you want to requery your database again when those data change. you can use restartLoader. You have to possibility to provide a different id, which can be used as identifiers for the specific loader. From the documentaion:
Starts a new or restarts an existing Loader in this manager, registers
the callbacks to it, and (if the activity/fragment is currently
started) starts loading it.

Is notifyItemInserted expensive

I'm moving my application from sqlite to Firebase. Previously I would read N items from the DB to an arraylist and call notifyItemRangeInserted. Now the most convenient way to get data from Firebase delivers objects one by one. I was wondering if anyone benched the cost of calling notifyItemInserted for each list item. Is that fine or should I batch my loads? I'm displaying everything in a RecyclerView.
Based on the documentation for RecyclerView:
There are two different classes of data change events, item changes and structural changes. Item changes are when a single item has its data updated but no positional changes have occurred. Structural changes are when items are inserted, removed or moved within the data set.
notifyItemInserted : Notify any registered observers that the item reflected at position has been newly inserted
notifyItemRangeInserted : Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted
Its a bit tricky to quantify and compare these two.
Based on the functionality: Both of them seems to perform same operation. They don't alter the existing items binding but rather alter existing items position.
Based on impact after notify observer: Calling notifyItemInserted in short interval would trigger the registered observers frequently. If those observers are doing some heavy computation then notifyItemInserted for each item would be expensive.
However, if you do have list that is not sequential in a range (e.g. remove item 1, 2 and 4), or if you want to perform insertion and removal simultaneously, and have it animated accordingly or you frequently modifying the adapter's dataset, efficient way would be to implement DiffUtil which is available in support library.
Most of the time our list changes completely and we set new list to
RecyclerView Adapter. And we call notifyDataSetChanged to update
adapter. NotifyDataSetChanged is costly. DiffUtil class solves that
problem now. It does its job perfectly
You can find more information here.

Start loader from another loader. Right or Wrong?

I have listview that is populated through CursorLoader. CursorLoader is created by LoaderManager.LoaderCallback's createLoader method. I have no problems with this. The problem is in that I want to start another task when listview population is completed and fill listviews with additional data.
My current solution is to start a another loader inside onLoadFinished method.
Is this right solution or it can be done in more elegant and efficient way? Could you give some advice, because I don't have much experience in android development.
Thanks in advance.
Loading from onLoadFinished will working without a problem. I did something similar in a pet project I had. I loaded data from my own ContentProvider and from there loaded contacts data from the phone's Contact ContentProvider. Each entry in my db could reference multiple contacts so I had to load my item before I knew which contact info to load. I chained the init/restart LoaderManager call to when I first received my item data in onLoadFinished. It works just fine and I used the contact data as a list afterwards. Granted I didn't use this approach to load data into a view inside an existing list view item. I used the data inside its own list in a detail view for my item. It should still work with what you want to do, but it can get messy appending data to the views and whatnot, especially since the view "lifecycle" is outside your control.
A better approach, IMO, would be to code a custom CursorAdapter that would use an AsyncTask or AsyncQueryHandler to fetch the extra data as the views are being created. Make sure to cache the data for subsequent use as the list scrolls. This second approach has the benefit of being independent of the external/secondary loader. It encapsulates all the logic required to display the data you need which includes loading the missing parts. It keeps the view data and display logic cohesive, safely tucked away inside a reusable module.

Categories

Resources