I understand I may be doing this a bit backwards, but haven't found a suitable solution yet and this is the closest I have come. I am first presenting a Login view to the user and submitting the inputted credentials to a web service which validates credentials and returns a HTTP 200 if the logon was successful. Upon success, I am then making a HTTP JSON request to the same web service to download a list of JSON objects. These objects are then parsed, and placed into a SQLite DB. There are around 100 or more objects, so an async task is appealing so I don't lock up the UI. The point of this is to display a ProgressBar (indeterminate) to the user saying there is work being done in the background, then return them to a separate Intent once the DB is populated. Here is the code snippets:
Validating the login and calling the login action:
switch(statusCode) {
case 200:
PrepareLogin doLogin = new PrepareLogin();
doLogin.execute();
if (doLogin.getStatus().equals(AsyncTask.Status.FINISHED)) {
// Redirect to intent?
}
break;
case 401:
Toast.makeText(Login.this, "Invalid Username/Password", Toast.LENGTH_LONG).show();
}
Async task with helper functions in the background:
private class PrepareLogin extends AsyncTask<Void,Void,Void> {
ProgressDialog dialog;
#Override
protected void onPreExecute() {
dialog = new ProgressDialog(Login.this);
dialog.setTitle(getString(R.string.get_login_dialog_title));
dialog.setMessage(getString(R.string.get_login_dialog_message));
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
#Override
protected Void doInBackground(Void... params) {
Looper.myLooper().prepare();
Looper.myLooper().loop();
DBHelper dbHelp = new DBHelper(Login.this);
WSHelper wsHelp = new WSHelper();
long timeNow = System.currentTimeMillis();
dbHelp.resetDB(Login.this); // For dev environment only...remove in prod
dbHelp.create();
dbHelp.createUser(username, password, timeNow, "false");
dbHelp.close();
wsHelp.getEmployees(Login.this, username, password);
Looper.myLooper().quit();
return null;
}
protected void onPostExecute() {
Intent myIntent = new Intent(login.getContext(), Main.class);
startActivityForResult(myIntent, 0);
dialog.dismiss();
}
}
The only way I can get this to function without throwing the "Handler must prepare a looper" error, is with the Looper.myLooper.prepare() function.
I can see in the logs that the connection is made and DB populated, but the dialog continues to spin and it never reaches the onPostExecute() function of the Async task. It hits the Looper.myLooper().quit(); line though.
Any suggesstions?
If you get "Handler must prepare a looper" error, most probably you are calling the AsyncTask in a wrong context. I don't remember I ever need to explicitly call Looper.prepare() function in any AsyncTask. Once you get rid of the looper, it would be fine, the AsyncTask is one of the easiest to use class in Android. You should post your code in the Login class (I suppose it is an Activity, right?).
Another problem I can find is:
case 200:
PrepareLogin doLogin = new PrepareLogin();
doLogin.execute();
if (doLogin.getStatus().equals(AsyncTask.Status.FINISHED)) {
// Redirect to intent?
}
break;
Your doLogin.execute(); should not be a blocking call, so the next if is guaranteed to be wrong, so the redirect would never fire.
It appears that my WSHelper class was attempting to making some sort of change to the UI from the background process. By removing the wsHelp initialization from the AsyncTask and making it a constant, I was able to proceed just fine. It may also be worth noting I changed the return value to void for the wsHelp.getEmployees() method.
Related
For my project I need to check a URL to see if it exists or not.
So I created a HttpURLConnection and connected to the url and wait for the response code.
Since I can't use that HttpURLConnection in my Main Thread, i had to move it to an AsyncTask and let it run on a separate Thread.
This is what my code for that AsyncTask looks like (only the important parts):
private class URLExists extends AsyncTask<String, Void, Boolean> {
protected Boolean doInBackground(String... urls)
{
boolean isValid = false;
try
{
URL myurl = new URL(urls[0]);
HttpURLConnection.setFollowRedirects(false);
HttpURLConnection connection = (HttpURLConnection) myurl.openConnection();
connection.setUseCaches(true);
connection.setRequestMethod("HEAD");
connection.connect();
int code = connection.getResponseCode();
...
connection.disconnect();
if(code != 404)
isValid = true;
else
isValid = false;
}
...
return isValid;
}
protected void onPostExecute(Boolean isValid)
{
isTrue = isValid;
}
}
So basically i then get a boolean value isTrue which tells me if the url is valid. When I run this i get the right results, everything works.
But now, when i execute this AsyncTask, it runs parallel to my main Thread, obviously.
I have this code:
new URLExists().execute("https://www.example.com");
if(isTrue)
{
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Do Something
}
});
}
Since my AsyncTask runs parallel, my Main Thread executes the next operation without waiting for the result of the AsyncTask. Since isTrue is false by default, the if statement never runs.
Of course i know about the protected void onPostExecute() in the AsyncTask, but does that mean that all the code that follows in my main Thread has to be in that function in the parallel Thread? It all depends on the outcome of the URL check.
I know about the .get() method, but that freezes my MainThread and the UI, so that's not really an option for me.
Is there any other way of making the Main thread wait for the AsyncTask? I know the defeats the purpose of the AsyncTask, but all I want is to check if the URL exists and I can only do checks in an AsyncTask.
You don't need multithread for your task. All what you need is to check that url is available. Right? Of course it may take some time and you have to wait this time in your main thread. That's right. But you still need to wait. Don't create unnecessary complexity. Just read url, get response code, validate it, check for timeout exception and you are good to go.
We need to look at the specification of AsyncTask. Typically in any asynchronous method you have a callback function that lets you know the background operation has completed. In the case of Android it is onPostExecute()
It can be found here. http://developer.android.com/reference/android/os/AsyncTask.html
When an asynchronous task is executed, the task goes through 4 steps:
onPreExecute(), invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
For your specific case you might want to do something like this..
yourAsyncTask{
#Override
protected Boolean doInBackground(Void... params){
// CHECK YOUR URL HERE
}
.
.
.
#Override
public void onPostExecute(Result result){
if(isTrue) {
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Do Something
}
});
}
}
}
I am developing an Android application and I need show remind dialog from time to time in according to variable from shared preferences. If user does not want to see reminder message, he can disable this option also in my PreferenceActivity. I am using AsyncTask class to show remind message to user from time to time . User can press options button at every moment to go to PreferenceActivity to enable/disable reminder option and set pause time value between reminds, and the go back to activity. So I am checking all values in onResume method. And if users does not want to see remind messages I need to finish current working AsyncTask, or if user changed value between reminds, I need to restart current AsyncTack with new pause value. But I see an unpredictable behavior of my AsyncTack: sometimes it stops, sometimes not and continue working and showing message)))), sometimes it works and sometimes - not))))). Here is a piece of code:
This is my AsyncTack class
private class ReadReminderTask extends AsyncTask<Long, Void, Void> {
private long mRemindTime;
private volatile boolean running = true;
public ReadReminderTask(){
}
#Override
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (running){
try{
Thread.sleep(mRemindTime);
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... progress) {
super.onProgressUpdate(progress);
// showReminder is a method where I show remind message
showReminder();
}
#Override
protected void onCancelled() {
running = false;
}
}
This is onResume method of my Activity where I need to show reminder message:
if(!settings.getBoolean("needToRemind", false)) {
mReadReminderTask.cancel(true);
} else if(settings.getBoolean("needToRemind", false)) {
mReadReminderTask = new ReadReminderTask();
mReadReminderTask.execute(settings.getLong("timeRemind", 1));
}
Can anyone help me with my problem?
That's really not such a great way to do that. The "unpredictable behavior" happens because you leave the task running even after your Activity is stopped. The call to cancel in your onResume is not only too late, it actually is not doing anything at all.
Have a look at Handler.postDelayed. You want something like this:
In your onCreate
hdlr = new Handler();
reminder = new Runnable() { public void run() { showReminder(); } }
EDITED TO ADD ESSENTIAL 2ND PARAM TO postDelayed
... in your onResume:
hdlr.postDelayed(reminder, mRemindTime);
... and, in your onPause:
hdlr.removeCallbacks(reminder);
Please note that AsyncTask.cancel() does not really cancel running task. Instead it only sets a flag to notify that cancel has been requested. It is up to AsyncTask implementation if and when to take this flag into account.
Your current code does not check for cancellation after Thread.sleep returns. Correct implementation would be something like:
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (!isCancelled()){
try{
Thread.sleep(mRemindTime);
if(!isCancelled())
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
Also attribute 'running' and onCancelled() method in your asynctask are not needed. You can use method 'isCancelled()' instead.
It's generally a bad solution. AsyncTasks are not great in handling config changes.
Depending on how long the delays are, you can use AlarmManager.setInexactRepeating in order to show these dialogs. You can create new PendingIntent using PendingIntent.getService or PendingIntent.getBroadcast depending on how you'd like to implement showing dialog – (from Service you will be able to show only system-like dialogs).
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intentStart = new Intent();
intentStart.setAction(SHOW_DIALOG_ACTION);
PendingIntent pendingIntentStart = PendingIntent.getBroadcast(context, 0, intentStart, 0);
And register a BroadcastReceiver which will handle showing the dialog.
If the delays are not long, you can create a custom ThreadPoolExecutor which will do what you want.
I don't think AsyncTask should be used for that. Thread.sleep(mRemindTime) is also very ugly! If you need a timer use CountDownTimer.
I have following piece of code:
public class SomeActivity extends Activity {
Context context;
List<MenuItem> menuItems;
public void importList(View v) {
menuItems = new ArrayList<MenuItem>();
ProgressDialog dialog = ProgressDialog.show(this.context, "TITLE", "MSG");
MyAsyncTask task = new MyAsyncTask(context); // Context is here because I tried to create ProgressDialog inside pre/postExecute, but it doesn't work either
task.execute();
try {
// menuItems = task.get();
} catch(Exception e) {
// : (
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
this.context = this;
}
}
When I comment the line, where i get values from AsyncTask ("menuItems = task.get()") everythings work ok. But when I uncomment it, ProgressDialog appears AFTER the task is finished, and value returned. Why is that?
I think that it has sth to do with these contexts (that's why I included onCreate method) but I don't have any idea how to fix it. Obviously, I want ProgressDialog to display BEFORE task is finished, not after.
Not sure if relevant - MyAsyncTask is doing http request and some json parsing.
I think that it has sth to do with these contexts
Not at all. Also, when sending Context from an Activity you don't need to create a variable. Simply use this or ActivityName.this.
MyAsyncTask task = new MyAsyncTask(this);
But when I uncomment it, ProgressDialog appears AFTER the task is finished, and value returned. Why is that?
Calling get() blocks the UI, this is why you don't see the progress until it is done. You don't need to call this. The point of onProgressUpdate() is so the background thread (doInBackground()) can call it to update the UI since doInBackground() doesn't run on the UI.
Remove that line from your code and never look at it again. If there is a reason that you think you need it then please explain and we can help you find a better way.
Edit
See this SO answer on updating when the task is finished
I've written an application to use the box-api to upload files from a phone. I authenticate the user via:
Intent intent = new Intent(this, BoxAuthentication.class);
intent.putExtra("API_KEY", Version.BOX_API_KEY);
startActivityForResult(intent, BOX_RESULT);
and I get the token returned via:
if (resultCode == BoxAuthentication.AUTH_RESULT_SUCCESS)
{
if(data.getStringExtra("AUTH_TOKEN") != null) {
writeStringPref(Global.boxToken, data.getStringExtra("AUTH_TOKEN"), getApplicationContext());
} else {
toast("An error occured authenticating, please try again"); finish();
}
} else if (resultCode == BoxAuthentication.AUTH_RESULT_FAIL) {
toast("Unable to log into Box"); finish();
}
In my upload method I call box via:
final Box box = Box.getInstance(Version.BOX_API_KEY);
I then create a folder to upload the file to via:
box.createFolder(token, 0l, Global.folderName, true, new CreateFolderListener(){
#Override
public void onIOException(IOException e) {
//HANDLE
}
#Override
public void onComplete(BoxFolder boxFolder, String status) {
//DO STUFF
}
);
The problem I'm having is the first time the user authenticates I can use that saved token to upload files no problem. But, for example, an hour later when I make the call to create the folder it seems to just hang. Nothing is returned and neither method in the listener is called.
This also seems to happen when I push an update of the code to the device, it stops working and the only solution is to re-authenticate the user.
Reading the documentation it seems to the token shouldn't expire and as nothing is returned it doesn't seem like it is invalid.
I'm calling this code via an Intent Service if that makes a difference.
Anyone have any ideas what the issue could be?
Resolved this myself.. I was calling box from an Intent Service, which isn't on the main thread, therefore when making calls to the box api I was essentially calling a background thread from a background thread.. I have no idea why this worked sometimes and not others but using a handler (main thread) and calling the box-api via that seems to have resolved the issue.
One of my activities make a http request to a webservice to get some weather data when I start the application.
The issue that the activity will take 3-4 seconds to display because of the webservice request. ( Tested on actual device )
I know I m not doing this the right way. All I m doing is on the onCreate method, I m making the request , getting the xml back, parsing and displaying the data.
What is the best way to deal with webservice requests in Android so the application won't display a white screen while the request is being made? Maybe some threads.......
I know this is not happening on other application I have in my device that make request to get live data.
Notes:
1) The xml I getting back is not that big ( 5 elements with 5 nested elements on each one).
2) I tried with the 3G network and Wifi but the response time is still the same.
sample code:
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.clock_weather);
// this is where it is making the request and parsing the xml.
WeatherSet set = getWeatherCondition("New York, NY");
TextView currentWeather = (TextView) findViewById(R.id.current_weather);
currentWeather.setText("" + set.getWeatherCurrentCondition().getTempFahrenheit());
TextView currentWeatherH = (TextView) findViewById(R.id.current_weatherH);
currentWeatherH.setText("H: " + set.getWeatherForecastConditions().get(0).getTempMaxFahrenheit());
TextView currentWeatherL = (TextView) findViewById(R.id.current_weatherL);
currentWeatherL.setText("L: " + set.getWeatherForecastConditions().get(0).getTempMinFahrenheit());
ImageView currentWeatherIcon = (ImageView) findViewById(R.id.current_weather_icon);
String imageUrl = set.getWeatherCurrentCondition().getIconURL();
Drawable bitmapDrawable = getImageBitmap(imageUrl);
currentWeatherIcon.setImageDrawable(bitmapDrawable);
setForecastInfo(set, R.id.day1, R.id.day1_icon, R.id.day1_temp, 1 );
setForecastInfo(set, R.id.day2, R.id.day2_icon, R.id.day2_temp, 2 );
setForecastInfo(set, R.id.day3, R.id.day3_icon, R.id.day3_temp, 3 );
setForecastInfo(set, R.id.day4, R.id.day4_icon, R.id.day4_temp, 4 );
}
The time for your response is unpredictable - your network connection can be very poor and take seconds to transfer a few bytes. So the correct way to do this ( as you propose ) is to use thread. In our case android provides very useful class to handle this situations - AsynTask. After you read the docs you will notice that it has 3 very powerful methods that can help you
onPreExecute runs in the ui thread - very helpful to show some spinner or some progress indicator to show the user that you are doing some work in background
doInBackground runs in background - do your background work here
onPostExecute runs in the ui thread- when your are done with your background work hide the progress and update the gui with the newly received data.
private class getWeather extends AsyncTask<Context, Void, Cursor> {
ProgressDialog dialog = null;
protected void onPreExecute () {
dialog = ProgressDialog.show(CLASS.this, "",
"Loading. Please wait...", true);
}
#Override
protected Cursor doInBackground(Context... params) {
WeatherSet set = getWeatherCondition("New York, NY");
return null;
}
protected void onPostExecute(Cursor c) {
dialog.dismiss();
}
}
Then where you have WeatherSet set = getWeatherCondition("New York, NY"); now, you'll put new getWeather().execute(this);
I suggest reading how the AsyncTask works, and see why this should work. It goes outside the onCreate() method.
This is regarding AsyncTask, I just want to help understanding the concept, it is really useful:
DownloadFilesTask dft = new DownloadFilesTask(this);
//Executes the task with the specified parameters
dft.execute(Void1...);
...
...
...
dft.cancel(boolean);
private class DownloadFilesTask extends AsyncTask<Void1, Void2, Void3> {
//Runs on the UI thread before doInBackground(Void1...)
protected void onPreExecute() {
}
//runs in BACKGROUNG threat
protected Void3 doInBackground(Void1... urls) {
//it can be invoked from doInBackground(Void1...) to publish updates
//on the UI thread while doInBackground(Void1...) is still running
publishProgress(Void2...);
}
//Runs on the UI thread after publishProgress(Void2...) is invoked
protected void onProgressUpdate(Void2... progress) {
}
//Runs on the UI thread after doInBackground(Void1...) has finished
protected void onPostExecute(Void3) {
}
//runs in UI threat after cancel(boolean) is invoked and
//doInBackground(Void1...) has finished
protected void onCancelled(Void3) {
}
}
You can use AsynchTask class for your web service.You can write your time consuming task in on doInBackground.Also you can use a progress Dialog.
Here You can see how to work with AsynchTask.You can also update your UI while web service is parsing without waiting for the complete parsing using onPostUpdate method.
The response time is normal. Don't worry. Make it a point to run the web-service call in a separate thread.
Regarding the white screen, as soon as you start the web service call, fire a ProgressDialog box. This will run till you receive the response. As soon as you receive the response, dismiss the progressDialog box and start the new activity where you can display the result.
Use the following URLs for reference
http://www.helloandroid.com/tutorials/using-threads-and-progressdialog
http://thedevelopersinfo.wordpress.com/2009/10/16/showing-progressdialog-in-android-activity/
I have implemented the idea I'm giving you and it works perfectly.
Hope I was of some help
What is the best way to deal with webservice requests in Android so the application won't display a white screen while the request is being made?
Because you said 'white screen" I am assuming you are not using a progress dialog. You need to show a progress spinner/dialog to let the user know you are processing.
Have you check how large the data is? If the data is too large you really cant do anything , if you have control over the service its best to reduce the size.