Tom De Decker
02/03/2023, 10:08 AMassertNotNull()
function are no longer nullable. Would I have to write a custom linter rule to achieve this?Sam
02/03/2023, 10:21 AM@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
fun <T: Any> assertNotNull(value: @kotlin.internal.Exact T?) {}
Sam
02/03/2023, 10:22 AMSam
02/03/2023, 10:24 AM@Suppress
annotation should be a sign that this isn’t a particularly sensible thing to do 😄 but I think it worksRobert Williams
02/03/2023, 10:27 AMAdam S
02/03/2023, 10:30 AMrequireNotNull()
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/require-not-null.html - do you want the same thing for your assertNotNull()
?Tom De Decker
02/03/2023, 10:36 AMTom De Decker
02/03/2023, 10:36 AMAdam S
02/03/2023, 10:42 AMrequireNotNull()
, it’s the contract {}
block that does the magic. It can be difficult to set up the Kotlin contracts, and they’re experimental, which I suspect might be too much work for a nice helper during refactoring? You might be better off just using requireNotNull()
, and then you can remove it later?Robert Williams
02/03/2023, 10:48 AMTom De Decker
02/03/2023, 10:59 AM@OptIn(ExperimentalContracts::class)
fun <T> assertNotNull(value: T?): T? {
contract {
returnsNotNull() implies (value != null)
}
assertInDebug(value != null)
return value
}
Our current usage typically boils down to the following:
val foo = assertNotNull(nullableFoo) ?: return
In production scenarios this will log the failed assertion to our backend but keep the application going (even though this might cause unexpected behavior down the line) and only in debug builds will this actually throw an exception.
Ideally I'd also like the nullableFoo
to be automatically smart cast to a non-null value after the ?: return
but even with the contract that does not seem to happen. For now though I'd be happy if I could make sure that we can catch the cases where nullableFoo
is not actually nullable anymore.Adam S
02/03/2023, 11:03 AMreturnsNotNull()
, but the function does return nullTom De Decker
02/03/2023, 11:08 AM?: return
) but I guess that's not the case. Is there any way around this (by e.g. changing the contract) or should I just look into using something like detekt?Adam S
02/03/2023, 11:14 AMAdam S
02/03/2023, 11:29 AMrequireNotNull()
comes from an IntelliJ inspection, it’s not related to the Kotlin contracts stuff. So you could try adapting the inspection to also check your assertNotNull()
function to show a warning if the the argument isn’t nullableTom De Decker
02/03/2023, 12:19 PMfun showDialog(dialogViewModel: DialogViewModel) {
val dialogText = assertNotNull(dialogViewModel.textContent) ?: return
// Code to show dialog goes here
}
In this case the DialogViewModel's might be nullable for legacy reasons. During development we'd like to know if there are still scenarios where the textContent could be null and fail fast, but in production we'd rather have the assertion be logged to our backend (so we still know the assertion triggered) without crashing the application. In this case the showDialog()
function would return before showing a dialog due to one of the preconditions failing which avoids a NullPointerException but also causes unexpected behavior for the end user (i.e. the dialog does not open).
In the past we'd have a lot of functions like the the snippet below which would silently hide errors in our application:
fun foo(foo: String?, bar: Int?, baz: Float?) {
foo ?: return
bar ?: return
baz ?: return
// ...
}
That's why we opted for our assertNotNull
implementation while we're cleaning up and trying to avoid having nullables where they don't make sense altogether.
As for your second answer, good to know that the warning is from an inspection! I'll see if I can make this work for our scenario.
Thanks for the help everyone!