[SOLVED]NPE when calling function in lazy delegate inside my kotlin library from Android app


#1

I’m creating a library consumed in my Android app.
In library side, i have these:

package a.b.util

internal fun  isFileNameValid(fileName: String) : Boolean {
    val lowerCase = fileName.toLowerCase()
    return lowerCase.endsWith(".jpg")
}
package a.b.c
import a.b.util.isFileNameValid

class Attachment(val name: String) {
    val isValidName by lazy { isFileNameValid(name) }
}

Let me add more info.
I use the library to get the data from server in JSON format, i’m using Retrofit2 to get the data and converted to Attachment. My idea is to delay the checking of valid name until needed, that’s why i’m using lazy for isValidName (isValidName is not exist in the JSON string).

In android side, the getAttachment below is to get the Attachment converted from JSON.
But when i access isValidName, i always receive null pointer exception.

val attachment = getAttachment()
Log.d("result", attachment.name) // no NPE, i can see the name
Log.d("result", attachment.isValidName) // crashed NPE

Is it bad codes or wrong ProGuard setting?
What is the ProGuard setting i should write in this case?


#2

I got better result with these codes below without lazy. I managed to update to kotlin 1.0 release version.

class Attachment(val name: String) {
    val isValidName = name.toLowerCase().endsWith(".jpg")
}

But not with this:

class Attachment(val name: String) {
    val isValidName by lazy { name.toLowerCase().endsWith(".jpg") }
}

#3

How getAttachment is implemented? Does it create an Attachment using its constructor?


#4

the getAttachment will return Attachment which created by Retrofit2 using GSON library.
If i tried create Attachment directly using constructor, there was no problem when calling isValidName.

I also faced similar problem with this class:

class Status(val id: Int, val label:String) {
    val type = Type.from(id)

    enum class Type(val id: Int) {
        Open(1),
        Closed(2),
        Merged(3);

        companion object {
            fun from(id: Int): Status.Type = when(id) {
                1 -> Open
                2 -> Closed
                3 -> Merged
                else -> throw IllegalArgumentException("Illegal id $id")
            }
        }
    }
}

When i tried calling status.type i got null value.
Then i tried registering type adapter StatusDeserializer to GsonBuilder which will create Status instance using it’S constructor. And i don’t have null when calling status.type

Type field is not available in json string, the value is inherited from id field.


#5

I suppose the root cause is GSON creating an object without invoking its constructor.
The instance being created has no chance to initialize its fields and check that required invariants and preconditions are met.

This answer on SO suggests either to implement no-argument constructor or to register an InstanceCreator for your class.

You can have no-argument constructor generated if you make all arguments of primary constructor optional:

class Attachment(val name: String = "") {
    val isValidName by lazy { isFileNameValid(name) }
}

#6

Thank you very much.
I also try with deserializer to create the Attachment instance in GSON.
No crash anymore.

Thank you for your help.


#7

Hi, what about this code? I started getting NPE frequently with debug build, after adding some code to the lazy block. But previously the lazy property didn’t cause NPE.

E/AndroidRuntime: FATAL EXCEPTION: main
Process: letstwinkle.com.twinkle, PID: 3021
java.lang.RuntimeException: Unable to resume activity {letstwinkle.com.twinkle/letstwinkle.com.twinkle.MatchFilterSettingsActivity}: java.lang.NullPointerException: Attempt to invoke direct method ‘android.support.design.widget.NavigationView letstwinkle.com.twinkle.widget.NavigationDrawerManager.getNavigationView()’ on a null object reference
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2951)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2982)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2368)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5223)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Caused by: java.lang.NullPointerException: Attempt to invoke direct method ‘android.support.design.widget.NavigationView letstwinkle.com.twinkle.widget.NavigationDrawerManager.getNavigationView()’ on a null object reference
at letstwinkle.com.twinkle.widget.NavigationDrawerManager.attachToActivity(NavigationDrawerManager.kt:46)
at letstwinkle.com.twinkle.MatchFilterSettingsActivity.onResume(MatchFilterSettingsActivity.kt:396)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1243)
at android.app.Activity.performResume(Activity.java:6024)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2942)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2982)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2368)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5223)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

The lazy delegate and the code that calls it is:

    internal class NavigationDrawerManager : NavigationView.OnNavigationItemSelectedListener {
    var drawer: DrawerLayout? = null
    private var context: Activity? = null
    private @IdRes var selectedItemId: Int = 0
    private val drawerListener = DrawerListener()
    private val navigationView: NavigationView by lazy {
        if (context == null) {
            Log.e(tag_, "(*&% can't create NavigationView because don't have context yet")
        }
        val nav = this.context!!.layoutInflater.inflate(R.layout.view_navview, null) as NavigationView
        val match = ViewGroup.LayoutParams.MATCH_PARENT
        nav.layoutParams = DrawerLayout.LayoutParams(match, match, Gravity.START)
        nav.setNavigationItemSelectedListener(this)
        val avatar = nav.getHeaderView(0).findViewById(R.id.navHeaderAvatar) as ImageView
        User.profile.avatar?.uri?.let { uri ->
            bindSrcURI(avatar, uri, "circle")
        }
        nav
    }

    fun attachToActivity(activity: Activity) {
        if (activity !== this.context) {
            this.context = activity
            val drawer = activity.findViewById(R.id.drawer) as DrawerLayout
            this.drawer?.removeDrawerListener(this.drawerListener)
            drawer.addDrawerListener(this.drawerListener)
            (this.navigationView.parent as ViewGroup?)?.removeView(this.navigationView)
            drawer.addView(this.navigationView)
            this.drawer = drawer
        }
        this.syncSelectedNavItem()
    }
}

The code that calls attachToActivity:

    override fun onResume() {
        super.onResume()
        this.twinkleApplication.trackScreen(this)
        this.twinkleApplication.drawerManager.attachToActivity(this)
    }

#8

Caused by: java.lang.NullPointerException: Attempt to invoke direct method ‘android.support.design.widget.NavigationView letstwinkle.com.twinkle.widget.NavigationDrawerManager.getNavigationView()’ on a null object reference
at letstwinkle.com.twinkle.widget.NavigationDrawerManager.attachToActivity(NavigationDrawerManager.kt:46)

It seems that the instance of NavigationDrawerManager is null on which the getter of navigationView property is invoked. Could you show what exactly the line NavigationDrawerManager.kt:46 is?


#9

Hi Ilya,

I have attached the source file. Note, as far as I can tell, this NPE never happens when 32-35 are commented out. When they are uncommented, I get the exception intermittently.NavigationDrawerManager.kt.zip (1.4 KB)

Thanks!
Julian


#10

Can’t reproduce the NPE if I extract the navigationView into a variable in attachToActivity.


#11

I have to take that back, unfortunately; I just did get the NPE even with the above code change. Debug build