Simple fix for scrolling when custom adapter is changing? - android

Using my Custom Adapter - I am populating the listiew using an AsyncTask. The doInBackground updates the ArrayLists which are used for the Custom Adapter. The onProgressUpdate calls the adapter.notifyDataSetChanged();
When loading a lot of files, I wanted the UI to be responsive, but when you try to scroll when the list is still being populated, I get this error:
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes.
#Override
protected Boolean doInBackground(DbxFileSystem... params) {
//Opens thumbnails for each image contained in the dropbox folder
try {
DbxFileSystem fileSystem = params[0];
numFiles = fileSystem.listFolder(currentPath).size();
for (DbxFileInfo fileInfo: fileSystem.listFolder(currentPath)) {
String filename = fileInfo.path.getName();
try{
if(!fileInfo.isFolder)
{
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
pix.add(image);
paths.add(fileInfo.path);
publishProgress(1); //use this to update the ListView
}
else
{
//must be a folder if it has no thumb, so add folder icon
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.dbfolder);
pix.add(image);
paths.add(fileInfo.path);
publishProgress(1);
}
catch(Exception e)
{
e.printStackTrace();
}
System.gc();
}
}
catch (Exception e) {
e.printStackTrace();
return false;
} finally {
}
return true;
}
#Override
protected void onProgressUpdate(Integer...progress) {
if(pix.size()==1) // //not ideal but works for now, only bind the adapter if its the first time we have looped through.
{
adapter = new ImageAdapter(getApplicationContext(), pix, paths, numFiles);
lstView.setAdapter(adapter);
}
adapter.notifyDataSetChanged();
lstView.requestLayout();
super.onProgressUpdate(progress);
}
Can anyone see what the problem is here? And what can I do to prevent it?
I was originally using a progressbar and only displayed the fill contents once they had all loaded, but I would much rather show the incremental load and let the user scroll even as its loading the content.
p.s. I see this is a common enough issue and have read several similar questions, but I still cannot work out what I need to change.

Your adapter has pix, numFiles and paths as data source and since you are modifying those collections in doInBackground() which is running on a non UI thread you get this exception.
new ImageAdapter(getApplicationContext(), pix, paths, numFiles); passes those collection via reference.

Related

AsyncTask show loading progress and return a value jsoup

I'm working on a web application that will parse the site and load the news dynamically into the CardView. For now it works and does all the needed stuff. But it's not exactly what I want.
Here's a piece of my code to understand what I am talking about:
public class NewsPage extends ActionBarActivity {
List<NewCard> listNC = new ArrayList<NewCard>();
class NewsParser extends AsyncTask<Void,Void,List<NewCard>> {
Document doc;
List<NewCard> nc = new ArrayList<NewCard>();
ProgressDialog progressDialog;
#Override
protected void onPreExecute()
{
// progressDialog= ProgressDialog.show(NewsPage.this, "Parsing the site", "Please wait while the information is loading...", true);
};
#Override
protected List<NewCard> doInBackground(Void... params) {
try {
//some code skipped
nc.add(new NewCard(forHeader.html(), forDesc, URLforImg, forHeader.attr("href")));
}
} catch (IOException e) {
e.printStackTrace();
}
return nc;
}
protected void onPostExecute(String[] s) {
progressDialog.dismiss();//This method is being called out by new <class name>.execute();
//listNC = new ArrayList<NewCard>(nc);
}
}
In here I am retrieving article headlines for further opening.
This is my onCreate() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news_page);
RecyclerView recList = (RecyclerView) findViewById(R.id.cardList);
recList.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);
try {
NewsParser np = new NewsParser();
np.execute();
listNC = np.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
NewsAdapter na = new NewsAdapter(listNC);
size = na.sizes;
recList.setAdapter(na);
I'm using my adapter to fill the cards with information and to handle onClick events.
My question is this:
I need to retrieve information from my AsyncTask in order to create a
list of cards (in this case I need an amount of cards) and I am not
sure I can go on without returning values. But it makes my app freeze
and not show any interface until the action is completed. How is it
better to handle? Maybe I need to make it all different? How do I
load news headlines separately (not all together but in order)? And
what kind of loop (I don't know how to call it correctly) do I need
to add news as they load (because my program doesn't work if it
doesn't have the list before doing UI stuff)?
I've tried to tell every detail from my code and if it's needed I might add my Adapter code too.
Your UI is freezing because your get() method in the try block is blocking waiting on the AsyncTask to complete. This defeats the purpose of even using the AsyncTask. Instead, create your Adapter before you kick off the AsyncTask and then in the onPostExecute() set the data for the adapter to be the new result and call the adapter's notifyDataSetChanged() method. This will cause the UI to pick up the changes.
Be careful with your use of AsyncTask or any other threading mechanism. They are not lifecycle aware, so if the onPostExecute() method has any strong references to the Activity or its inner member fields and tries to directly use them it could run into state exceptions.

ListView scroll bugging while images downloading via AsynkTask

I want to show a listview with some texts and images. When i'm creating a view for listview, i'm calling method show of my PictureImageView, that downloads and showing image. Download is running in new thread in AsyncTask. But while image downloading i can't normally scroll listview, it's twitches.
To run AsyncTask in new thread i call executeOnExecutor method. I tried to call execute method, but then scroll stops at all till download is over.
Here my class.
public class PictureImageView extends LinearLayout {
private Drawable image_drawable = null;
private ImageView image = null;
...
protected String getImageURL() {
...
return uri;
}
public void show() {
if (image_drawable != null) {
image.setImageDrawable(image_drawable);
addView(image);
} else {
// target Android API >= 14 so executeOnExecutor works in another thread
new RequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getImageURL());
}
}
protected void onResponse(Drawable image) {
if (image != null) {
image_drawable = image;
show();
}
}
class RequestTask extends AsyncTask<String, String, Drawable> {
#Override
protected Drawable doInBackground(String... urls) {
Drawable image = null;
HttpURLConnection connection = null;
InputStream connection_stream = null;
try {
URL url = new URL(urls[0]);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setUseCaches(true);
connection.connect();
int response_code = connection.getResponseCode();
//#see http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html
if (response_code == HttpURLConnection.HTTP_OK || response_code == -1) {
connection_stream = connection.getInputStream();
image = Drawable.createFromStream(connection_stream, null);
}
} catch (MalformedURLException e) {
} catch (IOException e) {
} finally {
if (connection != null) {
connection.disconnect();
}
if (connection_stream != null) {
try {
connection_stream.close();
} catch (IOException e) {
}
}
}
return image;
}
#Override
protected void onPostExecute(Drawable image) {
PictureImageView.this.onResponse(image);
}
}
}
How can i fix it? I guess, the problem is that there is no any another thread, but how to check it?
I've delt with this exact problem first hand. The twitching comes from updating the ListView each time a picture is downloaded. There are 2 different approaches I took to fix this. Depending on your project set up one my work
Approach 1: Minimize twitching by only updating once
In my case I used an AsyncTask as a seperate class with a call back to the starting activity. What I did was use a singleThreadExecutor so that the task to download each user's picture were serialy executed and a counter to track how many treads were started/left - increamenting each time I added one to the executor, decrementing each time the call back was called. For example
#Override
public void userPic(Bitmap pic){
if(pic != null){
//use picture
}
taskCounter--
if(taskCounter == 0){
updateUserListView();
}
}
By updating once all threads were done I was able to minimize the twitching by only refreshing the list once, thus allowing scroll and jumping back to the top only once all picutres were done
Approach 2: eliminate twitch by using mem cache
Eventually what I ened up doing was using a cache to store bitmaps. This approach completely eliminated the jumping issue beacuse the list was no longer being refreshed, rather the adapter was loading bitmaps from the cache only when views were recycled. I still used a seperate task with a call back
#Override
public void userPic(Bitmap pic){
if(pic != null){
memCache.addPicture(pic);
}
}
only this time rather than update the list directly, if a picture was downloaded I stored it to the cache. Then in my adapter code, I set the picutre field to update from cache if present
if(picture_view != null){
if(memCache.contains(u.getId()){
picture_view.setImageBitmap(memCache.getPicture(u.getId()));
} else {
picture_view.setImageBitmap(memCache.getPicture("default"));
}
this approach takes advatage of the fact that views are updated in a ListView automaticaly once they are recycled. As you scroll and the views are rebuilt, the adapter will automatically populate the fields with new data if it has changed.
Downsides - the list does not auto upate. If pictures are downloaded for fields that are currently visible, they will not be updated until you scroll away from that view. Also, slightly more set up in creating a cache. I chose to use a singelton pattern to do this since I was accessing the cache from multiple places (e.g. adding pictures in one place and getting in another).

Parse asynchronous data loader (findInBackGround) does not reconnect to activity

On my android app, I use the Parse.com online database to store my data. In the onCreate() method of my activity, I use the method findInBackground() to asynchronously load data.
The findInBackground() method does not initially reconnect to the activity and continue to run forever. However, if I click on the home button of my phone and then re-load the app, the findInBackGround() method finally reconnects and load the data.
I would like to:
make the findInBackground() method reconnect with the activity without needing to reload the app
show a loading image (animated gif ?) while data is loading.
Would you guys have any advice on my problem ?
Thank you in advance for your help,
Alex
PS: I already tried the find() method of parse. Even if it reconnects automatically with the app, I don't think it's the right way to proceed since it blocks the UI of the caller activity until data is loaded.
==================================================================================
I finally found the answers to my questions:
I put the code to populate the listView INSIDE a method of the findCallBack class. Thus, I make sure I will use the result of the findInBackground() method only AFTER it has finished run. Previously, I had put the code to populate the listView OUTSIDE of the findCallBack class, so even if it came after in my code, it was actually executed before the end of findInBackground(), so didn't work.
For the loading image, I used an answer found on this site, which consists in activitating and stopping a ProgressDialog at the appropriate time (before and after findInBackground()).
startLoading(); //Show the loading image
query.findInBackground(new FindCallback() {
public void done(List<ParseObject> allQuestionsVal, ParseException e) {
if (e == null) {
for(int i = 0; i<=allQuestionsVal.size()-1;i++){
ParseObject questionVal = allQuestionsVal.get(i);
Question question = new Question(questionVal.getObjectId(),
questionVal.getString("FIELD1"),
questionVal.getString("FIELD2"),
allQuestions.add(question);
}
stopLoading(); //Remove the loading image
//Use the result of the Query (allQuestions) to populate listVIew
ListView list = (ListView) findViewById(R.id.all_questions);
AllQuestionsAdapter adapter=new AllQuestionsAdapter(AllQuestions.this, allQuestions);
list.setAdapter(adapter);
}
else {
stopLoading(); //Remove the loading image
}
}
});
protected ProgressDialog proDialog;
protected void startLoading() {
proDialog = new ProgressDialog(this);
proDialog.setMessage("loading...");
proDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
proDialog.setCancelable(false);
proDialog.show();
}
protected void stopLoading() {
proDialog.dismiss();
proDialog = null;
}
PS: any comments are welcomed :)

ImageViews which is in ListView doesn't displayed

I have ListView with EventAdapter.
public class EventAdapter extends BaseAdapter {
...
public View getView(int position, View view, ViewGroup parent) {
...
cache.imgIcon.setImageDrawable(ImgCache.setImg(url, progressBar));
...
}
ImgCache its class for caching images.
public class ImgCache {
public static HashMap<String, Drawable> imgCache;
// get img from cache if exist, or download and put in cache
public static Drawable setImg(final String link, final ProgressBar progressBar) {
final Drawable[] image = {null};
if (imgCache.containsKey(link)) {
image[0] = imgCache.get(link);
progressBar.setVisibility(View.INVISIBLE);
} else {
new AsyncTask<Void, Void, Drawable>() {
#Override
protected Drawable doInBackground(Void... params) {
URL url = null;
try {
url = new URL(link);
URLConnection connection = url.openConnection();
image[0] = Drawable.createFromStream(connection.getInputStream(), "src");
} catch (Exception e) {
e.printStackTrace();
}
imgCache.put(link, image[0]);
return image[0];
}
#Override
protected void onPostExecute(Drawable result) {
progressBar.setVisibility(View.INVISIBLE);
}
}.execute();
}
return image[0];
}
}
What the problem is?
After I open my Activity with ListView all images begin loading. But after the loading is finished they don't displayed. It is looks like:
Then I try to scroll 2 items down and then return to previous position. After this manipulation I can see 2 upper items with images. Also all images down are also visible when I scroll to them.
According to your problem, it seems like you need to refresh your ListView after the images has been downloaded (because when you scroll they do appear):
adapter.notifyDataSetChanged();
AsyncTask is asynchronous so the flow for your app is:
ListView Item needs to be displayed -> Calls Adapter.getView(...) for List item -> if image is not in cache, execute AsyncTask and return (not waiting for result)
So, when you scroll down and back up, the Adapter.get(...) method is called again, however this time the image is in cache so it returns the Drawable object which is displayed
One way to resolve this issue would be to have a callback to the Adapter from the AsyncTask that will update the image once it is retrieved calling notifyDataSetChanged on the Adapter, setting specific Drawable directly or something similar (display a loading gif for images in the meanwhile?)
Or
Call the AsyncTask get(long timeout, TimeUnit unit) method which will block the man thread and wait for the AsyncTask to finish. After it is finished then it will return the result (your Drawable in this case). This will cause the main UI thread to hang while fetching images, so not optimal way to go about this.
The issue is that your view loads and populates your list OnCreate, but at that time your Async task hasn't returned your list yet so when getView calls your cache it's empty, due to android View Recycling when you scroll it calls getView again, this time your cache has been populated.
I recommend that onPostExecute you call NotifyDataSetChanged on your ListView adapter, this will force a redraw once your have your images.

refresh data in listview when data from server

I'm having a problem refresh the data in list view.
I get the data in the list from a server, and when I want to refresh the data I need to go to the server and receive the new data.
the notifyDataSetChanged() not helping and also the ListView.invalidateViews not helping.
when I rotate the device the list updated.
how can I load the list view in the same way the screen rotation do it?
This is the code on create that fill the list view.
thanks in advance.
query = new ParseQuery(PET_CLASS_NAME);
petListView.addHeaderView((View)getLayoutInflater().inflate(R.layout.header_row, null));
petDetailIntent = new Intent(getApplicationContext(), PetDetailActivity.class);
selectCityIntent = new Intent(this, CitiesActivity.class);
loadingIntent = new Intent(getApplicationContext(), LoadingActivity.class);
startActivityForResult(loadingIntent, LOADING_INTENT_CODE);
/*the user see list of pets that are still missing*/
query.whereEqualTo(PET_FOUNDED, false);
selectedCity = settings.getString("cityQuery", "");
if(selectedCity != ""){
query.whereEqualTo(PET_CITY, selectedCity);
}
query.findInBackground(new FindCallback() {
#Override
public void done(List<ParseObject> list, ParseException e) {
if (e == null) { //objects retrieved well
petList.addAll(list);
//MyAdapter
adapter = new MyAdapter(
getApplicationContext(),
android.R.layout.simple_list_item_1,
R.id.tv_pet_name,
petList);
setListAdapter(adapter);
}
else{
toaster(getResources().getString(R.string.error_message_load_pets));
finish();
}
finishActivity(LOADING_INTENT_CODE);
}
});
Use a AsyncTask for loadData from Server. It will load it faster.
Try this out:
private class YourTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... s) {
//Here you have to make the loading / parsing tasks
//Don't call any UI actions here. For example a Toast.show() this will couse Exceptions
// UI stuff you have to make in onPostExecute method
}
#Override
protected void onPreExecute() {
// This method will called during doInBackground is in process
// Here you can for example show a ProgressDialog
}
#Override
protected void onPostExecute(Long result) {
// onPostExecute is called when doInBackground finished
**// Here you can for example fill your Listview with the content loaded in doInBackground method**
}
}
And than you just have to call this AsyncTask always if you loading content from your server:
new YourTask().execute("");
Try it out!.. Hope this helps..
When you rotate the device, the activity is actually started stopped and started and your initial request will be made again.
You should place your request code into a method and recall it yourself
Similar to what Rawkode mentioned, it doesn't seem like the code that actually does the work of retrieving data from the server is reusable (since it lives in onCreate()). Take a look at this diagram: http://developer.android.com/images/activity_lifecycle.png. As you can can see, the onCreate() method only gets executed once, unless the Activity is re-created (i.e. rotating screen).
Also, from the given code, there doesn't seem to be evidence of a refresh method either. How will users be able to refresh the data? Consider refactoring your code such that the work is done in a method that you can call later on (i.e. refreshData()) and then figure out a way in which you would like your users to refresh. For example, you can use either the ActionBar with a refresh ActionItem, or a menu option or even a button.

Categories

Resources