We write Android UI with clean code without additional libs


Today is a foggy and cold Sunday and I had a desire to write some “useful” article for habr.

Nowadays Android programmers do not write UI clean code very often, with the exception of such libraries as Jetpack Compose

Of course, this may seem like a perversion, you will have to use ViewGroup.LayoutParams in order to set the indents between buttons or texts, or you will use GradientDrawable to set a custom background.

In general, this is a tedious job, but luckily for us, Kotlin appeared in 2011!

Thanks to global extension functions, you can write UI almost like in Jetpack Compose!

Well, let’s get started!

Helper Classes and Kotlin Extensions

I’m not going to consider all View components and their properties, but I will only concentrate on the main points.

Let’s create a regular TextView with red text in our MainActivity:

// выглядит неплохо, неправда ли?
val textView = textView(this) {
 		text("Hello, World!")
    color(Color.RED)
}

// все благодаря глобальным функциям, ну и конечно Kotlin extensions!

// функция для создания нашей текстовки
fun textView(context: Context, init: AppCompatTextView.() -> Unit) : AppCompatTextView {
    val text = AppCompatTextView(context)
    text.init()
    return text
}

// теперь мы можем указать любое значение в качестве текста,
// он будет привидено к строке
fun <T> AppCompatTextView.text(value: T) {
    text = value.toString()
}

// цвет текста
fun AppCompatTextView.color(color: Int) {
    setTextColor(color)
}

Let’s create FrameLayout and place our text in the center:

// наша текстовка
val textView = textView(this) {
    text("Hello, World!")
    color(Color.RED)
    // чтобы текстовка находилась в центре мы должны
    // указать для нее FrameLayout.LayoutParams
    layoutParams(frameLayoutParams().center().params())
}

// создаем FrameLayout и добавим в него текстовку
val parent = frameLayout(this) {
	addView(textView)
}

// указываем наш FrameLayout вместо activity_main макета
setContentView(parent)

// функция для создания FrameLayout
fun frameLayout(ctx: Context, builder: FrameLayout.() -> Unit) : FrameLayout {
    val frame = FrameLayout(ctx)
    builder(frame)
    return frame
}

// функция для создания FrameLayout.LayoutParams
fun frameLayoutParams() = FrameLayoutLP()

You might be wondering what kind of class this is. FrameLayoutLP?

Let’s take a look at it:

// FrameLayoutLP является удобной оберткой для FrameLayout.LayoutParams
class FrameLayoutLP(private val params: FrameLayout.LayoutParams = 
    FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 
                             FrameLayout.LayoutParams.WRAP_CONTENT)) {
  
    fun matchWidth() = FrameLayoutLP(params.apply {
        width = FrameLayout.LayoutParams.MATCH_PARENT
    })

    fun wrapWidth() = FrameLayoutLP(params.apply {
        width = FrameLayout.LayoutParams.WRAP_CONTENT
    })

    fun matchHeight() = FrameLayoutLP(params.apply {
        height = FrameLayout.LayoutParams.MATCH_PARENT
    })

    fun wrapHeight() = FrameLayoutLP(params.apply {
        height = FrameLayout.LayoutParams.WRAP_CONTENT
    })

    fun center() = FrameLayoutLP(params.apply {
        gravity = Gravity.CENTER
    })

    fun params() = params
    
  	// ...
  	// вы можете изучить полный код на Github
    // https://github.com/KiberneticWorm/LearningApps/blob/master/FadingList/app/src/main/java/ru/freeit/fadinglist/core/FrameLayoutLP.kt
}

We have considered the most important functions and classes that make up our own mini UI lib.

You can go ahead and create more helper functions and extensions for the views, and also implement wrappers for LinearLayout.LayoutParams and ConstraintLayout.LayoutParams

Creating a RecyclerView list

Probably almost every application has some kind of lists and therefore it was a sin not to mention our beloved RecyclerView

Let’s create a list of my friends:

// Ну как вам?
val list = listView(this) {
    linearVertical()
    adapter(listOf("Вадим", "Света", "Кристина", "Рамиз"), object: ViewHolderWrapper<String>() {
        override fun view(ctx: Context): View {
            return textView(ctx) {
                padding(16.dp(context))
                color(white())
               
                listenItem { _, friendName ->  
                    text(friendName)
                }
            }
        }
    })
}

Maybe it looks a little strange and looks like hell callbacks

In fact, it’s not like that.

We can move the view of the list item into a separate class and our code will again become almost linear)

Let’s take a look at RecyclerView extensions:

// функция для установки вертикального LinearLayoutManager
fun RecyclerView.linearVertical(reverse: Boolean = false) {
    layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, reverse)
}

// создание нашего списка
fun listView(ctx: Context, init: RecyclerView.() -> Unit) : RecyclerView {
    val list = RecyclerView(ctx)
    list.init()
    return list
}

// и самое вкусненькое)
// здесь мы создаем CoreAdapter, который является наследником
// RecyclerView.Adapter и с помощью объекта ViewHolderWraper<T>
// получает элементы списка и связывает их с вьюшками
fun <T> RecyclerView.adapter(items: List<T>, viewHolder: ViewHolderWrapper<T>) {
    adapter = CoreAdapter(items, viewHolder)
}

// код CoreAdapter вы можете изучить самостоятельно
// https://github.com/KiberneticWorm/LearningApps/blob/master/FadingList/app/src/main/java/ru/freeit/fadinglist/core/CoreAdapter.kt

I think no special explanations are needed.

Is that the implementation CoreAdapter and ViewHolderWrapper<T> deserves a separate article.

Let’s summarize

Building a UI with pure Kotlin code is actually quite an enjoyable and challenging challenge, thanks to tools such as:

  • Global functions in Kotlin

  • Extension functions

  • method apply

  • functional programming capabilities

  • Kotlin collections and data classes

I almost forgot, link to Github’chik (in this repository there is a mini-application from which you can take the helper classes that are in the core package and use them for your own purposes)

Friends, coders! More rest for you, pleasant moments in life, and of course love, which we all lack so much 🙂

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *