Weather App – Kotliny Kotlin

I used IntelliJ to convert to Kotlin. It gave me the class below. While it compiles and works, it does not look like Kotlin. I would like to take the resulting code and use Kotlin’s features to make it look more like Kotlin

The starting point is this ugly if – which has the lower clauses collapsed to fit in the screenshot. This needs to go.

This long if statement consists of pretty much duplicated code for minutely, hourly and daily.

Templates

My first thought was templates. But after a little bit of scratching around I figures out the problem was not different types. All arrays are of type IntervalData, so that would not bring anything to the table. Maybe I’m wrong. I’ve not done much with templates in any language.

Base Class Method

And I could not try to implement it in the base class either, as I have called each array by different names – minutelyData, hourlyData and dailyData. I don’t want to change that. That conforms to my Objective C upbringing, where you name a variable with what it is, not some cryptic reference to it. Anyway that is not a very Kotliny solution

when

If you have an ugly if statement in Kotlin, the obvious starting place is when. This is a pretty cool piece of kit in Kotlin – it’s their answer to switch.

I am watching a pretty cool course on Udacity: Kotlin Bootcamp for Programmers, free at the time of writing. It doesn’t nauseate you with trying to describe what a variable is. Every single concept is backed up by exercises, and it is 50% video and 50% exercises. Lesson 3 ‘Functions’, see ’14. Compact Functions’ where he takes a when statement, and ships out the logic to other variables and methods. Pretty cool. I’d like to do this if I can.

I have got rid of the ugly if. . I’ve added a method called timeTilRain which takes in a time multiplier for number of minutes. But this is still not very readable, and it calculates the result twiceMore work required:

Screenshot showing Kotlin when statement
The when statement is a lot more concise than the long ugly if statement

indexOfFirst

There is a function called ‘periodWhenIntensityExceeded’ which uses a while loop. This looks like it needs a lambda:

Screenshot showing indexOfFirst functionality
Nice, replaced all that waffle with an easy to read one-liner

Kotlin Ternary Operator

It’s slightly different in Kotlin – just a case of remembering. Starts with the keyword if, and no curly braces.

Screenshot showing a function with the Kotlin usage of the if statement on one line
Using if on one line is probably more readable than the standard ternary if statement

Or let’s go one better and use the Elvis operator ?: This is for providing a default in the case of null.

precipIntensity = jsonFloatValueFor(WeatherConstants.PRECIP_INTENSITY, jsonHourly) ?: 0f

Kotlin Class Properties

You can declare the properties directly in the class parameters, by adding val or var before the variable name. Modifiers such as private, protected etc included.

Screen shot showing the Precipitation class declared with properties
Properties declared directly in the class statement parameters.

Kotlin Playgound

The playground is really nice for trying out code. If you used it before on a Chromium browser and hated it, try it now. They’ve sorted out the viewing. It now is really a good experience. Nice one guys. https://play.kotlinlang.org/

Starting Code

Here is my starting class, as provided by IntelliJ when I clicked Convert to Kotlin.

package com.sners.snowforecast.data

import java.util.ArrayList

//precipIntensity: A numerical value representing the average expected intensity (in inches of liquid water per hour)
// of precipitation occurring at the given time conditional on probability (that is, assuming any precipitation occurs at all).
// A very rough guide is that a value of 0 in./hr. corresponds to no precipitation,
// 0.002 in./hr. corresponds to very light precipitation,
// 0.017 in./hr. corresponds to light precipitation,
// 0.1 in./hr. corresponds to moderate precipitation,
// and 0.4 in./hr. corresponds to heavy precipitation.

/**
 * Everything about Precipitation
 * @param inDaily daily weather data
 * @param inHourly hourly weather data
 * @param inMinutely minutely weather data
 * @param inCurrently current weather data
 */
class Precipitation(inDaily: Daily?, inHourly: Hourly?, inMinutely: Minutely?, inCurrently: Currently) {

    /**
     *  @property daily Daily weather info
     */
    private val daily: Daily?

    /**
     *  @property hourly Hourly weather info
     */
    private val hourly: Hourly?

    /**
     *  @property minutely Minutely weather info
     */
    private val minutely: Minutely?

    /**
     *  @property currently Current weather info
     */
    private val currently: Currently

    /**
     *  @property weatherHelper Helper class with useful weather functions
     */
    private val weatherHelper = WeatherHelper()

    /**
     *  @property minPrecipLight Min precip to take into account
     */
    private val minPrecipLight = 0.002

    /**
     *  @property minPrecipMedium Min precip to take into account if we're ignoring light precip
     */
    private val minPrecipMedium = 0.017

    init {
        daily = inDaily
        hourly = inHourly
        minutely = inMinutely
        currently = inCurrently

    }

    /**
     * Time til Precipitation
     * @param toIgnoreLightPrecip boolean
     * @return Minutes til precipitation
     */
    private fun timeTil(toIgnoreLightPrecip: Boolean): Long {

        var minutesTilPrecip = -1
        val minPrecip = if (toIgnoreLightPrecip) minPrecipMedium else minPrecipLight

        val precipIntensity: Float = currently.precipIntensityNum
        if (precipIntensity > minPrecip) {
            // It's raining now
            minutesTilPrecip = 0
        } else {
            // Check minutely
            if (null != minutely) {
                val rainMinutes =
                    periodWhenIntensityExceeded(minutely.minutelyData,minPrecip)
                if (rainMinutes >= 0) {
                    minutesTilPrecip = (rainMinutes + 1)
                }
            }
            if (minutesTilPrecip < 1) {
                // Check hourly
                if (null != hourly) {
                    val rainHours: Int =
                        periodWhenIntensityExceeded(hourly.hourlyData, minPrecip)
                    if (rainHours >= 0) {
                        minutesTilPrecip = ((rainHours + 1) * 60)
                    }
                }
            }
            if (minutesTilPrecip < 1) {
                // Check hourly
                if (null != daily) {
                    val rainDays: Int =
                        periodWhenIntensityExceeded(daily.dailyData, minPrecip)
                    minutesTilPrecip = if (rainDays >= 0) {
                        ((rainDays + 1) * 60 * 24)
                    } else {
                        -1
                    }
                }
            }
        }
        return minutesTilPrecip.toLong()
    }

    /**
     * Time til Precipitation as string
     * @param toIgnoreLightPrecip boolean
     * @return Formatted time string
     */
    fun timeTilString(toIgnoreLightPrecip: Boolean): String? {

        var timeTilString: String? = WeatherConstants.NONE_FORECAST
        val timeTil = timeTil(toIgnoreLightPrecip)
        if (timeTil > 0) {
            timeTilString = weatherHelper.formatTime(timeTil)
        } else if (timeTil == 0L) {
            timeTilString = WeatherConstants.NOW
        }
        return timeTilString
    }

    /**
     * Period when Intensity Exceeded
     * @param intervalData the data to search
     * @param minValue The minimum value under which we ignore
     * @return Integer index of element where condition was first met
     */
    private fun periodWhenIntensityExceeded(intervalData: ArrayList<IntervalData>, minValue: Double): Int {
        var periodFound = -1
        var precipExceedsMin = false
        var intervalCounter = 0
        while (intervalCounter < intervalData.size && !precipExceedsMin) {
            val fieldValue = intervalData[intervalCounter].precipIntensity
            if (fieldValue > minValue) {
                precipExceedsMin = true
                periodFound = intervalCounter
            }
            intervalCounter++
        }
        return periodFound
    }



}`

Leave a Reply

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