According to a response made by Yigit Boyar from Google, Live Data is not the best use case for a chat app, because it may lose displaying some items if they come at the same time. He recommends using the new Google's Paging Library. I've been using ItemKeyedDataSource for my inbox(all the people that have message the user), and the inside chat(the messages themselves). The problems are the following:
1- From the chat, when the user scrolls downwards, the user retrieves old messages, which means that the insertion of those messages should be in position 0 of the adapter, not sequentially like the paging library does. How can I alternate the position of the inserted items to be sequentially for new messages, and in position 0 for old messages?
2- From the inbox(people that have message the user), again I'm using ItemKeyedDataSource here, the problem is that I want to maintain the multiple document listener from the repository (I'm using Firebase Firestore), so I can detect every time a new person talks to the user. The problem is that callback.onResult is called only once, and fails when Firebase sends another user. How can I maintain an update-able list?
I understand that this answer is probably too late, but maybe it can help someone in future.
Position of item in RecyclerView is determined by the position of corresponding data object (of type T) inside PagedList<T>. PagedList is designed to look alike good old List<T>, but can be thought of as an "endless" list of elements.
PagedList gets its elements by pages on demand through something called DataSource.Factory. A Factory is used because DataSource by itself can only grow in one direction. If you need to prepend elements in PagedList, or change or remove existing elements you must invalidate the DataSource and a new instance will be created through DataSource.Factory.
So, to insert your data elements where you want them you should implement your own DataSource and DataSource.Factory by subclassing these base classes.
Note: Room, data persistence library from AndroidX, provides facilities to automatically generate instances of these classes for your data. You can write SQL query like this:
SELECT * FROM messages WHERE threadId=:threadId ORDER BY timestamp DESC
then get DataSource.Factory from this, use the factory to create LivaData<PagedList<Message>> and finally use the paged list to display messages in a RecyclerView in a chat application. Then, when you insert, update or remove data inside DB these changes will automatically propagate to the UI. This can be very useful.
I recommend you to read a few related examples a do codelabs:
https://codelabs.developers.google.com/codelabs/android-paging/#0
https://github.com/googlesamples/android-architecture-components/tree/master/PagingSample
https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample
Related
I'm trying to implement paging on Android using a DataSource implementation, plus PagedAdapter.
Initially, the requirements were to have a full list in memory (not using RoomDB) and I wanted to take a moving "view" over that data as the user scrolls - i.e. feeding it to the adapter in pages. I've accomplished that with the use of PositionalDataSource.
However, now I have a new requirement. Some of the items in the original full list are actually "loading" items (i.e. spinners) and I need to fetch the data that these cells represent in chunks. These chunks have undetermined sizes. When a chunk loads in, the "loading" item should move down the list, and the loaded chunk be inserted where the "loading" item used to be. This should continue until all chunks that the "loading" item represents have been loaded in, at which point the "loading" item at the end of the list should be removed.
This means, my underlying data source actually grows dynamically as the user scrolls through the list. Which I think means PositionalDataSource is not the right type of data source to use as the source docs state:
* Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
* arbitrary page positions.
Emphasis on fixed-size and countable - obviously my data set is not fixed size (and is therefore also uncountable).
I've looked at other implementations of DataSource and think I've found the right one; ItemKeyedDataSource. Each of my items do indeed have a unique key, and the source docs of this class state that:
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
Which to me indicates that I can use it for my required purposes. I.e. when it needs to load in a range for an item with the given key which also happens to be a "loading" item, it can use the loading items data to determine what to load.
However, I'm struggling a bit with actual implementation of this, as the official docs don't give any real example usage, and the links to example code assumes the use of RoomDB or retrofit, neither of which is the approach I need.
Could anyone help with giving me an overview of how this DataSource is supposed to function conceptually and/or in code examples using an in memory data set that needs to grow dynamically?
I realize this is pretty vague, I've only started working with this class this morning and I'm struggling.
Paging already loads paginated data for you - the point of implementing a DataSource is to provide Paging a way to incrementally load more data as user scrolls near the end. The one caveat is that in Paging 2.x, load state is not built into the library so you need to track it yourself and show the spinner using some method such as ConcatAdapter.
If you want to try the v3 apis (still in beta), LoadState is a built-in concept and you can simply use the .withLoadStateFooter() transform to turn a PagingDataAdapter to a ConcatAdapter which automatically shows the loading spinner when Paging fetches a new page.
To clarify the bit on the docs about counted snapshots - Paging operates with a single source of truth (DataSource / PagingSource), which is supposed to represent a static list (once fully loaded). This doesn't mean you have to have the whole list in memory, but the items each instance of DataSource fetches should generally match the mental model of a static list. For example if you are paging in data from DB, then a single instance of DataSource / PagingSource is only valid while there are no changes in the DB. Once you insert / modify / delete a row, that instance is no longer valid, which is where DataSource.Factory comes into play, giving you a new PagedList / DataSource pair.
Now if you need to also incrementally update the backing dataset (DB in this example) via layered source approach, Paging v2 offers a BoundaryCallback you can register to fire off network fetch when Paging runs out of data to load, or alternatively in v3 the new API for this is RemoteMediator (still experimental).
I am using Android API Level 28 with android components.
I have an app that displays our data visually in a chart. I'd like to background load the data and begin charting it as it comes in. I also would like you to be able to view the data as a list.
Ideally, the data for the chart and the list would be the same object instances. After all, the network call to retrieve the data is expensive. Plus, depending on the filters selected, there could be a lot of data to be charted.
I cannot use a server-based chart building API; I must draw graphics from the data on the device.
I am able to download the data in the background and build the chart. I looked into PagedList et al for the list view and that works terrifically.
What I cannot figure out is how to share the data. In other words, if you view the chart first then the list, the list should not trigger a download again since the data is already there. Likewise if the user scrolls through the list then looks at the chart.
This is especially helpful for tablets where one side displays the list and the other is the chart (eg, tapping on an item in the list highlights the data point in the chart).
Any pointers would be greatly appreciated.
What I tend to do is to build a class that handles the downloading of the data, then hand it out to whoever wants it
like
class BananaProvider {
private var _bananaData: List<String>? = null
val bananas: List<String>
get() {
_bananaData?.let{ return _bananaData!!}
_bananaData = getBananaData()
return _bananaData!!
}
fun getBananaData(){
(...)
}
}
and perhaps have a function for refilling your babana's with fresh data if you want.
Then, just drop an instance of the BananaProvider in every fragment/function/whatever that needs it.
Alternatively, if you want to share the data between activities, you could serialize it and send it in an intent, or drop it in an SQLite database if you want to save it locally for later use.
What I wound up doing was:
Created a DataSource with the functions to fetch the data from the server. I'm using a PositionalDataSource.
Created a DataSourceFactory to deliver my DataSource
Created a LiveData using LivePagedListBuilder with my factory and a PagedList.Config (to bring in 100 records at a time, which seems optimal for my needs)
It's the LiveData that I share between the RecyclerView and the chart.
The object which controls the download for the chart keeps track of the current download position and size. If the chart winds up creating the shared LiveData, this will trigger the initial download and populate the underlying PagedList that LiveData is observing.
Each time data comes down, I update the download position and size and draw points for the data just received. If the data isn't complete, I found that I have to wait a few milliseconds before triggering the next download using the PagedList's loadAround function.
If the RecyclerView is the thing which is opened first by the user, its PagedListAdapter automatically brings the data down through the same DataSource and it fills the shared LiveData as the user scrolls.
When I switch back to the Chart, since the LiveData is already populated (all or partially), it can draw the points. If the data isn't complete yet, the PagedList will attempt to load the rest, drawing the points of the data coming down.
It's a pretty neat method. The key was keeping the LiveData around and reusing it and figuring out how to "manually" populate it when not using RecyclerView.
I have:
a table for my entity, obj.
a (model) class, Obj.
a Room Entity, ObjEntity.
a Room DAO, ObjDao.
and finally a RecyclerView for my model, Obj.
Within my Obj class, I've added an insert method - Obj#Post, which handles storing this Obj to the app's database (handles converting to ObjEntity, foreign keys for child entities / lists, etc.)
The first thought is to requery the obj table for all objs, before taking this ObjEntity[], mapping it to a Obj[] before updating the (cached) in memory version of all objs - and notifying the recycler view adapter of an update.
This obviously doesn't seem like the best approach, as it would only update on an insert, and not a delete etc. This also doesn't follow the pattern of separation of concerns.
What I ideally want is for the RecyclerView to dynamically query the database itself, and update accordingly. However, the implementation of the adapter works based off of an ordered list structure. Now, the objs won't always have nicely contiguous ids - so unless I did some funky hack to map from the value returned by RecyclerView.Adapter#getItemCount to each ID , this doesn't seem like it would work. Also, most importantly, there's no notifying of changes to the adapter with this solution. It'd just be querying whenever it usually does - I don't know the implementation, I'm guessing some form of polling, or user interaction trigger.
Does Room have some form of callback API, i.e. ObjDao#onTableUpdated?
What you need is exactly designed in Android architecture component.
The LiveData notifies the view whenever there is a change in the Room Database.
The ViewModel handle the data operation independent to UI, that is complete CURD operation.
The Repository modules handle data operations. They provide a clean API so that the rest of the app can retrieve this data easily. They know where to get the data from and what API calls to make when data is updated. You can consider repositories to be mediators between different data sources, such as persistent models, web services, and caches.
App using these components
If you want to use it for pagination purpose in the recyclerview use PagedListAdapter instead of RecyclerView.Adapter
I was reading the doc about observing queries.
Query<Task> query = taskBox.query().equal(Task_.completed, false).build();
subscription = query.subscribe().observer(data -> updateUi(data));
From what i understand , this code returns all the data every time. but for RecyclerView add/remove animation to work, we need to know which data is changed and we need to know what kind of change is happened to data (remove/change/add).
is there anyway to get changed data only?
It's not a responsibility of ObjectBox to define a change. There is DiffUtil that responsible for that in android. If you google that you can find tons of examples (e.g. sample). The only advice there is to put DiffUtil payload to background thread if your list contains lots of items or items are fat(contain dozens of fields).
I have a project that loads a list from the server. This data will eventually be stored into a database, but for now is stored in memory in a MutableLiveData. A RecyclerView's adapter is watching the data and displaying it. So far everything is working as expected, using a FAB the user can post a new entry which will go at the top of the list, on success I get a 200 and here's the main part where I'm getting lost...
When I want to add a single item to a list stored in a LiveData, the observer is unaware of the delta. I currently make a call to RecyclerView.Adapter.notifyDataSetChanged(), though the ideal in my case would be to call notifyItemInserted(0) or in other cases I can see various other notifications. What the best way to do this? The lifecycle architecture library appears to be very well thought of, I assume I'm missing something simple. I can't imagine having to manually perform a diff between the lists?