I want to develop an Android application which asks a server for some data, and displays these data in a ListView.
Currently, I am using a single Activity (without fragments), and the layout is very simple: it consists of an ImageView, an EditText and a ListView. When the ImageView is clicked it gets the content of the EditText and sends it to the server as a new item and automatically updates the Listview (am calling the method of retreiving the objects after the add operation).
I created an AsyncTask class with a progress dialog inside the Activity which the job in background is getting the objects from the server and then assigning them to a List (member of the enclosing class).
With that practice, am facing a lot of problems: the list gets displayed correctly but very slowly! and when I press the ImageView the AsyncTask is then called to do its job after adding the new item but the problem is that its dialog never dismisses.
My question is what is the best practice with this situation in Android? what is the best design pattern? should I use fragments? How should I manage my Threads?
UDATE:
here is the AsyncTask:
class RemoteDataTask extends AsyncTask<Void, Void, Void> {
private UserDetailsActivity context;
RemoteDataTask(UserDetailsActivity context) {
this.context = context;
}
#Override
protected void onPreExecute() {
super.onPreExecute();;
mProgressDialog = ProgressDialog.show(context, "Looking for posts", "Loading...", true, false);
}
#Override
protected Void doInBackground(Void... params) {
UserDetailsActivity.this.posts.clear();
posts = new PostManager(context).userPosts(ParseUser.getCurrentUser());
return null;
}
#Override
protected void onPostExecute(Void result) {
postList = (ListView) findViewById(R.id.post_list);
adapter = new PostsListAdapter(context, UserDetailsActivity.this.posts);
postList.setAdapter(adapter);
mProgressDialog.dismiss();
}
}
And the method wich retreives the posts:
public void refreshPostList() {
try {
BusInfo.getInstance().register(UserDetailsActivity.this); // register the Bus to recieve results.
} catch (Exception e) {
Log.d("My application says : ;) ", "Erro registering " + e);
}
pd = ProgressDialog.show(this, "Please Wait", "Loading");
new ExprienceEdit(this, "hello").execute();
}
And the Button with its method
public void newPost(View v) {
ParseObject post = new ParseObject("Post");
post.put("content", editText.getText().toString());
post.saveInBackground();
refreshPostList();
}
<ImageView
android:id="#+id/new_post"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:onClick="newPost"
android:padding="10dp"
android:src="#drawable/ic_action_post" />
Regarding the progress dialog not being dismissed:
Where is mProgressDialog dialog declared? I suggest you move it into the RemoteDataTask. (I'm guessing you are at some point overriding the current instance and therefore the dismiss isn't working)
Regarding the slow refresh of the list, post your Adapter code. You should do correct recycling of views and you shouldn't recreate the Adapter everytime but set the data and call notifyDataSetChanged so the listView will recycle the views with the new data. Look into this answer regarding correct recycling of views: https://stackoverflow.com/a/6923513/348378
Edit 1
I also suggest this to prevent having multiple refreshTasks:
public void refreshPostList() {
if(dataTask == null) {
dataTask = new RemoteDataTask(this).execute();
}
}
#Override
protected void onPostExecute(Void result) {
// you stuff
dataTask = null;
}
You can also consider cancelling the current task and starting a new one depending on required behavior.
you should pass ProgressDialog to your AsyncTask class constructor and in any class that want to use AsyncTask class(in your case RemoteDataTask) you should instantiate progress dialog and pass as second argument to your RemoteDataTask to control the visibility from specific custom class.
maybe this help.
The best way to deal with asynctasks is by using otto :
Otto actually is a singltone bus : please refer to this website http://square.github.io/otto/
Any piece of code would be great to help you more with the problem you are facing.
Any questions I am ready to answer.
BusInfo.getInstance.register(ActivityName.this) // register the Bus to recieve results.
pd = ProgressDialog.show(ActivityName.this, "Please Wait", "Loading");
new ExperienceEdit(getApplicationContext(), "hello").execute(); //async task to be executed let us say on button click
Now the experience edit is:
public class ExperienceEdit extends AsyncTask<Void, Void, String> {
Context c;
String id;
public ExperienceEdit(Context c, String id\) {
this.c = c;
this.id = id;
}
#Override
protected String doInBackground(Void... voids) {
//right the call to back here
}
#Override
public void onPostExecute(String result) {
try {
BusInfo.getInstance().post(new ExperienceEditResult(result));
} catch (Exception e) {
e.printStackTrace();
}
}
}
The result after posting is subscribed at the activity like this :
#Subscribe
public void onAsyncTaskResult(EditExperienceResult result) {
if (pd != null)
pd.dismiss();
object = result.getResult();
if (object != null) {
if (object.equals("success")) {
Toast.makeText(getApplicationContext(), "Success", Toast.LENGTH_SHORT).show();
onBackPressed();
} else Toast.makeText(getApplicationContext(), "Failure", Toast.LENGTH_SHORT).show();
} else
Toast.makeText(getApplicationContext(), "Please try again later", Toast.LENGTH_SHORT).show();
}
The ExperienceEditResult here happens to be a string (you can have it whatever you want) :
public class ExperienceEditResult {
private String result;
public ExperienceEditResult(String result) {
this.result = result;
}
public String getResult() {
return result;
}
}
The BusInfo class is :
public class BusInfo {
private static final Bus BUS = new Bus();
public static Bus getInstance() {
return BUS;
}
}
Do not forget to unregister the bus onDestroy of the activity: BusInfo.getInstance().unregister(ActivityName.this);
If you aslso want to prevent the progress dialogue from always showing because sometimes it is showing twice due to a double click on button add this : if(pd!=null&&pd.isShowing()){
Log.v("pd is showing","showing");
} else {pd= ProgressDialgue.show...}
Related
I am trying to unzip a folder using Android's AsyncTask. The class (called Decompress) is an inner class of Unzip where Unzip itself is a non-Activity class. The pseudo-code is:
public class Unzip {
private String index;
private String unzipDest; //destination file for storing folder.
private Activity activity;
private boolean result; //result of decompress.
public void unzip(String loc) {
Decompress workThread = new Decompress(loc, activity);
workThread.execute();
if(unzip operation was successful) {
display(index);
}
//Class Decompress:
class Decompress extends AsyncTask<Void, Integer, Boolean> {
private ProgressDialog pd = null;
private Context mContext;
private String loc;
private int nEntries;
private int entriesUnzipped;
public Decompress(String location, Context c) {
loc = location;
mContext = c;
nEntries = 0;
entriesUnzipped = 0;
Log.v(this.toString(), "Exiting decompress constructor.");
}
#Override
protected void onPreExecute() {
Log.v(this.toString(), "Inside onPreExecute.");
pd = new ProgressDialog(mContext);
pd.setTitle("Unzipping folder.");
pd.setMessage("Unzip in progress.");
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
Log.v(this.toString(), "Showing dialog and exiting.");
pd.show();
}
#Override
protected Boolean doInBackground(Void... params) {
//unzip operation goes here.
unzipDest = something; //unzip destination is set here.
if(unzip operation is successful) {
result = true;
index = url pointing to location of unzipped folder.
} else {
result = false;
}
}
#Override
protected void onPostExecute(Boolean result) {
if(result) {
if(pd != null) {
pd.setTitle("Success");
pd.setMessage("folder is now ready for use.");
pd.show();
pd.dismiss();
pd = null;
Log.v(this.toString(), "Unzipped.");
index = unzipDest + "/someURL";
Log.v(this.toString(), "index present in: " + index);
}
} else {
pd = ProgressDialog.show(mContext, "Failure", "Cannot unzip.");
pd.dismiss();
}
}
}
Problems I am facing:
1. The value of unzipDest and index, updated in doInBackground, remain null to Unzip and all its objects. How can I ensure that the values remain updated?
2. I know that doInBackground occurs in a thread separate from the main UI thread. Does that mean that any values updated in the new thread will be lost once that thread returns?
How can I ensure that the values remain updated?
They will be updated since they are member variables. However, since AsyncTask is asynchrounous, they might not be updated yet when you check them. You can use an interface to create a callback when these values are updated. This SO answer covers how to do this
Does that mean that any values updated in the new thread will be lost once that thread returns?
No they shouldn't be "lost". They probably just haven't been changed in the AsyncTask when you check them.
Since this isn't your actual code I can't see when you are trying to access them but you can use the interface method or call the functions that need these values in onPostExecute(). You also can do a null check before trying to access them. It just depends on the functionality and flow that you need as to which is the best way. Hope that helps.
Edit
In the answer I linked to, you tell the Activity that you will use that interface and override its method(s) with implements AsyncResponse in your Activity declaration after creating the separate interface class
public class MainActivity implements AsyncResponse{
then, in your Activity still, you override the method you declared in that class (void processFinish(String output);)
#Override
void processFinish(String output){ // using same params as onPostExecute()
//this you will received result fired from async class of onPostExecute(result) method.
}
then this is called in onPostExecute() when the listener sees that it is done with delegate.processFinish(result); delegate is an instance of AsyncResponse (your interface class)
public class AasyncTask extends AsyncTask{
public AsyncResponse delegate=null;
#Override
protected void onPostExecute(String result) {
delegate.processFinish(result);
}
Interface example taken from linked answer above and adjusted/commented for clarity. So be sure to upvote that answer if it helps anyone.
i am new to Android and currently start learning how to implement PullToRefreshListview by using this library of chrisbanes. can you guys please explain to me, where should i put my code to call API for getting data, and which part should i set the(ImageBitmap) after i get the data (image URL) from API. as i know, we should do something in background to avoid the UI freeze when loading Image to UI, but i am not sure. Please help.
The following is the sample code from the library:
please explain to me what should i do in GetDataTask and onPostExecute. In the case like loading image.
#Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
// Do work to refresh the list here.
new GetDataTask().execute();
}
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
#Override
protected String[] doInBackground(Void... params) {
// Simulates a background job.
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
return mStrings;
}
#Override
protected void onPostExecute(String[] result) {
mListItems.addFirst("Added after refresh...");
mAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
mPullRefreshListView.onRefreshComplete();
super.onPostExecute(result);
}
}
Sorry for the newbie question, i jsust want to confirm it in order to follow the standard. sorry for my bad english
I think you should put your GetDataTask in a separated class with a Listener. This is an example of a listener you can have:
public abstract class RemoteCallListener implements IRemoteCallListener {
#Override
public abstract void onString(String s); //String is just an example.
#Override
public abstract void onError(Exception e);
}
You should give the listener to your constructor if your async task.
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
RemoteCallListener listener;
public GetDataTask(){
}
public GetDataTask(RemoteCallListener listener){
this.listener = listener;
}
#Override
protected String[] doInBackground(Void... params) {
// Simulates a background job.
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
return mStrings;
}
#Override
protected void onPostExecute(String[] result) {
listener.onString(result);
// mListItems.addFirst("Added after refresh...");
// mAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
// mPullRefreshListView.onRefreshComplete();
super.onPostExecute(result);
}
}
To make an instance of the call you should do something like
GetDataTask task = new GetDataTask(yourlistener);
task.execute("your link");
And in your 'controller'class you should make an instance of RemoteCallListener and when onString is called:
mListItems.addFirst("Added after refresh...");
mAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
mPullRefreshListView.onRefreshComplete();
You can also freeze the UI by the Dialogs class from android. An example is given here:
public final static ProgressDialog showLoading(Context c, String title,
String message, boolean indeterminate) {
ProgressDialog p = new ProgressDialog(c);
p.setTitle(title);
p.setMessage(message);
p.setCancelable(false);
p.setIndeterminate(indeterminate);
if (!indeterminate) {
// p.setProgressDrawable( c.getResources().getDrawable(
// R.drawable.progress ) );
p.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
p.setProgress(0);
p.setMax(100);
}
p.show();
return p;
}
But dont forget to close your Dialog!
You can contact me anytime if you have further questions
GetDataTask is used to call your webservice again to get the new list items. These things should be done in doInBackground. Once you get the new list item need to set the new adapter for your listview in onPostExecute.
I made an application for Android that originally targeted a lower version (2.3). After I got my proof-of-concept working, I tried to get it to work on Android 4. That's when I got the NetworkOnMainThread exception.
After doing some research, I quickly found the AsyncTask, which sounded awesome. The problem is, I'm having a hard time wrapping my mind around it. For instance, here's my original code:
public void Refresh(Context c)
{
SummaryModel model = MobileController.FetchSummary(c);
TextView txtCurrentWeight = (TextView)findViewById(R.id.txtCurrentWeight);
TextView txtWeightChange = (TextView)findViewById(R.id.txtWeightChange);
TextView txtAvgPerWeek = (TextView)findViewById(R.id.txtAvgPerWeek);
if(model.ErrorMessage == "")
{
txtCurrentWeight.setText(model.CurrentWeight);
txtWeightChange.setText(model.WeightChange);
txtAvgPerWeek.setText(model.Average);
}
else
{
Toast.makeText(c, model.ErrorMessage, Toast.LENGTH_LONG).show();
txtCurrentWeight.setText("");
txtWeightChange.setText("");
txtAvgPerWeek.setText("");
}
}
I created an AsychTask like this:
public class WebMethodTask extends AsyncTask<Object, Integer, Object> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
SummaryModel model = (SummaryModel)result;
// Can't seem to access UI items here??
}
#Override
protected Object doInBackground(Object... params) {
Context c = (Context)params[0];
return MobileController.FetchSummary(c);
}
}
How do I access the UI items from the onPostExecute method? Or, do I have the wrong idea on how to use AsyncTask?
Thanks!
You should be able to accessUI where you put your comments (in the postExecute method)
Additionally, I would suggest to use more specialized class with for AsyncTask, so that your code looks better :
public class WebMethodTask extends AsyncTask<Object, Integer, SummaryModel> {
private Activity source;
public WebMethodTask(Activity activity) {
this.source=activity;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(SummaryModel model) {
super.onPostExecute(model );
TextView txtCurrentWeight = (TextView)source.findViewById(R.id.txtCurrentWeight);
TextView txtWeightChange = (TextView)source.findViewById(R.id.txtWeightChange);
TextView txtAvgPerWeek = (TextView)source.findViewById(R.id.txtAvgPerWeek);
if(model.ErrorMessage.length()==0)
{
txtCurrentWeight.setText(model.CurrentWeight);
txtWeightChange.setText(model.WeightChange);
txtAvgPerWeek.setText(model.Average);
}
else
{
Toast.makeText(c, model.ErrorMessage, Toast.LENGTH_LONG).show();
txtCurrentWeight.setText("");
txtWeightChange.setText("");
txtAvgPerWeek.setText("");
}
}
#Override
protected SummaryModel doInBackground(Context ... params) {
Context c = params[0];
return MobileController.FetchSummary(c);
}
}
Edit : Added a reference to your activity, to take your last comment into account.
However, if you acynctask can be long, it's maybe not a very good idea to keep a reference on an activity.
It would be a better design to create a listenerclass that will accept some displayModel(CummaryModel) method, and whose responsability is to cal the setText methods if the activity has not been paused / stopped in the meanwhile...
Fill the ui items with the loaded model data in the WebMethodTask#onPostExecute method.
You need a reference to your UI controls. When passing references to your UI controls to the ASyncTask you will create problems.
Assume the following scenario:
show activity (activity instance 1)
call async task with te activity as reference.
rotate your device (by default a device rotation will create a new activity) -> (activity instance 2)
when the sync task is finished, activity instance 1 is used to display the results. However the activity no longer exists causing exceptions.
The conclusion is that the ASyncTask should not be used for network activity related background tasks.
Fortunately there is a solution: RoboSpice.
RoboSpice uses another approach. Look at https://github.com/octo-online/robospice/wiki/Understand-the-basics-of-RoboSpice-in-30-seconds for a good explanation.
More information: https://github.com/octo-online/robospice
create an inner class in refresh method as
enter code herepublic void Refresh(Context c)
{
SummaryModel model = MobileController.FetchSummary(c);
TextView txtCurrentWeight = (TextView)findViewById(R.id.txtCurrentWeight);
TextView txtWeightChange = (TextView)findViewById(R.id.txtWeightChange);
TextView txtAvgPerWeek = (TextView)findViewById(R.id.txtAvgPerWeek);
if(model.ErrorMessage == "")
{
txtCurrentWeight.setText(model.CurrentWeight);
txtWeightChange.setText(model.WeightChange);
txtAvgPerWeek.setText(model.Average);
}
else
{
Toast.makeText(c, model.ErrorMessage, Toast.LENGTH_LONG).show();
txtCurrentWeight.setText("");
txtWeightChange.setText("");
txtAvgPerWeek.setText("");
}
class WebMethodTask extends AsyncTask<Object, Integer, Object> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
SummaryModel model = (SummaryModel)result;
// Can't seem to access UI items here??
}
#Override
protected Object doInBackground(Object... params) {
Context c = (Context)params[0];
return MobileController.FetchSummary(c);
}
}
}
my issue is the same as this
Instance variable of Activity not being set in onPostExecute of AsyncTask or how to return data from AsyncTask to main UI thread
but i want to send the data back to the same calling activity. Doesnt startActivity for intents always restart the activity
On option is to use listeners, where you create an interface that your activity implents, something like:
public interface AsyncListener {
public void doStuff( MyObject obj );
}
That way, if you're subclassing AsyncTask, it is easy to add this listener, then in onPostExecute(), you could do something like:
protected void onPostExecute( MyObject obj ) {
asyncListener.doStuff(obj);
}
This depends on your class structure, but if your AsyncTask is a class within your Activity then you can reference methods of that activity.
What you would do is in your onPostExecute method call a function of your Activity that passes some data that was retrieved in the AsyncTask to the activity where you can then use it..
The code would look like this
class YourActivity extends Activity {
private static final int DIALOG_LOADING = 1;
public void onCreate(Bundle savedState) {
setContentView(R.layout.yourlayout);
showDialog(DIALOG_LOADING);
new LongRunningTask1().execute(1,2,3);
}
protected Dialog onCreateDialog(int dialogId) {
switch(dialogId) {
case DIALOG_LOADING:
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setTitle("Loading Data");
pDialog.setMessage("Loading Data, please wait...");
return pDialog;
default:
return super.onCreateDialog(dialogId);
}
}
private void onBackgroundTaskDataObtained(List<String> results) {
dismissDialog(DIALOG_LOADING);
//do stuff with the results here..
}
private class LongRunningTask extends AsyncTask<Long, Integer, List<String>> {
#Override
protected void onPreExecute() {
//do pre execute stuff
}
#Override
protected List<String> doInBackground(Long... params) {
List<String> myData = new ArrayList<String>();
for (int i = 0; i < params.length; i++) {
try {
Thread.sleep(params[i] * 1000);
myData.add("Some Data" + i);
} catch(InterruptedException ex) {
}
}
return myData;
}
#Override
protected void onPostExecute(List<String> result) {
YourActivity.this.onBackgroundTaskDataObtained(result);
}
}
}
So the typical flow is like this, set the view of the current page, and then show a progress dialog. Right after that start the async task (or whenever, it doesn't matter really).
After your async task is complete, call a function of the activity and pass it the data.
Don't use shared data within the async task or you risk issues with threading.. Instead once you are done with it pass it to the activity. If you want to update the view progressively while doing work you can use on onProgressUpdate
I am fetching an XML data from the web using HTTP GET/POST.
Right now i have done it in simple way (i.e. without threaed).
According to the below link, i tried to implement it with Progress bar dialog with Thread
http://www.ceveni.com/2009/09/sample-progress-bar-dialog-in-android.html
But how do i come to know about the finish time of fetching XML from the web.(i.e. what should be the sleep time and also when to dismiss the progress bar dialog box)
Let me clear more about my problem => In activity,when the user click on "Fetch data" button,
the "Progress bar" dialog box should be appeared and it should be disappear only when the fetching is completed
successfully.
I think this can be done with "AsyncTask" but dont know how to use that concept for this problem.
So how do i do it ?
public void onClick() {
new FetchTask(context).execute(null);
}
public class FetchTask extends AsyncTask<Object, Object, Object > {
private ProgressDialog dlg;
private Context ctx;
public FetchTask(Context context) {
ctx = context;
}
#Override
protected void onPreExecute() {
dlg = new ProgressDialog(ctx);
dlg.setMessage("Loading....");
super.onPreExecute();
}
#Override
protected void onPostExecute(Object result) {
dlg.dismiss();
if ( result instanceof Exception ) {
// show error message
} else {
// display data
}
super.onPostExecute(result);
}
#Override
protected Object doInBackground(Object... params) {
try {
// String rawData = doPost("yourUrl");
// XMLTree data = yourParser.parse(rawData);
// return data;
} catch ( Exception e ) {
return e;
}
}
}