Archive for the ‘Android’ Category

What happened to PouchDroid?

Just in case anyone is wondering about PouchDroid, a few things changed since I started working on it three months ago:

  1. I found out CouchBase Lite exists. So you may want to try that instead of some crazy JavaScript thing.
  2. I started working on PouchDB itself.

I do plan on eventually updating PouchDroid, but for the time being there’s still plenty to be done in PouchDB. Long-term goals will be:

  1. API parity with PouchDB (at least, the important parts).
  2. Get a rigorous suite of tests in place.
  3. Once the tests are passing, port it to pure Java.

If you still want to use PouchDroid in a Cordova/PhoneGap app, I’d recommend using PouchDB itself and the SQLite plugin instead. PouchDroid uses a slightly modified version of the SQLite plugin, which probably won’t give you any noticeable performance improvements. As for the XHR overrides, I’ll probably take that out, so you should just set up CORS on your CouchDB.

If you want to use PouchDroid in a Java Android project, it’s still pretty nifty for small sync tasks (e.g. the PouchDroidMigrationTask, which can sync a SQLite database to CouchDB). But if you want something more full-featured and reliable, wait for 1.0.

PouchDroid v0.1.0 is out!

PouchDroid

PouchDroid

I’ve managed to nail down the bulk of the API for PouchDroid, and I’m releasing a super-tentative 0.1.0 version today, just in time for Christmas.

Obligatory caveat: Please do not use it in production yet. God no, not yet. Your user’s data is more precious to me than that.

Do go try it out, though! There’s now a well-thought-out README and a “Getting Started” tutorial. And a dorky logo I made in GIMP. (Aw yeah.)

So, it’s official: PouchDB’s empire has spread to Android. And now that JavaScript is a first-class citizen on iOS, could that platform be far behind?

Porting PouchDB to Android: initial work and thoughts

Update: CouchDroid has been renamed to PouchDroid, and I’ve released version 0.1.0. The code examples below are out of date. Please refer to the instructions and tutorials on the GitHub page.

I love PouchDB. It demonstrates the strength and flexibility of CouchDB, and since it supports both WebSQL and IndexedDB under the hood, it obviates the need to learn their separate APIs (or to worry about the inevitable browser inconsistencies). If you know CouchDB, you already know PouchDB.

And most importantly, it offers two-way sync in just a few lines of code. To me, this is magical:

var db = new PouchDB('mydb')
db.replicate.to('http://foo.com:5984/db', {continuous : true});
db.replicate.from('http://foo.com:5984/db', {continuous : true});

I wanted to bring this same magic to Android, so I started working on an Android adapter for PouchDB. I’m calling it CouchDroid, until I can think of a better name. The concept is either completely crazy or kinda clever, which is why I’m writing this post, in the hopes of getting early feedback.

The basic idea is this: instead of rewriting PouchDB in Java, I fire up an invisible WebView that runs PouchDB in JavaScript. I override window.openDatabase to redirect to the native Java SQLite APIs, so that all of the SQL queries run on a background thread (instead of tying up the UI thread, like they normally would). I also redirect XMLHttpRequest into Java, giving me control over the HTTP request threads, and helping avoid any messy server-side configuration of CORS/JSONP for web security.

Result: it works on a fresh CouchDB installation, no assembly required. And it’s actually pretty damned fast.

The code is still a little rough around the edges, but it can already do bidirectional sync, which is great. Callbacks look weird in Java, but static typing, generics, and content assist make the Pouch APIs a dream to work with. (My precious Ctrl+space works!)

Here’s an example of bidirectional sync between two Android devices and a CouchDB server using CouchDroid. First, we define what kinds of documents we want to sync by extending PouchDocument. This is Android, so let’s store some robots:

public class Robot extends PouchDocument {

  private String name;
  private String type;
  private String creator;
  private double awesomenessFactor;
  private int iq;
  private List<RobotFunction> functions;

  // constructors, getters, setters, toString...
}
public class RobotFunction {

  private String name;

  // constructors, getters, setters, toString...
}

I’m using Jackson for JSON serialization/deserialization, which means that your standard POJOs “just work.” The PouchDocument abstract class simply adds the required CouchDB fields _id and _rev.

In our Activity, we extend CouchDroidActivity (needed to set up the Java <-> JavaScript bridge), and we add a bunch of robots to a PouchDB<Robot>:

public class MainActivity extends CouchDroidActivity {

  private PouchDB<Robot> pouch;

  // onCreate()...

  @Override
  protected void onCouchDroidReady(CouchDroidRuntime runtime) {

    pouch = PouchDB.newPouchDB(Robot.class, runtime, "robots.db");

    List<Robot> robots = Arrays.asList(
      new Robot("C3P0", "Protocol droid", "George Lucas", 0.4, 200, 
        Arrays.asList(
          new RobotFunction("Human-cyborg relations"),
          new RobotFunction("Losing his limbs"))),
      new Robot("R2-D2", "Astromech droid", "George Lucas", 0.8, 135,
        Arrays.asList(
          new RobotFunction("Getting lost"),
          new RobotFunction("Having a secret jetpack"),
          new RobotFunction("Showing holographic messages")))    
    );

    pouch.bulkDocs(robots, new BulkCallback() {

        @Override
        public void onCallback(PouchError err, List<PouchInfo> info) {
          Log.i("Pouch", "loaded: " + info);
        }
    });
  }
}

Meanwhile, on another Android device, we load a completely different list of robots:

List<Robot> robots = Arrays.asList(
  new Robot("Mecha Godzilla", "Giant monster", "Toho", 0.4, 82, 
    Arrays.asList(
      new RobotFunction("Flying through space"),
      new RobotFunction("Kicking Godzilla's ass"))),
  new Robot("Andy", "Messenger robot", "Stephen King", 0.8, 135,
    Arrays.asList(
      new RobotFunction("Relaying messages"),
      new RobotFunction("Betraying the ka-tet"),
      new RobotFunction("Many other functions"))),
  new Robot("Bender", "Bending Unit", "Matt Groening", 0.999, 120,
    Arrays.asList(
      new RobotFunction("Gettin' drunk"),
      new RobotFunction("Burping fire"),
      new RobotFunction("Bending things"),
      new RobotFunction("Inviting you to bite his lustrous posterior")))
  );

And, of course, we set up bidirectional replication on both pouches:

String remoteCouch = "http://user:password@myhost:5984/robots";
Map<String, Object> options = Maps.quickMap("continuous", true);

pouch.replicateFrom(remoteCouch, options);
pouch.replicateTo(remoteCouch, options);

Wait a few seconds (or pass in a callback), and voilà! You can check the contents on CouchDB:
C3P0 on CouchDB

And then check the contents of each PouchDB on Android:

pouch.allDocs(true, new AllDocsCallback<Robot>() {

@Override
  public void onCallback(PouchError err, AllDocsInfo<Robot> info) {
    List<Robot> robots = info.getDocuments();
    Log.i("Pouch", "pouch contains " + robots);
  }
});

This prints:

pouch contains [Robot [name=Bender, type=Bending Unit, creator=Matt Groening, 
awesomenessFactor=0.999, iq=120, functions=[RobotFunction [name=Gettin' drunk], RobotFunction 
[name=Burping fire], RobotFunction [name=Bending things], RobotFunction [name=Inviting you to bite 
his lustrous posterior]]], Robot [name=C3P0, type=Protocol droid, creator=George Lucas, 
awesomenessFactor=0.4, iq=200, functions=[RobotFunction [name=Human-cyborg relations], RobotFunction 
[name=Losing his limbs]]], Robot [name=Mecha Godzilla, type=Giant monster, creator=Toho, 
awesomenessFactor=0.4, iq=82, functions=[RobotFunction [name=Flying through space], RobotFunction 
[name=Kicking Godzilla's ass]]], Robot [name=R2-D2, type=Astromech droid, creator=George Lucas, 
awesomenessFactor=0.8, iq=135, functions=[RobotFunction [name=Getting lost], RobotFunction 
[name=Having a secret jetpack], RobotFunction [name=Showing holographic messages]]], Robot 
[name=Andy, type=Messenger robot, creator=Stephen King, awesomenessFactor=0.8, iq=135, functions=
[RobotFunction [name=Relaying messages], RobotFunction [name=Betraying the ka-tet], RobotFunction 
[name=Many other functions]]]]

So within seconds, all five documents have been synced to two separate PouchDBs and one CouchDB. Not bad!

In addition to adapting the PouchDB API for Java, I also wrote a simple migration tool to mirror an existing SQLite database to a remote CouchDB. It could be useful, if you just want a read-only web site where users can view their Android data:

new CouchDroidMigrationTask.Builder(runtime, sqliteDatabase)
    .setUserId("fooUser")
    .setCouchdbUrl("http://user:password@foo.com:5984/db")
    .addSqliteTable("SomeTable", "uniqueId")
    .addSqliteTable("SomeOtherTable", "uniqueId")
    .setProgressListener(MainActivity.this)
    .build()
    .start();

This converts SQLite data like this:

sqlite&gt; .schema Monsters
CREATE TABLE Monsters (_id integer primary key autoincrement, 
  uniqueId text not null,
  nationalDexNumber integer not null,
  type1 text not null,
  type2 text,
  name text not null);
sqlite&gt; select * from Monsters limit 1
1|001|1|Grass|Poison|Bulbasaur

into CouchDB data like this:

{
   "_id": "fooUser~pokemon_11d1eaac.db~Monsters~001",
   "_rev": "1-bd52d48dba37ce490c38d455726296f0",
   "table": "Monsters",
   "user": "fooUser",
   "sqliteDB": "pokemon_11d1eaac.db",
   "appPackage": "com.nolanlawson.couchdroid.example1",
   "content": {
       "_id": 1,
       "uniqueId": "001",
       "nationalDexNumber": 1,
       "type1": "Grass",
       "type2": "Poison",
       "name": "Bulbasaur"
   }
}

Notice that the user is included as a field and as part of the _id, so you can easily set up per-user write privileges. For per-user read privileges, you still need to set up one database per user.

CouchDroid isn’t ready for a production release yet. But even in its rudimentary state, I think it’s pretty damn exciting. As Android developers, wouldn’t it be great if we didn’t have to write so many SQL queries, and we could just put and get our POJOs? And wouldn’t it be awesome if that data were periodically synced to the server, so we didn’t even have to think about intermittent availability or incremental sync or conflict resolution or any of that junk? And wouldn’t our lives be so much easier if the data was immediately available in a RESTful web service like CouchDB, so we didn’t even need to write any server code? The dream is big, but it’s worth pursuing.

For more details on the project, check it out on GitHub. The sample apps in the examples directory are a good place to start. Example #1 is the migration script above, Example #2 is some basic CRUD operations on the Pouch API, and Example #3 is the full bidirectional sync described above. More to come!

Introducing the SuperSaiyanScollView: super-fast sectioned lists for Android

Update: This blog post is now out-of-date. Please see the official documentation on GitHub.

Say you’re writing an Android app, and you have a ListView you’d like to divide into sections:

Sectioned list views.

Sectioned list views.

Normally, you’d need to write a custom ListAdapter, where you define the resources yourself and juggle two different types of View. Not fun.

public class MyBoringAdapter<Foo> extends ArrayAdapter<Foo> {

    // constructors...
    
    public int getViewTypeCount() {
      return 2;
    }
    
    public int getItemViewType(int pos) {
      return isHeader(pos) ? 0 : 1;
    }

    // more boilerplate...
    
    public View getView(int pos, View view, ViewGroup parent) {
        if (isHeader(pos)) {
            // sigh
        } else {
            // so tired of this crap
        }
    }
}

Now, what happens if you want to change the ordering? Or add new sections? Or add fast-scroll overlays?

It’s one of the most common UI patterns in Android, and yet (surprisingly) it’s still a pain to implement. Nothing in the stock Android SDK provides this functionality.

Enter the SuperSaiyanScrollView (dramatic gong sound). It’s a standalone library that you can easily import into any Android app, and it seamlessly adds fast-scrolling, sorting, and sectioning. Plus, it looks great on both Android 4.0 and pre-4.0 devices, on tablets and phones.

SuperSaiyanScrollView on HTC Magic (Eclair) and Galaxy Nexus (Jelly Bean)

SuperSaiyanScrollView on HTC Magic (Eclair) and Galaxy Nexus (Jelly Bean)

Why “Super Saiyan”? Because:

  1. I made it, so I get to name it.
  2. It’s super-fast, super-powerful, and it kicks (stock) Android’s ass.
Their power levels are definitely over 9000.

Their power levels are definitely over 9000.

Usage

The SuperSaiyanScrollView code attemps to be as unobtrusive as possible. To use it, you just need to wrap your existing ListView in a SuperSaiyanScrollView and your existing Adapter in a SectionedListAdapter.

In your layout XML file, add a SuperSaiyanScrollView around your ListView:

    <com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView
      android:id="@+id/scroll"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        />

    </com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView>

(I like to set android:scrollbars="none", to remove the omnipresent gray scrollbars and stick with the “fast” blue scrollbars.)

Next, wrap your existing Adapter (e.g. an ArrayAdapter) in a SectionedListAdapter. The SectionedListAdapter uses a fluent “builder” pattern, similar to AlertDialog.Builder:

    SectionedListAdapter<MyCoolAdapter> adapter = 
        SectionedListAdapter.Builder.create(this, myCoolAdapter)
        .setSectionizer(new Sectionizer<MyCoolListItem>(){

          @Override
          public CharSequence toSection(MyCoolListItem item) {
            return item.toSection();
          }
        })
        .sortKeys()
        .sortValues()
        .build();

To include the SuperSaiyanScrollView in your Android app, simply follow these steps:

  1. Check out the code from GitHub:

    git clone https://github.com/nolanlawson/SuperSaiyanScrollView.git
    
  2. If you use Eclipse/ADT, go to Import -> Existing Android Code -> and choose the SuperSaiyanScrollView/library/ folder.
  3. If you use Proguard, add the following to your proguard.cfg:

    -keep class com.nolanlawson.supersaiyan.widget.** { *; }
    

For more information on importing library projects, read this section of the Android developer guide.

Examples

I’m going to walk through some short examples, which should demonstrate the simplicity and flexibility of the SuperSaiyanScrollView. The source code for these apps is included in the GitHub project, and you can download the APKs here:

Example #1: Countries

In this example, we have a list of countries, which we’d like to sort by continent. The finished app looks like this:

Example 1: Countries

We have a simple Country object:

public class Country {

  private String name;
  private String continent;

  /* getters and setters ... */
  
  @Override
  public String toString() {
    return name;
  }
}

We use a basic ArrayAdapter<Country> to display the countries:

ArrayAdapter<Country> adapter = new ArrayAdapter<Country>(
        this, 
        android.R.layout.simple_spinner_item, 
        countries);

Next, we wrap it in a SectionedListAdapter. In this case, we’d like to section countries by their continent, sort the continents by name, and sort countries by name:

    sectionedAdapter = 
        SectionedListAdapter.Builder.create(this, adapter)
        .setSectionizer(new Sectionizer<Country>(){

          @Override
          public CharSequence toSection(Country input) {
            return input.getContinent();
          }
        })
        .sortKeys()
        .sortValues(new Comparator<Country>() {
          
          public int compare(Country lhs, Country rhs) {
            return lhs.getName().compareTo(rhs.getName());
          }
        })
        .build();

A Sectionizer is a simple callback that provides a section name for the given list item. In your own code, this might be a HashMap lookup, a database query, or a simple getter (as in this example).

Notice also that the keys (i.e. the section titles) and the values (i.e. the list contents) can be sorted independently, or not sorted at all. By default, they’re sorted according to the input order.

Now, let’s try to change the sections dynamically! In the action bar, the user can switch between alphabetic sorting and continent sorting:

alphabetic sorting vs. continent sorting

To do so, we first get a reference to the SuperSaiyanScrollView:

SuperSaiyanScrollView superSaiyanScrollView = 
    (SuperSaiyanScrollView) findViewById(R.id.scroll);

Then, we call the following function whenever the user chooses alphabetic sorting:

  private void sortAz() {

    // use the built-in A-Z sectionizer
    sectionedAdapter.setSectionizer(
        Sectionizers.UsingFirstLetterOfToString);

    // refresh the adapter and scroll view
    sectionedAdapter.notifyDataSetChanged();
    superSaiyanScrollView.refresh();
  }

Notice that the SectionedListAdapter and SuperSaiyanScrollView need to be informed whenever their content changes.

Next, when the user switches back to continent sorting, we call this function:

  private void sortByContinent() {

    // use the by-continent sectionizer
    sectionedAdapter.setSectionizer(new Sectionizer<Country>(){

          @Override
          public CharSequence toSection(Country input) {
            return input.getContinent();
          }
        });

    // refresh the adapter and scroll view
    sectionedAdapter.notifyDataSetChanged();
    superSaiyanScrollView.refresh();
  }

Notice that you never need to call adapter.sort() or Collections.sort() yourself. The SectionedListAdapter handles everything. And it does so without ever modifying the underlying adapter, which means that view generation is lightning-fast.

Example #2: Pokémon

This example shows off some of the advanced functionality of the SuperSaiyanScrollView. We have three different sortings, the size of the overlay box changes to fit the text size, and we can dynamically hide both the overlays and the section titles.

alphabetic vs by-region sorting

First off, the size of the overlay can be configured in XML. In this example, we start off with a single-letter alphabetic sorting, so we want the overlays to be a bit smaller than normal.

Add a namespace to the root XML tag in your layout XML:

<RelativeLayout
  ...
  xmlns:myapp="http://schemas.android.com/apk/res/com.example.example1"
  ...
  >
</RelativeLayout>

Next, use values prefixed with ssjn_ to define the size of the overlay:

<com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView
  ...
  myapp:ssjn_overlaySizeScheme="normal">

  <ListView
    ...
    />

</com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView>

I include the built-in schemes small (for one letter), normal (for most use cases), and large and xlarge (for longer section titles). Section titles of up to two lines (separated by \n) are supported.

Small, normal, large, and xlarge overlays in my AMG Geneva app.

Small, normal, large, and xlarge overlays in my AMG Geneva app.

If you want, you can also manually specify the font size, width, height, and text color yourself:

<com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView
  ...
  myapp:ssjn_overlayWidth="400dp"
  myapp:ssjn_overlayHeight="200dp"
  myapp:ssjn_overlayTextSize="12sp"
  myapp:ssjn_overlayTextColor="@android:color/black" >

  <ListView
    ...
    />
</com.nolanlawson.supersaiyan.widget.SuperSaiyanScrollView>

Now, in the Java source, we have a PocketMonster object:

public class PocketMonster {

  private String uniqueId;
  private int nationalDexNumber;
  private String type1;
  private String type2;
  private String name;
  
  /* getters and setters */

  @Override
  public String toString() {
    return name;
  }
}

We have a simple PocketMonsterAdapter to define how the monsters are displayed in the list:

public class PocketMonsterAdapter 
    extends ArrayAdapter<PocketMonster> {
  
  // Constructors...
  
  @Override
  public View getView(int pos, View view, 
      ViewGroup parent) {
    
    PocketMonster monster = 
        (PocketMonster) getItem(pos);
    
    /* Create and style the view... */

    return view;
  }
}

We wrap this adapter in a SectionedListAdapter that, by default, sections and sorts everything alphabetically:

    adapter = SectionedListAdapter.Builder.create(this, subAdapter)
        .setSectionizer(Sectionizers.UsingFirstLetterOfToString)
        .sortKeys()
        .sortValues(new Comparator<PocketMonster>(){

          @Override
          public int compare(PocketMonster lhs, 
                PocketMonster rhs) {
            return lhs.getName().compareToIgnoreCase(
                rhs.getName());
          }})
        .build();

Notice that we call both sortKeys() and sortValues(), because we want both the section titles and the Pokémon to be ordered alphabetically. Since PocketMonster does not implement Comparable, we defined a custom Comparator.

Now let’s say we want to organize the Pokémon by region:

Pokémon sorted by region.

Some quick background: Pokémon are ordered by their “national ID,” an integer value that starts at 1 (Bulbasaur) and goes up to 718 (Zygarde). Every time Nintendo releases a new generation of Pokémon games, they add about 100 new monsters, set the game in a new “region,” and sell about a bazillion new Pokémon toys.

So basically, we can determine the regions from the Pokémon’s ID. We’ll define a new
Sectionizer, which is called when the user selects “sort by region”:

  private void sortByRegion() {
    adapter.setSectionizer(new Sectionizer<PocketMonster>() {

      @Override
      public CharSequence toSection(PocketMonster input) {
        int id = input.getNationalDexNumber();
        
        // Kanto region will appear first, followed 
        // by Johto, Hoenn, Sinnoh, Unova, and Kalos
        if (id <= 151) {
          return "Kanto (Generation 1)";
        } else if (id <= 251) {
          return "Johto (Generation 2)";
        } else if (id <= 386) {
          return "Hoenn (Generation 3)";
        } else if (id <= 493) {
          return "Sinnoh (Generation 4)";
        } else if (id <= 649) {
          return "Unova (Generation 5)";
        } else {
          return "Kalos (Generation 6)";
        }
      }
    });

    // uses the nat'l pokedex order, since 
    // that's the original input order
    adapter.setKeySorting(Sorting.InputOrder);
    adapter.setValueSorting(Sorting.InputOrder);
    scrollView.setOverlaySizeScheme(
        OverlaySizeScheme.Large);

    // refresh the adapter and scroll view
    adapter.notifyDataSetChanged();
    scrollView.refresh();
  }

Notice that we’ve changed the key and value sorting to Sorting.InputOrder, because now we want to order Pokémon by their national IDs, which was the order the data was read in. (A custom Comparator would have also done the trick.) Additionally, we’ve increased the size of the overlay to accommodate the longer section text.

Now, let’s say we want to organize Pokémon by type. Each Pokémon has at least one elemental type (such as “fire” or “water”), but some have two. Ideally we would like to list Pokémon in multiple categories, so they could appear multiple times in the list.

To do so, we will define a MultipleSectionizer instead of a regular Sectionizer:

  private void sortByType() {
    adapter.setMultipleSectionizer(
        new MultipleSectionizer<PocketMonster>() {

      @Override
      public Collection<? extends CharSequence> toSections(
          PocketMonster monster) {
        String type1 = monster.getType1();
        String type2 = monster.getType2();

        if (!TextUtils.isEmpty(type2)) { // two types
          return Arrays.asList(type1, type2);
        } else { // one type
          return Collections.singleton(type1);
        }
      }
    });
    adapter.setKeySorting(Sorting.Natural);
    adapter.setValueSorting(Sorting.InputOrder);
    scrollView.setOverlaySizeScheme(OverlaySizeScheme.Normal);

    // refresh the adapter and scroll view
    adapter.notifyDataSetChanged();
    scrollView.refresh();
  }

Notice that the key sorting has again changed, this time to Sorting.Natural, which simply sorts alphabetically. Value sorting has changed to Sorting.InputOrder, because we’ve decided to sort Pokémon by their national IDs.

This works as expected:

Pokémon sorted by type.

Notice that Charizard appears in both in the “Fire” and “Flying” sections, since he has two types.

This example app also shows how you can disable the section titles or section overlays, just in case you don’t like them. These values can also be set during the Builder chain, using hideSectionTitles() and hideSectionOverlays().

comparison of hiding overlays and hiding section titles

You can read the Java documentation for more information about customizing the SuperSaiyanScrollView and the SectionedListAdapter.

Summary

The SuperSaiyanScrollView is a cool new library, and you should be using it. File bugs on me ‘n’ stuff, if there are any missing features you’d like to have.

Creating a contact with multiple fields in Android

For the impatient: skip the article, download the code.

Recently, when writing a physician directory for the Canton of Geneva, I wanted to include a feature for adding a new contact. That is, I wanted a button that would pop up the “Add a new contact” screen, with various fields (such as phone number, postal address, and email address) already filled in. Piece of cake, right?

Adding a contact in the physicians app.

Adding a contact in the physicians app.

Unfortunately, it turns out that the Android docs and Stack Overflow are pretty bereft of clear, concise instructions for creating a contact with multiple fields of various types, e.g. work phone, mobile phone, or home fax (if such a thing still exists).

Plus, the entire ContactsContract changed in API level 11 (Honeycomb), meaning that anything written for ICS or Jelly Bean wouldn’t work in Gingerbread, and vice-versa. Oh joy.

Luckily for you — assuming you stumbled across this post after a frustrated trip to Google — I’ve written a helper class to do all the heavy lifting. It provides a simple, fluent API that works for Android version 2.1 (Eclair) through 4.2 (Jelly Bean), and it’s open source.

You create a contact like this:

Intent intent = new AddContactIntentBuilder("Joe Blow")
    .addFormattedAddress("123 Fake Street, Springfield USA",
        StructuredPostal.TYPE_HOME)
    .addPhone("555-867-5309", Phone.TYPE_HOME)
    .addPhone("555-123-4567", Phone.TYPE_WORK)
    .addPhone("555-987-6543", Phone.TYPE_FAX_WORK)
    .addEmail("joe.blow@gmail.com", Email.TYPE_HOME)
    .addEmail("joe@blow.com", Email.TYPE_WORK)
    .build();

startActivity(intent);

And here’s what this code produces, in both Jelly Bean and Gingerbread:

Adding a contact in Jelly Bean and Gingerbread.

Adding a contact in Jelly Bean and Gingerbread.

Happy contact creating!

Download or fork the code from GitHub.

KeepScore version 1.2.2: you asked for it, you got it

My favorite part about working on a software project with real-world users is the feedback I get. It’s often said by industry veterans that you don’t know what kind of app you’re building until your users actually get their hands on it, and the wisdom of this statement has proven itself to me over and over.

With KeepScore, the app itself is pretty simple – it just keeps score. And each time I write an update, I tell myself, “Welp, that’s about all it’ll ever need.” Then I get an email from an interested user with a cool new use case, and I just can’t help but code it up.

So the app keeps growing and growing, but at each step I have to be extra-careful to keep the UI itself streamlined, simple, and dead-easy to use. With KeepScore version 1.2.2 (released today), I think I’ve managed to strike a good balance between functionality and usability.

Here are the new features:

Share

As many of you requested, you can now share your KeepScore games with a friend. You can send a single game, specific games, or all your games.

Just choose the games you want, hit the “Share” button up top, and KeepScore will create a special XML file that a friend can open with KeepScore on their own device.

The "Share" feature.

The “Share” feature.

This feature also allows you to back up your saved games to Dropbox, Google Drive, or your favorite cloud storage service.

Automatic backups

Speaking of backups, there’s no more do-it-yourself! KeepScore automatically saves a backup whenever you start a new game. Look for them in the “Restore” popup.

All your games are automatically backed up.

All your games are automatically backed up.

These files are gzipped, so they take up a minimal amount of space on your external storage.

Export spreadsheet

As board gamers, we’re geeks. And as geeks, we love analyzing our board game habits in a number of different ways. Who wins the most games? Who’s scored the most points? What games do we play the most often?

The "Export Spreadsheet" feature.

The “Export Spreadsheet” feature.

Rather than create a separate screen to answer each of these questions, KeepScore now offers an “Export Spreadsheet” feature. The spreadsheet may be imported into Excel, LibreOffice, Google Docs, or any document editor that accepts CSV files.

Data nerds rejoice.

Data nerds rejoice.

Once you’ve opened up the spreadsheet, you can slice and dice the data to your geeky heart’s content.

More Holo goodness

KeepScore 1.2.2 expands support for the Android “Holo” theme, which means it will look more beautiful and more consistent across different Android devices.

Holo everywhere.

Holo everywhere.

Additionally, I’ve revamped the default “Light” theme to be more clean and minimalist. It’s inspired by the “card” interface from Google Now, which I adore.

The new, Holo-style look.

The new, Holo-style look.

And if you’re scared by change, the old look is still available in the settings under Color Scheme -> Classic Light.

The classic look.

The classic look.

Whose turn is it?

A perennial complaint from users is that it’s hard to know if you’ve forgotten to add a player’s score. For round-based games (like Hearts) or games where the scoring order is important (like cribbage) this can be a real nuisance.

KeepScore 1.2.2 solves this problem using a clever suggestion from my buddy Alex Lougheed: add a little bullet icon to show which player was updated last. This means you can go player-by-player, totaling up the individual scores, without ever losing your place.

The blue bullet indicates who was scored last.

The blue bullet indicates who was scored last.

And if you’re playing a game where the player order doesn’t matter, you can disable the bullet in the settings.

Zoom in on the chart

On many devices, the history chart doesn’t show up very well, because it either gets cut off or it’s too small to see. Rather than fiddle with the presets for every possible screen size, I’ve added some handy zoom in/zoom out buttons.

Zoom in, out, and all around.

Zoom in, out, and all around.

Of course, pinch-to-zoom would be even nicer, but this works in a pinch (no pun intended!).

Internationalization

As always, KeepScore is localized into French and Japanese by yours truly. The German translation is out of date, though, and no other languages are currently supported.

Parlez-vous nippon?

Parlez-vous nippon?

According to the Play Store statistics, the top languages of KeepScore users are:

  1. English (United States)
  2. English (United Kingdom)
  3. French (France)
  4. English (Canada)
  5. German (Germany)
  6. English (Australia)
  7. Japanese (Japan)
  8. Dutch (Netherlands)
  9. Italian (Italy)

So if you speak German, Dutch, or Italian, and if you have some free time, please offer a translation!

Donate

This isn’t really a new feature, but I’ve added a Donate version of KeepScore to the Google Play store for $2.99.

Since I started work on this app, many people have asked where they could throw some change in my jar. But I resisted adding a Donate button, because after all, it’s just a counting app.

Recently, though, I noticed that the number of code commits to the KeepScore repository has actually surpassed any other Android app I’ve written (even CatLog and Pokédroid!). So I’ve had to admit to myself that this little counting app has morphed into quite the serious project.

So if you’d like to support KeepScore, you can download the Donate version from the Google Play Store, or just donate via PayPal.

Rest assured, though, that I will continue working on KeepScore regardless of your donations. For me, it’s just a fun app to write, and plus there’s still a lot of work to do. Next up: colors per player and battery-saving enhancements.

KeepScore version 1.2: more style, more substance

KeepScore v1.2

I had always thought of KeepScore as a fairly simple app. Functional, yes. But beautiful? Meh.

It’s a counting app. Counting apps only have to do one thing right, and that’s count. This is not brain surgery, people. Just keep it simple, and you’re already 95% of the way there.

A few weeks ago, though, I decided to make KeepScore my guinea pig for trying out some new design elements from the “Holo” theme, introduced in Android 3.0. At the same time, I also added some fit-and-finish features that were sorely needed, giving the app a much more coherent feel.

The result is KeepScore version 1.2, probably the biggest update I’ve ever written for the app. It looks and functions so differently now, I feel like I barely recognize my own app.

What I like most about this update, though, is that it adds a fresh coat of paint without subtracting anything from the usability. In fact, I think KeepScore is actually much easier to use than it was before, to the point where I feel a little embarrassed for having bragged about it in previous posts.

New home screen

The new home screen is a design I’ve been wanting to do for awhile. Here’s a side-by-side comparison of the old and new looks:

Out with the old, in with the new.

The new design basically takes the “Load Game” screen and transplants it onto the welcome screen. I find it’s a huge improvement. There was a ton of wasted space with the old design, and plus it took two clicks to get to your saved games. Now everything the user needs is front and center, without sacrificing any usability or app branding.

I also wanted to make sure that the new design wasn’t so cluttered that it would confuse first-time users. Their experience is still pretty streamlined: there’s a big “New Game” button the size of a barn that you can’t possibly miss.

Big gray squares. Your thumb is drawn to them.

The new home screen also makes use of the “Action Bar” paradigm, which was introduced in Android 3.0 and back-ported thanks to the wonderful Action Bar Sherlock library.

The in-game view

In-game, not a whole lot has changed. If it ain’t broke, why fix it? All I added was a very small graphical flourish:

Never change, KeepScore. Never change.

Did ya miss it? The last score in the score history now has a “fade-out” gradient, to indicate that the list has been cut off at the bottom.

This is to solve a common problem I heard from users, which is that they could never remember whether the list was ordered top-down or bottom-up. Hell, I kept forgetting myself! So hopefully this subtle change will make that clearer.

Rematch button

This is something I struggled with for a long time. In early versions of KeepScore, I had a “Reset” button, which prompted the user with “Overwrite game or start new game with same players?” Knowing that users don’t read anything, though, I was unsatisfied with this dialog.

The old, confusing dialog.

In later versions, I replaced it with “Reset” and “Copy Game”:

“Reset” and “Copy Game” buttons.

Now I’ve combined them both into “Rematch”:

New “Rematch” button.

What I realized about “Reset” and “Copy Game” is that they’re an inelegant solution to a common problem. 99% of the time, if you’re still using the app after the game is over, it’s because you want to start a new game with the same players. However, I didn’t want users to overwrite their old scores, because then they’d lose all their history from the previous game. Hence the option of copying the game before resetting it.

“Rematch” captures this concept much more succinctly than “Copy Game” and “Reset.” And plus, it makes it more difficult for users to shoot themselves in the foot, i.e. by overwriting their scores.

Unfortunately I can’t take the credit for this idea. I borrowed it from Rounds, which is a pretty slick round-based score keeper that was actually originally built on KeepScore’s source code. When I saw the “Rematch” button in that app, I slapped myself on the forehead and wondered how I’d never thought of it.

Edit Players

This is a pretty nifty new feature. In the previous versions, I had an “Add Player” button and a “Shuffle” button, but there was no way to manually reorder players or delete players.

Every day I’m shuffling, and adding, players.

Now all of that is handled in a separate “Edit Players” screen, which makes it a breeze to change players mid-game. You can even touch and drag to get the order exactly right.

Clearly, Storm Eagle should go after Mega Man.

This screen also makes it easier to change players’ names. Previously, the only way to do that was to long-press on a player’s name, which is kind of low on discoverability. But hopefully the button with the pencil icon is a lot easier to figure out.

A tip of my hat goes to Carl Bauer for the drag-and-drop list implementation.

History chart

One occasionally-requested feature was a line chart to show the players’ scores over time. Well, ask and ye shall receive:

Fact: nerds love data. And gamers are all nerds.

Players’ scores are on the Y axis, rounds on the X axis. It’s probably useless for any non-round-based game, but kind of neat nonetheless.

I realize the history chart is probably the most unpolished out of the new visual features I added. The colors are pretty bland, and it’s all very MS Paint-esque, because Android has no native graphing library, so I had to whip this up from scratch. But I’m not too concerned, since most people don’t bother going into the History anyway. And for those that do, I think it’s a nice little feature.

Other new features

Besides all the UI changes, I also added some new functionality:

  • Backup/restore. Back up your games to an XML file on USB storage, and load them later. Duplicates are handled automatically based on unique game IDs.
  • Undo/redo. Self-explanatory. Any action in-game can be undone or redone, i.e. scores subtracted, scores added, etc.
  • Better German translations. Germany is the Mecca of modern-day board gaming, so this has got to be worth something. The app is already available in French and Japanese.
  • Dropped support for pre-Eclair devices. Android 1.5 and 1.6 only account for 0.5% of the user base, and the new backup/restore feature required some XML libraries from Eclair. Sorry, Cupcake and Donut! You were delicious while you lasted.

So there you have it. KeepScore v1.2 has a fresh new look, a better UI, and it’s still free and open-source. So go grab it from the Google Play Store!