1
votes

I'm a complete newbie to android development and I've been stuck on this problem for the past two days and I've never felt more frustrated in my life. A little backstory first,

I'm creating the most basic Book Library app and I was trying to add a Navigation Drawer to the app. Inside the kotlin file, When I declare all the variables (corresponding to the tags created in the layout file) using lateinit, it throws me a nullPointerException. For this reason I've taken to declaring my variables like this:

var drawerLayout: DrawerLayout? = null

which helps me avoid the exception. Now coming to the real problem,

I was trying to create a click listener for my actionBarDrawerToggle inside the onCreate method this way:

    val actionBarDrawerToggle = ActionBarDrawerToggle(this@MainActivity, drawerLayout,  R.string.open_drawer, R.string.close_drawer)

    drawerLayout.addDrawerListener(actionBarDrawerToggle)   
    actionBarDrawerToggle.syncState()

and for some reason, the drawerLayout part of the "drawerLayout.addDrawerListener(actionBarDrawerToggle)" line is underlined in red meaning there's an error. When I run it, this is the error it shows me in build window:

    Smart cast to 'DrawerLayout!' is impossible, because 'drawerLayout' is a mutable property that could have been changed by this time

I have no ideo how to proceed with this. I've tried a lot of things and none of them is working. I think the error might have something to do with the declaration method I use that I described above. It would be great if someone could help me out

2
Have you asked a question on why you were getting a NullPointerException when using lateinit? Maybe you should try fixing your core issue rather than trying to work around it by using a var.ianhanniballake
@ianhanniballake so what exactly should I do to fix that? My view Ids and my variable names all match up so there's no error in that, I've tried using the non null asserted call this way: var drawerLayout: DrawerLayout!! and that isn't working as wel , I've cleaned the project, invalidated caches, I've searched far and wide but I just cant seem to find a solution around thisjas

2 Answers

0
votes

Tactically, change that line to:

drawerLayout?.addDrawerListener(actionBarDrawerToggle)

?. in Kotlin is the "safe call" operator. It says:

  • If drawerLayout is not null, call addDrawerListener()

  • If drawerLayout is null, do nothing (technically, it evaluates to null, but you are not using that here)

That will allow you to compile. Whether the code will work will depend on whether you have successfully set a non-null value on drawerLayout or not by the time you reach that line. The fact that your lateinit var declaration was failing suggests that you are not populating drawerLayout — with lateinit var, you would crash at runtime; with the nullable property and the safe call, the line will wind up being ignored. Neither is what you want.

Ideally, the books that you are reading (or courses that you are taking) on Kotlin and Android app development would cover things like safe calls, how to avoid having properties like drawerLayout, and so on. If you are not reading books or taking courses on Kotlin and Android app development, that may be something that you should consider.

0
votes

You've defined drawerLayout as a nullable (DrawerLayout?) var, which means the value can change at any time, and that value can be null. So even if you do a null check to make sure it's not null, that could change at any moment! There's no way for the compiler to guarantee the call is safe.

Here's some options, in order from bad to good:


Explicitly mark the variable as non-null by referencing drawerLayout!!. This is you telling the compiler "I know what's up and it's fine, trust me". You should avoid ever doing this because it's a red flag, you ignore warnings, and you'll end up running into a situation where you're wrong


Copy the non-null value into a temporary variable, that you know won't change. This is the typical way to handle nullables in Kotlin, and you use one of the scope functions like let:

drawerLayout?.let { drawer -> //  do stuff with drawer }

That's doing a null check (with the ?) and if it's not null, it calls the let block with that non-null value. And you know that value isn't going to change inside the block. I've called the variable drawer but you can omit that, it's called it by default. There's other scope functions like run too, slightly different ways of doing the same thing.

You can also just make a call on the checked variable itself, if that's all you need to do:

drawerLayout?.doAThingItsNotNullSoItsFine()

which is basically doing the same thing under the hood


Don't make drawerLayout nullable in the first place! It's never actually supposed to be null, right? It should always exist, and have a value when you try to access it? Then save yourself the aggro and the need to null check it all the time - which is what lateinit is for.

lateinit allows you to declare a variable without actually giving it a value. Usually you have to, which is what you've done here right - you've made it nullable, just so you can stick a temporary null in there. lateinit is a promise to the compiler that it's ok, you're not providing a value yet, but you will definitely have set one before anything tries to access it. (That's why it's called late init!)

So the problem you were running into was that you hadn't actually set that value before something tried to read it. You can check if it's been set with ::drawerLayout.isInitialized, but really you should understand your code flow enough to ensure that the reading is only possible after the intialisation. If you can't guarantee that, then you have a possible state where you don't have a value for a variable that other stuff might need to access - and that's a good candidate for making it nullable and using the null-handling features to deal with it smoothly