Mar
21
2009
The Friends List
Sometimes you need to manage a list of friends inside your Android application. In my case the friends list is a list of contacts entries.
Using a contacts entry as a friend is a good idea for your application because:
- the user has to use a well known activity (the Edit Contact activity) to edit the friend's contact details
- you don't have to write the code for that part :)
public final class FriendsColumns implements BaseColumns {
public static final String PERSON_ID = "person";
public static final String IS_FRIEND = "is_friend";
// cached details from contacts
public static final String DISPLAY_NAME = "display_name";
// additional application-related columns
}
The column is_friend is a boolean column that marks the contact as a friend.
The column display_name is filled with the contact's display name while synchronizing the table. It's purpose is only to remove the need for additional queries while rendering the list activity.
For synchronization I created service that runs in a background thread and makes use of the CursorJoiner class.
Cursor peopleCursor = contentResolver.query(Contacts.People.CONTENT_URI,
new String[] {Contacts.People._ID, Contacts.People.DISPLAY_NAME},
null,
null,
Contacts.People._ID);
Cursor friendsCursor = contentResolver.query(Friends.CONTENT_URI,
new String[] {Friends._ID, Friends.PERSON_ID, Friends.DISPLAY_NAME},
null,
null,
Friends.PERSON_ID);
CursorJoiner joiner = new CursorJoiner(peopleCursor,
new String[] {Contacts.People._ID},
friendsCursor,
new String[] {Friends.PERSON_ID});
Uri friendUri;
for (CursorJoiner.Result result : joiner) {
switch (result) {
case LEFT:
ContentValues insertValues = new ContentValues();
insertValues.put(Friends.PERSON_ID, peopleCursor.getLong(0));
insertValues.put(Friends.IS_FRIEND, 0); // false
insertValues.put(Friends.DISPLAY_NAME, peopleCursor.getString(1));
contentResolver.insert(Friends.CONTENT_URI, insertValues);
break;
case RIGHT:
friendUri = ContentUris.withAppendedId(Friends.CONTENT_URI,
friendsCursor.getLong(0));
contentResolver.delete(friendUri, null, null);
break;
case BOTH:
ContentValues updateValues = new ContentValues();
updateValues.put(Friends.DISPLAY_NAME, peopleCursor.getString(1));
friendUri = ContentUris.withAppendedId(Friends.CONTENT_URI,
friendsCursor.getLong(0));
contentResolver.update(friendUri, updateValues, null, null);
break;
}
}
friendsCursor.close();
peopleCursor.close();
The synchronization service is started when the friends list activity is created and when the contents of content://contacts/people changed.
public void onCreate(Bundle savedInstanceState) {
// ...
synchronizeFriendsWithContacts();
ContentObserver contentObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
synchronizeFriendsWithContacts();
}
}
getContentResolver().registerContentObserver(Contacts.People.CONTENT_URI,
true,
contentObserver);
// ...
}
private void synchronizeFriendsWithContacts() {
startService(new Intent(this, FriendsSynchronizer.class));
}
By using this approach I achieved:
- great performance in rendering the list activity, because of the local copies from contacts
- when adding a new friend to the list I already have a list of remaining contacts, by using is_friend as false
- removing a friend from the list is done by just setting is_friend to false
- when the user removes a contact the friends list is automaticaly cleaned too
- I don't need to write a new activity that edits the friend's contact details :)