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?
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.
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) }
}
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)
}
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?
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)