Lucidly yours, Listview!
Recently we implemented a feature for our app that involved developing a rich list view, full of high-res images and text. After playing around in the world of list views for a week and a half, I can surely say that it’s not child’s play! As a developer, a lot of things need to be kept in mind while implementing lists that scroll efficiently, look great and work seamlessly. Through this and some subsequent posts, I wish to share some of the challenges I faced, and how we went around solving them:
PART 1: Using AsyncTask for Background fetching of Images
The feature involved displaying high-res images, each almost a quarter of a MB, on the list view. Downloading big images can be a bigger task, especially if the user is active on mobile data and is subject to varying signal strengths. It is hence absolutely essential to use a background task to download the images. This prevents the UI thread from being blocked till the download completes.
Initial approach :
The initial approach we took was the spawn a separate async task for every background image. The download method returned a boolean yes/no for the download success and accordingly we used to update the list view. But eventually we realized that this might be an overhead for us, since the API usually returned a high number of items for display.
So, finally we went on with a single async task and passed it the list of urls from where the images had to be downloaded.
If you have already worked with listviews, you will be knowing that in android listviews use adapters to render themselves, which in turn feed in data from a data source. We initialize the d This data source is passed to the adapter while setting data for that row. For example in our listview, we have:
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
ViewHolderItem viewHolder;
if(convertView == null){
LayoutInflater inflater = context.getLayoutInflater();
convertView= inflater.inflate(R.layout.list_item, null, true);
viewHolder = new ViewHolderItem();
initializeViewHolder(convertView, viewHolder);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolderItem) convertView.getTag();
}
// Get data at position i
Data data = dataArray.get(i);
setDataForRowView(viewHolder, data);
return convertView;
}
The getView() method of our listview is responsible for rerendering the listview. When we call onnotifydatasetchanged(), it refreshes the listview from the data source,
private void refreshTheListView() {
if(getView() != null && listAdapter != null){
listAdapter.notifyDataSetChanged();
}
}
The download task, downloads and saves images to the directory mentioned as an argument. The structure of our task looked as follows:
public class DownloadRemoteImageAsset extends AsyncTask<String, Void, Boolean> {
private final String subDir;
private Activity activity;
private Callback downloadCompleteCallback;
public DownloadRemoteImageAsset(Activity activity, String subDir, Callback downloadCompleteCallback) {
this.activity = activity;
this.downloadCompleteCallback = downloadCompleteCallback;
this.subDir = subDir;
}
@Override
protected Boolean doInBackground(String... urls) {
Boolean isDownloadSuccessful= false;
for(String url:urls){
if(isCancelled()) break;
Boolean wasFileDownloaded = Utils.downloadAndSaveLocalImageResource(activity.getCacheDir() ,url, getImageName(url), subDir);
if(wasFileDownloaded){
//Will be false only if ALL image downloads fail, in all other cases we need to call onsucess.
isDownloadSuccessful = true;
}
}
return isDownloadSuccessful;
}
public String getImageName(String url) {
String baseName = Utils.getBaseName(url);
String extension = Utils.getExtension(url);
return (baseName +"."+ extension).toLowerCase();
}
protected void onPostExecute(final Boolean isSuccess) {
if (isSuccess)
downloadCompleteCallback.onSuccess(null);
else
downloadCompleteCallback.onFailure(null);
}
The boolean variable isDownloadSuccessful keeps track of the download. In case we have even 1 out of n images downloaded, we need to call the success callback that refreshes the listview on the UI thread. The callback was passed on while initializing the task.
private void downloadImages(){
downloadRemoteAsset = new DownloadRemoteImageAssetTask(getActivity() , imageDirectory(), new Callback() {
@Override
public void onSuccess(List<BaseResponse> response) {
refreshTheListView();
}
@Override
public void onFailure(ErrorResponse response) {
}
}
This approach works as a charm. Will be covering more details about async tasks and things to take care of, in the next post! Till then, stay tuned :)













