Paulo Buchsbaum
Paulo Buchsbaum

Reputation: 2649

Why Kotlin throws an exception in a very simple piece of code?

I've found a very kinky error in Kotlin using updated Android 3.4.2.

First I've run my test code in my computer (not Android) using main modules in any kotlin file in my only module. It always works for me, but it has started to give an error that I comment below and then another error that don't allow to run the main module anymore.

Searching in Stack Overflow, one user as claimed that the last described error ends when one uses test files under java folder (Not Android test files)

However is has continued to give the first error, which I commented above, which I managed to reduce to the following schematic example:

class Cl(
  var a:Int=0
)
var vCl = arrayListOf<Cl>()

And the main module is:

fun main(){
    println("start")
    vCl.clear()  // error points to this line
    println("ok")
}   

I just point to fun main() line in test file and click the green icon.

The error message

Exception in thread "main" java.lang.ExceptionInInitializerError

Suddenly the error stops when I've changed one global statement from one file to another file (in the top part, outside any class or function).

var timings = TimingLogger("MyTag", "Your")

It's crazy. I'm freaking out!

Update:
=======

I've made newerror, a new tiny project with one module to reproduce the error decribed in this question. Below is the complete code:

Gradle: No change after project creation.

AndroidManifest.xml: No change after project creation.

activity_main.xml: It is bare bone because all my views are dinamically created:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/myLayout"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent" tools:context=
          "br.com.greatsolutions.paulo.myerror.MainActivity">
</RelativeLayout>

The MainActivity.kt code:

package br.com.greatsolutions.paulo.myerror

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

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

The compiler.kt code

package br.com.greatsolutions.paulo.myerror

import android.util.TimingLogger
var timings = TimingLogger("MyTag", "Your")
class Cl(
    var a:Int=0
)
var vCl = arrayListOf<Cl>()

Finally the test.kt code:

package br.com.greatsolutions.paulo.myerror

fun main(){
    println("start")
    vCl.clear()
    println("ok")
}

The complete error message when I run test.kt code in my computer (not in Android):

start
Exception in thread "main" java.lang.ExceptionInInitializerError
    at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt:5)
    at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt)
Caused by: java.lang.RuntimeException: Stub!
    at android.util.TimingLogger.<init>(TimingLogger.java:59)
    at br.com.greatsolutions.paulo.myerror
              .CompilerKt.<clinit>(compiler.kt:5)
    ... 2 more

And, again, if I put the below declaration inside MainActivity.kt and run the test.kt again in my computer ...

var timings = TimingLogger("MyTag", "Your")

... and the error is gone!

start
ok

For those who want to see to believe:

crazy bug

Conclusion: Now I know how to avoid this crazy error, but I don't understand why it works! Ih theory, the source file that I use to make my declarations should be interchangeable, because all files are in the same module!


                              Solution of puzzle          

Alexey Romanov has hit the nail on the head!

I've researched a little bit more and I've found that Android Studio only executes the declarations in a file if any variable of the scope was used in a computer test.

When

var timings = TimingLogger("MyTag", "Your")

is inside MainActivity.kt, no error is shown.

After I put in this file the following code:

open class Fool(val a:Int=5){
   init { println("fool") }
} 

class SuperFool(a:Int=8):Fool(a) {
  init { println("what a big fool") }
}
var v = SuperFool()

When I put println(v.a) my code in test.kt becomes:

package br.com.greatsolutions.paulo.myerror

fun main(){
    println("start")
    println(v.a)   // new line
    vCl.clear()    
    println("ok")
}

And it gives the same error than before! Take out this line and no error again!

The solution is if one has any declaration of some variable in an Android class in your projet you MUST use lateinit and just initializate in other point of code that will not run in test execution.

In this case one can do

lateinit var timings:TimingLogger

And put the initialization in other place (inside onCreate in MainActivity class, for instance. In my case, immediately before the 1st. call addSplit, one of the methods from TimingLogger class

timings = TimingLogger("MyTag", "Your")

Now my test code gently prints

start
fool
what a big fool
ok

Upvotes: 0

Views: 520

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170713

The problem has nothing to do with Kotlin; you can't just use Android classes in code running directly on your computer and not in an emulator, because the versions in the standard jar will crash on first use. They are just there to provide class files with the same names and method signatures as will exist on the device, so that your code can compile (not run!).

Use Robolectric to get a usable-for-JVM-tests version of the Android library.

if I put the below declaration inside MainActivity.kt and run the test.kt again in my computer

Then your test doesn't use any Android classes (see below for details).

It is worth remembering that, within the same module, all declarations, regardless of what file they are in, are executed.

That's wrong. What happens with top-level val/var/fun declarations in Kotlin is that they get wrapped into a single class for each file, so your code works like this:

// compiler.kt
package br.com.greatsolutions.paulo.myerror

object CompilerKt { // you can actually see the name in the stack trace
    var timings = TimingLogger("MyTag", "Your")
    val vCl = arrayListOf<Cl>()
}

class Cl(
    var a:Int=0
)

// test.kt
package br.com.greatsolutions.paulo.myerror

object TestKt {
    fun main(){
        println("start")
        CompilerKt.vCl.clear()
        println("ok")
    }
}

So calling TestKt.main() forces loading and initialization of CompilerKt because you reference vCl there. This includes calling the constructor TimingLogger("MyTag", "Your") which thrown an exception when the stub library is used.

Calling TestKt.main() does not load MainActivity (which you can confirm by adding some print to that file), so if you move var timings there, nothing in the stub library gets called.

Upvotes: 3

Joel Libby
Joel Libby

Reputation: 116

Your missing a an quote on your first line.

Upvotes: 0

Related Questions