In my AsyncTask, I use Jsoup to pull all of the p tags from a web page, and I add them to an ArrayList that should then be used by an ArrayAdapter to fill the screen with the posts, but for some reason, the ArrayList is empty when I go to check it after the methods.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
newsItems = new ArrayList<String>();
fillNewsItems();
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
newsItems));
Log.d("news", Integer.toString(newsItems.size()));
}
private class GetNewsItemsTask extends AsyncTask<String, Void, String> {
protected String doInBackground(String... urls) {
try {
Document doc = Jsoup.connect(URL).get();
for (Element e : doc.getElementsByTag("p")) {
newsItems.add(e.text());
}
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Couldn't fetch articles, try again later.",
Toast.LENGTH_SHORT).show();
}
return null;
}
}
private void fillNewsItems() {
GetNewsItemsTask getNews = new GetNewsItemsTask();
getNews.execute(URL);
}
}
Does anyone know why the log statement in onCreate returns 0, and my list is empty?
AsyncTask has more possibilities than you are using right now. Basically the AsyncTask is a thread (which cannot change UI elements by default) but it provides a special feature: it synchronizes to the UI thread in the method onPostExecute().
So you can use this to set the ArrayAdapter inside the AsyncTask. Feel free to make use of onPreExecute() to show an information dialog.
This code should to the trick:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fillNewsItems();
Log.d("news", Integer.toString(newsItems.size()));
}
private class GetNewsItemsTask extends AsyncTask<Void, Void, ArrayList<String>> {
protected ArrayList<String> doInBackground(Void... urls) {
try {
ArrayList<String> items = new ArrayList<String>();
Document doc = Jsoup.connect(URL).get();
for (Element e : doc.getElementsByTag("p")) {
items.add(e.text());
}
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Couldn't fetch articles, try again later.",
Toast.LENGTH_SHORT).show();
}
return items;
}
#Override
protected void onPostExecute(ArrayList<String> items) {
newsItems = items; // I assume that newsItems is used elsewhere.
// If that's not the case -> remove it
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
}
}
private void fillNewsItems() {
GetNewsItemsTask getNews = new GetNewsItemsTask();
getNews.execute();
}
This a nice tutorial about asynchronous programming in Android: Android Threads, Handlers and AsyncTask
Most likely because the AsyncTask hasn't finished executing yet.
An AsyncTask is just that, async. It runs in the background at the same time.
It looks like you are expecting your code to block on fillNewsItems() until the AsyncTask
has finished, when in reality it returns almost immediately, right after starting the AsyncTask. So when you are trying to get the size of the list it still is zero, the AsyncTask hasn't finished yet.
Related
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...}
I have an Activity that extends a ListActivity , in onCreate method i set the contentView and call an AsyncTask to load data and the list get filled as I want, the problem is when I check for example the detail activity and want go back the the listView: I get the onCreate method executed, data are loaded again and the listView is scrolled to the top loosing my previous position.
What I want to achieve is something like google gmail app for example: the list view get loaded once and the scroll position is saved.
I've looked so much over here and I tried many solutions but none is working.
What is the best way to achieve this scenario?
my activity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
new LoadEvents().execute();
}
my asyncTask :
class LoadEvents extends AsyncTask<String, String, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(Home.this);
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
protected String doInBackground(String... args) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
JSONObject json = jParser.makeHttpRequest(AgendaIConstantes.URL_EVENTS, "GET", params);
......Processing
eventList.add(map);
}
} else {
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String file_url) {
pDialog.dismiss();
runOnUiThread(new Runnable() {
public void run() {
ListAdapter adapterRes = getListAdapter();
if(adapterRes == null){
ListAdapter adapter = new SimpleAdapter(Home.this, eventList,R.layout.list_item,
new String[] { .....},
new int[] { .....});
setListAdapter(adapter);
}
}
});
}
Just do not execute your loading task every call of onCreate method. You can use either boolean flag in your activity or save state using onSaveInstanceState.
By the way, calling getListAdapter in onPostExecute is not very ok method and your null checking shows it.
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 am getting from time to time testing my app error:
03-04 20:57:01.929: E/TestApp(13673): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
From questions like this: Whats this exception?, and my own experience (I got this same error from time to time as in mentioned question) I would like to ask you guys what I can do to get rid of it?
As far as I know, I can do some stuff on AsyncTask connected to View, so I don't know why I am getting this info.
This is my code:
private MyDBAdapter mySQLiteAdapter;
private ListView wordList;
private AsyncDBDownload asycn;
private ProgressDialog dbUpdate;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.smart_guide_ocr);
asycn = new AsyncDBDownload();
wordList = (ListView) findViewById(R.id.wordsList);
//...
}
#Override
protected void onResume() {
super.onResume();
asycn.execute(null);
}
private class AsyncDBDownload extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
try {
refreshList();//upload of contetn and set of adapter
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
dbUpdate.dismiss();
}
#Override
protected void onPreExecute() {
dbUpdate = ProgressDialog.show(TestAppActivity.this, "Wait",
"DB download");
}
}
private void refreshList() {
mySQLiteAdapter = new MyDBAdapter(TestAppActivity.this);
mySQLiteAdapter.open();
String[] columns = { MyDBAdapter.KEY_TRANSLATED, MyDBAdapter.KEY_WORD, MyDBAdapter.KEY_LANG,
MyDBAdapter.KEY_ID };
Cursor contentRead = mySQLiteAdapter.getAllEntries(false, columns,
null, null, null, null, MyDBAdapter.KEY_ID, null);
startManagingCursor(contentRead);
Log.d(TAG, Integer.toString(contentRead.getCount()));
RowItem adapterCursor = new RowItem(this, R.layout.save_word_row,
contentRead, columns, new int[] { R.id.translatedWord, R.id.orgWord, R.id.langInfo }, 0);
wordList.setAdapter(adapterCursor);
mySQLiteAdapter.close();
}
You must not call wordList.setAdapter(adapterCursor); from within refresList method. That's a way of "changing a view from a non-UI thread".
So, instead, save the adapterCursor instance and use it from within the onPostExecute method.
You can not manipulate your Views within a background task. Do all the loading you need in your AsyncTask, pass it back into the activity in onPostExecute and set your adapter then. Doing any form of UI manipulation in a background task or service will throw this error.
I'm using a ListView on my Activity and it takes a while to load from a SQLite DB, so I wanted to show a ProgressDialog to the user to let them know something is loading. I tried to run the task on a separate thread but I'm getting a CalledFromWrongThreadException. Here's my main Activity code:
#Override
public void onCreate(Bundle savedInstanceState)
{
try
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.open_issues);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
//Set Window title.
final TextView title = (TextView) findViewById(R.id.customTitle);
if (title != null)
title.setText("Open Issues");
//Call Async Task to run in the background.
new LoadIssuesTask().execute();
}
catch (Exception e)
{
Errors.LogError(e);
}
}
And the LoadIssuesTask code:
private class LoadIssuesTask extends AsyncTask<Void, Void, Cursor> {
ProgressDialog pdDialog = null;
protected void onPreExecute()
{
try
{
pdDialog = new ProgressDialog(OpenIssues.this);
pdDialog.setMessage("Loading Issues and Activities, please wait...");
pdDialog.show();
}
catch (Exception e)
{
Errors.LogError(e);
}
}
#Override
protected Cursor doInBackground(Void... params) {
LoadIssues();
return null;
}
#Override
protected void onPostExecute(Cursor c) {
pdDialog.dismiss();
pdDialog = null;
}
}
And the LoadIssues code:
private void LoadIssues(){
//Set listview of Issues.
ListView lvIssues = (ListView)findViewById(R.id.lvIssues);
lvIssues.setOnItemClickListener(viewIssuesListener);
IssueCreator = new IssueInfoCreator(this, Integer.parseInt(AppPreferences.mDBVersion));
IssueCreator.open();
lvIssues.setAdapter(new IssueInfoAdapter(this, IssueCreator.queryAll()));
IssueCreator.close();
}
Constructor for IssueInfoAdapter:
public IssueInfoAdapter(Context c, List<IssueInfo> list){
mListIssueInfo = list;
//create layout inflater.
mInflater = LayoutInflater.from(c);
}
It's throwing the error on the .setAdapter method inside LoadIssues().
ERROR:
03-12 10:41:23.174: E/AndroidRuntime(11379): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
You're trying to access the views in the doInBackground method that doesn't run on the main UI thread. You'll have to set your adapter in the method onPostExecute that runs on the UI thread:
#Override
protected void onPostExecute(List<IsueInfo> items) {
pdDialog.dismiss();
ListView lvIssues = (ListView)findViewById(R.id.lvIssues);
lvIssues.setOnItemClickListener(viewIssuesListener);
lvIssues.setAdapter(new IssueInfoAdapter(this, items));
}
and in your doInBackground method:
#Override
protected List<IssueInfo> doInBackground(Void... params) {
IssueCreator = new IssueInfoCreator(this, Integer.parseInt(AppPreferences.mDBVersion));
IssueCreator.open();
IssueCreator.close();
return IssueCreator.queryAll();
}
Also your AsyncTask should be:
private class LoadIssuesTask extends AsyncTask<Void, Void, List<IssueInfo>>
In private void LoadIssues method call handler.setMessage(0) and create a private Handler instance to call setAdapter method
Use Handler instead of Asynctask.