Monday, December 4, 2017

Leap years in Android CalendarView

Use CalendarView with caution.

MonthView uses Utils.getDaysInMonth() that defines a leap year as follows:

case Calendar.FEBRUARY:
    return (year % 4 == 0) ? 29 : 28;

And this is how a leap year should be defined:

Exactly Which Years Are Leap Years?

We add a Leap Day on February 29, almost every four years. The leap day is an extra, or intercalary, day and we add it to the shortest month of the year, February.

In the Gregorian calendar three criteria must be taken into account to identify leap years:


  • The year can be evenly divided by 4;
  • If the year can be evenly divided by 100, it is NOT a leap year, unless;
  • The year is also evenly divisible by 400. Then it is a leap year.


This means that in the Gregorian calendar, the years 2000 and 2400 are leap years, while 1800, 1900, 2100, 2200, 2300 and 2500 are NOT leap years.

So, 2100 is not a leap year, but according to Android, it is:


There are more problems related to CalendarView.  For example, the control does not allow a user to select February 29, 2100, but it allows to select February 28.  Again, be cautious.

Friday, October 20, 2017

Adding a dependency to compileReleaseSources or compileDebugSources

Simply adding a dependency to compileReleaseSources causes a gradle error.

compileReleaseSources.dependsOn 'someTask'

Could not get unknown property 'compileReleaseSources' for project 

The dependency need to be added in whenTaskAdded closure:

tasks.whenTaskAdded { t ->
    if (t.name in ['compileReleaseSources', 'compileDebugSources']) {
        t.shouldRunAfter 'someTask'
        t.dependsOn 'someTask'
    }
}

Note that you need both t. shouldRunAfter (or t.mustRunAfter) and t.dependsOn.

Wednesday, October 18, 2017

Packaging dependencies into an AAR in Android Studio

When assembling a release AAR, proguard processes external dependencies using -libraryjars directive.  This is because the dependencies are not packaged into the resulting AAR.

In order for proguard to process dependencies using -injars directive, the external dependencies need to by converted to local dependencies by simply copying the JAR files to the project's libs directory and removing explicit external dependencies from dependencies section of your build.gradle file. Then local dependencies will be packaged into the AAR and obfuscated.  If you don't use proguard to minimize/obfuscate AAR, the dependencies will be packaged as is.

Just make sure that you have this line in the dependencies section:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ...
}

Thursday, September 28, 2017

Delaying enforcement of foreign key constraints until the end of the outermost transaction

Inserting two rows that circularly depend on each other fails after first insert because of foreign key constraint violation:  java.lang.AssertionError: expected:<1> but was:<-1>

@Test
public void testCycle() {
    db.execSQL("pragma foreign_keys=true");
    db.execSQL("create table A(id integer primary key, bId integer references B(id))");
    db.execSQL("create table B(id integer primary key, aId integer references A(id))");
    final ContentValues cvA = new ContentValues();
    cvA.put("id", 1);
    cvA.put("bId", 2);
    final long aId = db.insert("A", null, cvA);
    assertEquals(1, aId);
    final ContentValues cvB = new ContentValues();
    cvB.put("id", 2);
    cvB.put("aId", 1);
    final long bId = db.insert("B", null, cvB);
    assertEquals(2, bId);
}

Scoping by a transaction does not help, we are getting the same error.

@Test
public void testCycle() {
    db.execSQL("pragma foreign_keys=true");
    db.execSQL("create table A(id integer primary key, bId integer references B(id))");
    db.execSQL("create table B(id integer primary key, aId integer references A(id))");
    db.beginTransaction();
    try {
        final ContentValues cvA = new ContentValues();
        cvA.put("id", 1);
        cvA.put("bId", 2);
        final long aId = db.insert("A", null, cvA);
        assertEquals(1, aId);
        final ContentValues cvB = new ContentValues();
        cvB.put("id", 2);
        cvB.put("aId", 1);
        final long bId = db.insert("B", null, cvB);
        assertEquals(2, bId);
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
}

Appending "deferrable initially deferred" after "... bId integer references B(id)" fixes the second test.  Another option is to issue "PRAGMA defer_foreign_keys=true" right after the beginning of a transaction.


Dealing with circular data dependencies in SQLite-based content providers

By default SQLite does not defer enforcing foreign key constraints.  This means that inserting into tables that circularly depend on each other does not work by default even if insertions are scoped by a transaction. There are two options to make insertions scoped by a transaction work:

  • Declare foreign key constraints as DEFERRABLE INITIALLY DEFERRED
  • Execute PRAGMA defer_foreign_keys=true at the beginning of each transaction that deals with circular dependencies
When using ContentProvider API, applyBatch() is the only option to deal with circular data dependencies.  By default applyBatch() method does not scope individual operations by a transaction.  SQLite-based content provider should overwrite applyBatch() method to run the operations in a transaction scope:

@NonNull
@Override
public ContentProviderResult[] applyBatch(
    final @NonNull ArrayList<ContentProviderOperation> operations) 
    throws OperationApplicationException {

    final SQLiteDatabase db = dbHelper.getWritableDatabase();
    db.beginTransaction();
    db.execSQL("PRAGMA defer_foreign_keys=true;");
    try {
        final ContentProviderResult[] results =
            super.applyBatch(operations);
        db.setTransactionSuccessful();
        return results;
    } finally {
        db.endTransaction();
    }
}

Worth mentioning: ContentProvider.applyBatch() calls individual methods, such as insert(), update(), and delete().  If the individual methods are already scoped by transactions, we'll end up with nested transactions. The individual methods can use SQLiteDatabase.inTransaction() to detect this situation if nested transactions are not desired.

Tuesday, September 26, 2017

Enforcing foreign key constraints in SQLite-based content providers

By default SQLite does not enforce foreign key constraints starting since version 3.6.19.  To change this behavior we need to run PRAGMA foreign_keys=true when configuring the database:

class MyDbHelper extends SQLiteOpenHelper {
    @Override
    public void onConfigure(SQLiteDatabase db) {
        super.onConfigure(db);
        db.execSQL("PRAGMA foreign_keys=true");
    }
}

Click here for more information.

Sunday, June 4, 2017

Configuring and accessing flatDir repositories in gradle

Lets assume there are two projects: Project1/module1 and Project2/module2, and module2 depends on a JAR file built by module1.

Step 1: Declare repository in Project2/module2/build.gradle

repositories {
    flatDir {
        dirs new File(project.rootDir, 
                      "/../Project1/module1/build/libs")
    }
}

Step 2: Declare dependency in Project2/module2/build.gradle

dependencies {
    compile ':module1-1.0-SNAPSHOT'
}

All you need to do now is to run JAR gradle task in Project1/module1.  It'll place module1-1.0.SNAPSHOT.jar into Project1/module1/build/libs folder.  After that Project2/module2 will be able to access that jar.  Note that you need a colon (':') in front of 'module1-1.0-SNAPSHOT'.

This quick solution works really well if you need to iterate fast and don't want to setup a local maven repository.