- Database schema creation.
- SQL queries.
- Data access (objects mapped to and from database).
- Business logic.
There is only a small amount (less than 10%) of Android-specific GUI and notification code.
Other than the code that creates the database schema (which is database-specific), all of the non-Android code is device- and OS-agnostic. It just needs a Java runtime.
I wanted to be able to easily reuse the portable code to run the application on a website, and on other Java-based handhelds.
The first step involved abstracting the database so that any database can be used, instead of just Android's binding to SQLite:
http://code.google.com/p/blog-code-hosting/source/browse/#svn/trunk/android/reuse/CommonPortable/src/main/java/com/jimandlisa/common/database
and also abstracting the logger:
http://code.google.com/p/blog-code-hosting/source/browse/#svn/trunk/android/reuse/CommonPortable/src/main/java/com/jimandlisa/common/logging
Once that was done, I could test the database and logger independently from Android:
http://code.google.com/p/blog-code-hosting/source/browse/#svn/trunk/android/reuse/CommonPortableTestUtils/src/main/java/com/jimandlisa/common/testutils
http://code.google.com/p/blog-code-hosting/source/browse/#svn/trunk/android/reuse/CommonDatabaseTestUtils/src/main/java/com/jimandlisa/common/testutils/database
http://code.google.com/p/blog-code-hosting/source/browse/#svn/trunk/android/reuse/CommonPortableTests/src/test/java/com/jimandlisa/common/tests
The portable business logic is in another set of projects:
MyAppPortable
com.jimandlisa.myapp.common
com.jimandlisa.myapp.controller
com.jimandlisa.myapp.database
com.jimandlisa.myapp.model
MyAppPortableTestUtils
com.jimandlisa.myapp.testutils
MyAppPortableTests
com.jimandlisa.myapp.tests
Finally, the Android-specific code is in two more projects:
MyAppAndroid
com.jimandlisa.myapp
MyAppAndroidTests
com.jimandlisa.myapp.android.tests
The inter-project dependencies look like this:
CommonPortable:
n/a
CommonPortableTestUtils:
CommonPortable
CommonPortableDatabaseTestUtils:
CommonPortable
CommonPortableTestUtils
CommonPortableTests:
CommonPortable
CommonPortableTestUtils
CommonPortableDatabaseTestUtils
MyAppPortable:
CommonPortable
MyAppPortableTestUtils:
CommonPortable
MyAppPortable
MyAppPortableTests:
CommonPortable
CommonPortableTestUtils
CommonDatabaseTestUtils
MyAppPortable
MyAppPortableTestUtils
MyAppAndroid:
CommonPortable
MyAppPortable
MyAppAndroidTests:
CommonPortable/target/classes
MyAppPortable/target/classes
MyAppAndroid/bin
CommonPortableTestUtils
MyAppPortableTestUtils
The CommonDatabaseTestUtils are broken out from CommonPortableTestUtils because they depend on JARs that aren't loaded on the device, so they can't be included in the dependencies for MyAppAndroidTests.
MyAppAndroidTests depends on target/classes and bin for reasons explained in http://jimshowalter.blogspot.com/2009/10/developing-android-with-multiple.html.
So was it worth it?
Well, yes, and no.
There are definitely advantages:
- I can write any other database-intensive application by cloning this setup and reusing the code in com.intuit.jimandlisa.common.*.
- The database-abstraction layer doesn't have the problems reported in Google Android issues #3302, 3296, and 3304.
- Having much of the code be regular, portable Java makes it easier to write coverage tests because EasyMock, JMockit, and Cobertura are available.
- This approach creates a lot of separate projects, and that's kind of a pain to set up.
- I have to maintain the database-abstraction layer.
- More work is required to mature the database-abstraction layer. (It currently doesn't support binding args or compound keys, all keys must be longs, etc.)
- Because the database-abstraction layer isn't a native binding, it's slower. For a cellphone app with a few database operations per hour, this doesn't matter, but it will matter on a website.
It might be better to model the application, and generate the code. With so many devices, OSs, and vendors, code generation might be the only pragmatic way to develop a cross-platform application.
Update: The sample code has been updated to add createSchema and updateSchema to AbstractDatabase in order to make execSql private.