Loading Imageviews inside Custom Listview with LRU cache

ListView is a kind of widget that will drive crazy if you not carefully handling it. For custom adapter to any listview you will inflate views only through getView() method. This is where many developers fail to handle the situation. In one of my project I was trying to show image views inside listview items. Everything is fine when I wrote the simple code shown below, but only thing I was facing is that scrolling effect of listview is not smooth. I hope this will make angry to any user who uses my app.

public class myListAdapter extends ArrayAdapter<MyListView>{

.....

@SuppressLint("NewApi")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vHolder;
if(convertView == null){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.my_list_view_item,null);
vHolder = new ViewHolder();

vHolder.dName = (TextView)convertView.findViewById(R.id.docuName);
vHolder.dateNTime = (TextView)convertView.findViewById(R.id.docuDateNTime);
vHolder.iView = (ImageView)convertView.findViewById(R.id.docuPic);

convertView.setTag(vHolder);
}
else
    vHolder = (ViewHolder)convertView.getTag();

// My Listview
MyListView mListView = (MyListView)listOfItem.get(position);
vHolder.dName.setText(mListView.getItemName());
vHolder.dateNTime.setText(mListView.getitemCreatedAt());

if (getBitmapImage(mListView.getItemPicPath()) != null){
vHolder.iView.setImageBitmap(getBitmapImage(mListView.getItemPicPath()));
}
else{
vHolder.iView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.list_placeholder));
}

return convertView;

}

// getting bitmap image
private Bitmap getBitmapImage(String itemPicPath) {

BitmapFactory.Options options = new BitmapFactory.Options();

// downsizing image as it throws OutOfMemory Exception for larger
// images
options.inSampleSize = 16;

return BitmapFactory.decodeFile(Uri.parse(itemPicPath).getPath(),options);
}

}

After doing some research I found out using AsyncTask will do the job, Hence I modified some parts of my code as below,

public class myListAdapter extends ArrayAdapter<MyListView>{

.....

@SuppressLint("NewApi")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vHolder;

if(convertView == null){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.my_list_view_item,null);

vHolder = new ViewHolder();

vHolder.dName = (TextView)convertView.findViewById(R.id.docuName);
vHolder.dateNTime = (TextView)convertView.findViewById(R.id.docuDateNTime);

vHolder.iView = (ImageView)convertView.findViewById(R.id.docuPic);

convertView.setTag(vHolder);
}

else
vHolder = (ViewHolder)convertView.getTag();

// My Listview
MyListView mListView = (MyListView)listOfItem.get(position);
vHolder.dName.setText(mListView.getItemName());
vHolder.dateNTime.setText(mListView.getitemCreatedAt());

if(vHolder.iView != null)
    new imageDownloaderTask(vHolder.iView).execute(mListView.getItemPicPath());

return convertView;
}

private class imageDownloaderTask extends AsyncTask<String,Void,Bitmap>{

WeakReference<ImageView> iViewRef;

public imageDownloaderTask(ImageView iView) {
iViewRef = new WeakReference<ImageView>(iView);
}

@Override
protected Bitmap doInBackground(String... params) {
return getBitmapImage(params[0]);
}

@SuppressLint("NewApi")
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);

if(isCancelled()){
bitmap = null;
}

if(iViewRef != null){

ImageView imageView = iViewRef.get();

if(imageView != null){

if (bitmap != null){
imageView.setImageBitmap(bitmap);
imageView.setRotation(90);
}
else{
imageView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.list_placeholder));
imageView.setRotation(90);
}
}
}
}

// getting bitmap image
private Bitmap getBitmapImage(String itemPicPath) {

BitmapFactory.Options options = new BitmapFactory.Options();

// downsizing image as it throws OutOfMemory Exception for larger
// images
options.inSampleSize = 16;

return BitmapFactory.decodeFile(Uri.parse(itemPicPath).getPath(),options);
}
}

// ViewHolder
static class ViewHolder{
TextView dName;
TextView dateNTime;
ImageView iView;
}

}

This made my listview scroll smoothly. But again one thing I noted that every imageview regains it own view again and again while scrolling. This is not desired.

Then again I done some research and found out LRU cache is the best solution Hence I modified my code accordingly

@Override
public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder vHolder;

if(convertView == null){

LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.my_list_view_item,null);

vHolder = new ViewHolder();

vHolder.dName = (TextView)convertView.findViewById(R.id.docuName);
vHolder.dateNTime = (TextView)convertView.findViewById(R.id.docuDateNTime);

vHolder.iView = (ImageView)convertView.findViewById(R.id.docuPic);

convertView.setTag(vHolder);
}

else
vHolder = (ViewHolder)convertView.getTag();

// My Listview
MyListView mListView = (MyListView)listOfItem.get(position);

vHolder.dName.setText(mListView.getItemName());
vHolder.dateNTime.setText(mListView.getitemCreatedAt());

if(vHolder.iView != null)
Image.loadToView(mListView.getItemPicPath(),vHolder.iView);

return convertView;
}

private static class Image{

private static LruCache<String, Bitmap> mMemoryCache = null;
private static int cacheSize = 1024 * 1024 * 10;

private static class imageDownloaderTask extends AsyncTask<String,Void,Bitmap>{

private ImageView mTarget;

public imageDownloaderTask(ImageView target) {
this.mTarget = target;
}

@Override
protected void onPreExecute() {
mTarget.setTag(this);
}

@SuppressLint("NewApi")
@Override
protected Bitmap doInBackground(String...urls) {

String url = urls[0];

Bitmap result = null;

if (url != null) {
result = load(url);

if (result != null) {
mMemoryCache.put(url,result);
}
}
return result;
}

@Override
protected void onPostExecute(Bitmap result) {

if(mTarget.getTag() == this) {
mTarget.setTag(null);

if(result != null)
mTarget.setImageBitmap(result);

} else if (mTarget.getTag() != null) {

((imageDownloaderTask) mTarget.getTag()).cancel(true);
mTarget.setTag(null);
}
}
}

public static Bitmap load(String urlString) {

BitmapFactory.Options options = new BitmapFactory.Options();

// downsizing image as it throws OutOfMemory Exception for larger
// images
options.inSampleSize = 16;

Bitmap bitmap = BitmapFactory.decodeFile(Uri.parse(urlString).getPath(),options);

if (bitmap == null)
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.list_placeholder);

return bitmap;
}

@SuppressLint("NewApi")
public static void loadToView(String url,ImageView view) {

if (url == null || url.length() == 0)
return;

if (mMemoryCache == null) {

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

@Override
protected int sizeOf(String key, Bitmap bitmap) {
return (bitmap.getRowBytes() * bitmap.getHeight());
}
};
}

Bitmap bitmap = getBitmapFromMemCache(url);

if (bitmap == null) {

final imageDownloaderTask task = (imageDownloaderTask) new imageDownloaderTask(view);

view.setTag(task);
view.setRotation(90);

task.execute(url);
} else {
view.setImageBitmap(bitmap);
view.setRotation(90);
}
}

// getting Bitmap from memory cache
@SuppressLint("NewApi")
public static Bitmap getBitmapFromMemCache(String url) {
return (Bitmap)mMemoryCache.get(url);
}
}

// ViewHolder
class ViewHolder{

TextView dName;
TextView dateNTime;
ImageView iView;
}

Finally I my listview scrolls smoothly and imageview is loaded only once, and reuses the image cache. Hope code is quiet readable …

Enjoy …!

Advertisements

How to create release keystore / digitally sign / publish Android App to Google Play (Android Market)

Publishing an app to Google Play is not a simple task as they are mentioned in online stuff. I experience lot much when I tried to publish my own app to Google Play market. Hence I drived me to write a blog to help anyone who is facing similar issues as faced by me.

In simple words, any android application must be digitally signed before deploying it to any device and make it work. During development  process you need to sign your app with something like debug.keystore before deploying .apk to device or Emulator. But Eclipse ADT do all this stuffs itself and makes our work easy. App signed with debug.keystore should not be published to android market since it won’t run on any other device. So, in order to make our to work at different device we need to digitally sign our app with something called  let say release.keystore.

So I will discuss about release.keystore here, as you completed your development and its time to release your app, please follow the below procedure to create release.keystore then sign .apk publish app to market.

1) Open Eclipse ADT, Open the required project, Go to File > Export > Export Android Application > Next > “Select name of the app” > Next.

2) If you done the above steps, Now you will be at the below screen.

Image

3) If you are doing this procedure for the first time. Check “Create new keystore“. Choose any “Location” for the new keystore. Choose any required “password“.

Image

4) Clicking “Next” will drop you at below screen. Enter your own “alias“, “password” and other fields click “Next

Image

5) Now choose any destination for resulting signed .apk to be released at market and click “Finish“.

Image

6) On clicking “Finish“, a file named “dummy.keystore” and a “QforQuest.apk” is created at prescribed location. Thats it! your app is ready to be published at Google play. Choose above created .apk which is digitally signed with your release keystore dummy.keystore to publish at Google Play. Beware!! backup “dummy.keystore“(release keystore) without fail since to release any updated version of your app to android market you need to sign the .apk file with same keystore which was used earlier to sign you .apk file. Google will not accept your updated version of app when signed with some other keystore. Hence Ultimately you will be not able to release the updated versions for your apps. My suggestion is just upload your release keystore to your Cloud database if available.

7) Next time when you release updated version to your app just check “Use existing keystore” and choose required release keystore. 

Up to this every thing is fine … You can release your app to market.

Issues with Google Maps V2..

If your app uses Google Maps V2, please follow the below mentioned extra procedure.

A) Everything is similar as described earlier. An additional step is to get New API_KEY from developer console and replace with old API_KEY which was used during development.

B) API_KEY which we add in manifest.xml, is created based on SHA1 fingerprint, which is different for release mode keystore.

C) I faced lot of problems because of this Google Maps V2. I followed below procedure and succeeded.

1) Do again Step1 to Step 6.

2) After Step 6 you may get as below

6

3) Note down the SHA1 finger print, use the same to get API_KEY again from “Google Play Developer Console” for more on this.

4) Replace the old API_KEY with the new API_KEY inside manifest. In short you are replacing the API_KEY of debug keystore with API_KEY of release keystore using their respective SHA1 finger print.

5) Again do Step 1 to Step 7 and upload your final .apk you won’t face any problem again.

Hope this helps…!

Enjoy …!

BQuiet – an automation app

Everybody expects their phone to do tasks automatically. This keeps away from manually handling the device. BQuiet is an automation app that makes the device quiet(mute) for certain period and informs device state to anyone who tries to contact the device during quiet period. This way it makes two benefits

Image 

1) Your device is muted for your desired time period.

2) It informs state of your device to any one who tries to connect.

Google Geofence technology adds the powerful feature to this app. Let us assume a scenario, you need to mute your device when you are attending a meeting, this can be done by virtually creating a fence around your office. BQuiet makes this fence as your boundary and mutes your device when you are inside the fence and unmutes when you get out of fence. BQuiet does not share or track your location.

Image

Information is transmitted to others via SMS, however your network service provider will charge for your SMS. You can also send custom SMS to others. 

Passcode feature makes this app private to you. Hence BQuiet is the must have app for anyone. You can get it from Google Play @BQuiet

BQuiet team expects valuable feed backs from its users.