Loading...
Home
  • News
    • Android News
    • Droidcon News
  • Conferences
    • Conference Videos
    • Upcoming Conferences
    • Become a Partner
    • Past Events
  • Community
    • droidcon Team
    • Forums
    • Keep Me Informed
    • How to Hold a Droidcon
  • Jobs
    • jobs.droidcon.com
Sign In

Global CSS

 

Kotlin: when statement, when expression… oh my! or How we created our custom Detekt rule

 

 
Oleg Osipenko
Senior Android Engineer at Auto1 Group GmbH, Berlin, Germany. All views and thoughts are my own.
Published: September 19, 2019
Tweet
Share
 

Back in 2015 when I only started trying Kotlin (many thanks to Jake Wharton for his thorough investigation) and then later, when Kotlin 1.0 was released and year after announced by Google as a priority language for development on Android, in the long list of shiny Kotlin features, presented by the articles and blog posts related to the new language, sealed classes were one of the most interesting for me. They were advertised as better enums, enums with super power etc. And one of the answers to the question why it’s better enum, was the ability of compiler to complain if you forgot to handle one of the cases while using when on sealed class.
 


If you try to compile this code, you will receive the error: 'when' expression must be exhaustive. Add remaining branches or 'else' branch.

I remembered that piece of information and, until recently, completely believed into it. Now you could imagine, how I was surprised when I found that it’s not true. Or being precise, not always true.
 

When expression and when statement

So, recently we started a new project here in AUTO1 and of course Kotlin was our choice. And we decided to use sealed classes to represent the set of states of our screens. Something like this:
 


Inside the fragment I created render(viewState: EmailLoginViewState)function and started implementing it:
 


Surprisingly, I didn’t get any compiler warnings at this point, although I knew that I haven’t covered other cases of this sealed class. How could it be possible? So I decided to start my investigation from reading documentation.
 

It’s very useful to read documentation…

It’s very useful to read documentation…


So, that’s what I found in Kotlin sealed classes documentation:
 

The key benefit of using sealed classes comes into play when you use them in a when expression. If it's possible to verify that the statement covers all cases, you don't need to add an else clause to the statement. However, this works only if you use when as an expression (using the result) and not as a statement.


In my case I was using when as a statement, I didn’t use the result of the when anywhere in my code, that’s why it wasn’t expression. And what’s why compiler wasn’t complaining about not covered cases in my sealed class.
 

Make when expression out of statement

The recipe was obvious: I need to use the result of when expression in order to enable compiler warnings. But how to make it in the most idiomatic way?

We considered a number of options:

1. Assign the result of the when expression to some variable:
 


2. Return the result of the when exression out of the function:
 


3. Add empty let block after when expression:
 


All these methods transform when statement to when expression, enabling compiler warnings. But they all are not perfect: they are not readable; they bring some layer of ambiguity into our code; they hide the knowledge since the purpose of extra let block or storing result of when expression in some variable was unclear.

There’s no perfect solution to this problem. We decided to follow one of the advices and to create an extension property:
 


It works similar to empty let block, but it’s more readable — it’s name is self-explanatory:
 


Now compiler checks our when expressions and that could have been the happy end of our story…


So far so good as now we have compiler warnings. But still part of the problem is present — that is a hidden knowledge: new team member having no idea about exhaustive property would never use it.

Of course, we can add it to the documentation of our project, we can make this topic part of the on-boarding process… But still there’s left some place for human factor. Maybe it’s better to delegate this to machine? So, let’s do it!

We are using Detekt for static analysis of our Kotlin code so we decided to create a custom Detekt rule to check if when block was used in pair with exhaustive property.

For those of you who haven’t had experience with Detekt I could recommend great intro into Detekt by Mohit Sarveiya (slides here):
 


The article of Niklas Baudy was also kind of inspiration and guide for us.
 

PSI

Detekt uses a Kotlin PSI to perform checks. PSI states for Program Structure Interface, and each JetBrains IDE uses it for code completion, syntax highlighting etc. Every program could be represented as a hierarchy of PSI elements. For example, this simple program:
 


will give us the following PSI tree (very simplified):
 


This tree helps Detekt and other static analysis tools to assess any element of the code. And we will use it to check our when directives. And from the PSI point of view there could be 4 possible situations:
 

1. If we are assigning the result of our when expression to some variable it is considered to be a KtProperty:


2. If we are returning the result of our when expression from the function it’s the KtBlockExpression:


3. If we are chaining some extension after our when block, for PSI it becomes KtDotQualifiedExpression:


4. And our initial when statement (if we are not using the result) in terms of PSI is KtWhenExpression:


Having this, we can formulate what our check should do: it should visit content of our Kotlin classes and look for instances of KtWhenExpession and yell at us if so.
 

Rule implementation

Let’s start with creating a new module in our project and name it detekt-rules. And we need to add to our module’s build.gradle detekt-apiartifact as a dependency:
 


Now we can create class for our Detekt rule. Rule class describes the issue and also contains the logic of detecting it. Let’s call our class NonExhaustiveWhen:
 

We are passing number of parameters to the constructor of the superclass:

  • We need to specify severity of our issue. Here we chose Defect.
  • We need to provide a description of the issue
  • We need to specify time estimate to fix the issue, so that Detekt could calculate total amount of time required to fix all issues in the codebase. Since this fix only requires adding one method call, we estimated it to minimum amount of FIVE_MINS.

We are extending here Rule class. Its constructor parameters, as we’ve already seen, describe the issue. But in order to check our code for the issue Rule class also inherits from KtVisitor class . It provides us with a huge API: more than one hundred methods — each one for visiting specific PSI element, such as visitFile(file: PsiFile?), visitClassBody(classBody: KtClassBody), visitArgument(argument: KtValueArgument) etc. And we can override any of these methods to add our checks. And we will override visitNamedFunction() method to add our check. For each KtNamedFunctionthat is detected while traversing the PSI, this method will be called exactly once:
 


Here we are filtering children of the KtNamedFunction looking out for instances of KtWhenExpession. If it’s empty, everything is fine. Otherwise, we use report method to signal our Finding. We are calling report method and passing some message, providing some hints on how to fix this issue.

Our class now looks like this:
 


Testing our rule

In order to believe in our rule veracity let’s write some unit test for it. To test rules we need add detekt-test artifact in our module’s build.gradle:
 


There’s an extension function lint() for BaseRule class, which executes the check and we will use it for our tests.

We need to create some inputs for our tests. For the sake of simplicity let’s create simple string values for our test inputs:
 


Now, we can test our rule. First, let’s check non-compliant input, representing when statement:
 


Then, let’s test compliant inputs:
 


Since we have 3 different inputs, which should provide us the same result, I am using JUnit 5 ParameterizedTest feature to run the same test with different inputs. I use MethodSource as inputs provider:
 


Thanks for JUnit 5 annotations ParameterizedTest and DisplayName we can get meaningful outputs in the test results:
 


RulesetProvider

Now we have our rule and tests running, our rule is ready to check kotlin files. In order to make our check accessible by Detekt we need to create instance of RuleSetProvider:

RulesetProvider is responsible for storing the registry of all the rules, accessible by Detekt. Since it’s just a hook for Detekt to look for rules there’s nothing special happens here. We just need to provide unique id for our ruleset and include our custom check classes into the list of rules.

Other important step here is to notify Detekt about our ruleset provider. For that we need to create a file with the name io.gitlab.arturbosch.detekt.api.RuleSetProvider and place it in the directory src/main/resources/META-INF/servises. The content of this file should be the fully qualified class name of our custom ruleset provider:
 


 

Enabling our check

By default Detekt has a number of built-in rulesets. Before enabling our ruleset we need to add dependency of our detekt-rules module:
 


Then we should include our ruleset and rule into Detekt configuration:
 


Here on top level we list our ruleset name, the next level is there we list individual rules from this ruleset. Each of them we can turn on or off.
 

Tying things together

Now let’s try run Detekt on our code and you will see the output like this:
 


Learning a new programming language is always exciting and could be much more useful if combined with reading documentation. That could help to better understand the subtle nuances of technology you are trying to master. And if you want to establish some practice of using some language feature, it’s better to offload this responsibility to static analysis tool, such as Detekt.

Detekt offers great API to create custom rules and it’s not very difficult to write and add your rules. This will improve your code and make your life easier.

You can check the source code for the sample here.

 

Tags: Kotlin, Detekt, Android App Development, AndroidDev, Mobile Apps Development

 

View original article at: 


 

 

Android News
Creating an Expandable Floating Action Button in Android — Part 2
Creating an Expandable Floating Action Button in Android — Part 2

By TJ

It’s been a little over a year since the post above, and a lot of things can change in a year. Specifically, I’ve since moved to Kotlin as my primary language for Android development, and I’ve found a cleaner and more aesthetically pleasing way to create an expandable floating action button in Android; let’s talk more about the latter.

 

By ProAndroidDev -
Android News
Phantom Types in Kotlin
Phantom Types in Kotlin

By Danny Preussler

While working on my KotlinConf presentation about types I stumbled upon this post by Maximiliano Felice about Phantom types with Scala. The idea: prevent objects from having an illegal state or forbid illegal operation at compile time. The example given was a class Door. A door is either open or closed. In Kotlin this would look like:

By ProAndroidDev -
Android News
Exploring the new CameraX library in Android
Exploring the new CameraX library in Android

By Siva Ganesh Kantamani

CameraX is a Jetpack support library which is in Alpha at present. CameraX library is build to simplify the usage of camera features in app development. CameraX provides an in-depth analysis of what camera is pointed at through Image Analysis and also provides a way to use built-in device camera features like HDR, portrait mode and so on through extensions.

By ProAndroidDev -
Android News
A fusion between WorkManager and AlarmManager
A fusion between WorkManager and AlarmManager

By Stavro Xhardha

Android’s WorkManager has been around for a while. However my own expectations about it were a little higher. I wished that the WorkManager could fire events at exact timing. But since it was made to respect doze mode, I should respect that too. That means that if the phone is idle, the WorkManager  won’t run.

By ProAndroidDev -

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2019. All rights reserved.

powered by Breakpoint One