You CAN have your cake and eat it — A type-safe collection of elements of any type in Kotlin

Kajetan
8 min readJan 2, 2020
Cakes
Who does not like cakes? / Photo by Rakesh Gohil from Pexels

Before you start reading, let me put one note here. Obviously, it could be achieved in Java (or another statically typed language) as well but as my example comes from an Android library, choosing Kotlin was the most appropriate option.

Type safety

What are your superpowers again? “1” + 1 = “11”

I am sure most of you have non-zero experience in either JavaScript or Python. This is because they are great for smart small tools and prototyping. But it comes at a price. Oh, how we love these cute little red lines in the console after some variable changes its type in a very magical way… right? No. The bigger the project you are working on, the more flaws and claws of dynamically typed language you will start to experience. Here, take a look:

And the result is:

boolean false
boolean false
string true
boolean true
string false
boolean true

Do you see any logic here? Me neither. Especially the last one does not make any sense.

Enter the Statically Typed Language

Type safety is a great feature of all statically typed languages. It allows us to forget about at least one source of problems. If we are working with an object of some type, we know for sure that it contains fields and methods of this type and we can safely refer to them. Moreover, our IDE also knows it and hence very often our coding will come down to choosing the appropriate hint from the list. As if that was not enough, the compiler also knows it thanks to which we will get a program definitely more efficient than its counterparts written using dynamically typed languages.

Generic collections

Generic Types a.k.a. Write Once — Use in Thousands of Different Cases. Another fantastic thing we use every day. With a little work, it allows us to generalize the code for a theoretically infinite number of cases, making our program clean, readable and flexible. Above all, however, we gain time (less w̵r̵i̵t̵i̵n̵g̵ copy-pasting) and safety (prevents us from forgetting about one of the thousands of cloned lines when changing one, faulty line of code.) Shining example is different types of collections. Thanks to generics, we do not need to create classes like StringList, BucketList, WishList in order to make use of type safety. One class is enough: List<ItemType>.

Ingredients selection (Problem)

Alright, after this lengthy introduction, let us get to what you expected to find in this article — a recipe for a delicious cake! Let us start with something trivial. Imagine you need to create a collection that will contain data of many different types but these types are not and cannot be related in any way. Well, the only solution is to go back to ancient times, when dinosaurs were running on the streets of New York and there were no generics in Java. Let us make a collection of type Any.

I/System.out: I am ApplePie.
I/System.out: I am Cheesecake.
I/System.out: I am Coffee.

The solution above is neither generic nor does it provide type safety (unless simplifying the object to the most primitive parent class can be called type safety.) So how does this relate to the article? We will get to that in a moment, be patient. Let us face it, it was fairly simple. Let us move on to something a bit more sophisticated. Assume that we want to create a collection of many types again, but this time we have full control over them and thus we are able to connect them with some layer of abstraction. I will choose the interface because it gives more flexibility than the abstract class.

I/System.out: Yummy apple pie!
I/System.out: Yummy cheesecake!
I/System.out: Delicious coffee!

What we have done is create a simple hierarchy and make use of the fundamental OOP feature to populate the list of related objects and access them via a common interface. And that is pretty cool. That is why we use this mechanism so often. Sometimes though, it is not enough. Let us go a step further, shall we? We would like to combine both requirements into one. Have a collection of arbitrary, unrelated types, but keep access to the fields and methods of each. If we know every possible type that can appear in the collection, it should not cause a big problem. However, it will result in a relatively long, inefficient and error-prone code.

I/System.out: Yummy apple pie!
I/System.out: Yummy cheesecake!
I/System.out: Delicious coffee!
I/System.out: Meow?
I/System.out: Hey! You forgot about ThisClass!

In this approach, the class of each collection item should be checked and handled accordingly. One of the many disadvantages is that any change in the set of supported models should also affect all these when/switch blocks and this task, unfortunately, rests on the programmer’s shoulders.

If you look carefully, you will see a factory of strategies. Does the use of two design patterns make it a good solution? You can judge for yourself.

Now it is time for the most difficult scenario. We know nothing about the exact types… but we still want to access their fields and methods safely. Do you have any ideas? Is it impossible? Is it a paradox? We know for sure that the previous way will not work because we will not include all standard classes and all classes from the libraries we use. Not only because it would be a waste of life but also because it is simply impossible as we have no control over libraries our users import (assuming that we are creating a library ourselves.) So what shall we do now? Give up? Walk away with our heads down? Of course not!

They taught us that you cannot have your cake and eat it. But is it true? Let us see if it is possible, and if so, how to keep information about types in a collection that does not mind to store a double-precision floating-point number 3.141593 next to a string literal "PI".

Baking a cake (Solution)

While working on Kandy List Views, the Android library for rapid and elastic list views creation, I encountered this exact problem. I wanted to make it possible for developers to forget about all of these verbose adapters, type views, etc. But how to do it when any class can be a list item model? Moreover, theoretically, each list item can be expressed by different class with a different view and different behavior. It can be done, though. Using this pattern, that is how:

I/System.out: Yummy apple pie!
I/System.out: Yummy cheesecake!
I/System.out: Delicious coffee!
I/System.out: Meow?

Please keep in mind that many things have been simplified in order to emphasize the idea itself. The library is open source so you can see this pattern in all its glory on GitHub.

Okay, let me rationalize the magic behind it. We have a ListItem class that holds an object of any type and a controller (here AbstractItemPrinter) responsible for using this object within the interface we need in our collection. For simplicity’s sake, this interface contains only one method — print. While print‘s main purpose is to provide a convenient way of inheriting the controller thanks to the access to the type of object (without having to cast it every time), the delegatePrint method is used outside and allows to pass any argument after losing information about actual types in a collection of elements of any type. And indeed this information is lost, as soon as the wildcard ( ListItem<*> ) is used. In Kotlin, in this scenario, the generic type simply becomes Nothing which makes all fields and methods using the ItemType completely useless. The delegatePrint solves this problem.

Voilà! We have our cake and ate it. Can we insert a completely arbitrary object into a collection? Of course. Wrapped in the ListItem. Do we have access to the actual class of each element? Of course. Wherever we need in our custom interface.

Bon appétit! (Final notes)

A cake
Photo by Farooq Sharif from Pixabay

This pattern is safe and works, but remember that the list element class (here ListItem) has to force type compatibility between stored data and the generic adapter parameter. In other words, you have to ensure that the cast in a delegate method is indeed always safe. If you do not want to force it as explicitly as in the example above, you would have to validate it in another way and be extremely careful if the model or holder is mutable.

I am aware that in this exact example the program could be even simpler and more secure. Moving the print method to the ListItem class would ensure that passing an argument of the wrong type is not only illogical but also impossible. However, it was not an option for the library from which this concept was taken. The RecyclerView.Holder cannot store information about the model, and therefore, applying it to the current example, it was impossible to have both an item and a printer in one class.

What we could do differently here is to create a hierarchy based on ListItem, not Printer (i.e. ConsumableListItem, CatListItem, etc.) Even though I can imagine a scenario in which this approach would be good, I can see two reasons why the current one is better. The first one is quite obvious. As mentioned earlier, the RecyclerView.Holder simply cannot store the model. The second one is summarized in a well-known phrase: “Prefer composition over inheritance”.

I am very glad to see you here! Does it mean that you read the whole article or just skipped it? 😏 If you had a good time reading, I would really appreciate any feedback. Leave a comment or simply clap so others can also find this content easier. 🙂

If you are interested in the library I mentioned, you can find the link below. Hopefully, it will save you precious time. 😉

What’s next

--

--