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.
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.
the problem that I am having is the following: For some reason onSuccess() is being call 2 minutes after all the code inside execute() is executed. Not sure why is taking that long.
CODE:
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Log.e("time-pre", Calendar.getInstance().getTime().toString());
RealmResults<TVRealm> tvRealms = realm.where(TVRealm.class)
.equalTo("favorite", true)findAll();
Log.e("time-post", Calendar.getInstance().getTime().toString());
}
}, new OnSuccess() {
#Override
public void onSuccess() {
Log.e("time-on-success", Calendar.getInstance().getTime().toString());
listener.onDataUpdate(tvList);
}
});
This is not happening on all devices, my understanding is that is only happening on devices that were using a really old realm db (like 0.84 on gradle) and after updating is causing this.
Does anyone has any clue about this issue?
I have a method that loads data from Firebase into ArrayList. After this,I use that ArrayList to construct RecyclerView. I've decided to load data on another thread. Below is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_just);
citiesRecyclerView =
(RecyclerView)findViewById(R.id.recyclerView);
handler = new Handler()
{
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what==1)
{
cityAdapter = new
CityAdapter(MainActivity.this,cities) ;
citiesRecyclerView.setAdapter(cityAdapter);
}
}
};
t = new Thread(new Runnable() {
#Override
public void run() {
//method that loads data into List.If this method was
//successfully done,then I send message 1 to Handler
loadDataFromFirebase();
}
});
t.start();
//other operations below
}
Hope,that everything understandable. Code works fine. And my problem is that I need to use loadDataFromFirebase method in thread again. I wanted to call t.start() again in order to call loadDataFromFirebase method,but there was error that thread already started. I checked that by writing this code:
if(t.getState()== Thread.State.NEW)
t.start();
else
someMethod();
else statement worked above.
And my questions are:
1) Does loadDataFromFirebase method work really on another thread by this way?
2) How to call loadDataFromFirebase method again in another thread, if something happened? Do I need to create another variable for Thread again?
It's not a good idea to handle all low-level thread work by your own.
Accroding to Android you could:
Use AsyncTask (but notice that they have many drawbacks such as context leak in some cases etc),
I could suggest you to get into RxJava - it's a painless way to use async work in your app.
To 'download' data from Firebase you could probably use FCM (push notifications) to load data on demand.
And what about your question:
"It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution."(c) http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#start()
If you are using firebase SDK you can use realtime database feature, so do not need to query it each time.
You should just subscribe one time and get updates. For example:
firebaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
YourDataObject value = dataSnapshot.getValue(YourDataObject.class);
Log.d(TAG, "Value is: " + value);
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException());
}
});
You can read docs here.
In my Android project i use Azure Mobile Services SDK and the way i make queries to the local sqlite database is like the following:
(example taken from http://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-get-started-data/).
The problem is that i get the following errors:
1)com.microsoft.windowsazure.mobileservices.table.sync.localstore.MobileServiceLocalStoreException: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed
2) A SQLiteConnection object for database 'LocalDatabase' was leaked! Please fix your application to end transactions in progress properly and to close the database when it is no longer needed
3) java.util.concurrent.ExecutionException: com.microsoft.windowsazure.mobileservices.table.sync.localstore.MobileServiceLocalStoreException: java.lang.IllegalStateException: attempt to re-open an already-closed object
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
final MobileServiceList<ToDoItem> result = mToDoTable.where().field("complete").eq(false).execute().get();
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.clear();
for (ToDoItem item : result) {
mAdapter.add(item);
}
}
});
} catch (Exception exception) {
createAndShowDialog(exception, "Error");
}
return null;
}
}.execute();
In this implementation there is no Cursor or SQLiteOpenHelper object to close. What could i do?
Thank you!
I think this is because you are getting the result in one thread but trying to use that same result in the UI thread. One possible method is to use Broadcast or Eventbus to send the data back to the UI once the operation completes:
Here is sort of what I would do:
Futures.addCallback(mToDoTable.where().field("complete").eq(false).execute(), new FutureCallback() {
#Override
public void onSuccess(Object result) {
//Do something with result
//I would use event bus or broadcast here to notify the UI
}
#Override
public void onFailure(Throwable t) {
}
});
If this doesn't work, I would use the debugger and put a break point on that execute().get() line and see what is happening internally.
Here is another possible way to do it:
mToDoTable.where().field("complete").eq(false).execute(new TableQueryCallback<Object>() {
#Override
public void onCompleted(List result, int count, Exception exception, ServiceFilterResponse response) {
}
});
I have listed of products with different category. I have to sort them. Because of the queries, It is taking more time to load. Between two activities, the screen is coming black. I want to run the query in the background. How can I do that and how to use its result in main activity?
private class InsertTask extends AsyncTask {
String cat;
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Boolean doInBackground(String... params) {
Boolean success = false;
try {
category(cat);
success = true;
} catch (Exception e) {
if(e.getMessage()!=null)
e.printStackTrace();
}
return success;
}
#Override
protected void onPostExecute(Boolean success) {
super.onPostExecute(success);
}
private void category(String category) {
try{
Cursor1 = mDbHelper.fetchcategory(category);
}catch(Exception e){
Log.v("Excep", ""+e);
}
}
And when called
InsertTask task = new InsertTask();
task.execute();
I have listed the category in buttons. How can I get the values then?
You should use AsyncTask for that. And some more info.
Its good you have thought of AsyncTask. Firstly, you can declare this class as inner in you class activity (if you haven't previously did) and so you are able to access you view class members.
You can do this also by creating thread and one handler that will be used to update your UI components. Remember that if you use threads you'll need to lock/unlock your database object because of the thread safety(if any other thread is accessing the database for any reason). Read more about thread safety of dbs.
I was doing some searching myself, and I came across this read, its rather long but looks extremely helpful, with lots of code examples. (I bookmarked it for myself).
Threads, Async, and Handlers O MY!
But some form of threading is the ticket.
From Android dev.
(My favorite code snippet)
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
//Do Work here
}
}).start();
}