What I want to achieve: I am loading data from some web service in recyclerView. I want to load first 10 data and display it in recyclerView. When User scrolls, call web service for another 10 data to display it.
What I have done: For above aim, I'm using .take operator of RxJava. But It seems not working for me or else I'm doing some mistake.
What issue I'm having: I'm getting all the data instead of first 5 data. There might be something that I'm missing it.
My code is like below.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getSimpleName();
private RecyclerView recyclerView;
private ProgressDialog mProgressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
setupProgressDialog();
ApiEndPoints apiEndPoints = ApiClient.getClient().create(ApiEndPoints.class);
mProgressDialog.show();
apiEndPoints.fetchAllUsers()
.take(5)
.subscribeOn(Schedulers.io()) //Background Thread
.observeOn(AndroidSchedulers.mainThread()) //UI Thread
.subscribe(new Observer<List<Pojo>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Pojo> pojoList) {
recyclerView.setAdapter(new RVAdapter(pojoList));
Log.e(TAG, "List Size: " + pojoList.size());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
mProgressDialog.dismiss();
}
}
);
}
private void setupProgressDialog() {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("Loading . . . ");
mProgressDialog.setIndeterminate(true);
mProgressDialog.setCancelable(false);
}
}
Ask me if anything required. Thanks in advance.
If you use take operator, It will emit first 5 items and than onCompleted function will trigger. So you need re observable source.
I found example application on github which is using rxjava for load more operation.
You receive a list, which is considered as an object. You need to iterate them, take first 5 and then get them back as a list:
apiEndPoints.fetchAllUsers()
// iterate users
.concatMap(new Func1<List<Pojo>, Observable<Pojo>>() {
#Override
public Observable<Pojo> call(List<Pojo> pojos) {
return Observable.from(pojos);
}
})
// take 5 from iteration
.take(5)
// put all 5 users back to a list
.toList()
.subscribeOn(Schedulers.io()) //Background Thread
.observeOn(AndroidSchedulers.mainThread()) //UI Thread
If you had an array (Pojos[] users) take() operator should work without iteration.
Related
I am working on an Android App for handheld Scan Devices and want to download around 4.500 items from an MySQL database via Retrofit2 into a SQlite Database on the device; hence, when I tried to download all items at once, it slowed down the UI and froze the app up to 5 minutes; I googled a lot on how to solve the problem and couldn´t come up with a fitting solution for now; hence I tried to download the Database with 7 columns for each item - hence, around 31.500 entries in the database - in "Chunks" by iterating in a For-each loop and using .stream() and .limit() in a Background threat, like this:
public static void writeItemsToDatabase(Context mContext, String basic) {
//creating the itemApi interface
ItemApi itemApi = retrofit.create(ItemApi.class);
//making the call object
Call<List<Item>> call = itemApi.checkItems(basic);
call.enqueue(new Callback<List<Item>>() {
#Override
public void onResponse(#NonNull Call<List<Item>> call,
#NonNull Response<List<Item>> response) {
if (response.isSuccess()) {
List<Item> itemList;
itemList = response.body();
int dataSize = response.body().size();
Log.d(TAGGG, String.valueOf(dataSize));
itemList.forEach(List -> Log.d(TAGGG, String.valueOf(List.getEan())));
itemList.forEach(List -> Log.d(TAGGG, String.valueOf(List.getNo())));
class DownloadTask extends AsyncTask<String, Integer, String> {
// Runs in UI before background thread is called
#Override
protected void onPreExecute() {
super.onPreExecute();
// Do something like display a progress bar
}
// This is run in a background thread
#Override
protected String doInBackground(String... params) {
// Do something that takes a long time, for example:
for (int i = 0; i <= 3 ; i++) {
try (DatabaseHandler itemsManager = new DatabaseHandler((XXXXApp)
mContext.getApplicationContext())) {
itemList.stream().limit(1500).forEach(item -> {
itemsManager.addItem(item);
itemsManager.close();
});
}
// Call this to update your progress
publishProgress(i);
}
return "this string is passed to onPostExecute";
}
// This is called from background thread but runs in UI
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// Do things like update the progress bar
}
// This runs in UI when background thread finishes
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// Do things like hide the progress bar or change a TextView
}
}
new DownloadTask().execute();
}
}
#Override
public void onFailure(Call<List<Item>> call, Throwable t) {}
});
return;
}
however, the result is not satisfying as the Database doesn´t get´s downloaded properly; I changed the values for i to 9 and .limit() to 500 (to achieve the same result, the Download of +4.500 Items) with the same result.
The problem certainly is in this code snippet:
for (int i = 0; i <= 3 ; i++) {
try (DatabaseHandler itemsManager = new DatabaseHandler((XXXApp)
mContext.getApplicationContext()))
{
itemList.stream().limit(1500).forEach(item -> {
itemsManager.addItem(item);
itemsManager.close();
});
}
// Call this to update your progress
publishProgress(i);
}
It is the nearest approach that I´ve found to what I want to achieve after googling a lot; the problem certainly is that it´s a For-Loop that closes the Database each time and reopens it; I am also not sure if the amount of Data is too big for an SQlite database; hence any help or hints on how to solve this properly would be very much appreciated, thanks!
Create once instance of DatabaseHandler(what is it? you can use room with more comfortable API) and reuse it.
Insert many(100-500) items in one transaction.
Or you can create sqlite db file on server side then download it and open as DB in android app.
I am a newbie in reactive world and trying to implement the following scenario with rxjava/rxandroid 2.x.
I have a local data set as ArrayList mItems in Application class. The same data set is synchronized with server and updated every time user opens the app. However before server returns the response, I want to display the local data set in RecycleView backed by adapter. As soon as the response is returned, the adapter should update the list with delta and without disturbing the order in the UI.
So far I have tried this:
public Observable<List<Item>> getItemsObservable() {
Observable<List<Item>> observeApi = itemServiceAPI.getItemsForUser(accountId);
if (mItems != null) {
return Observable.just(mItems).mergeWith(observeApi);
} else {
return observeApi;
}
}
To update the UI, the above method is invoked like this:
Observable<List<Item>> itemsObservable = appContext.getItemsObservable();
itemsObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
With this I get the onNext called twice for each local data set and remote data set. How to achieve the desired functionality? Does it need use of filter operators to exclude items?
What's the best approach to achieve this?
You can use 'startWith' operator: it subscribes to different observable first.
appContext.getItemsObservable()
.startWith(localCacheObservable)
.subscribe(adapter::updateData)
Adapter's update data should handle diff calculations.
UPDATE
First of all, why are you using Observable.just(mItems) ??? That's unnecessary.
Your code should look like
itemServiceAPI.getItemsForUser(accountId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
mAdapter.updateItems(items);
/* method in adapter class
*
* public void updateItems(List<Item> mList) {
this.items.addAll(mList);
notifyDataSetChanged();
}
* */
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Here, your adapter will be updated in onNext. make sure before calling API, you have to set your adapter with local items.
My app implements a ChildEventListener to load the data into an ArrayList (approximately 7000 items).
During childAdded execution for each item, the interface freezes completely and can not be used.
Is there any way to run it in the background and that does not impair usability?
I've already tried using an AsyncTask and a Thread but the app freezes anyway. Thanks in advance.
class FBTask extends AsyncTas {
#Override
protected Boolean doInBackground(final Boolean... params){
int size = 7000; //aprox,
final ArrayList<Model> aux = new ArrayList<>();
Query db = FirebaseDatabase.getInstance().getReference()
.child("List").orderByChild("Double");
ChildEventListener cEL = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Model x = dataSnapshot.getValue(Model.class);
if(x.getT()!=null) {
aux.add(x)
Log.i("onChildAdded", x.getId() + " Added, pos: " + dX.size());
if(aux.size()>=size) {
data = aux;
}
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
db.addChildEventListener(cEL);
}
#Override
protected void onProgressUpdate(Boolean... values) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onPostExecute(DownAdapter result) {
if(result != null) {
DownActivity.downRecView.setAdapter(result);
}
}
#Override
protected void onCancelled() {
}
}
All network interaction and other work the Firebase client does already happens off the main thread. The only things that happens on the main thread are callbacks to your code, such onChildAdded(). This is done so you can update your UI from that code.
My guess is that calling dataSnapshot.getValue(Model.class) 7000 times is taking too much times, which is causing frames to be skipped. Do you really need 7000 models? I'd normally recommend to only retrieve data that you're going to show directly to the user, and 7000 models sounds like more than could reasonably fit on screen for most Android devices.
If you really must retrieve and decode that many items, you will need to use a AsyncTask or a background service. If you're having trouble making those work, share the minimal code that reproduces where you got stuck.
Every callbacks are handled by your Main Thread ( UI thread). Because you have large number of items (7000 items), there is array creation, copy of items from smaller to large array list is happening in runtime. This is causing ANR ( freeze your app). To avoid this, you can simply use new thread to add items in the array list. when you complete adding all items, do inter thread communication ( notify main thread) so that main thread does the further work. This is the exact solution. I had solved in the past the similar problem.
I am using
RxSearchView.queryTextChangeEvents
to detect the events of “Search As You Type” and also when you submit a search,
SAYT is to get suggestions and when you do a submit, it executes a full search.
There are 2 problems I am having at the moment.
When you are typing, and getting suggestions, but suddenly you click submit then it executes the full search but the problem is that if there is an on going suggestion request there might be the case that they appear on screen when they should not as the full search has priority.
So I would like to cancel (unsubscribe) the request from the suggestions in case there is a submit on the full search.
How can I achieve this with this code?
Another problem is that when I am deleting the search term in the search view and it gets empty, then it clears the adapter but there are cases when I clear the search text, there is still an on going suggestions request and it sets the results but it just clear, so I would like to guarantee that if the user clears the searchview, it cancels all the requests.
Here is the Code I am using.
RxSearchView.queryTextChangeEvents(searchView)
.skip(1)
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(new Func1<SearchViewQueryTextEvent, Boolean>() {
#Override
public Boolean call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
final boolean empty = TextUtils.isEmpty(searchViewQueryTextEvent.queryText());
if (empty) {
//Dont show anything clear adapter
clearAdapter();
}
return !empty;
}
})
.concatMap(new Func1<SearchViewQueryTextEvent, Observable<Object>>() {
#Override
public Observable<Object> call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String searchTerm = searchViewQueryTextEvent.queryText().toString();
boolean submitted = searchViewQueryTextEvent.isSubmitted();
//Hide RecyclerView
//Show loading indicator
showLoading();
Observable<Object> observable;
if (submitted) {
observable = getFullSearch(searchTerm);
} else {
observable = getSuggestionsSearch(searchTerm);
}
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(new Action0() {
#Override
public void call() {
//Show RecyclerView
//Hide loading indicator
showContent();
}
});
}
})
.subscribe(new Subscriber<Object>() {
#Override
public void onNext(Object object) {
//Set data on recyclerview and notify change.
setData(object);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
});
}
You might want to try switchMap instead, it just uses the last emited value from the observable.
From the docs:
Returns a new Observable by applying a function that you supply to each item emitted by the source Observable that returns an Observable, and then emitting the items emitted by the most recently emitted of these Observables.
The resulting Observable completes if both the upstream Observable and the last inner Observable, if any, complete. If the upstream Observable signals an onError, the inner Observable is unsubscribed and the error delivered in-sequence.
Started using RxJava and I want to be able to show a progress bar alongside my subscription.
I have a List of entities that I emit using Observable.from. I want to convert each entity into a JSONObject by using an Observable in a flatMap. See below for code.
Observable.from(entities)
.flatMap(new Func1<Entity, Observable<JSONObject>>() {
#Override
public Observable<JSONObject> call(Entity entity) {
return entityToJSON(entity);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
// on next
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
Log.e(LOG_TAG,"Error: "+throwable.getLocalizedMessage());
}
}, new Action0() { // onComplete
#Override
public void call() {
// onComplete
}
});
My Question
How do I, during this conversion to JSON return progress updates that I can update my ProgressDialog with?
Possible Solution
As there is a ProgressDialog up, I run this on the main thread and pass the ProgressDialog to the Observable to update. I believe this would work but I want to stay off the ui thread.
You can use
.doOnNext()
For every converted element and change progress bar.
If you want chage multiple times, you need do this on entityToJSON(entity) method. But entityToJSON works on Schedulers.io() thread, and you need use runOnUiThread method or uiHandler from this method. Read more: Accessing views from other thread (Android)
Communicating with the UI Thread: https://developer.android.com/training/multiple-threads/communicate-ui.html