Top <Put_your_number> Kotlin utils we use all over our project

Michael Spitsin
ProAndroidDev
Published in
9 min readApr 28, 2021

--

Over the years I’m faced here and there with posts and articles dedicated to useful extensions or utils. “Top 5 useful Kotlin extensions”, “Top 10 super Kotlin utils”, “Top 20 neat extensions”, “Top 100 of the most needed things”. You can find a lot of articles all over the internet: here, there, this, that, here and here too.

I don’t want to go over each and put some comments on what is provided. Instead, I like the effort that library authors are putting into making our lives easier. And it’s our call what experience to adopt. And it also gave me the idea to share out extensions and utility methods that help us in project development.

Prepare some tea/coffee because I will not share just five methods or 20 methods, but the different areas in which we improving our code.

If you will like this article, please, give me claps, so I will prepare another pack of extensions/utils in one of the next articles.

1. ResourcesProvider — a way to wrap android resources

Problem statement

Android has a way to work with resources to get them from the res folder. You use context or resources to take integers, strings and etc. But imagine the next cases:

  • you using ViewHolder and in the binding stage, you want to take dimension as an integer. Then you will need to write something like
viewHolder.itemView.context.resources.getDimensionPixelSize(R.dimen.my_dimen)
//which is quite long :)
  • You have presentation logic, that you want to split from the Android UI side and you want to wrap resources to not use Android’s context/resources directly. For example, you have a mapper that builds the UI representation based on your domain model. And you want to cover it with unit tests.

Both cases can be:

  1. Of course, ignored. You do not necessarily have a unit testing policy in the project and team. And you do not necessarily have a pain in your heart when you see a long recurring code all the time.
  2. The first problem can be solved by creating BaseViewHolder and a base method for getting int dimen. The second problem can be resolved by mocking context/resources calls

Solution

We provided the way that solves both issues and at the same time operates on the old “Interface — Implementation” structure for unit tests without a need to mock Android-specific things.

Please, be aware that we made AppResourcesProvider as an inline class, which means that in some situations redundant object allocation can be avoided. But not in the case with mappers of course, since you will pass the reference to the interface, and thus real implementation should be provided.

For the aforementioned scenario with ViewHolder please consider looking into my previous old article about building declarative adapters. There is a section called Resources Management. Briefly, we will have the next base view holder:

Which is much more concise than declaring all your methods in that ViewHolder manually.

Additional Benefits

There are a few important benefits of that approach besides being split into a separate entity, more concise usage, and unit testing.

  • As I mentioned for some places you will not create an instance of that resources manager, which can be helpful in other extensions and approaches in the code. Consider the next example (which is also used in our project). You have view-binding library and want to have easy access to resources (more articles about view binding and view adapters will come later hopefully). You can just provide a single extension
val ViewBinding.res get() = AppResourcesProvider(root.context)//now compare
//1. root.context.resources.getColor(R.color.white)
//2. res.getColor(R.color.white)
//And no object allocation, since you using `AppResourcesProvider` directly
//Bonus, you can also write that extension for View
val View.res get() = AppResourcesProvider(context)

2. Finding enums — remove all same static/companion method

Problem statement

In our project. Well, in all project that I worked on, from time to time there were scenarios when you have let’s say id or code or something, and you want to find enum by it.

Previously I didn't either by having something like

And I needed to do that for every enum with a glimpse of the filtering requirement. But not anymore!

Solution

Generally, we have two methods:

  1. requireEnumBy — returns the enum of type T which matches the predicate that you passed or uses fallback callback to return the default enum in that case
  2. findEnumBy — returns the enum of type T or null, if there is no enum matching the criteria.

Let’s see how we can use those methods with the above example

Disadvantage

The only disadvantage of this approach is that your val code: String or other enum fields, that are participating in filtering should be public. In the vanilla approach, it can be private 🤔

We can quickly build a workaround method:

But in that case, the advantage of enum utility method is not so big. In our project using public val for enums, not a problem, since most of them are considered as models and we are fine to make models fields public. But I just wanted to warn you :)

3. View inline classes — declarative ways to do things

Problem Statement

Sometimes we want to change some offset of the view. Increase padding, Decrease parging. And sometimes it is hard to do that through xml because let’s say you need to adapt it dynamically for any reason.

Usually with padding everything more or less easy, if we talking about get method. Just use one of view.paddingStart, view.paddingTop etc. But to put new padding, you need to do something like:

view.setPaddingRelative(
newPadding,
view.paddingTop,
view.paddingEnd,
view.paddingBottom
)

Which is very inconvenient. KTX does not make your situation much better:

view.updatePaddingRelative(start = newPadding)

I already wrote a couple of years ago about another strange KTX solution with bundles and how you can do it more type-oriented and not lose the main key feature, that makes bundle different from just a map. If you are interested, just jump in.

Things are worse with margins because you can not just get or set margin to the view since this information is related to MarginLayoutParams. Again, KTX gives a pretty strange solution. On one hand, they say “here is a bunch of useful get methods”: view.marginStart, view.marginEnd etc. On the other hand, there is no something like view.marginStart = . Instead, you need to write:

view.updateLayoutParams {
updateMarginsRelative(start = newMargin)
}

Which is also not convenient for the end-user (an engineer who just wants to update poor margin).

Solution

The solution is basically to achieve a maximum declarative approach as possible. To have something like:

view.margin.start = newMargin
view.padding.total = newPadding
view2.padding.bottom = view1.padding.top

And Kotlin gently helps us with that by introducing inline classes, which gives the possibility to avoid any object allocation and make your code more structured in the same way.

And now you can achieve the truly declarative approach which is much more convenient than KTX solutions:

//Just compareview.padding.top = 9
//vs
view.updatePaddingRelative(top = 9)
view.margin.end = 6
//vs
view.updateLayoutParams {
updateMarginsRelative(end = 6)
}

Additional Benefit

Not only our solution is more readable and declarative. The only advantage is to use: view.padding.vertical = or view.margin.horizontal = , like in XML, which can also simplify things sometimes.

Bonus

The next version of this solution is to provide additional inline classes responsible for setting resources.

Suppose you want to set a particular dimension defined in res folder:

view.margin.start = view.context.resources.getDimensionPixelSize(R.dimen.small)

Or with our AppResourcesProvider from the beginning of the article, you can write:

view.margin.start = view.res.getDimenInt(R.dimen.small)

So let’s provide one more slight enhancement to make our approach even more convenient:

And now you can just write:

view.marginRes.start = R.dimen.small

Which is much briefer and does not lose readability at the same time.

4. View Attributes — small things to make your job more convenient

Problem statement

Sometimes we all do custom views. And sometimes we all add custom attributes to them (or reuse some android attributes instead). But working with them creates head pain over and over again. You need to remember to obtain all attributes by using obtainStyledAttributes and then recycle the received TypedArray.

Well, actually it. is not that bad, but still could be handy to remove that thought from the programmer’s head. And initially, I wanted to say that we create a special method for that. But then I discovered that KTX guys already provided it for us. Well, they provided straightforward and good implementation, though not the best for our usages.

Another problem related to getting typeface from the attributes. There is a method getFont which is available only from 26 API version :( So we need to handle this somehow too.

Solution

To resolve that we introduced a bunch of methods:

First of all, let’s discuss use method. KTX guys provided their own one, but they didn’t apply contract to it. So with their method, it is not possible to write something like:

val str: String
context.obtainStyledAttributes(attrSet, R.styleable.SomeView).use {
str = getString(R.styleable.some_text) ?: ""
}

You will face the next error:

Captured values initialization is due to possible reassignment

Second of all, we introducing here parseAttrs method, so you don’t need to write use every time. Small but handy improvement, as it turned out.

And third, is a possibility to get the font from AttributeSet.

Bonus

You can also mock up quick enhancement which will give a possibility to parse text appearance from the AttributeSet which can be really handy, when you have a custom view that shows text with the Canvas and Paint.

And then inside the custom view let’s say you can provide this:

5. Result<T> tiny extensions — small & last but not least!

Phew! 99% of the article is finished. The last section is a tiny one, but I still decided to include it since it contains just a few extensions yet still very important for our development.

Problem statement

We use Resut<T> in our codebase a lot. We working with coroutines and flows, but we don’t like the idea to write try/catch blocks all the time. For fellows like we Kotlin team created magnificent runCatching {} inline method, which returns Result<T>. This inline class contains either result or exception + a bunch of useful methods to control your flow after receiving the result (or error).

But a couple of situations are missing:

  1. When you have a consecutive call and the second one depends on the result of the first one, yet can still throw exceptions, there is a great method mapCatching. But what to do if the second method already returns Result<T2> instead of T2? mapCatching in that case will return Result<Result<T2>>
  2. When your method throws an exception, you can handle it in onFailure method. But suppose you need to convert the exception to another one. In our case, we for instance convert all Retrofit Exceptions to more structured OurCompanyException with additional info, like backendErrorCode and etc. How you can handle that?

Solution

Here are two small but powerful methods that resolve our problems :)

Now suppose we have:

interface Api {
suspend fun getUser(id: Int): Result<User>
suspend fun getAllUsers(name: String): Result<List<User>>
}
class MyCompanyException(
//with a lot of cool stuff inside
) : Exception(...)

You can easily write something like this with help of the above two methods:

fun findAllSimilarUsers(userId: Int): Result<List<User>> = 
api.getUser(id)
.then { api.getAllUsers(it) }
.mapError { MyCompanyException(it) }
//or something like that

Afterwords

If you get to this final point of that article, I would be glad to see your appreciation for claps and likes. If you want to see more utility things or extensions that we are using, please, leave comments or put more claps ;)

Brief and sugar coding to everyone!

Also, there are other articles, that can be interesting:

--

--

Love being creative to solve some problems with an simple and elegant ways