4 min read

Tags

  • Android
  • targetSdk
  • Date
  • Joda-time
  • Java8
  • java.time
  • sugar
  • desugar

Why Joda?

I had to implement a feature in our app which would trigger repeat alarms which will be set based on specific events. Our project minSdk was set to 23 (Yes, we support Android 6!) and I thought Java 8 Time APIs I cannot use. So Joda was the option I had.

Joda-Time provides a quality replacement for the Java date and time classes. The standard date and time classes prior to Java SE 8 are poor. By tackling this problem head-on, Joda-Time became the de facto standard date and time library for Java prior to Java SE 8. From Java SE 8 onwards, users are asked to migrate to java.time (JSR-310).

From the offcial documentation of Joda, it was clear that I could solve my problem with these superior APIs. Joda would solve the problem of providing one set of APIs which can be used across all OS versions.

So here’s how I had it started

Add the dependency

implementation 'net.danlew:android.joda:2.10.14'

The APIs which I used in our project for different purposes:

Get date in the given time zone

val zone: DateTimeZone = DateTimeZone.forID(timezoneId)
val time = DateTime(zone)

From Unix to human readable date at given timezone.

val zone: DateTimeZone = DateTimeZone.forID(timezoneId)
return DateTime(receivedTime * 1000L, zone)

Manipulate current time to trigger an alarm

var triggerTime = date.plusHours(notificationCount).plusMinutes(totalDelay).withSecondOfMinute(0).withMillisOfSecond(0)

If device is in different time zone, convert the time to device time zone

var deviceTime = triggerTime.withZone(DateTimeZone.getDefault())	

Convert the device time to Calender object to pass it to AlarmManager

val time = deviceTime.toGregorianCalendar()

Use this time to pass it to Alarm methods

alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(time.timeInMillis, pendingIntent), pendingIntent)

For Unit testing:

testImplementation 'joda-time:joda-time:2.10.14'

Update

Since Joda-time library is in maintenance mode, it is recommended to migrate to new Java 8 APIs, but still need to support Android 6 (App will crash with NoClassDefFoundError), I was skeptical, kept reasearching what everyone is doing. Even posted a question on SO. I got to know that I could replace Joda with Java 8 APIs while supporting Android 6! Cool right?! Let’s see how!.

Desugaring

What’s sugaring in programming world? Why we need desugaring and how it helps with above problem?

From one of the SO posts:

Sugar, in programming, usually refers to those sweet additions, mostly shortcuts, that make some constructs easier to type and to read (the latter being, in practice, the most important during the life cycle of your program). Java is widely seen as not being concise enough, especially compared to modern languages. That’s why those additions that help make the code faster to read are welcome.

“Desugaring” refers to automatically translating “sugar” constructs into other constructs when the compiler or runtime lacks native support for the sugared versions.

So what we are doing here? From Android documentation:

When building your app using Android Gradle plugin 4.0.0 and higher, you can use a number of Java 8 language APIs without requiring a minimum API level for your app. The Android Gradle plugin provides built-in support for using certain Java 8 language features and third-party libraries that use them. The default toolchain implements the new language features by performing bytecode transformations, called desugar, as part of the D8/R8 compilation of class files into dex code. So, you can include standard language APIs that were available only in recent Android releases (such as java.time) in apps that support older versions of Android.

To enable support for these language APIs on any version of the Android platform, update the Android plugin to 4.0.0 (or higher) and include the following in your app module’s build.gradle file:

android {
    defaultConfig {
        // Required when setting minSdkVersion to 20 or lower
        multiDexEnabled = true
    }

    compileOptions {
        // Flag to enable support for the new language APIs

        // For AGP 4.1+
        isCoreLibraryDesugaringEnabled = true
        // For AGP 4.0
        // coreLibraryDesugaringEnabled = true

        // Sets Java compatibility to Java 8
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
}

Now you can continue using all the Java 8 Time APIs!

After all the research, here is the effort that took for migrating all Joda calls to equivalent Java 8 APIs:

Get date in the current time zone

Joda:

val zone: DateTimeZone = DateTimeZone.forID(timezoneId)
val time = DateTime(zone)

Java:

val zoneId = ZoneId.of(timezoneId)
val time = ZonedDateTime.now(zoneId)

From Unix to human readable date at given timezone.

Joda:

val zone: DateTimeZone = DateTimeZone.forID(timezoneId)
return DateTime(receivedTime * 1000L, zone)

Java:

val zone: ZoneId = ZoneId.of(timezoneId)
val instant = Instant.ofEpochMilli(receivedTime * 1000L)
return ZonedDateTime.ofInstant(instant, zone)

Manipulate current time to trigger an alarm

Joda:

var triggerTime = date.plusHours(notificationCount).plusMinutes(totalDelay).withSecondOfMinute(0).withMillisOfSecond(0)

Java:

var triggerTime =
            date.plusHours(notificationCount.toLong()).plusMinutes(totalDelay.toLong()).withSecond(0)
                .withNano(0)

If device is in different time zone, convert to device time zone

Joda:

var deviceTime = triggerTime.withZone(DateTimeZone.getDefault())	

Java:

var deviceTime = triggerTime.withZoneSameInstant(ZoneId.systemDefault())

Convert the device time to Calender object to pass it to AlarmManager

Joda:

val time = deviceTime.toGregorianCalendar()

Java:

val time = GregorianCalendar.from(deviceTime)

Use this time to pass it to Alarm methods

alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(time.timeInMillis, pendingIntent), pendingIntent)

That’s it! Feel free to connect if you want to know any other equivalent Java 8 API while replacing Joda!