I've made an app that sends a request to a webserver in a specified interval and gets XML data. It then parses the XML data, gets information from the phone (text messages, contacts or something similar) and shoots it back to the server with a http post request.
The problem is that it usually takes a few seconds for the app to get the info, which often leaves the app crashing. A dialog comes up saying the app has become unresponsive and asks if i want to close the app or wait, if i press wait it eventually starts working again.
Is AsyncTask the right solution to this problem?
Another thing i don't really understand is how AsyncTask actually works. Let's say i have two methods that do a lot of work and crashes the app, can i put both of them in one AsyncTask and just call them from doInBackground()?
I have also implemented something similar you are trying. That is sending request to server, receive XML response, parse XML, show result. View This. I have used AsyncTask for this.
Here is how i have implemented it using AsynTask
private class AsyncClass extends AsyncTask<Void, Void, Bundle>{
#Override
protected Bundle doInBackground(Void... arg0) {
Bundle b=startProcess();
// startBundle() method do all the processing and return result in a bundle. You can as many methods from within startBundle() method
return b;
}
#Override
protected void onPostExecute(Bundle result) {
Log.d(TAG , "In onPostExecute");
dialog.dismiss();
if(result==null)
Toast.makeText(cont, "Can't process query.\nTry again later.", Toast.LENGTH_LONG).show();
else{
Intent in = new Intent(cont, QueryResultDisplay.class);
Log.d(TAG , "Displaying");
in.putExtras(result);
cont.startActivity(in);
}
}
I give you brief description about your problem.
There are many possibility that you don't get data from server
if your network speed is very slow and you try to get all the
information from server and XML data then in this case if network crash then it show you error
if you're making request to that page which is not in server
Now, if you are facing the problem in code, then I give you the complete code of AsyncTask class which I had implemented in my project and it work fine.
private class GetLoginResponse extends AsyncTask<Void, Void, Boolean> {
private ProgressDialog progressDialog;
private String email;
private String password;
public GetLoginResponse(String emailId, String paswd) {
this.email = emailId;
this.password = paswd;
}
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(LoginActivity.this, "",
"Loading....", true, false);
}
#Override
protected Boolean doInBackground(Void... params) {
try {
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpclient.execute(httpGet);
//here u can check the reponse is ok and 200
} catch (NetworkException e) {
isNetworkError = true;
}
return false;
}
#Override
protected void onPostExecute(Boolean data) {
progressDialog.dismiss();
System.out.println("lOGIN RESPONSE for email = " + email + data);
}
}// end AsyncTask
This would solve your problem.
Yes, you can use AsyncTask.
The code called in doInBackground() must not touch the UI.
You can touch the UI thread with publishProgress().
Related
I'm rewriting some of my code after years of stepping away from it, and wanted to ask the community for a push in the right direction from a more modern architecture perspective (if one is needed).
--
One of the things my app does is send JSON data (maybe about 15-20 key-value pairs in total) upon clicking a button in the app.
I wrote the code to use Volley inside an AsyncTask to interact with my API.
I created a progress bar that would basically block the UI with a loading spinner until the data sent out and then got a confirm back from the API.
When that is done, it would dismiss and the user could carry on, but in a few rare cases some of my users complain the data is slow and the progress bar takes a while to go away (a full 5-10 minutes, which is interesting) or just never goes away at all.
I believe I erred here in creating the spinner, so I will do away with that and have it run in the background.
I found myself more confused than ever at the seemingly various approaches and frameworks out there for interacting with APIs that weren't as available years ago, so wanted to ask here in hopes of getting a more unified / community-based response.
My Question
Is it still a modern/ideal/commonly found approach to
Create a class that extends AsyncTask<type here>
Add your your onPreExecute, doInBackground(String... params), onPostExecute, declarations and code
Use code like this to interact with your API using Volley:
My old code is below, and unfortunately I've wrote many tasks like this (practically every case of where I need to make an API call has this large bloated file that follows the format below).
#Override
protected String doInBackground(String... params)
{
RequestQueue mmRequestQueue = Volley.newRequestQueue(context);
StringRequest mmyReq = new StringRequest(Method.POST, "ENDPOINT HERE", new Response.Listener<String>()
{
#Override
public void onResponse(String rawResponse) {
JSONObject response = null;
try {
response = new JSONObject(rawResponse);
//do stuff with response here..
}
catch(Exception ex)
{
//logging code here
}
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
//handle my stuff here
}
}
)
{
#Override
protected Map<String, String> getParams()
{
Map<String, String> params = new HashMap<String, String>();
//put my params here, for example:
params.put("helloworld", val1);
params.put("helloworld2", val2);
return params;
}
};
mmRequestQueue.add(mmyReq);
try {
Thread.sleep(1000); //not sure why this is even here anymore, I can't recall if this was intentional or not, maybe it was to show the progress bar which I won't be using anymore anyway?
} catch (InterruptedException e) {
}
return "";
}
In my android app, after sometime (hour or so.. not something determined) the connection and response to Google-AppEngine takes very long, something like 10 seconds or more.
After the first connection all other enpoint requests are done pretty quickly and this is why I believe this is SW issue and not internet connection issue.
Should I establish a 'dummy' connection as the app is loaded ?
Here is a sample code of an AsyncTask which tried to get User entity from AppEngine endpoint :
private class getUser extends AsyncTask<Void, Void, Boolean> {
long mTaskUserId = Constants.USER_ID_NO_ID_INFDICATOR;
String mIdInPlatform = Constants.USER_ID_NO_ID_INFDICATOR.toString();
Long mServerScore;
Context mContext;
String mUserName;
getUser(String idInPlatform, String userName, Context c) {
mIdInPlatform = idInPlatform;
mUserName = userName;
mContext = c;
}
#Override
protected Boolean doInBackground(Void... params) {
Userendpoint.Builder builder = new Userendpoint.Builder(
AndroidHttp.newCompatibleTransport(), new JacksonFactory(), null);
builder = CloudEndpointUtils.updateBuilder(builder);
Userendpoint endpoint = builder.build();
try {
User user = endpoint.getUser().execute();
} catch (IOException e) {
Log.e(TAG, "Error getting user details from server ", e);
return false;
}
this.mUserName = user.getUserName();
this.mServerScore = user.getScore();
this.mTaskUserId = user.getId();
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
setUserFacebookIdInPreferences(mIdInPlatform, mContext);
setUserIdInPreferences(this.mTaskUserId, mContext);
setScoreInPreferences(this.mServerScore, mContext);
setUserNameInPreferences(this.mUserName, mContext);
} else {
Toast.makeText(mContext, R.string.string_login_failed, Toast.LENGTH_SHORT).show();
}
// Restart login activity.
moveToLoginActivity(result);
super.onPostExecute(result);
}
}
Your application in Google App Engine uses two types of server instances: Dynamic instances and Resident instances. The difference is that dynamic instances are created in demand to serve traffic requests. Resident instances are always on.
When traffic stops, all your dynamic instances will shut down to save resources (and help you save money). The first time a request hits the server, a new dynamic instance will spin off to serve that request. The process of starting a new instance might take some time.
This is very likely what you are seeing in your application. To avoid that initial latency you can do two different things:
1) Optimize the time it takes for your code to load up.
2) Set up a Resident instance.
You can find more information on the Google documentation here:
https://developers.google.com/appengine/docs/adminconsole/instances#Introduction_to_Instances
You can warm-up your instances so that they're live before any query hits them - saving you this 10s delay. See documentation at:
https://developers.google.com/appengine/docs/adminconsole/instances#Warmup_Requests
Ever get those moments where you stare at a piece of code for an hour and still can't come up with an answer? Yeah that's me now.
I'm working on a final project for class and I can't get this one piece of code to work. It is absolutely crucial that it works, or else it defeats the purpose of the program. I even asked my professor for help... and he doesn't know how to help me solve the issue. I posted a similar problem a day ago but I want to re-ask in a different way to see if it helps (Sorry if its a re-post, I have no other source for help :/).
My problem is that I need to access array elements on my MainActivity after its populated inside an AsyncTask class. The array is defined globally but as soon as I try to access it's element or size, it crashes. I need to be able to call this array outside of AsyncTask.
I've searched for hours and tried "returning" the array from AsyncTask but it crashes as well.
Here is my code (I've included comments as to where it crashes):
public class PostsActivity extends Activity {
public static GlobalRates[] gr;
TextView view;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_posts);
view = (TextView) findViewById(R.id.textView1);
BitRateFetcher br = new BitRateFetcher();
br.execute();
// !!! Line below crashes !!!
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
}
private class BitRateFetcher extends AsyncTask<Void, Void, GlobalRates[]> {
private static final String TAG = "BitRateFetcher";
public String BIT_PAY_SERVER = "https://bitpay.com/api/rates";
private ProgressDialog dialog;
GlobalRates[] test;
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(PostsActivity.this);
dialog.setMessage("Please Wait... Downloading Information");
dialog.show();
}
#Override
protected GlobalRates[] doInBackground(Void... params) {
try {
// Create an HTTP client
HttpClient client = new DefaultHttpClient();
HttpGet getBitRates = new HttpGet(BIT_PAY_SERVER);
// Perform the request and check the status code
HttpResponse bitRatesResponse = client.execute(getBitRates);
StatusLine bitRatesStatus = bitRatesResponse.getStatusLine();
if (bitRatesStatus.getStatusCode() == 200) {
HttpEntity entity = bitRatesResponse.getEntity();
InputStream content = entity.getContent();
try {
// Read the server response and attempt to parse it as
// JSON
Reader reader = new InputStreamReader(content);
Gson gson = new Gson();
test = gson.fromJson(reader, GlobalRates[].class);
content.close();
entity.consumeContent();
} catch (Exception ex) {
Log.e(TAG, "Failed to parse JSON due to: " + ex);
failedLoadingPosts();
}
} else {
Log.e(TAG, "Server responded with status code: "
+ bitRatesStatus.getStatusCode());
failedLoadingPosts();
}
} catch (Exception ex) {
Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
failedLoadingPosts();
}
return test;
}
#Override
protected void onPostExecute(GlobalRates[] test) {
Log.i(TAG, "Test Size: " + test.length); // Returns 158
gr = test;
Log.i(TAG, "Gr Size: " + gr.length); // Returns 158
if (dialog.isShowing()) {
dialog.dismiss();
}
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.posts, menu);
return true;
}
private void failedLoadingPosts() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(PostsActivity.this,
"Failed to load Posts. Have a look at LogCat.",
Toast.LENGTH_SHORT).show();
}
});
}
}
Here is the error Log:
04-21 20:30:01.954: E/AndroidRuntime(32595): FATAL EXCEPTION: main
04-21 20:30:01.954: E/AndroidRuntime(32595): Process: com.example.postsactivity, PID: 32595
04-21 20:30:01.954: E/AndroidRuntime(32595): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.postsactivity/com.example.postsactivity.PostsActivity}: java.lang.NullPointerException
My class partner and I are unsure as to how we can call the global array (gr) properly. I understand that onCreate is not going to wait for AsyncTask to finish. What can we do to get this to work? Thanks for the help and understanding. You guys can prove more useful than my professor...
the problem is that you are accessing array of globalrates before it was initialized in your postExecute(). There are a lot of way to do this one of which is creating an interface or creating callbacks that waits for globalrate to be initialized after ur background thread is done.
Create a Interface
public interface SampleInterface {
void globalResultBackground(GlobalRates[] gr);
}
implements the interface to ur activity
public class PostsActivity extends Activity implements SampleInterface
pass the interface to your asynctask
//in the activity
BitRateFetcher br = new BitRateFetcher(this);
br.execute();
//in the asynctask class
SampleInterface si;
public BitRateFetcher(SampleInterface si){
this.si = si;
}
pass the result of the background thread to ur interface method
#Override
protected void onPostExecute(GlobalRates[] test) {
si.globalResultBackground(test);
Log.i(TAG, "Test Size: " + test.length); // Returns 158
gr = test;
Log.i(TAG, "Gr Size: " + gr.length); // Returns 158
if (dialog.isShowing()) {
dialog.dismiss();
}
}
in the activity where the you implemented the interface and generated the globalResultBackground method
#Override
public void globalResultBackground(GlobalRates[] gr) {
//you can freely access the globalrates here because this is called when the
//background thread is done
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
}
Pass in your activity to the AsyncTask and directly access its gr in your onPostExecute
Sample code:
private class BitRateFetcher extends AsyncTask {
PostsActivity activity;
...
public BitRateFetcher(PostsActivity activity){
this.activity= activity;
}
...
#Override
protected void onPostExecute(GlobalRates[] test) {
activity.gr = ...
}
}
In PostsActivity,
BitRateFetcher br = new BitRateFetcher(this);
Take care to attach and reAttach the activity to handle scenarios such as device rotations.
EDIT: Just noticed you've got a static, so you don't even need to pass in the activity. The same principle applies though.
GlobalRates[] gr is public and static, so you dont really new GlobalRates[] test
simply replace every instance of test with gr:
test = gson.fromJson(reader, GlobalRates[].class);
to
gr = gson.fromJson(reader, GlobalRates[].class);
if you need to access gr in Oncreate then move that logic to postExecute
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
I have two activities, one for displaying entries and another for creating them. Here's a standard use case:
User opens app and it displays entries.
User clicks button to create new entry and it opens the new activity.
User finishes creating entry and clicks "Done".
Activity finishes with setResult() and finish().
What I want to add is an AsyncTask that runs after the viewing activity is completely loaded. This AsyncTask shows a dialog while it's running. The problem is, when I put this code into onActivityResult() like this:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
case (Activity.RESULT_OK): {
mEntryListFragment.updateContent();
runAsyncTaskHere();
}
}
}
It runs the AsyncTask before it goes back to the main activity, and no dialog shows. Any ideas what I can do?
Edit: here's the AsyncTask.
public static class LogInTask extends AsyncTask<String, Void, Integer> {
protected String username = "";
protected String password = "";
protected ProgressDialog dialog;
protected boolean showDialogs;
public LogInTask(boolean sd) {
this.showDialogs = sd;
}
#Override
protected void onPreExecute() {
if (this.showDialogs) dialog = ProgressDialog.show(MainActivity.getContext(), null, "Logging in...");
}
#Override
protected Integer doInBackground(String... login) {
if (Config.DEBUG) Log.d(Config.APP_NAME, "in doInBackground() of LogInTask");
HttpResponse response = null;
String username = login[0];
String password = login[1];
try {
HttpPost httpPost = new HttpPost(URL_BASE + URL_LOGIN);
// main (JSON) login method
JSONObject json = new JSONObject().put("user",
new JSONObject()
.put("email", username)
.put("password", password)
.put("remember_me", Config.REMEMBER_LOGIN)
);
this.username = username;
this.password = password;
StringEntity se = new StringEntity(json.toString());
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(se);
response = httpclient.execute(httpPost);
String responseEntity = EntityUtils.toString(response.getEntity());
JSONObject result = new JSONObject(responseEntity);
return result.getBoolean("success") ? 1 : KEY_ERROR; // Return 1 if successful and -1 if not
} catch (UnknownHostException e) {
e.printStackTrace();
KEY_ERROR;
} catch (Exception e) {
e.printStackTrace();
return KEY_ERROR;
}
}
#Override
protected void onPostExecute(Integer result) {
if (this.showDialogs) {
dialog.dismiss();
String text = result == 1 ? "Login was successful." : "Login failed!";
makeToast(text);
}
}
}
Edit 2: I tried this:
boolean needToUpdate = false;
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
case (Activity.RESULT_OK): {
mEntryListFragment.updateContent();
needToUpdate = true;
}
}
}
#Override
public void onResume() {
super.onResume();
if (needToUpdate) {
runAsyncTaskHere();
mEntryListFragment.updateContent();
}
needToUpdate = false;
}
and it did the same thing.
Edit 3: here're the relevant sections of the AndroidManifest:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AddEntryActivity"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:configChanges="orientation" />
AsyncTask is a class for background processing, please, don't try to use it to do something with your UI or Activities, it always leads to a bad and unpredictable behaviour.
Maybe I should elaborate a little. Activities come and go, get killed and restored, destroyed and recreated as a usual way of theirs lives. It's absolutely normal.
Your AsyncTask may easily outlive your Activity, so when you're trying to access UI, it's already gone. Destroyed and recreated again, and all you've got is a dead pointer, which gives you an exception. Load your files in AsyncTask, do networking, access database. Just don't try to access UI/Activities, or your application will eventually crash and burn.
When you click on the Button to create new Entry, launch New Activity. but keep the Activity with entries. do not call finish() after intent.
After creation, and on clicking DONE, just close the Current Activity.(call finish())
mEntryListFragment.updateContent write this method in onStart() of the activity displaying entries.
Use ProgressDialog to achieve this.Start the dialog onPreExecute() and finish it onPostExecute() methods.You have to override those methods in your async task.
i think , you have to use Concept of callbacks ,
like when ever , loading is complete , implement your callback inside which you can deal with AsyncTask and after performing your task inside doInBackGround() , you can use onPostExecute() .
try something like this ,
Callback_interface
{
void async_run_after_some_time() ;
}
main_activity_class extends Activity Implements Callback_interface
{
void async_run_after_some_time()
{
// implement your toast here
}
}
onPostExecute()
{
main_activity_class ob = new main_activity_class ();
ob.async_run_after_some_time();
}
Just a thought.
Are you sure you are retrieving the proper context.
Is the boolean set correctly?
Plus, I do believe you are using HttpCore. From what I remember it requires some more attention networking and dis/connection wise due to it's granularity (compared to the HttpConnection class available by default). As such I don't think it is the best to use it with AsyncTask classes since they have little connection with the UI thread. Thus don't give much control for the user and may live beyond application's lifespan.
I'm trying to use an AsyncTask-extended class to handle connecting to a URL, parsing JSON, displaying an indeterminate ProgressDialog during parsing, and returning the results as key-value pairs in a HashMap to the main Activity. The results of the HashMap will then be read by the main Activity and put into form fields. However, even though I'm populating the HashMap in my AsyncTask (evidenced by println statements), calling a method in the main Activity that returns the HashMap yields an empty result. I can't figure out if this is something I'm doing wrong, or if I'm misunderstanding the capabilities of AsyncTask.
I'm debating converting my class that extends AsyncTask to an Activity. Essentially, the user should not be able to do anything else during this data search/parsing and should wait until the ProgressDialog goes away before they can interact with the application again (or by hitting the back button). Also, my application needs to be able to handle certain cases in my AsyncTask where exceptions are caught (can't connect to URL, bad JSON, product ID to search by cannot be found) and custom error dialogs are tailored for those exceptions. I could easily do this if this class were an Activity, as I could send back different result codes when calling finish(), depending on if an exception is caught.
Again, I'm not sure if AsyncTask is the best solution here, since the user will not be doing anything else while the information is being gathered and parsed. Please advise me if a new Activity would make sense or if I'm just mangling my implementation of a background thread.
MainActivity.java
mInitiateProductLookupButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
ProductLookup pl = new ProductLookup(id, MainActivity.this);
pl.execute();
// The below variable is always empty!
HashMap<String, String> productInfo = pl.getProductInfo();
applyProductInfoToFormFields(productInfo);
}
});
ProductLookup.java
public class ProductLookup extends AsyncTask<Object, Void, HashMap<String, String>> {
private String mProductID;
private Context mContext;
HashMap<String, String> mProductInfo;
ProgressDialog mDialog;
public ProductLookup(String id, Context applicationContext) {
mProductID = id;
mContext = applicationContext;
mProductInfo = new HashMap<String, String>();
}
#Override
protected void onPreExecute() {
mDialog = new ProgressDialog(mContext);
mDialog.setMessage("Loading product info. Please wait...");
mDialog.setIndeterminate(true);
mDialog.setCancelable(false);
mDialog.show();
}
#Override
protected void onPostExecute(HashMap<String, String> result){
super.onPostExecute(result);
mDialog.dismiss();
mProductInfo = result;
}
#Override
protected HashMap<String, String> doInBackground(Object... params) {
try {
// Connect to URL, parse JSON, and add key-value pairs to mProductInfo...
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
finally {
try {
// Close input/output reader variables
} catch (IOException e) {
e.printStackTrace();
}
}
return mProductInfo;
}
public HashMap<String, String> getProductInfo(){
return this.mProductInfo;
}
}
When you issue .execute() that runs as threaded and doesn't wait for the result.
So whatever you call after this it, is empty as the data has not been loaded yet.
You need to set on PostExecuted the result directly to your Activity via a setter MainActivity.this.setProductInfo(result)
There is some miss concept for you. The statement after AsyncTask.execute() will be executed just after the call. While your doInBackground is performing in other thread. Here, when you are using productInfo map, the doInBackground was not completed so result is not populated there
Easy solution for you is to use the result in the onPostExecute methord.
protected void onPostExecute(HashMap<String, String> result){
mDialog.dismiss();
mProductInfo = result;
applyProductInfoToFormFields(productInfo);
}