I have list of files stored inside arraylist that I need to download in background thread. My initial thought is that AsyncTask should be up for that task. But, I have a problem, I don't know how to supply my list to doInBackground method.
My arraylist is defined as
private ArrayList<String> FilesToDownload = new ArrayList<String>();
My DownloadFiles subclass (the way it is written now) should be called with:
new DownloadFiles().execute(url1, url2, url3, etc.);
This is not suitable for me since I never know how many urls I will have. It is pretty much changing from case to case. So, I would like somehow to pass my arraylist to doInBackground method.
I tried to convert to array with toArray():
new DownloadFiles().execute(FilesToDownload.toArray());
But, eclipse tells me that execute is not applicable for argument Object[].
Suggestion was to cast to URL[], but when I tried that I got illegal casting error and app crashed.
It seems that doInBackground has to be implemented with parameters in varargs type (URL... urls).
Any ideas how to solve my problem? Thanks.
class DownloadFiles extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
Log.d("Evento", "&&&& downloading: " + FilesToDownload.get(i).toString());
// totalSize += Downloader.downloadFile(urls[i]);
// publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
//setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
//showDialog("Downloaded " + result + " bytes");
}
}
Your AsyncTask is declared with a param type of URL, but you're trying to pass String (or ArrayList) objects. Either prepare an Array of URL objects in the calling program or modify DownloadFiles to accept String instead of URL parameters and convert each String to a URL in the execute() method.
Better yet, since you are accessing FilesToDownload from within execute(), you don't need to pass anything to execute() and you could declare the first generic parameter of DownloadFiles as Void.
Related
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. [in ListView(2131034188, class android.widget.ListView) with Adapter(class .MainActivity$ListAdapter)]
This is what I do , I run a AsyncTask and get json data , then , onPostExecute, I call ListAdapter that makes the data to listview .
#Override
protected Void doInBackground(Void... arg0) {
Spots_tab1_json sh = new Spots_tab1_json();
String jsonStr = sh.makeServiceCall(url + page, Spots_tab1_json.GET);
if (jsonStr != null) {
JSONObject jsonObj = new JSONObject(jsonStr);
contacts = jsonObj.getJSONArray(TAG_CONTACTS);
for (int i = 0; i < contacts.length(); i++) {
JSONObject c = contacts.getJSONObject(i);
String onvan = new String(c.getString("onvan").getBytes("ISO-8859-1"), "UTF-8");
String id = new String(c.getString("id").getBytes("ISO-8859-1"), "UTF-8");
String dates = new String(c.getString("dates").getBytes("ISO-8859-1"), "UTF-8");
String price = new String(c.getString("gheymat").getBytes("ISO-8859-1"), "UTF-8");
HashMap<String, String> contact = new HashMap<String, String>();
contact.put("onvan", onvan);
contact.put("img", new String(c.getString("img").getBytes("ISO-8859-1"), "UTF-8"));
contactList.add(contact);
}
}
}
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (!isCancelled() && goterr == false) {
final ListAdapter ladap=new ListAdapter(MainActivity.this, contactList);
lv.setAdapter(ladap);
runOnUiThread(new Runnable() {
public void run() {
ladap.notifyDataSetChanged();
}});
}
}
and after that, my listView is built. I've tested it on several different devices , non of them had any problem but some users told me and I logged and see this error.
What should I do to solve it ? what is wrong ?
thanks you
Apologies for the belated reply, but it appears as suspected in my earlier comment: you're modifying contactList from two different threads.
ListView caches the element count and whenever it has to layout its children, it'll check this count againt the current number of items in the bound adapter. If the count has changed and the ListView wasn't notified about this, an error is thrown:
else if (mItemCount != mAdapter.getCount()) {
throw new 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. [in ListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
Source.
In other words: that's exactly the error you're getting. In your scenario, the count difference is being caused by modifying the backing dataset from more than a single thread, leading to a synchronization issue: the background thread may modify the dataset, get suspended, and the ui thread may then call layoutChildren() without having been notified about the dataset changes.
Now, onto the solution. The easiest one is to make sure you're not modifying the list that is bound to the ListView's adapter from different threads. That is, either make a copy that you can then freely modify, or allocate a new list.
So, do something like this, i.e. in the onPreExecute() method of the AsyncTask.
New list:
List<HashMap<String, String>> mNewContactList = new ArrayList<HashMap<String, String>>();
A (shallow) copy:
List<HashMap<String, String>> mNewContactList = new ArrayList<HashMap<String, String>>(contactList);
Then, in doInBackground(), add the data to mNewContactList. Finally, in onPostExecute(), you can:
Create a new adapter using mNewContactList and set it to the ListView.
Or (if you didn't make a copy of the original list) add the contents of mNewContactList to the already existing contactList and call notifyDatasetChanged() on the ListView.
data update and your adapter calls notifyDataSetChanged() must in the same code block.
for example:
Correct case:
runOnUiThread(new Runnable() {
#Override
public void run() {
mSelectedImages.add(image);
mAdapter.notifyDataSetChanged();
}
});
Wrong case:
mSelectedImages.add(image);
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
I want AsyncTask to download multiple files that i get as an array.
Using this code the AsyncTask only works once and ignore the rest of array.
private class DownloadFiles extends AsyncTask<String[], Integer, String> {
#Override
protected String doInBackground(String[]... sUrl) {
for (int p = 0; p < sUrl.length; p++) {
for (int i=0;i<sUrl[p].length;i++) {
getFileToDown(sUrl[p][i]);
}
}
For example i want to send more than array:
downloadFiles.execute(databasesArray,imagesArray,etc);
You really need not declare String[] as generic parameter, but just a String:
AsyncTask<String, Integer, String>
doInBackground(String... sUrl) by default expects an array of type parameter.
Also, ... is called varargs, and it represents an array of optional arguments.
Currently working on an app that takes results from a search, parses the JSON object returned, and then adds the resulting pieces into a few ArrayLists within a class created called VenueList.
Here is the method that receives the results from the service and parses the JSON:
private static List<String> getResultsFromJson(String json) {
ArrayList<String> resultList = new ArrayList<String>();
try {
JSONObject resultsWrapper = (JSONObject) new JSONTokener(json).nextValue();
JSONArray results = resultsWrapper.getJSONArray("results");
for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
resultList.add(result.getString("text"));
}
}
catch (JSONException e) {
Log.e(TAG, "Failed to parse JSON.", e);
}
return resultList;
}
What results of this becomes a List variable call mResults (to clarify: mResults = getResultsFromJson(restResult);. That is then used, among other places, in the following loop that puts the results into an ArrayAdapter that is used for displaying them in a ListFragment:
for (String result : mResults) {
VenueList.addVenue(result, "HELLO WORLD");
adapter.add(result);
}
I also add the result to a class called VenueList that manages the results and makes them accessible for multiple views. It essentially just holds multiple ArrayLists that hold different types of details for each venue returned in the search. The method I use to add a venue to VenueList is below (and you can see it used in the for loop above):
public static void addVenue(String name, String geo) {
venueNames.add(name);
venueGeos.add(geo);
}
I want the addVenue method to be able to take multiple arguments and update the VenueList class. Yet, when I call the addVenue method in the for loop, I can only pass it String result (from the parameters of the loop) and can't figure out how to pass it a second argument (which should also come from the JSON parsed by getResultsFromJson) so I've used "HELLO WORLD" as a placeholder for now.
I realize getResultsFromJson only has one list returned. I need to be able to take multiple elements from the JSON object that I parse, and then add them to VenueList in the right order.
So my questions are:
1) Given the getResultsFromJson method and the for loop, how can I use the addVenue() method as designed? How do I parse multiple elements from the JSON, and then add them to the VenueList at the same time? I plan on adding more arguments to it later on, but I assume if I can make it work with two, I can make it work with four or five.
2) If that's not possible, how should the getResultsFromJson, the for loop, and the addVenue method be redesigned to work properly together?
Please let me know if you need more detail or code - happy to provide. Thank you!
EDIT - Full VenueList class:
public class VenueList {
private static ArrayList<String> venueNames;
private static ArrayList<String> venueGeos;
public VenueList() {
venueNames = new ArrayList<String>();
venueGeos = new ArrayList<String>();
}
public static void addVenue(String name, String geo) {
venueNames.add(name);
venueGeos.add(geo);
}
public static String getVenueName(int position) {
return venueNames.get(position);
}
public static String getVenueGeo(int position) {
return venueGeos.get(position);
}
public static void clearList() {
venueNames.clear();
venueGeos.clear();
}
}
Clarification: I will have additional ArrayLists for each element of data that I want to store about a venue (phone number, address, etc etc)
1) I don't think methods getResultsFromJson(String json) and addVenue(String name, String geo) fit your needs.
2) I would consider rewriting method getResultsFromJson(String json) to something like this:
private static SortedMap<Integer, List<String>> getResultsFromJson(String json) {
Map<Integer, String> resultMap = new TreeMap<Integer, String>();
//...
return resultMap;
}
where the number of keys of your map should be equal to the number of objects you're extracting info, and each one of them will properly have their own list of items just in the right order you extract them.
With this approach you can certainly change your logic to something like this:
// grab your retuned map and get an entrySet, the just iterate trough it
SortedMap<Integer, String> result = returnedMap.entrySet();
for (Entry<Integer, String> result : entrySet) {
Integer key = result.getKey(); // use it if you need it
List<String> yourDesiredItems = result.getValue(); // explicitly shown to know how to get it
VenueList.addVenue(yourDesiredItems);
}
public static void addVenue(List<String> yourDesiredItems) {
// refactor here to iterate the items trough the list and save properly
//....
}
EDIT -- as you wish to avoid the go-between map i'm assuming you need nothing to return from the method
First i'm providing you with a solution to your requirements, then i'll provide you with some tips cause i see some things that could smplify your design.
To save VenueList things directly from getResultsFromJSON do something like this:
private static void getResultsFromJson(String json) {
try {
JSONObject resultsWrapper = (JSONObject) new JSONTokener(json).nextValue();
JSONArray results = resultsWrapper.getJSONArray("results");
for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
//FOR EXAMPLE HERE IS WHERE YOU NEED TO EXTRACT INFO
String name = result.getString("name");
String geo = result.getString("geo");
// and then...
VenueList.addVenue(name, geo, ..., etc);
}
} catch (JSONException e) {
Log.e(TAG, "Failed to parse JSON.", e);
}
}
This implies that your addVenue method should know receive all params needed; as you can see this is just a way (that you can consider a workaround to your needs), however as i don't know all requirements that lead you to code this model, i will point to a few things you might consider:
1. If there's a reason for VenueList class to use everything static, consider doing this:
static{
venueNames = new ArrayList<String>();
venueGeos = new ArrayList<String>();
//....
}
private VenueList(){
}
This way you won't need to get an instance every time and also will avoid null pointer exceptions when doing VenueList.addVenue(...) without previous instantiation.
2. Instead of having an ArrayList for every characteristic in VenueList class consider defining a model object for a Venue like this:
public class Venue{
String name;
String geo;
//... etc
public Venue(){
}
// ... getters & setters
}
then if you need that VenueList class you will just have a list o Venue objects (List<Venue>), this means that instead of calling the method addVenue, you will first create a brand new instance of Venue class and will call the setter method of each characteristic, as an example of the refactored for loop from the workaround i provided you you'd be using something like this:
List<Venue> myListOfVenues = new ArrayList<Venue>();
for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
// THIS WOULD REMAIN THE SAME TO EXTRACT INFO
String name = result.getString("name");
String geo = result.getString("geo");
// and then instead of calling VenueList.addVenue(name, geo, ..., etc)...
Venue v = new Venue();
v.setName(name);
v.setGeo(geo);
// ...etc
myListOfVenues.add(v);
}
// Once you're done, set that List to VenueList class
VenueList.setVenueList(myListOfVenues);
So VenueList class would now have a single property List<Venue> venueList; and would suffer minor tweeks on methods getVenueName, etc... and everything would be more readable... i hope this helps you to get another approach to solve your problem, if i still don't make my point let me know and i'll try to help you out...
I am new to threads. I am trying to load a small array of photos. Right now I am using Async tasks/ threads, but how do I make the outcome sequential? Below is an illustration:
What I want:
a[0] = photo1;
a[1] = photo2;
a[2] = photo3;
a[3] = photo4
What my program gives me instead. Note that the order changes and is random:
a[0] = photo[2];
a[1] = photo[1];
a[2] = etc
Here's a snippet of my code:
...
for (int i = 0; i < mNoOfContacts; i++) {
String stringContactUri = storeSettings.getString("contactUri"+i, "");
if (stringContactUri != ""){
Uri contactUri = Uri.parse(stringContactUri);
loadContactInfo(contactUri);
}
...
private void loadContactInfo(Uri contactUri) {
AsyncTask<Uri, Void, ContactInfo> task = new AsyncTask<Uri, Void, ContactInfo>() {
#Override
protected ContactInfo doInBackground(Uri... uris) {
return mContactAccessor.loadContact(getContentResolver(), uris[0]);
}
#Override
protected void onPostExecute(ContactInfo result) {
Contacts[mNoOfContacts] = result;
Toast.makeText(getApplicationContext(), mNoOfContacts+"Picked Contact"+Contacts[mNoOfContacts].getDisplayName(), Toast.LENGTH_SHORT).show();
mNoOfContacts++;
}
};
task.execute(contactUri);
}
...
My code is a modification of the Google Android demo app - Business Cards.
Please help! Thanks!
What might be the easiest thing to do is make the onPostExecute method call a routine that does the sorting: pass both the ContactInfo and the Uri and then compare the Uri with the original and put it in the proper place.
Alternatively modify your AsyncTask so instead of launching one per Uri you launch one you pass all the Uris at once and then go from there.
I would think that, by definition, "Asynchronous Tasks" are not synchronized...Asynchronous literally means "not synchronized." If you want it synchronized, just load them the old fashioned way by doing a URI call, get your photo and do it again once the last photo was loaded.
I'm curious about the get(long, java.util.concurrent.TimeUnit) function in AsyncTask, but I'm having a hard time locating an example of it's usage.
get(long, java.util.concurrent.TimeUnit)
Can anyone provide an example of it's use?
It appears as though AsyncTask.get() blocks the caller thread, where AsyncTask.execute() does not.
You might want to use AsyncTask.get() for test cases where you want to test a particular Web Service call, but you do not need it to be asynchronous and you would like to control how long it takes to complete. Or any time you would like to test against your web service in a test suite.
Syntax is the same as execute:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
new DownloadFilesTask().get(5000, TimeUnit.MILLISECONDS);
Another use of AsyncTask is to know when several AsyncTasks have processed:
AsyncTask1 a1 = new AsyncTask();
AsyncTask1 a2 = new AsyncTask();
a1.execute();
a2.execute();
a1.get();
a2.get();
Log.d("Example", "a1 and a2 have both finished, you can now proceed");