Reputation: 3250
I am trying to learn the "Kotlin native way" to do things in Android, while being an expert in neither Kotlin, Java, nor Android development. Specifically, how to properly deal with nullability in Android interfaces/extensions.
Below is a snippet of a working example using a RecyclerView
that seems to be abusing Kotlin's nullability paradigm.
First, the Adapter class:
class PersonListAdapter(private val list: ArrayList<Person>,
private val context: Context) : RecyclerView.Adapter<PersonListAdapter.ViewHolder>() {
Note: this example uses ArrayList<>
, instead of ArrayList<>?
.
Then in the main activity, it has the following snippet:
private var adapter: PersonListAdapter? = null
private var personList: ArrayList<Person>? = null
private var layoutManager: RecyclerView.LayoutManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
personList = ArrayList<Person>()
layoutManager = LinearLayoutManager(this)
adapter = PersonListAdapter(personList!!, this)
Notice here two things: (1) personList
is an ArrayList<>?
(note the nullability), and (2) the list is later called with personList!!
, basically escaping us out of the whole point of safely using nullable objects in Kotlin.
I don't understand why the author did this. Why not simply assign the list to something non-null initially? For instance, something like:
private var personList = ArrayList<Person>()
Or, if this cannot easily be done, why not initialize the adapter with a nullable list? I'm presuming the latter cannot be done because we're extending the RecyclerView.Adapter
, but I wanted to ask to be sure. I am thinking something like the following:
class PersonListAdapter(private val list: ArrayList<Person>?,
private val context: Context) : RecyclerView.Adapter<PersonListAdapter.ViewHolder>() {
(Note the ArrayList<Person>?
instead of ArrayList<Person>
.)
Or is there an even better paradigm?
What's the right way to handle nullability in Kotlin when working with Android interfaces, extensions, and other situations where we're building on top of a deep base of Java code?
I'd really like to avoid using !!
whenever possible. While I am a newbie in this realm, it seems to me that the point of the ?
is to avoid the billion dollar mistake, and that using !!
is essentially saying, "nope, I want that billion dollar mistake back", isn't it?
Upvotes: 0
Views: 566
Reputation: 17854
Either the author of this code just wasn't thinking, or they're also a beginner in Kotlin.
As long as the Object you need to instantiate doesn't require a working Context Object in its constructor, it can be instantiated outside of any methods. I know that when I was starting out in Android development and noticed that certain things could only be instantiated in onCreate()
, I went the safe route and just instantiated everything there. It's possible this is what the author of the code was doing, and transferred that habit over to Kotlin.
Now, usually the better way to handle a late init variable is with, well, the lateinit
modifier:
private lateinit var personList: ArrayList<Person>
and then instantiate it in onCreate()
. It's no longer nullable, so it won't need the !!
modifier.
Of course, if you want to keep the current nullable status, you could always just set the parameter in the Adapter's constructor to be nullable as well. You'll just have to do some funky stuff with the Adapter itself:
getItemCount(): Int = list?.size!! //or list!!.size
val item = list?.get(position)
item?.whatever
I can only speculate, but I think the author just got a little carried away with late initialization. Both of the other parameters can't be initialized immediately, so it could have been just a reflex to do the same for the list.
To be fair, though, the !!
here isn't really taking that "billion dollar mistake" back. The list is initialized right above personList!!
, so unless you have another Thread running that could make personList
null again before your main Thread reaches the Adapter's initialization, personList
is guaranteed to be non-null.
As for the "best" way to deal with null-safe... well, there is none. The "best" way in this case, however, I could argue, would be something like this:
private val personList = ArrayList<Person>() //this needs to be at the top now
private val adapter by lazy { PersonListAdapter(personList, this) } //by lazy will initialize this variable once it's first referenced and then retain that instance for future calls
private val layoutManager by lazy { LinearLayoutManager(this) }
Now everything is taken care of when the class is initialized (or when a variable is first referenced), and you don't have to worry about nullability.
I do have an example of where your quoted code's structure could come into play, though: singletons.
class SomeSingleton private constructor() {
companion object {
private var instance: SomeSingleton? = null
fun getInstance(): SomeSingleton {
if (instance == null) instance = SomeSingleton()
return instance!! //since technically instance could've been made null from another Thread, Kotlin requires that you assert the non-null state
}
}
}
This is actually how by lazy
works, if you look at a decompiled Kotlin JVM project. It won't be inside its own class, of course, but Kotlin will create a global variable and a getVariable()
method that does exactly what the getInstance()
method I put above does.
That whole thing could probably be replaced by:
class SomeSingleton private constructor() {
companion object {
val instance by lazy { SomeSingleton() }
}
}
TL;DR, I can only speculate, but it seems like the author is holding onto some Java programming styles, either forgetting, not realizing, or refusing to use the Kotlin alternatives.
Upvotes: 1