New Academic Blog

http://eshyumusings.wordpress.com/

Advertisements
Posted in Uncategorized | 1 Comment

Using SQLite Transactions with your ContentProvider

In the world of databases, a transaction is a unit of work (including insertions, deletions, updates) that is Atomic, Consistent, Isolated, and Durable. By default in SQLite, each insertion is a transaction. And to preserve data integrity, SQLite will wait until data is stored on the disk before completing the transaction. So if you have a large data set that you’re trying to insert into your database, inserting each piece individually is going to seem extremely slow.

You want to use transactions, not just because they will increase the performance of your database operations. Because transactions are atomic, they will help you ensure your database is consistent. For example, if you need to process a large batch of instructions, then either everything happened correctly, or if something went wrong then that whole transaction failed (so it’s all or nothing).

By default the ContentResolver API provides a bulkInsert() method, but its not atomic and its slow as hell, so let’s override the bulkInsert() method in our ContentProvider.

public class YourProvider extends ContentProvider {
//..... define constants for the UriMatcher
public static final int EVENTS = 1;
public static final int FESTIVITIES = 2;
//....

private static final UriMatcher sUriMatcher = buildUriMatcher();
private YourDatabase mOpenHelper;


/**
 * Creates the Uri matcher
 */
private static UriMatcher buildUriMatcher(){
	final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
	final String authority = YOUR_CONTENT_AUTHORITY;

   //let the matcher match URIs with your defined constants
	matcher.addURI(authority, "events", EVENTS);
	matcher.addURI(authority, "festivities", FESTIVITIES);
   //.....

	return matcher;
}

@Override
public boolean onCreate() {
	mOpenHelper = new YourDatabase(getContext());
	return true;
}

@Override
public String getType(Uri uri) {
	final int match = sUriMatcher.match(uri);
	switch(match){
	case EVENTS:
		return EVENTS.CONTENT_TYPE;
	case FESTIVITIES:
		return FESTIVITIES.CONTENT_TYPE;
    //......
	default:
		throw new UnsupportedOperationException("unknown: uri " + uri);
	}
}

@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
	final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
	final int match = sUriMatcher.match(uri);
	switch(match){
	case EVENTS:
                int numInserted= 0;
		db.beginTransaction();
		try {
            //standard SQL insert statement, that can be reused
			SQLiteStatement insert = 
				db.compileStatement("insert into " + YOUR TABLE
						+ "(" + COLUMN1 + "," + COLUMN2
						+ "," + COLUMN3 + ")"
						+" values " + "(?,?,?");
			
			for (ContentValues value : values){
                //bind the 1-indexed ?'s to the values specified
				insert.bindString(1, value.getAsString(COLUMN1));
				insert.bindString(2, value.getAsLong(COLUMN2));
				insert.bindString(3, value.getAsString(COLUMN3));
				insert.execute();
			}
			db.setTransactionSuccessful();
            numInserted = values.length
		} finally {
			db.endTransaction();
		}
		return numInserted;
    //.... 
	default:
		throw new UnsupportedOperationException("unsupported uri: " + uri);
	}}

}

So, for each ContentURI you are suppporting (each table in your db), write its respective bulkInsert case as above. And now you will witness an absolutely HUGE increase in performance (for me it cut the bulkInsert() time from 20 seconds to 1), and the return value of the bulkInsert() will now let you know if the transaction was successful or not. Also look here to see the transaction API.

Posted in Android | Tagged , , , , , | 2 Comments

CursorAdapter with Alphabet-indexed Section Headers

Hey everyone, today I would like to share how to organize a CursorAdapter into sections alphabetically. It works as long as your queries in the Cursor are alphabetically sorted (so the sortOrder parameter of your query is ASC by the column you want to alphabetize by).

CursorAdapter with Alphabetical Section Headers

This is made easier by the AlphabetIndexer widget, which uses binary search to finds the position of the first word of each starting letter in your data. However, there are still a few subtleties that must be addressed.

Alphabetical Section Headers Transformation
We wish to go from a un-sectioned data set to a list with the alphabet headers in the correct positions, like above. Here is my solution to this problem.

There are two methods inherited from BaseAdapter that we need to consider:
getCount(): the number of items the ListView should display. Since now we wish to include one extra header in the list, for every alphabetical section, this should return num_items_in_data + num_sections
getItem(positition): returns the item in the data set associated with the position in the ListView. Note that there is an offset depending on how many section headers appear in the data. So in the picture above, to get Fig in the list with headers, since 4 headers appear (A,B,C,F), we want the data item with position 6, instead of the list index which has position 10.

And there are two methods that we implement for SectionIndexer:
getPositionForSection(section): returns the position of the beginning of each section (which in our case is alphabetical). As mentioned before, the AlphabetIndexer will give us the position of the first word starting with each letter in our data set (so it will tell us where A cooresponds to 0, B cooresponds to 1, F to 6, etc). We must offset these positions by the number of other headers that have appeared, so the new position of A is 0 + 0, new position of B is 1 + 1, new position of F is 6 + 3.
getSectionForPosition(position): returns which section each position belongs to. The AlphabetIndexer does this by linearly comparing the first letter of each word to each possible letter in the alphabet, but in our case we now have an offset to consider. I choose to not use the AlphabetIndexer at all for this, but do a similar thing.

The first issue is to find out which alphabetical headers we actually need (in the above picture we only need A, B, C, F). Since at very least the number of headers we actually use needs to be quickly on the spot (say when getCount() is called), and the only way to determine this is to scan the entire data set, we should do some pre-computation.

The second issue is to calculate the offset for each section. This isn’t very hard, because each time we have a new section, there is a new extra header in our list, so the offset increases by 1. So convenience, we can define a map from section number to offset.

Here is the set-up:

private AlphabetIndexer indexer;

//this array is for fast lookup later and will contain the just the
//alphabet sections that actually appear in the data set
private int[] usedSectionIndicies;

//map from alphabet section to the index it ought
//to appear in
private Map<Integer, Integer> sectionToPosition;

//map from alphabet section to the number of other sections
//that appear before it
private Map<Integer, Integer> sectionToOffset;

	{
			indexer = new AlphabetIndexer(c, c.getColumnIndexOrThrow(YOU_COLUMN_NAME), "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
			sectionToPosition = new TreeMap<Integer, Integer>(); //use a TreeMap because we are going to iterate over its keys in sorted order
			sectionToOffset = new HashMap<Integer, Integer>();

			final int count = super.getCount();
			
			int i;
			//temporarily have a map alphabet section to first index it appears
			//(this map is going to be doing somethine else later)
			for (i = count - 1 ; i >= 0; i--){
				sectionToPosition.put(indexer.getSectionForPosition(i), i);
			}

			i = 0;
			usedSectionNumbers = new int[sectionToPosition.keySet().size()];
			
			//note that for each section that appears before a position, we must offset our
			//indices by 1, to make room for an alphabetical header in our list
			for (Integer section : sectionToPosition.keySet()){
				sectionToOffset.put(section, i);
				usedSectionNumbers[i] = section;
				i++;
			}

			//use offset to map the alphabet sections to their actual indicies in the list
			for(Integer section: sectionToPosition.keySet()){
				sectionToPosition.put(section, sectionToPosition.get(section) + sectionToOffset.get(section));
			}
	}

Now for the four discussed methods above, there are a few more subtleties with the implementation of ListView and FastScroller that must be considered.

		@Override
		public int getCount() {
			if (super.getCount() != 0){
				//sometimes your data set gets invalidated. In this case getCount()
				//should return 0 and not our adjusted count for the headers.
				//Any easy way to know if data is invalidated is to check if
				//super.getCount() is 0.
				return super.getCount() + usedSectionNumbers.length;
			}
			
			return 0;
		}
		
		@Override
		public Object getItem(int position) {
			if (getItemViewType(position) == TYPE_NORMAL){//we define this function in the full code later
				//if the list item is not a header, then we fetch the data set item with the same position
				//off-setted by the number of headers that appear before the item in the list
				return super.getItem(position - sectionToOffset.get(getSectionForPosition(position)) - 1);
			}

			return null;
		}

		@Override
		public int getPositionForSection(int section) {
			if (! sectionToOffset.containsKey(section)){ 
				//This is only the case when the FastScroller is scrolling,
				//and so this section doesn't appear in our data set. The implementation
				//of Fastscroller requires that missing sections have the same index as the
				//beginning of the next non-missing section (or the end of the the list if 
				//if the rest of the sections are missing).
				//So, in pictorial example, the sections D and E would appear at position 9
				//and G to Z appear in position 11.
				int i = 0;
				int maxLength = usedSectionNumbers.length;
				
				//linear scan over the sections (constant number of these) that appear in the 
				//data set to find the first used section that is greater than the given section, so in the
				//example D and E correspond to F
				while (i < maxLength && section > usedSectionNumbers[i]){
					i++;
				}
				if (i == maxLength) return getCount(); //the given section is past all our data

				return indexer.getPositionForSection(usedSectionNumbers[i]) + sectionToOffset.get(usedSectionNumbers[i]);
			}

			return indexer.getPositionForSection(section) + sectionToOffset.get(section);
		}

		@Override
		public int getSectionForPosition(int position) {
			int i = 0;		
			int maxLength = usedSectionNumbers.length;
	
			//linear scan over the used alphabetical sections' positions
			//to find where the given section fits in
			while (i < maxLength && position >= sectionToPosition.get(usedSectionNumbers[i])){
				i++;
			}
			return usedSectionNumbers[i-1];
		}

Now to put it all together, first we need a layout for the header. A simple TextView will suffice:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/header"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="6dp"
    android:textSize="18sp" 
    android:textStyle="bold"
    android:gravity="center"
    android:background="@android:color/white"
    android:textColor="@android:color/black"
    />

Now we need our CursorAdapter to implement SectionIndexer, and use the previously discussed method, with a few additional methods (also I’m just extending a SimpleCursorAdapter so I don’t need to implement newView() or bindView() ).

	public class MyAlphabetizedAdapter extends SimpleCursorAdapter implements SectionIndexer{

		private static final int TYPE_HEADER = 1;
		private static final int TYPE_NORMAL = 0;

		private static final int TYPE_COUNT = 2;

		private AlphabetIndexer indexer;

		private int[] usedSectionNumbers;

		private Map<Integer, Integer> sectionToOffset;
		private Map<Integer, Integer> sectionToPosition;
	
		public MyAlphabetizedAdapter(Context context, int layout, Cursor c,
				String[] from, int[] to) {
			super(context, layout, c, from, to);
			
			indexer = new AlphabetIndexer(c, c.getColumnIndexOrThrow(YOUR_COLUMN_NAME, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
			sectionToPosition = new TreeMap<Integer, Integer>();
			sectionToOffset = new HashMap<Integer, Integer>();

			final int count = super.getCount();
			
			int i;
			for (i = count - 1 ; i >= 0; i--){
				sectionToPosition.put(indexer.getSectionForPosition(i), i);
			}

			i = 0;
			usedSectionNumbers = new int[sectionToPosition.keySet().size()];
			
			for (Integer section : sectionToPosition.keySet()){
				sectionToOffset.put(section, i);
				usedSectionNumbers[i] = section;
				i++;
			}

			for(Integer section: sectionToPosition.keySet()){
				sectionToPosition.put(section, sectionToPosition.get(section) + sectionToOffset.get(section));
			}
		}

		@Override
		public int getCount() {
			if (super.getCount() != 0){
				return super.getCount() + usedSectionNumbers.length;
			}
			
			return 0;
		}
		
		@Override
		public Object getItem(int position) {
			if (getItemViewType(position) == TYPE_NORMAL){//we define this function later
				return super.getItem(position - sectionToOffset.get(getSectionForPosition(position)) - 1);
			}

			return null;
		}

		@Override
		public int getPositionForSection(int section) {
			if (! sectionToOffset.containsKey(section)){ 
				int i = 0;
				int maxLength = usedSectionNumbers.length;
				
				while (i < maxLength && section > usedSectionNumbers[i]){
					i++;
				}
				if (i == maxLength) return getCount();

				return indexer.getPositionForSection(usedSectionNumbers[i]) + sectionToOffset.get(usedSectionNumbers[i]);
			}

			return indexer.getPositionForSection(section) + sectionToOffset.get(section);
		}

		@Override
		public int getSectionForPosition(int position) {
			int i = 0;		
			int maxLength = usedSectionNumbers.length;

			while (i < maxLength && position >= sectionToPosition.get(usedSectionNumbers[i])){
				i++;
			}
			return usedSectionNumbers[i-1];
		}

		@Override
		public Object[] getSections() {
			return indexer.getSections();
		}

		//nothing much to this: headers have positions that the sectionIndexer manages.
		@Override
		public int getItemViewType(int position) {
			if (position == getPositionForSection(getSectionForPosition(position))){
				return TYPE_HEADER;
			} return TYPE_NORMAL;
		}

		@Override
		public int getViewTypeCount() {
			return TYPE_COUNT;
		}

		//return the header view, if it's in a section header position
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			final int type = getItemViewType(position);
			if (type == TYPE_HEADER){
				if (convertView == null){
					convertView = getLayoutInflater().inflate(R.layout.header, parent, false); 
				}
				((TextView)convertView.findViewById(R.id.header)).setText((String)getSections()[getSectionForPosition(position)]);
				return convertView;
			}
			return super.getView(position - sectionToOffset.get(getSectionForPosition(position)) - 1, convertView, parent); 
		}


		//these two methods just disable the headers
		@Override
		public boolean areAllItemsEnabled() {
			return false;
		}

		@Override
		public boolean isEnabled(int position) {
			if (getItemViewType(position) == TYPE_HEADER){
				return false;
			}
			return true;
		}
	}

Finally, make sure to make sure that fastScroll is enabled for your ListView, so you can take advantage of the sweet fast scrolling tab 🙂

There is one more subtle problem, which is if the size of the cursor changes (so say in the pictorial example, our data set only contains Apple, Banana, Cranberry now) and a new instance of this list adapter isn’t made, then must take care to redo all the pre-computation. If this is the case and the new cursor has non-zero count, then you might want to wrap the pre-computation in its own method and call it during onCursorChanged().

Edit: As requested by Nick, here is a full-working demo of this list adapter in action!

/**
 * ListActivity demonstrating using the AlphabetIndexer to derive section headers
 * @author Eric
 *
 */
public class DemoActivity extends ListActivity {

	private SQLiteDatabase db;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		//NOTE: you should never actually start database operations from the context of the UI thread
		final DbHelper helper = new DbHelper(this);
		db = helper.getWritableDatabase();
		
		//populate the db with our dummy data, might take a while, so in real scenarios spawn a 
		//new thread to do this
		final int result = helper.insertDummyData(db); 
		
		if (result > 0){ 
			//query the db to obtain our cursor and set the list adapter, only if the rows were 
			//successfully inserted
			
			final Cursor cursor = db.query(DbHelper.TABLE_COUNTRIES, null, null, 
					null, null, null, DbHelper.COUNTRIES_NAME + " ASC" );
			startManagingCursor(cursor);
			Toast.makeText(this, "Finished populating.", Toast.LENGTH_SHORT).show();	
			
			setListAdapter(new MyAlphabetizedAdapter(this, android.R.layout.simple_list_item_1, 
					cursor, new String[]{DbHelper.COUNTRIES_NAME}, new int[]{android.R.id.text1}));
			
			//don't ever forget to do this, either here or in your ListView layout
			getListView().setFastScrollEnabled(true);
			
		} else {
			Toast.makeText(this, "Database could not be populated. Restart the activity.", Toast.LENGTH_LONG).show();	
		}

	}

	@Override
	protected void onDestroy() {
		db.close();
		super.onDestroy();
	}

	/**
	 * CursorAdapter that uses an AlphabetIndexer widget to keep track of the section indicies.
	 * These are the positions where we want to show a section header showing the respective alphabet letter.
	 * @author Eric
	 *
	 */
	public class MyAlphabetizedAdapter extends SimpleCursorAdapter implements SectionIndexer{

		private static final int TYPE_HEADER = 1;
		private static final int TYPE_NORMAL = 0;

		private static final int TYPE_COUNT = 2;

		private AlphabetIndexer indexer;

		private int[] usedSectionNumbers;

		private Map<Integer, Integer> sectionToOffset;
		private Map<Integer, Integer> sectionToPosition;
	
		public MyAlphabetizedAdapter(Context context, int layout, Cursor c,
				String[] from, int[] to) {
			super(context, layout, c, from, to);
			
			indexer = new AlphabetIndexer(c, c.getColumnIndexOrThrow(DbHelper.COUNTRIES_NAME), "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
			sectionToPosition = new TreeMap<Integer, Integer>(); //use a TreeMap because we are going to iterate over its keys in sorted order
			sectionToOffset = new HashMap<Integer, Integer>();

			final int count = super.getCount();
			
			int i;
			//temporarily have a map alphabet section to first index it appears
			//(this map is going to be doing somethine else later)
			for (i = count - 1 ; i >= 0; i--){
				sectionToPosition.put(indexer.getSectionForPosition(i), i);
			}

			i = 0;
			usedSectionNumbers = new int[sectionToPosition.keySet().size()];
			
			//note that for each section that appears before a position, we must offset our
			//indices by 1, to make room for an alphabetical header in our list
			for (Integer section : sectionToPosition.keySet()){
				sectionToOffset.put(section, i);
				usedSectionNumbers[i] = section;
				i++;
			}

			//use offset to map the alphabet sections to their actual indicies in the list
			for(Integer section: sectionToPosition.keySet()){
				sectionToPosition.put(section, sectionToPosition.get(section) + sectionToOffset.get(section));
			}
		}

		@Override
		public int getCount() {
			if (super.getCount() != 0){
				//sometimes your data set gets invalidated. In this case getCount()
				//should return 0 and not our adjusted count for the headers.
				//The only way to know if data is invalidated is to check if
				//super.getCount() is 0.
				return super.getCount() + usedSectionNumbers.length;
			}
			
			return 0;
		}
		
		@Override
		public Object getItem(int position) {
			if (getItemViewType(position) == TYPE_NORMAL){//we define this function in the full code later
				//if the list item is not a header, then we fetch the data set item with the same position
				//off-setted by the number of headers that appear before the item in the list
				return super.getItem(position - sectionToOffset.get(getSectionForPosition(position)) - 1);
			}

			return null;
		}

		@Override
		public int getPositionForSection(int section) {
			if (! sectionToOffset.containsKey(section)){ 
				//This is only the case when the FastScroller is scrolling,
				//and so this section doesn't appear in our data set. The implementation
				//of Fastscroller requires that missing sections have the same index as the
				//beginning of the next non-missing section (or the end of the the list if 
				//if the rest of the sections are missing).
				//So, in pictorial example, the sections D and E would appear at position 9
				//and G to Z appear in position 11.
				int i = 0;
				int maxLength = usedSectionNumbers.length;
				
				//linear scan over the sections (constant number of these) that appear in the 
				//data set to find the first used section that is greater than the given section, so in the
				//example D and E correspond to F
				while (i < maxLength && section > usedSectionNumbers[i]){
					i++;
				}
				if (i == maxLength) return getCount(); //the given section is past all our data

				return indexer.getPositionForSection(usedSectionNumbers[i]) + sectionToOffset.get(usedSectionNumbers[i]);
			}

			return indexer.getPositionForSection(section) + sectionToOffset.get(section);
		}

		@Override
		public int getSectionForPosition(int position) {
			int i = 0;		
			int maxLength = usedSectionNumbers.length;
	
			//linear scan over the used alphabetical sections' positions
			//to find where the given section fits in
			while (i < maxLength && position >= sectionToPosition.get(usedSectionNumbers[i])){
				i++;
			}
			return usedSectionNumbers[i-1];
		}

		@Override
		public Object[] getSections() {
			return indexer.getSections();
		}
		//nothing much to this: headers have positions that the sectionIndexer manages.
		@Override
		public int getItemViewType(int position) {
			if (position == getPositionForSection(getSectionForPosition(position))){
				return TYPE_HEADER;
			} return TYPE_NORMAL;
		}

		@Override
		public int getViewTypeCount() {
			return TYPE_COUNT;
		}

		//return the header view, if it's in a section header position
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			final int type = getItemViewType(position);
			if (type == TYPE_HEADER){
				if (convertView == null){
					convertView = getLayoutInflater().inflate(R.layout.header, parent, false); 
				}
				((TextView)convertView.findViewById(R.id.header)).setText((String)getSections()[getSectionForPosition(position)]);
				return convertView;
			}
			return super.getView(position - sectionToOffset.get(getSectionForPosition(position)) - 1, convertView, parent); 
		}


		//these two methods just disable the headers
		@Override
		public boolean areAllItemsEnabled() {
			return false;
		}

		@Override
		public boolean isEnabled(int position) {
			if (getItemViewType(position) == TYPE_HEADER){
				return false;
			}
			return true;
		}
	}
}
/**
 * A database helper to create the db table with country names
 * @author Eric
 *
 */
public class DbHelper extends SQLiteOpenHelper {

	public static final String TABLE_COUNTRIES = "countries";
	public static final String COUNTRIES_NAME = "name";

	private static final String DATABASE_NAME = "alphabetical_tutorial.db";
	private static final int DATABASE_VERSION = 1;

	public DbHelper(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("create table " + TABLE_COUNTRIES + " (" + 
				BaseColumns._ID + " integer primary key autoincrement,"
				+ COUNTRIES_NAME + " text not null,"
				+ "unique (" + COUNTRIES_NAME + ") on conflict replace)");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("drop table exists" + TABLE_COUNTRIES);
		onCreate(db);
	}

	/**
	 * Inserts the list of country names into the db.
	 * We use SQL transactions for data integrity and efficiency.
	 * @param db
	 * @return
	 */
	public int insertDummyData(SQLiteDatabase db){
		int numInserted = 0;
		db.beginTransaction();
		try {
			SQLiteStatement insert = db.compileStatement("insert into " + 
					TABLE_COUNTRIES + "(" + COUNTRIES_NAME + ")" 
					+ "values " + "(?)");
			for (String country : COUNTRIES){
				insert.bindString(1, country);
				insert.execute();
			}
			db.setTransactionSuccessful();
			numInserted = COUNTRIES.length;
		} finally {
			db.endTransaction();
		}
		return numInserted;
	}

	//borrow the list of countries from the ListView tutorial on developer.android.com/
	static final String[] COUNTRIES = new String[] {
		"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
		"Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
		"Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
		"Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
		"Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
		"Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory",
		"British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi",
		"Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
		"Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
		"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
		"Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
		"Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
		"East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
		"Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
		"Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
		"French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
		"Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
		"Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
		"Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
		"Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
		"Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
		"Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
		"Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
		"Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
		"Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
		"Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
		"Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
		"Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
		"Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
		"Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
		"Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
		"Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
		"Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
		"Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
		"Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
		"The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
		"Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
		"Ukraine", "United Arab Emirates", "United Kingdom",
		"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
		"Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
		"Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
	};
}

This also demonstrates using SQL transactions, which my next post is about.

Posted in Android | Tagged , , , , , , , , | 79 Comments

Persisting Cookies through Activity Life Cycle

If you are coding an Android REST application using the Apache HttpClient, there may be times when you bring another application into the foreground (like Google Maps to get driving directions), and the process containing your application is killed because of low system memory. Your instance of the HttpClient is dead, and so it no longer has the session information cookies needed to run the app.

After the process is killed, navigating back to your activity causes onCreate() to be called (Application Fundamentals). Thus, in the onCreate() method, we can first check to see if the session cookies are gone, and if so, re-add them to the HttpClient.

In order to put the Cookie in a Bundle, we must make it serialiazable. Unfortunately Cookie is not serializable in the Apache API version that Android currently uses, so we must define our own class to do this.

public class SerializedCookie implements Serializable {

	private static final long serialVersionUID = 5327445113190674523L; //arbitrary

	private String name;
	private String value;
	private String domain;
	
	public SerializedCookie(Cookie cookie){
		this.name = cookie.getName();
		this.value = cookie.getValue();
		this.domain = cookie.getDomain();
	}
	
	public String getName(){
		return name;
	}
	
	public String getValue(){
		return value;
	}
	public String getDomain(){
		return domain;
	}
}

Now in our Activities we need to override the onCreate() and onSaveInstanceState() methods.

DefaultHttpClient client; //declared here, but get the client however you must. 

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		List<Cookie> cookies =client.getCookies();
		if (!cookies.isEmpty()){
			Cookie sessionInfo = cookies.get(0);
			outState.putSerializable("sessionInfo", new SerializedCookie(sessionInfo));
		}
		
 DefaultHttpClient client; //declared here, but get the client however you must. 

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		if (client.getCookies().isEmpty()){
			if (savedInstanceState.containsKey("sessionInfo")){
				SerializedCookie cookie = (SerializedCookie) savedInstanceState.
					getSerializable("sessionInfo");
				
				BasicClientCookie newCookie = new BasicClientCookie(cookie.getName(),
						cookie.getValue());
						newCookie.setDomain(cookie.getDomain());
						
				client.addCookie(newCookie);
			} else {
                                //for whatever reason the session information couldn't be obtained,
                                //take action here
			}
		}

Now if you open another application and the process running your app is killed, when you navigate back to it, your activity will try to re-insert the session cookie into the HttpClient, and your app will run normally.

Posted in Android | Tagged , , , , , | 2 Comments

Converting a Hex String to N-bit Binary String

Recently I have wanted to keep track of unique IDs of the Android devices using one of my applications. These are found under Settings.Secure.ANDROID_ID and are 64bit numbers given as Hex Strings, but I needed the actual binary number, with leading zeros included. I did a lot of research but couldn’t find a complete elegant solution, so here is what I came up with:

	private static String hexStringToNBitBinary(String hexString, int N){
		long decimalResult = 0;
		int length = hexString.length(); //store the length in memory, for devices without a JIT
		int i;
		
		for (i = 0; i < length; i++){
			//equivalent to multiplying the result by 16 and adding the value of the new digit, but uses bit operations for performance
			decimalResult = (decimalResult << 4 ) | Character.digit(hexString.charAt(i), 16);
		}
		String binary = Long.toBinaryString(decimalResult); //gives us a binary string, but is missing leading zeros
		length = binary.length();
		if (length == N){
			return binary;
		}  else if (length < N){
			int difference = N-length;
			char[] buffer = new char[N]; //allocate a new char buffer of the desired length 
			for (i = 0; i < difference; i ++){
				buffer[i] = '0'; //fill in the needed number of leading zeros
			}
			binary.getChars(0, length, buffer, difference); //copies the original binary string into the buffer after leading zeros
			return new String(buffer);
		} else{
			throw new IllegalArgumentException("Hex String is not a N bit number!");
		}
	}

I believe that this is has better performance than using String concatenations or StringBuilder appends, since we allocate just the number of memory we know we need immediately. Let me know if there is a more efficient way.

Posted in Programming | Tagged , , , | 1 Comment

Implementing a Generic Method with Function as Parameters Using Interfaces

Consider the following: Say the function getListItems() needs to download a JSON output, parse it, and return a list of items of a specific type. Depending on the URL we get the output from, we might want to parse the JSON differently. So it would make our lives much easier if we could pass the parsing function as a parameter into the getListItems() function!

I. Passing Functions as Parameters using Interfaces

Unlike C++/C (and even similarly in the likes of Python), Java does not allow one to explicitly pass functions as the arguments of functions. What one can do, however, is make use of interfaces. The idea is to create an interface with a method, and create a unique implementation of each method. Let’s see this at work:

public interface Parser{

public List<Items> parseMethod(String url);

}

public class ParserForA implements Parser{

public List<Items> parseMethod(String url){

//do something specific for A

}

}

public class ParserForB implements Parser{

public List<Items> parseMethod(String url){

//do something specific for B

}

}

Now we can  parametrize getListItems with a Parser instance! To actually use it we might do something like


public List<Items> getListItems(String url, Parser parser){

return parser.parser(url)

}

And voila! getListItems(url, new ParserForA()) will use the parsing method for A, while getListItems(url, new ParserForB()) will use the parsing method for B.

II. Using generics to return a List of any type

Now so far we have made both parsing functions return a list of items which are of the same type. Often, if the JSON parsing method is different, most likely the information that we want to be processing and extracting is different. That is, what if we want to make the parser for A return a list of ItemA, while the parser for B returns a list of ItemB? To accomplish this we must make use of generics. Here’s how we code the interface:

public interface Parser<T>{

public List<T> parseMethod(String url);

}

public class ParserForA implements Parser<A>{

public List<A> parseMethod(String url){

//do something specific for A

}

}

public class ParserForB implements Parser<B>{

public List<B> parseMethod(){

//do something specific for B

}

}

Now for the actual getListItems function, we can use the following syntax to establish a generic method:


public <T> List<T> getListItems(String url, Parser<T> parser){

return parser.parser(url)

}

And now the compiler will be happy when we claim that getListItems(url, new parserForA()) returns a List (and likewise for B)!

Posted in Programming | Tagged , , , , , | Leave a comment

Syncing cookies between an HttpClient and a WebView

Hey all,

Today I would like to show how to share cookies between an HttpClient and a WebView. This is necessary when your application use an HttpClient to make POST/GET calls, and then opens a WebView to a webpage that needs the same session information. Say that we are using a DefaultHttpClient named client.


DefaultHttpClient client;

//gets all cookies from the HttpClient's cookie jar
List<Cookie> cookies = client.getCookieStore().getCookies();

		if (! cookies.isEmpty()){

			CookieSyncManager.createInstance(YourContext.this);
			CookieManager cookieManager = CookieManager.getInstance();

                        //sync all the cookies in the httpclient with the webview by generating cookie string
			for (Cookie cookie : cookies){
				
				sessionInfo = cookie;
	
					String cookieString = sessionInfo.getName() + "=" + sessionInfo.getValue() + "; domain=" + sessionInfo.getDomain();
					cookieManager.setCookie(YOUR_DOMAIN, cookieString);
					CookieSyncManager.getInstance().sync();
			}
		}

Make sure that YOUR_DOMAIN corresponds to the domain of the website you need session info from, else this won’t work.

On StackOverflow there is a similar solution, but I found that calling cookieManager.removeSessionCookie() makes it often not work for me (in particular if I got a new session cookie by logging out/changing user). I’m not sure why, since in the thread they have the opposite happening. But, the above code has never failed for me.

Posted in Android | Tagged , , , | 19 Comments