UI is not drawn until AsyncTask completes - java

I have a few fragments in my activity. Each fragment makes a call to an AsyncTask which loads data from the local DB in the onCreate() method like this:
#Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
(new DataLoaderTask()).execute();
...
}
I have three fragments that do this. In doing so, the fragments do not finish drawing their UI until the DataLoaderTask completes. Why is this? I tried changing the call to this and I no longer have the issue:
#Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
(new DataLoaderTask()).execute();
}
}
...
}
Why does making the call inside a Runnable passed into a Handler work? Shouldn't the AsyncTask be running in the background anyway and hence the UI should get drawn before it completes?
Thanks for your help.
UPDATE: Adding more info.
Here's the constructor of DataLoaderTask():
public DataLoaderTask(Object object) {
mObject = object;
mListeners = new ArrayList<OnUpdateListener>();
}
DataLoaderTask does NOT override the onPreExecute() method. It does override the onPostExecute() method. I've timed my onPostExecute() method and it takes approx ~2ms. All it's doing is updating some objects and calling a method on any Listeners provided.
UPDATE: Here's the full onPostExecute() method:
#Override
protected void onPostExecute(Object result) {
synchronized(mDataManagerLock) {
if (Config.isLogging()) {
Log.i(TAG, "*** *** *** *** Finished syncing with database (onPostExecute()).");
}
if (mObject instanceof Playlist && result instanceof Playlist) {
if (((Playlist) result).isMyPlaylist()) {
synchronized(mTmpMyPlaylist) {
if (mTmpMyPlaylist != null && !mTmpMyPlaylist.isEmpty()) {
((Playlist) result).addPlaylistActions(mTmpMyPlaylist.getPlaylistActions());
mTmpMyPlaylist.clear();
}
}
}
if (mergeLocalPlaylistChanges((Playlist) result) && Config.isLogging()) {
Log.i(TAG, "Local playlist changes during sync merged.");
} else if (Config.isLogging()) {
Log.i(TAG, "No local playlist changes were made during sync.");
}
((Playlist) mObject).replace((Playlist) result);
putPlaylist((Playlist) mObject, null /*newClips*/);
} else if (mObject instanceof Catalog && result instanceof Catalog) {
((Catalog) mObject).replace((Catalog) result);
putCatalog((Catalog) mObject, null);
} else if (mObject instanceof NotificationsList) {
// We've synced NotificationsList in Memory with Disk.
mNotificationsList.setLastNetworkFetchTime(mApp.getNotificationsNetworkFetchTime());
} else if (mObject instanceof SetsList) {
// We've synced SetsList in Memory with Disk.
mSetsList.setLastNetworkFetchTime(mApp.getExploreNetworkFetchTime());
}
if (Config.isLogging()) {
Log.i(TAG, mObject.getClass().getSimpleName() + " after sync with database: " + mObject);
}
}
if (Config.isLogging()) {
Log.i(TAG, "Finished syncing in memory object/list with database.\n *** Time taken: " + (System.currentTimeMillis() - mStartTime)/1000 + " milliseconds.");
}
if (mListeners != null) {
for (OnUpdateListener listener : mListeners) {
if (listener != null) {
listener.onUpdated();
}
}
}
}

As you mentioned, anything resource intensive or that is going to take some time should be done in doInBackground. This includes not running anything that might hold it off in onPostExecute since we know that it runs in the main thread (with some exceptions) and might cause unexpected behavior like this.
I've ran some tests, apparently the main thread is being hold by onPostExecute.
I created a dummy Task:
class MyAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(Void result) {
try {
Log.i(TAG, "I'm holding the UI back.");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.onPostExecute(result);
}
}
Even if you post to a handler it takes the task and holds it. It wasn't until I added it to a Thread that the UI was printing on time:
#Override
protected void onPostExecute(Void result) {
new Thread(new Runnable() {
#Override
public void run() {
try {
Log.i(TAG, "I'm NOT holding the UI back.");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
super.onPostExecute(result);
}
Moral of the story, DO NOT do anything that might hold the main thread. And I am not saying you should create a Thread in onPostExecute either, that is beyond a bad practice if you are not somehow managing that thread.

Related

How can I modify a variable declared in the UI Thread, from an other thread?

I'm currently working on my first Android application.
The application accesses a database to get some informations that I want to print on the screen. To send requests and get answers on the network, I need to use a new thread (I'll name it "N thread"), different from the UI Thread. This part is ok.
Now, I want to modify the variable eventList to get the values stored in a collection, in the N thread.
public class MainActivity extends AppCompatActivity {
public List<Event> eventList = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/* I fill the list in an other thread */
new Thread(new Runnable() {
public void run(){
eventList = new WebService().getEvents(); //returns a list
}
// if I check here, eventList contains elements
}).start();
/* I check the result */
TextView respView = (TextView) findViewById(R.id.responseView);
if(eventList != null)
{
respView.setText("Ok");
} else {
respView.setText("Not ok");
}
...
}
The problem is : eventList is not modified. How can modify this variable and print it from the UI thread ?
Thank you for your help.
You can use runOnUiThread function or Handler to update UI from other thread. I suggest you reading the below tutorial first: AndroidBackgroundProcessing
Try this
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params)
{
eventList = new WebService().getEvents();
runOnUiThread(new Runnable() {
#Override
public void run() {
TextView respView = (TextView) findViewById(R.id.responseView);
if(eventList != null)
{
respView.setText("Ok");
} else {
respView.setText("Not ok");
}
}
});
}
}.execute();
private class EventsDownloader extends AsyncTask<Void, Void, Void> {
protected Long doInBackground(Void... params) {
eventList = new WebService().getEvents()
}
protected void onPostExecute(Void result) {
TextView respView = (TextView) findViewById(R.id.responseView);
if(eventList != null)
{
respView.setText("Ok");
} else {
respView.setText("Not ok");
}
}
}
This AsyncTask does what you want, the doInBackground runs on a thread and the 'onPostExecute' runs on the UI thread, and it's only called after the doInBackground finishes. This class is "managed" by the OS. To run it you just need to instantiate it and call 'execute'. I recommend doing something like this
The thing with your code is that the thread runs at the same time as the rest of your code (the calls to the setText), this means when it runs the setText the Thread is still getting the events.

AsyncTask connection timeout not cancelling the task

I have created a simple AsyncTask and I want to cancel the same if it takes more than 3 seconds. This is what I have done after following this post :
Async Caller:
execTask = new StartParseDownload();
execTask.execute();
//new StartParseDownload(this).execute();
Handler handler = new Handler();
handler.postDelayed(new Runnable()
{
#Override
public void run() {
if ( execTask.getStatus() == AsyncTask.Status.RUNNING )
execTask.cancel(true);
}
}, 300 );
The task:
public class StartParseDownload extends AsyncTask<String, Void, Boolean> {
private ProgressDialog dialog;
private boolean result = false;
public StartParseDownload() {
}
protected void onPreExecute(){
}
#Override
protected void onPostExecute(final Boolean success){
super.onPostExecute(success);
}
#Override
protected void onCancelled() {
Log.e("Task cancelled", "Task cancelled");
}
protected Boolean doInBackground(final String... args){
parObj.saveInBackground(new SaveCallback() {
#Override
public void done(com.parse.ParseException e){
if (e == null) {
result = true;
goIntoMainScreen();
} else {
result = false;
}
if (e != null) {
}
}
});
return false;
}
}
Where am I going wrong? Why is the onCancelled() not getting called?
1 .Your value is 300 , not 3000(3 Seconds)
2 .Also check like :
if ( execTask.getStatus() == AsyncTask.Status.RUNNING ){
boolean s = execTask.cancel(true);
Log.e("Taskcancelled", "Task cancelled " +s);
}else{
Log.e("Taskcancelled", "Task cancelled already");
}
May be your task already completed before call to cancel.
Inside your doinbackground you have to check if the AsyncTask gets cancelled.
You call cancel but the AsyncTask will continue running if it's still in the dounbackground state. Then after it's done onCancel gets called. Perhaps in your case it doesn't get called after 3 seconds because your doinbacground method takes longer than 3 seconds...or quicker!
By the way I think you mean to run the Handler for 3000 not 300
This answer might help.
The documentation states about the two variants:
Added in API level 11:
protected void onCancelled (Result result)
Added in API level 11
Runs on the UI thread after cancel(boolean) is
invoked and doInBackground(Object[]) has finished.
The default implementation simply invokes onCancelled() and ignores
the result. If you write your own implementation, do not call
super.onCancelled(result).
Added in API level 3:
protected void onCancelled ()
Added in API level 3
Applications should preferably override
onCancelled(Object). This method is invoked by the default
implementation of onCancelled(Object).
Runs on the UI thread after cancel(boolean) is invoked and
doInBackground(Object[]) has finished.
I haven't tested this, but you might want to try implementing both overrides:
#Override
protected void onCancelled() {
Log.e("Task cancelled", "Task cancelled");
}
#Override
protected void onCancelled(Boolean result) {
Log.e("Task cancelled", "Task cancelled, result: " + result);
}
It might also help to log the result of cancel() to make sure that you are successfully canceling the AsyncTask.
if ( execTask.getStatus() == AsyncTask.Status.RUNNING ){
boolean cancelled = execTask.cancel(true);
if (cancelled){
Log.d("cancel", "Task cancelled");
}
else{
Log.e("cancel", "Task not cancelled");
}
}

In run(), how can I call a method without my app crashing (Java/Android)?

I'm trying to make a simple little program that will increment a number once a second. In this case, I'm implementing a thread that should loop once per second and add 1 to "potato" each time it loops. This works fine until it gets back to the display method potatoDisp(). For some reason this causes my app to crash. Removing potatoDisp() from run() fixes the problem, but the display is not updated as "potato" increases.
public int potato = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
potatoDisp();
start();
}
public void potatoDisp() {
TextView text = (TextView) findViewById(R.id.textView1);
text.setText("You currently have " + potato + " potatoes");
}
public void start() {
Thread thread = new Thread(this);
thread.start();
}
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
potato++;
potatoDisp();
}
}
I'm doing this for an Android app, if that helps. I've tried searching for an answer but I'm pretty lost when it comes to the proper way to work threads.
You need a runnable / handler like this:
private Runnable potatoRun = new Runnable() {
#Override
public void run () {
potatoDisp();
}
};
then change
potatoDisp();
to:
runOnUiThread(potatoRun);
You can't update the views when you're not on the UI thread.
You are probably getting an exception for updating the UI in the background. Since, potatoDisp(); is called from a background Thread but that function updates the UI it will give you problems. You need to call it with runOnUiThread().
#Override
public void run() {
while (true) {
try
{
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
potato++;
runOnUiThread(new Runnable()
{
#Override
public void run()
{
potatoDisp();
}
});
}
}
Something like this should work.
The issue is that you are trying to update the UI (calling text.setText(...)) on a thread other than the main UI thread.
While I would suggest using a TimerTask instead of calling Thread.sleep(...), there are two main ways to edit your current code to work as expected.
-- Use a Handler
Define a Handler class that will accept messages and update your UI as needed. For example:
private final String POTATO_COUNT = "num_potatoes";
Handler handler = new Handler() {
public void handleMessage(Message msg) {
int numPotatoes = msg.getData.getInt(POTATO_COUNT);
mText.setText("You currently have " + numPotatoes + " potatoes");
}
}
Then in your code where you want to call your handler to update your text view, whether or not you are on the main UI thread, do the following:
Bundle bundle = new Bundle();
bundle.putInt(POTATO_COUNT, potato);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
-- Call runOnUiThread(...)
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
potato++;
runOnUiThread(new Runnable()
{
public void run()
{
potatoDisp();
}
}
}
}
I think you should be using Async Task to update the UI from a thread: http://developer.android.com/reference/android/os/AsyncTask.html

Exception "Can't create handler inside thread that has not called looper prepare" in android app

I'm working on my first android app and I'm trying to use Quickblox.com as my backend.
In order to use it, I need to authorize the app by creating a session using their SDK.
So, I have the following code:
// Initialize QuickBlox application with credentials.
QBSettings.getInstance().fastConfigInit(Consts.APP_ID, Consts.AUTH_KEY, Consts.AUTH_SECRET);
// Authorize application
QBAuth.createSession(new QBCallback() {
#Override public void onComplete(Result result) {}
#Override
public void onComplete(Result result, Object context) {
if (result.isSuccess()) {
showMainScreen();
} else {
// print errors that came from server
Toast.makeText(getBaseContext(), result.getErrors().get(0), Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.INVISIBLE);
}
}
}, QBQueries.QB_QUERY_AUTHORIZE_APP);
This code works well with an emulator, but it doesn't work if I try with a real android phone. I have a connection timeout error. I think I need to make this kind of requests (Web Services) in the background right?
So I tried to use the AsyncTask to make the request to QB in the background, and changed the code to this:
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
// Initialize QuickBlox application with credentials.
QBSettings.getInstance().fastConfigInit(Consts.APP_ID, Consts.AUTH_KEY, Consts.AUTH_SECRET);
// Authorize application
QBAuth.createSession(new QBCallback() {
#Override public void onComplete(Result result) {}
#Override
public void onComplete(Result result, Object context) {
if (result.isSuccess()) {
showMainScreen();
} else {
// print errors that came from server
Toast.makeText(getBaseContext(), result.getErrors().get(0), Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.INVISIBLE);
}
}
}, QBQueries.QB_QUERY_AUTHORIZE_APP);
return null;
}
}.execute();
I've seen a lot of similar questions here at SO, but I can't seem to find an answer that works with my code. I saw that functions that deal with the UI need to be called from the main thread, so I suppose that the code I have inside
onComplete(Result result, Object context)
should be inside a block like this right?
runOnUiThread(new Runnable()
{
public void run()
{
// code here
}
});
But I tried that as well and it didn't work. Any guesses?
I believe the problem is not because of the Toast and showMainScreen(). It still fails with this code:
// Initialize QuickBlox application with credentials.
QBSettings.getInstance().fastConfigInit(Consts.APP_ID, Consts.AUTH_KEY, Consts.AUTH_SECRET);
QBAuth.createSession(new QBCallback() {
#Override
public void onComplete(Result arg0, Object arg1) {
// TODO Auto-generated method stub
}
#Override
public void onComplete(Result arg0) {
// TODO Auto-generated method stub
}
}, QBQueries.QB_QUERY_AUTHORIZE_APP);
But it doesn't fail if I just create the QBCallback object, without passing it to the QBAuth.createSession function.
You can't show a Toast in doInBackground of AsyncTask as it is a different threat and not the main UI thread.
To show a toast or any UI related task you have to do it in onPostExecute method of AsyncTask.
What you can do is
new AsyncTask<Void, Void, boolean>() {
#Override
protected boolean doInBackground(Void... params) {
// Initialize QuickBlox application with credentials.
QBSettings.getInstance().fastConfigInit(Consts.APP_ID, Consts.AUTH_KEY, Consts.AUTH_SECRET);
boolean res = false;
// Authorize application
QBAuth.createSession(new QBCallback() {
#Override public void onComplete(Result result) {}
#Override
public void onComplete(Result result, Object context) {
if (result.isSuccess()) {
//showMainScreen();
res = true
}
//else {
// print errors that came from server
//Toast.makeText(getBaseContext(), result.getErrors().get(0), //Toast.LENGTH_SHORT).show();
//progressBar.setVisibility(View.INVISIBLE);
//}
}
}, QBQueries.QB_QUERY_AUTHORIZE_APP);
return res;
}
protected void onPostExecute(boolean result) {
if(result) {
showMainScreen();
} else {
Toast.makeText(getBaseContext(), result.getErrors().get(0), Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.INVISIBLE);
}
}
}.execute();
You don't need to use AsyncTasks, because all operations already perform in background.
And also this one
QBAuth.createSession(new QBCallback() {
it performs request in background to QB and call callback in Main thread.
Could you explain what 'connection timeout error' you got.
Is there anything in log?

Show ProgressDialog during a network call until it's FINISHED

I am very frustrated as I've been trying to implement a super simple loading wheel while waiting on a network call. I have searched and read dozens of SO questions and I just feel like I must be missing something, unless nobody really does what I'm trying to do. I have tried going down the AsyncTask route, but that's not what I want.
Let me also say that right now my app works perfectly, it's just that the transition from screen to screen appears to hang as it waits on the network. I just want a loading wheel so that in the 1-2 seconds the user knows the app is working and didn't freeze.
Here's what my current network call looks like:
private static String sendDataToServer(String arg1, String arg2)
{
Thread dbThread = new Thread()
{
public void run()
{
// do the call that takes a long time
}
};
dbThread.start();
try {
// I do this so that my program doesn't continue until
// the network call is done and I have received the information
// I need to render my next screen
dbThread.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
Now, why can't I just add the ProgressDialog like this? If I do this, the progressDialog never appears.
private static String sendDataToServer(String arg1, String arg2)
{
final ProgressDialog progress = new ProgressDialog(BaseActivity.getInstance());
progress.setIndeterminate(true);
progress.setMessage("Loading...");
progress.show();
Thread dbThread = new Thread()
{
public void run()
{
// do the call that takes a long time
}
};
dbThread.start();
try {
dbThread.join();
progress.dismiss();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
I think I'm stuck because the network call needs to be on a separate thread from the UI thread, yet I don't want to continue in my application because I need the results of that call to continue. But if I do thread.join() I hold up everything. I thought I needed AsyncTask but that went downhill quickly. Here's my question on that if you're curious.
Android's AsyncTask: multiple params, returning values, waiting
How the heck to I just show a loading dialog while this call happens without proceeding through the rest of my application?
EDIT
Here's my AsyncTask attempt.
private class PostToFile extends AsyncTask<PostToFile, Void, Void>{
private String functionName;
private ArrayList<NameValuePair> postKeyValuePairs;
private String result = "";
public PostToFile(String function, ArrayList<NameValuePair> keyValuePairs){
functionName= function;
postKeyValuePairs = keyValuePairs;
}
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(BaseActivity.getInstance(), "Loading", "Please wait...", true, false);
}
#Override
protected Void doInBackground(PostToFile... params) {
ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair(FUNCTION_KEYWORD, functionName));
for (int i = 0; i < postKeyValuePairs.size(); i++) {
nameValuePairs.add(postKeyValuePairs.get(i));
}
try{
// ***do the POST magic.***
result = response.toString();
}
catch (Exception e){
// clean up my mess
}
return null;
}
private String getResult(){
return result; // can I use this somehow???
}
#Override
protected void onPostExecute(Void result) {
progressDialog.dismiss();
}
}
And when I use it:
new PostToPHP(FUNCTION_NAME, postPairings){
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
try
{
if (result != null && !result.startsWith("null"))
{
JSONArray jArray = new JSONArray(result);
parseData(jArray);
}
}
catch (JSONException e)
{
Log.e(Constants.LOG_TAG, e.toString());
}
};
}.execute()
The problem is, I have a couple of these calls back to back, and they're each dependent on each other. So the first one starts, and the second one starts immediately after the first one starts, but before the first one is finished. So I get erroneous behavior. How can I start the second call only after the first is completely done?
Maybe this will work, I haven't tested, but you can try:
public class MyTask extends AsyncTask<String, Void, String> {
private int flag;
public MyTask(int flag) {
this.flag = flag;
}
#Override
protected String doInBackground(String... params) {
switch (flag) {
case 1:
return doNetworking1();
break;
case 2:
return doNetworking2();
break;
case 3:
return doNetworking3();
break;
default:
return doNetworking1();
}
}
#Override
protected void onPreExecute() {
//show progress dialog
}
#Override
protected void onPostExecute(String s) {
//hide progress dialog
switch (flag) {
case 1: //do something with result
new MyTask(2).execute();
break;
case 2: //do other stuff
new MyTask(3).execute();
break;
case 3: //do event more stuff
break;
default:
//do something
}
}
}
and usage:
new MyTask(1).execute();
In cases of network connections I would use IntentService instead of AsyncTask.
For example create IntentServices for network connection:
public class NetworkCallIntentService extends IntentService {
public static final String BROADCAST_ACTION = "com.yourpackage:NETWORK_CALL_BROADCAST";
public static final String RESULT = "com.yourpackage:NETWORK_CALL_RESULT";
public NetworkCallIntentService() {
super(NetworkCallIntentService.class.getSimpleName());
}
#Override
protected void onHandleIntent(Intent intent) {
// get data from intent if needed
// do the call that takes long time
// send broadcast when done
Intent intent = new Intent(BROADCAST_ACTION);
intent.putExtra(RESULT, "some_result");//and more results
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Next, start that service from activity, show progress dialog and move code responsible for showing next screen to BroadcastReceiver#onReceive() method:
public class SomeActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//start service
Intent serviceIntent = new Intent(this, NetworkCallIntentService.class);
//put extras into intent if needed
//serviceIntent.putExtra("some_key", "some_string_value");
startService(serviceIntent);
//here just show progress bar/progress dialog
}
#Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mNetworkCallReceiver,
new IntentFilter(NetworkCallIntentService.BROADCAST_ACTION));
}
#Override
protected void onPause() {
super.onPause();
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(mNetworkCallReceiver);
}
private BroadcastReceiver mNetworkCallReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//hide progress bar/progress dialog
//here get results from intent extras
String result = intent.getStringExtra(NetworkCallIntentService.RESULT);
//process results and continue program(go to next screen, show error message etc.)
}
}
}
Declare service in manifest file:
<service
android:name="com.yourpackage.DownloadSvtValuesIntentService"
android:exported="false" >
</service>

Categories

Resources