In one of my android activity, I've a ListView lvFeedsList.
Each row element in the listView will contain 3 textViews - RSSFeedName, publishDate & feedLength
The contents of the feeds is retrived from a HTTPRsponse.
I'm fetching this response in an AsyncTask.
So, in the doInBackground(), I've send the HTTPRequest & received & parsed the response & prepared the ArrayList containing 3 above mentioned information.
Then inside the doInBackground() only, I'm creating the customized ArrayAdapter for forming the 3 TextViews in row element.
My intetions are to set this adapter on ListView in onPostExecute().
But, when I run the application, the ListView does not display anything.
I tried to debug & it seems like getView() in the ArrayAdapter class is not getting called. (But I'm not sure if this is the reason).
Here is the code, sorry for the length...it seemed necessary.
Activity Code:
public class GenericFeedsActivity extends Activity{
private ListView lvFeedsList;
private ArrayList<FeedsClass> feedList;
protected void onCreate(Bundle savedInstanceState) {
...
lvFeedsList = (ListView) findViewById(R.id.lvFeedsList);
lvFeedsList.setOnItemClickListener(this);
lvFeedsList.setEnabled(false);
...
new AsyncResponseHandler(this).execute();
}
class AsyncResponseHandler extends AsyncTask {
Context context;
FeedListAdapter adapter;
public AsyncResponseHandler(Context c) {
this.context = c;
}
#Override
protected Object doInBackground(Object... params) {
...
/*
* Sending HTTPRequest to a URL & getting list of feeds
* Saving this list of feeds in a ArrayList -feedList, containing elements of type FeedsClass (declared above)
* Below line parses the HTTPResponse XML & stores various information in feedList.
*/
feedList = utils.parseRssResponseXML(in); // Working fine, geeting elements
adapter = new FeedListAdapter(
GenericFeedsActivity.this, feedList);
in.close();
return null;
}
protected void onPostExecute(Object e) {
// Setting Arrayadapter
lvFeedsList.setAdapter(adapter);
lvFeedsList.setEnabled(true);
}
}
}
Adapter Code:
public class FeedListAdapter extends ArrayAdapter {
private Context context;
private ArrayList<FeedsClass> feedList;
public FeedListAdapter(Context c, ArrayList<FeedsClass> data) {
super(c, R.layout.rowlayout);
this.context = c;
this.feedList = data;
}
class ViewHolder {
TextView tvFeedName;
TextView tvFeedPubDate;
TextView tvFeedLength;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder = null;
if (row == null) {
LayoutInflater inflator = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflator.inflate(R.layout.rowlayout, null);
holder = new ViewHolder();
holder.tvFeedName = (TextView) row.findViewById(R.id.tvFeedName);
holder.tvFeedPubDate = (TextView) row.findViewById(R.id.tvFeedPubDate);
holder.tvFeedLength = (TextView) row.findViewById(R.id.tvFeedLength);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
// Getting values of feedName, publishDate & feedLength
String feedName = feedList.get(position).getTitle();
String feedDate = feedList.get(position).getPublishDate();
String feedLength = feedList.get(position).getStreamLength();
holder.tvFeedName.setText(feedName);
holder.tvFeedPubDate.setText(feedDate);
holder.tvFeedLength.setText(feedLength);
}
return row;
}
}
The issue is that you are subclassing ArrayAdapter. This doesn't work because ArrayAdapter internally thinks you do not have any elements in your data; it doesn't just magically know to look in the lvFeedsList variable because the data set it uses is internal.
Instead, in your constructor make sure to call this constructor instead:
Adapter code:
public FeedListAdapter(Context c, ArrayList<FeedsClass> data) {
super(c, R.layout.rowlayout, data); // add 'data'
this.context = c;
this.feedList = data;
}
Which will make everything work correctly.
adapter.notifyDataSetChanged()
could help at the end on of AsyncResponseHandler.onPostExecute(). If not - check whether ArrayList which hold data for adapter is empty or not.
Related
I am new to Java Android. I am using retrofit for restful calls and i found some minutes ago reactivex for observable objects. The goal is to update a listview "Realtime" when a value on the database changes. To be more clear:
I have a backend with restful webservices. The app calls this services with retrofit and puts result in a listview.
Here are my code snippets
The rest client
public interface SalaClientObservable {
#GET("kingestEE_war_exploded/rest/sala/{codAtt}")
public Observable<List<Sala>> getSale(#Path("codAtt") String codAtt);
#GET("kingestEE_war_exploded/rest/sala/aggiungiSala/{codAtt}/{nomeSala}")
public Observable<String> aggiungiSala(#Path("codAtt") String codAtt,#Path("nomeSala") String nomeSala);
#GET("kingestEE_war_exploded/rest/sala/rimuoviSala/{codAtt}/{nomeSala}")
public Observable<String> rimuoviSala(#Path("codAtt") String codAtt,#Path("nomeSala") String nomeSala);
}
The Activity
public class SceltaSala extends AppCompatActivity {
ListView lista;
TextView labelBenvenuto;
private ListView mListView;
private CompositeDisposable mCompositeDisposable;
private AdattatoreSale mAdapter;
private LinkedList<Sala> listaSale;
Retrofit.Builder builder = new Retrofit.Builder().baseUrl("http://10.0.2.2:8080/");
Retrofit retrofit;
int chiamata = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scelta_sala);
Gson gson = new GsonBuilder()
.setLenient()
.create();
builder = builder.addConverterFactory(GsonConverterFactory.create(gson));
retrofit = builder.build();
lista = (ListView) findViewById(R.id.ListaSale);
labelBenvenuto = (TextView) findViewById(R.id.LabelBenvenuto);
mCompositeDisposable = new CompositeDisposable();
initListView();
loadJSON();
}
private void initListView() {
mListView = (ListView) findViewById(R.id.ListaSale);
}
private void loadJSON() {
SalaClientObservable requestInterface = builder
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build().create(SalaClientObservable.class);
mCompositeDisposable.add(requestInterface.getSale("Stefano")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse,this::handleError));
}
private void handleResponse(List<Sala> listSala) {
listaSale = new LinkedList<>(listSala);
Sala[] arr = new Sala[listaSale.size()];
int i = 0;
for(Sala s : listaSale){
arr[i] = s;
i++;
}
mAdapter = new AdattatoreSale(this,R.layout.rigasala,arr);
mListView.setAdapter(mAdapter);
}
private void handleError(Throwable error) {
System.out.println(error.getMessage());
}
#Override
public void onDestroy() {
super.onDestroy();
mCompositeDisposable.clear();
}
The custom adapter
public class AdattatoreSale extends ArrayAdapter<Sala> {
List<Sala> nomiSale;
int immagineInt = R.drawable.immagineprova;
public AdattatoreSale(#NonNull Context context, #LayoutRes int resource, Sala[] objects) {
super(context, resource, objects);
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.rigasala, null);
ImageView immagine = (ImageView) convertView.findViewById(R.id.immagine);
TextView testoRiga = (TextView) convertView.findViewById(R.id.nomeSala);
Sala s = getItem(position);
testoRiga.setText(s.getNomeSala());
return convertView;
}
The ListView is updated only when I rotate the screen. But I think that the rotation calls (someway) the "onCreate" method of the activity, so is not the compositeDisposable that updates the values but simply the recall of the GET method.
What is wrong in this snippet that I found online?
Or better, is the right way to make the automatic update of the listview?
If you are trying to get "live" data from the DB then that's where your adapter's data needs to come from. Normally you don't need to use Rx for that and you could get away with using LoaderManager.LoaderCallbacks with which you subscribe to a table in your database (but it's a little bit of a pain to set up)
However, if you want to use Rx and database in your app, you should make use of the new Android's Architecture Components, more specifically Room, which creates a nice abstraction layer on top of DB. After setting it up you can subscribe to DB changes with a Flowable, which will emit everytime there is a change in a specific table, and with that all your service layer would have to do is save the data returned from the network request into the appropriate table.
Hope this helps!
I have created my own custom adapter class in my android app and I am calling it from one of my activity. I am adding some elements to the view from the adapter class and I need to access those variables from my activity class.
Now, ideally I would expect it to fill the view and then execute the further code in my activity class, but adapter class is taking some time to populate the view and in the meanwhile further code in my activity class is getting executed where no such elements have been added yet.
How do I handle this situation? I come from a js background. Do we have something like promises in java?
According to the answers I have my changed my code to this:
public class HomeActivity extends Activity {
GridView grid;
String text[] = {"Calendar","Uber","Weather","News","Youtube","Clock","Email","Maps","Twitter","Facebook"};
String list_app_name[] = {"calendar","uber","weather","news","youtube","clock","email","maps","twitter","facebook"};
String id_button[] = {"button_calendar","button_uber","button_weather","button_news","button_youtube","button_clock","button_email","button_maps","button_twitter","button_facebook"};
int image[] = {R.drawable.social_icons1,R.drawable.social_icons2,R.drawable.social_icons3,R.drawable.social_icons4,
R.drawable.social_icons5,R.drawable.social_icons6, R.drawable.social_icons7,R.drawable.social_icons8,
R.drawable.social_icons9,R.drawable.social_icons10};
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_home);
//setting up the adapter for gridView
grid = (GridView)findViewById(R.id.simpleGrid);
ImageAdapter ia = new ImageAdapter(this,image,text,id_button);
grid.setAdapter(ia);
ia.notifyDataSetChanged();
try {
initStateOfApps();
} catch (JSONException e) {
e.printStackTrace();
}
}
public void initStateOfApps() throws JSONException {
Log.d("here","here");
ArrayList<String> list = getEnabledApps();
Log.d("apps",list.toString());
for(int i=0;i<list.size();i++) {
String app_name = list.get(i);
ToggleButton button=null;
if(app_name.equals("calendar")) {
button = (ToggleButton)findViewById(R.id.button_calendar);
button.setChecked(true);
}
}
}
}
So what is happening is that I am creating some toggle buttons that are getting populated in the ImageAdapter class that I wrote.
Once the ImageAdapter is called, I call the notifydatasetchanged() on the adapter in order to update the view.
What I am doing inside the adapter is giving each of the toggle buttons some custom ID I wrote in res/values/ids.xml.
After using setId on each of the toggle buttons, I try using that ID in my activity class but it gives me nullPointerException in the initStateOfApps() where I am trying to change the state of button.
So even after using the notifyDataSetChanged it is still behaving the same.
ImageAdapter.java
public class ImageAdapter extends BaseAdapter {
private Context context;
private final int item_image[];
private final String item_text[];
private final String button_id[];
public ImageAdapter(Context context, int item_image[], String[] item_text,String[] button) {
this.context = context;
this.item_image = item_image;
this.item_text = item_text;
this.button_id = button;
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = new View(context);
// get layout from custom_gridview.xml
gridView = inflater.inflate(R.layout.item, null);
// set value into imageview
final ImageView image = (ImageView) gridView.findViewById(R.id.item_image);
image.setImageResource(item_image[position]);
// set value into textview
TextView text = (TextView) gridView.findViewById(R.id.item_text);
text.setText(item_text[position]);
final ToggleButton button_ = (ToggleButton) gridView.findViewById(R.id.item_button);
if(position==0) {
button_.setId(R.id.button_calendar);
image.setId(R.id.image_calendar);
}
button_.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener()
{
#Override
public void onCheckedChanged(CompoundButton toggleButton, boolean isChecked)
{
if(context.getResources().getResourceEntryName(toggleButton.getId()).equals("button_calendar")) {
if(isChecked) {
try {
setStateOfApp("calendar","true");
} catch (JSONException e) {
e.printStackTrace();
}
Intent intent = new Intent(context, GoogleApp.class);
((Activity) context).startActivityForResult(intent,10);
} else {
try {
setStateOfApp("calendar","false");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
}
} else {
gridView = (View) convertView;
}
return gridView;
}
}
You are trying to access View which is not a part of Activity's content view. So you can't access that view directly.
button = (ToggleButton)findViewById(R.id.button_calendar); // will return null
This ToggleButton will be null because findViewById will fail to find out ToggleButton in current content view because that view is present in your Adapter not in content view.
And you are getting nullpointerException because you are trying to access property on null view.
button.setChecked(true); // This button is null
In java we have <Future>, but I don't think it's what you're looking for.
The adapter (extending BaseAdaper) behaviour lets you create the adapter and, even in a second moment, change underlying data via getAdapter().setData() or whatever method you choose to add.
From this perspective, the adapter is a "stupid" component acting as A View containers, you should retrieve data elsewhere (CursorAdapter is different).
So, in your Activity, fill the adapter with needed data and, when finished, call adapter.notifyDatasetChanged(). This will inform the adapter that its own data has changed and it must refresh views
Yes, ideally, the population of the adapter should be coming from the outside. The adapter should really just take in a list of data and map that data to the views. For example, some method or task in the Activity could produce a list of data (probably asynchronously...since you mentioned it) that you then pass into the adapter and then you can notifyDataSetChanged() if you need to.
I can't see your code, but if for some reason the data is truly required to be populated from inside the adapter, you could use an event bus and subscribe to it in the Activity. I would recommend going with the first option, but here are some links if you choose to use an event bus:
https://github.com/greenrobot/EventBus
http://square.github.io/otto/
As per my understanding with your question
You are not properly managed the adapter data in your activity.
If any of the data or code interlinked with your adapter data or values
Then you can start those code after you retrieve the values or data and update the view in your activity.
Please note that use Viewholders in adapter to avoid slow populating and scrolling in listviews.
Viewholders will smooth your process.
I personally suggest you that
Please go with Recyclerview and RecyclerViewAdapter.
So many Android developers are using it.
If you have background tasks in adapter you can prefer to use RX Java or EventBus
If you provide the code
It's better for us to suggest exact solution
I created the following CursorAdapter which shows messages from my SQL database, everything is added well until I scroll the list, I know that the objects are recycled, but in a wrong way. Here is my CursorAdapter class:
public class ChatAdapter extends CursorAdapter {
public ChatAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.chat_item, parent,
false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// Find fields to populate in inflated template
TextView left = (TextView) view.findViewById(R.id.lefttext);
TextView right = (TextView) view.findViewById(R.id.righttext);
LinearLayout rightBubble = (LinearLayout) view
.findViewById(R.id.right_bubble);
LinearLayout leftBubble = (LinearLayout) view
.findViewById(R.id.left_bubble);
TextView leftDate = (TextView) view.findViewById(R.id.leftdate);
TextView rightDate = (TextView) view.findViewById(R.id.rightdate);
// Extract properties from cursor
String from = cursor.getString(cursor.getColumnIndexOrThrow("from"));
String txt = cursor.getString(cursor.getColumnIndexOrThrow("message"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("t"));
String id = cursor.getString(cursor.getColumnIndexOrThrow("id"));
// Parse time
long datevalue = Long.valueOf(date) * 1000;
Date dateformat = new java.util.Date(datevalue);
String convert = new SimpleDateFormat("HH:mm").format(dateformat);
// Populate fields with extracted properties
if (from.equals("me")) {
right.setText(txt);
left.setText("");
rightBubble
.setBackgroundResource(R.drawable.balloon_outgoing_normal);
leftBubble.setBackgroundDrawable(null);
rightDate.setText(convert);
leftDate.setVisibility(View.GONE);
}
else {
left.setText(txt);
right.setText("");
leftBubble
.setBackgroundResource(R.drawable.balloon_incoming_normal);
rightBubble.setBackgroundDrawable(null);
leftDate.setText(convert);
rightDate.setVisibility(View.GONE);
}
}
}
Unfortenately, after scrolling the list, dates from the rightDate and leftDate dissapears after moving back. I think it't due the .setVisibility(View.GONE)
Any suggestions to fix this?
when the view is recycled, it is in the previous state, android did not clear the status for you.
To fix your problem, you have to set the view in question to VISIBLE when needed
Edit:
like this, add the 2 lines
if (from.equals("me")) {
// your original code
rightDate.setVisibility(View.VISIBLE); //add this
}
else {
// your original code
leftDate.setVisibility(View.VISIBLE); //add this
}
I've searched all the posts I can find, and none seem to help with my situation. I have an android project that uses web services to pull down hourly weather data and populate a listView with the results.
The weird problem I'm having is that when I debug the project on my android phone, the main activity is blank and the listView isn't populated. If I run the project from android studio with my phone locked, and then unlock my phone the app opens on my phone with all of the listView properly formatted and populated.
I feel like it's a race condition issue between the asynctask and the adapter, but I can't seem to resolve it. I tried making my asyncTask an inner private class and calling notifyDataSetChanged on the adapter inside the onPostExecute method, but to no avail. I feel it must be something simple, but I'm relatively new to Android dev, so I'm stuck.
I have three classes that I'll post the pertinent code from
MainActivity.java (onCreate)
public class MainActivity extends ActionBarActivity {
ArrayList<Weather> w = new ArrayList<Weather>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DownloadWeatherTask myTask = new DownloadWeatherTask(w);
WeatherAdapter myAdapter = new WeatherAdapter(this,w);
ListView l = (ListView) findViewById(R.id.weatherList);
l.setAdapter(myAdapter);
myTask.execute();
}
}
WeatherAdapter.java
public class WeatherAdapter extends ArrayAdapter<Weather>{
public WeatherAdapter(Context context, ArrayList<Weather> weather) {
super(context, R.layout.item_weather, weather);
}
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Weather forecast = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_weather, parent, false);
}
// Lookup view for data population
TextView tvTime = (TextView) convertView.findViewById(R.id.listTime);
TextView tvDescr = (TextView) convertView.findViewById(R.id.listDescr);
TextView tvTemp = (TextView) convertView.findViewById(R.id.listTemp);
TextView tvHumid = (TextView) convertView.findViewById(R.id.listHumid);
ImageView ivWeather = (ImageView) convertView.findViewById(R.id.weatherImg);
// Populate the data into the template view using the data object
tvTime.setText(forecast.time);
tvDescr.setText(forecast.description);
tvTemp.setText(forecast.temperature+"°(F)");
tvHumid.setText(forecast.humidity+"% humidity");
ivWeather.setImageBitmap(forecast.weatherImg);
// Return the completed view to render on screen
return convertView;
}
}
DownloadWeatherTask.java
public class DownloadWeatherTask extends AsyncTask<Void,Void,Void>{
ArrayList<Weather> data;
public DownloadWeatherTask(ArrayList<Weather> a){
data = a;
}
public ArrayList<Weather> getData() {
return data;
}
protected Void doInBackground(Void...params) {
try {
String website = "http://api.wunderground.com/api/1111111111111/geolookup/q/autoip.json";
URL site = new URL(website);
HttpURLConnection weatherUnderground = (HttpURLConnection) site.openConnection();
weatherUnderground.connect();
JsonParser weatherParser = new com.google.gson.JsonParser();
JsonElement weatherJson = weatherParser.parse(new InputStreamReader((InputStream) weatherUnderground.getContent()));
JsonObject weatherObj = weatherJson.getAsJsonObject();
String zip = weatherObj.get("location").getAsJsonObject().get("zip").getAsString();
String city = weatherObj.get("location").getAsJsonObject().get("city").getAsString();
String state = weatherObj.get("location").getAsJsonObject().get("state").getAsString();
String hourly = "http://api.wunderground.com/api/111111111111/hourly/q/" + state + "/" + city + ".json";
URL hourlySite = new URL(hourly);
HttpURLConnection hourlyConnection = (HttpURLConnection) hourlySite.openConnection();
hourlyConnection.connect();
com.google.gson.JsonParser hourlyParser = new com.google.gson.JsonParser();
JsonElement hourlyWeatherJson = weatherParser.parse(new InputStreamReader((InputStream) hourlyConnection.getContent()));
JsonArray weatherArr = hourlyWeatherJson.getAsJsonObject().get("hourly_forecast").getAsJsonArray();
int l = weatherArr.size();
for (int i = 0; i < l; i++) {
String date = weatherArr.get(i).getAsJsonObject().get("FCTTIME").getAsJsonObject().get("pretty").getAsString();
String temp = weatherArr.get(i).getAsJsonObject().get("temp").getAsJsonObject().get("english").getAsString();
String condition = weatherArr.get(i).getAsJsonObject().get("condition").getAsString();
String humidity = weatherArr.get(i).getAsJsonObject().get("humidity").getAsString();
String iconUrl = weatherArr.get(i).getAsJsonObject().get("icon_url").getAsString();
Bitmap icon = getBitmapFromURL(iconUrl);
data.add(new Weather(date, condition, temp, humidity, icon));
}
} catch (IOException e) {
Log.e("Error: ",e.toString());
}
return null;
}
protected void onPostExecute(Void...params){
}
}
Below are links to my screenshots showing the app not populating the listView, and the app working properly when the program is run while the phone is initially locked.
Any help would be greatly appreciated!!
Thanks
In postExecute(), you need to update the adapter's List and then invoke its notifyDataSetChanged method. I suspect that you were forgetting to update the adapter's data.
The other option is to create a new adapter with the new data, and set the new adapter on the ListView.
I figured out what the issue was! I hadn't added #Override to my onPostExecute() method so it was never being called.
I added the notifyDataSetChanged to my onPostExecute as suggested, which worked once I added the #override to my method.
I'm trying to download images for each artist that has music on my phone, then show these images in a GridView. I'm using the lastfm-java library that Last.fm recommends using. The method you call to fetch an artists image is getImageURL(ImageSize size), but before you do this, you need to tell it which artist you want to reference with a String parameter. So, in full it would be something like this:
#Override
protected String doInBackground(Object... arg0) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
return artist.getImageURL(ImageSize.EXTRALARGE);
}
Getting all the artists that are on my phone isn't a problem, you just reference MediaStore. You would do something like this:
private void getArtists() {
String[] projection = new String[] {
MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST,
};
String sortOrder = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER;
Cursor c = getActivity().getContentResolver().query(
MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder);
if (c != null) {
int count = c.getCount();
if (count > 0) {
final int ARTIST_IDX = c.getColumnIndex(MediaStore.Audio.Artists.ARTIST);
for (int i = 0; i < count; i++) {
c.moveToPosition(i);
}
}
c.close();
c = null;
}
}
The Adapter for my GridView isn't anything special, it simply extends BaseAdapter.
Note AQuery is a library I'm using that helps cache and load a Bitmap from a URL.
public class GridViewAdapter extends BaseAdapter {
private final String[] imageURLs;
private final LayoutInflater mInflater;
private final Activity mActivity;
public GridViewAdapter(String[] urls, Activity activity) {
imageURLs = urls;
mActivity = activity;
mInflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return imageURLs.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewholder = null;
// Inflate GridView items
if (convertView == null) {
convertView = mInflater.inflate(R.layout.gridview_items, null);
viewholder = new ViewHolder();
viewholder.mImage = (ImageView)convertView.findViewById(R.id.gridview_image);
convertView.setTag(viewholder);
} else {
viewholder = (ViewHolder)convertView.getTag();
}
AQuery aq = new AQuery(convertView);
aq.id(viewholder.mImage).image(imageURLs[position], false, false, 0, 0, null, 0, 0.75f);
return convertView;
}
}
class ViewHolder {
public ImageView mImage;
}
So in full, my AsyncTask is as follows:
public class LastfmArtistGetImageURL extends AsyncTask<Object, Integer, String[]> implements
Constants {
private static final String tag = LastfmArtistGetImageURL.class.getSimpleName();
private GridViewAdapter mGridAdapter;
// Test
private final String[] imageIds = {
"http://userserve-ak.last.fm/serve/252/71875544.png",
"http://userserve-ak.last.fm/serve/252/6258507.jpg",
"http://userserve-ak.last.fm/serve/252/51274303.png",
"http://userserve-ak.last.fm/serve/252/58672183.png",
"http://userserve-ak.last.fm/serve/252/72029714.png",
"http://userserve-ak.last.fm/serve/252/17666215.jpg",
"http://userserve-ak.last.fm/serve/252/63247381.png",
"http://userserve-ak.last.fm/serve/252/33665463.jpg"
};
private final String artistOrMbid;
private final GridView mGridView;
private final Activity mActivity;
public LastfmArtistGetImageURL(String name, GridView gv, Activity activity) {
artistOrMbid = name;
mGridView = gv;
mActivity = activity;
}
#Override
protected String[] doInBackground(Object... arg0) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
Collection<String> col = new ArrayList<String>();
col.add(artist.getImageURL(ImageSize.EXTRALARGE));
return col.toArray(new String[0]);
}
#Override
protected void onPostExecute(String[] result) {
if (result != null)
mGridAdapter = new GridViewAdapter(imageIds, mActivity);
mGridView.setAdapter(mGridAdapter);
super.onPostExecute(result);
}
}
When I call my AsyncTask, I call it in my getArtists() method like this:
new LastfmArtistGetImageURL(c.getString(ARTIST_IDX), mGridView, getActivity())
.execute();
Problem
When I call this, all of the artists images download, but they download one after the other at position 0 of my GridViewAdapter. In other words, one image loads, then next, and so on all in the first position when I need them to be placed into each available position in the GridView. When I return my test String[] in my AsyncTask everything works like it should. All of the images are placed in order in each available space in the GridView.
Question
My question is, how do I return each artist image I download into my GridView correctly and why are the images currently only being loaded at the first position in my GridViewAdapter?
Edit - Shubhayu's answer
I moved setting my GridViewAdapter into my getArtists() method like so. This results in all the images being downloaded (As says LogCat), but only the last one being set in my GridView.
String[] test = new LastfmArtistGetImageURL(c.getString(ARTIST_IDX),
mGridView, getActivity()).execute().get();
mGridAdapter = new GridViewAdapter(test, getActivity());
mGridView.setAdapter(mGridAdapter);
smoak's answer
This results in only the last artist image (by the default order) being downloaded and applied in my GridView.
String[] test = {c.getString(ARTIST_IDX)};
new LastfmArtistGetImageURL(test, mGridView, getActivity()).execute();
Your AsyncTask looks like you are executing it each time for each Artist. Thus, your AsyncTask returns only one Artist's image and your GridView gets that Artists image, then you run the AsyncTask for the next Artist, GridView gets updated with new image and so on. What you need to do is modify your AsyncTask to take a String array of Artist names and loop over them in the doInBackground to get their image's.
// ... SNIPPED
public LastfmArtistGetImageURL(String[] names, GridView gv, Activity activity) {
artistsOrMbids = names;
mGridView = gv;
mActivity = activity;
}
#Override
protected String[] doInBackground(Object... arg0) {
Collection<String> col = new ArrayList<String>();
for (String nameOrMbid : this.artistsOrMbids) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
col.add(artist.getImageURL(ImageSize.EXTRALARGE));
}
return col.toArray(new String[0]);
}
// .... SNIPPED
And pass in all the artist names:
String[] artists = { "The Black Keys", "Rush", "The Allman Brothers" };
new LastfmArtistGetImageURL(artists, mGridView, getActivity()).execute();
here's what is happening, when you pass the test string it has a list of images and hence the gridview shows them properly. but when you use it to download an image for each artist, things go wrong.
Every time you call
new LastfmArtistGetImageURL(c.getString(ARTIST_IDX), mGridView, getActivity()).execute();
it runs the doInBackground(), completes it and then immediately calls the onPostExecute() where it creates a new adapter and passes your result which basically contains a single image of the single call.
So what u need to do is in your asynctask download all the images and then create a single adapter and pass all the images to it. That is not happening currently.
EDIT
If you see the AsyncTask, you will realize that everytime you call it, the string array returns only one image. So instead of returning a string array, return a string.
Next, I would suggest you use an ArrayList in your Adapter instead of a String array.
In your getArtists(), create an ArrayList and everytime you call
new LastfmArtistGetImageURL(test, mGridView, getActivity()).execute();
add the result to your ArrayList. Once you have looped through all the artists, your ArrayList will contain all the images.
Now set it to the Adapter. (You would have t change the adapter a bit if you change it from string to arraylist.)