Time

Time for non-Time Lords – Part 1

Time is hard. Any developer who tells you differently is either lying; thinks they understand it when they really don’t; or is a bona fide Time Lord. In this series we’ll explore some of the issues that time can pose, and take a look at some tools that we can use to help make time handling a little easier.

One area where time can really cause problems is in API design. I’m not just talking about APIs for Open Source libraries, it can be every bit as much of an issue for internal APIs used within a project. The issue is that often the units for time are not obvious.

I recall a school teacher who was a stickler including the units. If I were to omit them from my homework and just include, for example, the number ‘1’, I would get a sarcastic note suggesting some random unit – “1 what? Elephant? Banana?”. As I have grown older, I have realised that this teacher was not just being awkward, it is vital to understand what units are being used.

To see this problem in real-world code, let’s consider this simple example:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        text.alpha = 0f
        text.animate()
                .alpha(1f)
                .apply { duration = 1000 }
                .start()

    }
}

Hopefully this is easy enough to understand – we want to perform an animated fade-in of a View. The highlighted line demonstrates the issue, though. I happen to know that we specify the duration of an Animator in milliseconds because I use these APIs very often. But if this were an API that I was unfamiliar with, I could easily get it wrong. Even if we look at the method signature (both in decompiled source, and in the official Javadoc) it does not make it any clearer:

ViewPropertyAnimator setDuration (long duration)

If the argument were named durationMillis instead of duration we may get more of a clue as to the units, but without this we need to actually read the Javadoc itself to learn that the value needs to be specified in milliseconds. It is worth mentioning that I am not singling out this specific API for criticism here, I am merely using it as an example of how the lack of units can be problematic – there are many other examples I could have used instead!

The problem here is not only that the developer has to look up the Javadoc to determine the units that the argument should be provided in, but also so does anyone performing code review of this.

One way of resolving this is to actually accept a unit specification in the method call. TimeUnit is part of the concurrency package and has been around since Java 1.5. It provides a mechanism for the caller to clearly declare which unit the duration is being supplied in.

Let’s simulate how we can improve on this API by using a wrapper around it using a Kotlin extension function:

fun ViewPropertyAnimator.setDuration(durationInUnits: Long, unit: TimeUnit): ViewPropertyAnimator =
        apply {
            duration = unit.toMillis(durationInUnits)
        }

We don’t care what the user has supplied, we simply use the unit specification to convert the supplied value to millis. (I also return the ViewPropertyAnimator instance so we can use this function in a more idiomatic manner).

Our consumer code now looks like this:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        text.alpha = 0f
        text.animate()
                .alpha(1f)
                .setDuration(1, TimeUnit.SECONDS)
                .start()
    }
}

Functionally this is identical to the previous example, but it is simply more explicit about what is happening. Not only does it make it easier for the developer writing the code, it also makes it easier for anyone performing code review to understand.

It is actually pretty trivial to add a TimeUnit argument to any function which takes a time value as an argument, but it does make your APIs much more robust and easy to understand and call correctly.

However, when dealing with third-party APIs which we don’t control, there is still a trick that we can use without making any changes to the API whatsoever. We can use the same TimeUnit API in our code to annotate a vague method call:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        text.alpha = 0f
        text.animate()
                .alpha(1f)
                .apply { duration = TimeUnit.SECONDS.toMillis(1)}
                .start()

    }
}

Once again, this is functionally identical to the first example. The benefit here is that, although we are still calling the same setDuration() method we are annotating our code to make it obvious that setDuration() takes an argument in milliseconds. Not only will this help anyone reviewing the code to understand it without having to check the Javadoc, but also it will help any developer who has to maintain this code in the future. And that developer may well be you!

The difficulties with time are not just confined to vague APIs which lack clearly defined units, but there are some real-world issues which can really bite you. In the next article we’ll take a deeper look in to this.

The source code for this article is available here.

© 2017, Mark Allison. All rights reserved.

Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

4 Comments

  1. You can also use a Duration class (JSR-310, i.e. using ThreeTenABP), which will allow you to pass duration object without explicitly specifying time unit.
    It makes code even neater:
    setDuration(Duration duration)

    It allows you to create Duration object using any time unit, i.e. Duration.ofSeconds(1), Duration.ofHours(2).
    And if you really need to convert to a specific time unit (i.e. if you don’t control the API), it has all the necessary “toXXX()” methods (minutes, hours, millis, seconds, etc).

    Sure, it requires additional dependency, but if you are making any Date/Time operations in your app, you should anyway already be using JSR-310 as it is a much better and safer API than what Java (pre-1.8) or JodaTime provides.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.