I'm currently developing my android app and have a problem in displaying data retrieved from API in my Home Activity. Here is design for my Home activity.
On that page, I want to show three data: Latest news, most popular news, and categories. Each of the data obtained from different API. So, there are three API request when displaying the data.
The scenario that I want is to perform all requests simultaneously, showing a progress bar when taking such data and dismiss the progress bar when all of the data from such three API is successfully obtained. If I've got the latest news data, the progress bar wouldn't be dismissed until popular news and categories data successfully obtained. And when the latest news data and categories data successfully obtained, but I've got a problem when getting popular news, then all the data wouldn't be displayed on the page, and the page will display an error message.
I'm using retrofit in accessing my API. I've also tried combining it with RxJava for accessing the API. I've tried to use RxJava combine sequences method like combineLatest, merge, but the problem that I found is that it can't combine more than two requests/sequence at the same time. So I haven't met the solution.
Is there any good solution for this case?
My initial (incorrect) answer is struck through below. Apologies for the inaccuracy.
combineLatest does appear to do what you'd like. A current comment under the question has the right idea, but I think the handling of the emission of combineLatest can be done slightly better, so here is my take for that.
Observable.combineLatest(
api.getLatest(),
api.getPopular(),
api.getCategories(),
NewsReaderViewModel::new)
// This is very simple progress bar logic, and will actually break if this code
// errors. Handling of the progress bar should be slightly different, but for the
// sake of this example, I'm going to keep it as simple as possible.
.doOnSubscribe(disposable -> loadingProgressBar.setVisibility(View.VISIBLE))
// Depending on your requirements, you might not need to continue observing this
// stream. If you expect your results are going to continue to be updated, then
// you won't want to add this next line, but I don't imagine this data is going to
// live update.
.firstElement()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayResults, out::println);
I created the NewsReaderViewModel to hold the results, as you may want to perform some logic on the results, and couldn't do that in a reasonable way when using the ArrayList solution. Here is the NewsReaderViewModel class. It is very simple and just contains three lists of Strings.
static final class NewsReaderViewModel {
final List<String> latest;
final List<String> popular;
final List<String> categories;
NewsReaderViewModel(List<String> latest, List<String> popular, List<String> categories) {
this.latest = latest;
this.popular = popular;
this.categories = categories;
}
}
I've omitted the rest of the code, but here is a link to the Github repository with the full example that you'll be able to clone and run. Let me know if you have any questions and I hope this helps!
Here is the Github link
I'll also upload a YouTube video of the thought process behind the answer and link that video once it's available. Sometimes it's even more seeing the thoughts and mistakes and process behind the answer rather than just the answer itself.
I think you may actually want to use zip rather than combine latest. Zip will wait until all of the zipped observables have emitted once. That sounds closer to your requirements.
Here is the documentation for zip: http://reactivex.io/documentation/operators/zip.html
You'll subscribe to the resultant stream of zip and will display the results of the UI there and hide the progress bar. If you use combineLatest, you'll have to check in the resultant subscription whether or not the contents are null, and that is not idiomatic Rx
Related
I have a list with pagination which I implemented using Paging library. Items on this list can be modified (changed/deleted).
According to official documentation, I'm first changing in-memory list cache from which my DataSource gets pages and after that calling datasource.invalidate() in order to create new pair PagedList/DataSource:
If you have more granular update signals, such as a network API signaling an update to a single item in the list, it's recommended to load data from network into memory. Then present that data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot can be created.
It works and looks WELL if user modifies items on first page.
However, if user is on page two or further during datasource.invalidate() he will be thrown at the end of the first page.
Debugging shows this happens because new PagedList has only first page when it's submitted to PagedListAdapter.submitList. Adapter compares old and new lists and removes all items not from first page. It happens always but not visible for user if he is on the first page.
So to me, it looks like new pair PagedList/DataSource have no idea about number of pages which fetched previous pair and datasource.invalidate() doesn't fit for the situation in docs. Behavior that I see acceptable for cases then user updates all list (like swipe-to-refresh) but not
an update to a single item in the list
Has anybody faced such issue or somehow archived things I want? Maybe I'm missing some trick which helps me to get new PagedList already with all pages.
For clarification: library version 2.1.0. Custom PageKeyedDataSource based on in-memory cache and remote servise (No Room)
I want to share my research in case anybody is interested:
Issue ("lack of feature") is known, at least I've found the couple related discussions on official tracker one two
If you are using PositionalDataSource or ItemKeyedDataSource you should dig into the direction of requestedStartPosition/requestedInitialKey from initial params as this answer says. I didn't have much time to build the whole solution but those params are indeed different for initial load after invalidation
About my case : PageKeyedDataSource. Here you can read that there is no similar to requestedInitialKey params in this type of data source. Still, I found a solution which fits me, very simple, although, feels like a dirty trick:
When loadInitial() is called after invalidate() in-memory cache returns all already loaded pages instead of just first one.
At first I was worry that something will break if, for example, requestedLoadSize is 5 but the result is 50 items list but turns out it's just a hint and it can be ignored. Just don't forget to pass nextPageKey which corresponds to the last cached page and not the first one.
Hope it will help
With observable method you will only get first page list items....if you want to edit other items you can get that list by adapter.currentlist method.
Example:
private fun list():MutableList<String>{
val list = mutableListOf<String>()
for (value in videosAdapter.currentList.orEmpty()) {
val abc = value.snippet.resourceId.videoId
list.add(abc)
}
return list
}
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
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?
I will start this by saying that on iOS this algorithm takes, on average, <2 seconds to complete and given a simpler, more specific input that is the same between how I test it on iOS vs. Android it takes 0.09 seconds and 2.5 seconds respectively, and the Android version simply quits on me, no idea if that would be significantly longer. (The test data gives the sorting algorithm a relatively simple task)
More specifically, I have a HashMap (Using an NSMutableDictionary on iOS) that maps a unique key(Its a string of only integers called its course. For example: "12345") used to get specific sections under a course title. The hash map knows what course a specific section falls under because each section has a value "Course". Once they are retrieved these section objects are compared, to see if they can fit into a schedule together based on user input and their "timeBegin", "timeEnd", and "days" values.
For Example: If I asked for schedules with only the Course ABC1234(There are 50 different time slots or "sections" under that course title) and DEF5678(50 sections) it will iterate through the Hashmap to find every section that falls under those two courses. Then it will sort them into schedules of two classes each(one ABC1234 and one DEF5678) If no two courses have a conflict then a total of 2500(50*50) schedules are possible.
These "schedules" (Stored in ArrayLists since the number of user inputs varies from 1-8 and possible number of results varies from 1-100,000. The group of all schedules is a double ArrayList that looks like this ArrayList>. On iOS I use NSMutableArray) are then fed into the intent that is the next Activity. This Activity (Fragment techincally?) will be a pager that allows the user to scroll through the different combinations.
I copied the method of search and sort exactly as it is in iOS(This may not be the right thing to do since the languages and data structures may be fundamentally different) and it works correctly with small output but when it gets too large it can't handle it.
So is multithreading the answer? Should I use something other than a HashMap? Something other than ArrayLists? I only assume multithreading because the errors indicate that too much is being done on the main thread. I've also read that there is a limit to the size of data passed using Intents but I have no idea.
If I was unclear on anything feel free to ask for clarification. Also, I've been doing Android for ~2 weeks so I may completely off track but hopefully not, this is a fully functional and complete app in the iTunes Store already so I don't think I'm that far off. Thanks!
1) I think you should go with AsynTask of Android .The way it handle the View into `UI
threadandBackground threadfor operations (Like Sorting` ) is sufficient enough to help
you to get the Data Processed into Background thread And on Processing you can get the
Content on UI Thread.
Follow This ShorHand Example for This:
Example to Use Asyntask
2) Example(How to Proceed):
a) define your view into onPreExecute()
b) Do your Background Operation into doInBackground()
c) Get the Result into onPostExceute() and call the content for New Activty
Hope this could help...
I think it's better for you to use TreeMap instead of HashMap, which sorts data automatically everytime you mutate it. Therefore you won't have to sort your data before start another activity, you just pass it and that's all.
Also for using it you have to implement Comparable interface in your class which represents value of Map.
You can also read about TreeMap class there:
http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html