Mobile - - By Ankul Jain

Building a UI with Kotlin and Anko

Since the beginning of Android development working with UI has been an XML thing. Though theoretically, UI could be programmed using Java, it has not been of much use. Not long ago, JetBrains introduced Kotlin, a modern language targeting the JVM, which could serve this purpose for Android.

Jetbrains announced Anko as a faster and easier style of development in Android. Kotlin offers the Anko library as a DSL(Domain Specific Language) to design an Android screen. A quick example:

Following is a plain Android UI consisting of an imageView and a Button.

Here’s its Anko code:

verticalLayout{
        imageView(R.drawable.anko_logo).
                lparams(width= matchParent) {
                    padding = dip(20)
                    margin = dip(15)
        }
        button("Tap to Like") {
                onClick { toast("Thanks for the love!") }
        }
    }

Here we have defined a Vertical Linear layout which acts a container for the image and button. The positioning of the views within a layout has been defined using lparams(). And, what happens on the button click is also defined inside the UI definition, thanks to Kotlin inline function.

Advantages of using Anko

  • We can embed UI layouts inside the source code, thus making it type-safe.
  • Since we are not writing in XML, it adds to the efficiency as there is no need to waste CPU time in parsing the XML.
  • After the programmatic transformation of UI, we can put an Anko DSL fragment into a function. Thus facilitating code reuse.
  • And clearly, the code is more succinct, readable and graspable.

Now, let’s build a to-do app that lists tasks using Anko Layout and Kotlin.

You can find the repository to this To-do App on GitHub

Adding Anko Library to Android Studio

Take a look at Streamline Android Java Code with Kotlin to learn how to add Kotlin to your Android project. Along with Kotlin, we need to add Anko dependencies in app/build.gradle so that we are able to compile the project:

compile 'org.jetbrains.anko:anko-sdk15:0.8.3'
//sdk19, 21 and 23 are also available

This dependency can be added based on which minSdkVersion you target your application for. The above example describes that it targets 15 <=minSdkVersion< 19. You can check which other Anko libraries are available that you may need on Anko’s GitHub repository.
We are going to be using the following libraries as well:

compile 'org.jetbrains.anko:anko-design:0.8.3'
compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'

Calling Anko Layout in Activity

We are not writing XML layouts anymore, so we don’t need to call XML Views nor use findViewById() method. Suppose our Anko UI class is MainUI, then we can set our activity’s content with MainUI as:

var ui = MainUI()           //MainUI class replaces the XML layout
ui.setContentView(this)     //this refers to the Activity class

Now create a new Kotlin file MainActivity.kt and add the following code to it:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.jetbrains.anko.*;
import java.util.*

class MainActivity : AppCompatActivity() {

    val task_list = ArrayList<String>()         //list consisting of tasks

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        savedInstanceState?.let {
            val arrayList = savedInstanceState.get("ToDoList")
            task_list.addAll(arrayList as List<String>)
        }
        var adapter=TodoAdapter(task_list)      //define adapter
        var ui = MainUI(adapter)                //define Anko UI Layout to be used
        ui.setContentView(this)                 //Set Anko UI to this Activity

    }
    override fun onSaveInstanceState(outState: Bundle?) {
        outState?.putStringArrayList("ToDoList", task_list)
        super.onSaveInstanceState(outState)
    }
}

task_list is the ArrayList which will populate the TodoAdapter of Listview in our to-do app. MainUI(adapter) is our Anko UI file which takes an adapter of TodoAdapter class as argument. So, let’s create the TodoAdapter class next.

Building Adapter for ListView

TodoAdapter class has a member field list of type ArrayList<String> and extends the BaseAdapter. So, we need to override following 4 member functions:

public int getCount()
public Object getItem(int i)
public long getItemId(int i)
public View getView(int i, View view, ViewGroup viewGroup)

In the getView() method we will design the layout of a list item using Anko.

override fun getView(i : Int, v : View?, parent : ViewGroup?) : View {
    return with(parent!!.context) {
        //taskNum will serve as the S.No. of the list starting from 1
        var taskNum: Int = i +1

        //Layout for a list view item
        linearLayout {
            lparams(width = matchParent, height = wrapContent)
            padding = dip(10)
            orientation = HORIZONTAL

            //Task Number
            textView {
                id = R.id.taskNum
                text=""+taskNum
                textSize = 16f
                typeface = Typeface.MONOSPACE
                padding =dip(5)
            }

            //Task Name
            textView {
                id = R.id.taskName
                text=list.get(i)
                textSize = 16f
                typeface = DEFAULT_BOLD
                padding =dip(5)
            }
        }
    }
}
  • In this function, we are returning a view containing a List item which is a horizontalListView layout. This is accomplished using Kotlin’s with syntax, which allows us to call many methods on an object instance at a time.
  • Each List Item contains two textview for displaying task number and task name.
  • linearLayout,textView are extension functions. Extensions give us the power to enable any class with a new functionality.
  • text,textSize,typeface have their getter and setter methods defined in the android.widget.TextView class. padding is an extension property defined in Anko.

Moving ahead, we need to define manipulation functions for the list. So, we have add(String) and delete(Int) functions in the TodoAdapter class. add(String) takes the Task Name to be added as an argument. The position of the item serves as the argument in delete(Int) function as shown below:

//function to add an item to the list
fun add(text: String) {
    list.add(list.size, text)
    notifyDataSetChanged()          //refreshes the underlying dataset
}

//function to delete an item from list
fun delete(i:Int) {
    list.removeAt(i)
    notifyDataSetChanged()          //refreshes the underlying dataset
}

So, now we have designed the list and we can add and delete items to our list as well. This completes the code for this adapter class:

import android.graphics.Typeface
import android.graphics.Typeface.DEFAULT_BOLD
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.LinearLayout.HORIZONTAL
import org.jetbrains.anko.*
import java.util.*

class TodoAdapter(val list: ArrayList<String> = ArrayList<String>()) : BaseAdapter() {
    override fun getView(i : Int, v : View?, parent : ViewGroup?) : View {
        return with(parent!!.context) {
            //taskNum will serve as the S.No. of the list starting from 1
            var taskNum: Int = i +1

            //Layout for a list view item
            linearLayout {
                id = R.id.listItemContainer
                lparams(width = matchParent, height = wrapContent)
                padding = dip(10)
                orientation = HORIZONTAL

                textView {
                    id = R.id.taskNum
                    text=""+taskNum
                    textSize = 16f
                    typeface = Typeface.MONOSPACE
                    padding =dip(5)
                }

                textView {
                    id = R.id.taskName
                    text=list.get(i)
                    textSize = 16f
                    typeface = DEFAULT_BOLD
                    padding =dip(5)
                }
            }
        }
    }

    override fun getItem(position : Int) : String {
        return list[position]
    }

    override fun getCount() : Int {
        return list.size
    }

    override fun getItemId(position : Int) : Long {
        //can be used to return the item's ID column of table
        return 0L
    }

    //function to add an item to the list
    fun add(text: String) {      
        list.add(list.size, text)
        notifyDataSetChanged()
    }

    //function to delete an item from list
    fun delete(i:Int) {
        list.removeAt(i)
        notifyDataSetChanged()
    }

}

Note that we must import org.jetbrains.anko.* to use Anko DSL in our Class files.

Designing To-Do Screen

Anko provides us the convenience of having the UI for the Activity in a separate Kotlin class. Thus, each screen can be thought of as a UI-Activity pair of Kotlin classes. This UI class is developed by extending the capabilities of the AnkoComponent<T> interface defined in org.jetbrains.anko package. Along with this interface, JetBrains offers a DSL layout preview feature for free. This is how Anko DSL Layout Preview looks like in Android Studio:

(Source: blog.jetbrains.com)
DSL Layout Preview

The corresponding plugin for Anko Preview can be downloaded from here. Please note that at the time of writing this article, Anko DSL Preview for Android Studio 2.2 was listed as an open issue.
Coming back to the To-do-App, We’ll now design the MainUI class which hosts the list of all the tasks. MainUI class extends the interface AnkoComponent<T>, where T refers to the owner of the UI i.e. the activity whose content would be this UI. In our case, the owner is MainActivity that we have already defined above. Next, at the time of initialization, we must pass the TodAadapter object to this class as this adapter will be used to populate the list. So, the MainUI class declaration becomes:

class MainUI(val todoAdapter : TodoAdapter) : AnkoComponent<MainActivity>

Now, we need to override the function createView() which will take AnkoContext object as an argument and return a View type:

override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {}

The UI definition that we provide inside the createView() function is returned to the owner activity, which in this case is MainActivity. So, let’s get down coding the createView() method.

Step1- Designing Home screen

To-do Home
Initially, the home screen has an empty list of tasks. So, we have a textView that asks the user to create a Todo List for the day:

    return relativeLayout {
        //declaring the ListView
        var todoList : ListView? =null

        //textView displayed when there is no task
        val hintListView = textView("What's your Todo List for today?") {
            textSize = 20f
        }.lparams {
            centerInParent()
        }
}

centerInParent() is the helper method to define the layout of the view to be relatively center vertically and horizontally.
As it is a todo app, its essence lies in a list displaying the tasks. So, here we define our listView:

//listView
verticalLayout {
    todoList=listView {
    //assign adapter
        adapter = todoAdapter
        }
}.lparams {
        margin = dip(5)
}

todoAdapter is the member variable of MainUI class that we have defined in the class declaration. We initiate the adapter of the listView with the value of todoAdapter which is a TodoAdpater class object and will populate the list.
To help the user to add a task we have provided a floatingActionButton at the bottom right of the home screen following the Material design principles. So, in Anko we program the floatingActionButton as:

floatingActionButton {
            imageResource = android.R.drawable.ic_input_add
        }.lparams {
            //setting button to bottom right of the screen
            margin = dip(10)
            alignParentBottom()
            alignParentEnd()
            alignParentRight()
            gravity = Gravity.BOTTOM or Gravity.END
        }
Step2- Displaying the AddTask alert dialog

Anko provides an easy manner to set onClickListener for View. So, we can add an onClickListener to the floatingActionButton by adding the onClick() method inside it. Let’s create a custom dialog box appearing on click of the floatingActionButton, that will ask the user to enter the task and add it to the list:

floatingActionButton {
            imageResource = android.R.drawable.ic_input_add
            onClick {
                val adapter = todoList?.adapter as TodoAdapter
                alert {
                    customView {
                        verticalLayout {
                        //Dialog Title
                            toolbar {
                                id = R.id.dialog_toolbar
                                lparams(width = matchParent, height = wrapContent)
                                backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent)
                                title = "What's your next milestone?"
                                setTitleTextColor(ContextCompat.getColor(ctx, android.R.color.white))
                            }
                            val task = editText {
                                hint = "To do task "
                                padding = dip(20)
                            }
                            positiveButton("Add") {
                                if(task.text.toString().isEmpty()) {
                                    toast("Oops!! Your task says nothing!")
                                }
                                else {
                                    adapter.add(task.text.toString())
                                    showHideHintListView(todoList!!)
                                }
                            }
                        }
                    }
                }.show()
            }
        }.lparams {
            //setting button to bottom right of the screen
            margin = dip(10)
            alignParentBottom()
            alignParentEnd()
            alignParentRight()
            gravity = Gravity.BOTTOM or Gravity.END
        }
  • alert{} is the inline function to create an Anko dialog box. By default in an Anko dialog box, we can set a text message and provide a postiveButton and negativeButton. We can customize the alert dialog using customView.
  • verticalLayout is a linearLayout with orientation as vertical.
  • We have added the title to the dialog using toolbar, thus customizing it. Note that how do we assign a color to a view in the dialog: backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent)
    Here ctx refers to Context defined in the AlertDialogBuilder class in the package org.jetbrains.anko, that we need to pass as an argument so that we let Android know the context we are referring to.
  • postiveButton() is an Anko Helper method that lets us define what happens when the user submits the dialog. Here we are checking if the task is not empty then we are adding the task to the list adapter using the add method we have defined in TodoAdapter class.
  • What is showHideHintListView(todoList!!)? Well, it’s a method that we have defined to hide the textView hintListView that comes on the home screen so as to make space for our list. When the listView is empty we show the hintListView else we hide it.

    //function to show or hide above textView
    fun showHideHintListView(listView: ListView) {
    if (getTotalListItems(listView)>0) {
    hintListView.visibility = View.GONE
    } else {
    hintListView.visibility = View.VISIBLE
    }
    }

Here the getTotalListItems(listView) is the member method of MainUI class that returns the count of number of items in listView passed. Its a normal Kotlin function:

//function to get total number of items in list
fun getTotalListItems(list: ListView?) = list?.adapter?.count ?: 0

Finally on clicking on floatingActionButton we see the dialog:

Add Task

And once we add a few tasks we can see the list of tasks:

Task List

Step3- Deleting a task

Remember, we have defined the delete(Int) method in TodoAdapter class that deletes an item from the list. Now’s the time to design the UI that will in turn call this
method. Following the Android design patterns, we can present the task options on tap and hold of a task. So, let’s define what happens onLongClick of a list item. Go back to the listView definition and add the following:

onItemLongClick { adapterView, view, i, l ->
val options = listOf("Delete")
    selector("Task Options", options) { j ->
            var task=adapter.getItem(i)
            todoAdapter?.delete(i)
            //check if list is empty then show hint
            showHideHintListView(this@listView)
            longToast("Task ${task} has been deleted")
    }
    true
}
  • Here todoAdapter is the object of TodoAdapter class. Calling the delete method on adapter gives an error saying it might have changed by the time. So, we must call delete method on todoAdapter. Another option is to typecast adapter to TodoAdapter. And the kotlin way to do it is:
    (adapter as TodoAdapter)?.delete(i)
    i refers to the item position that is being clicked.
  • selector is a kind of Anko dialog that gives us option to define a list of clickable items. Here we have taken only one option i.e. Delete. We can provide user with other options to choose from. Below is an example:
verticalLayout {
    todoList=listView {
        adapter = todoAdapter
        onItemLongClick { adapterView, view, i, l ->
            val options = listOf("Completed","In Progress","Not Started","Delete")
            selector("Task Options", options) { j ->
                if (j == 3) {
                    var task=adapter.getItem(i)
                    todoAdapter?.delete(i)
                    showHideHintListView(this@listView)
                    longToast("Task ${task} has been deleted")
                }else{
                    longToast("Task ${adapter.getItem(i).toString()} has been marked as \"${options[j]}\"")
                }
            }
            true
        }
    }
}.lparams {
        margin = dip(5)
}

Database updating, notifying the user, or any other code can be executed in place of the toast to enhance the functionality of the To-do App. This is how selecter dialog looks like on screen:

Task Options

Thus, the complete code for MainUI class is:

import android.support.v4.content.ContextCompat
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import android.widget.ListView
import org.jetbrains.anko.*
import org.jetbrains.anko.appcompat.v7.toolbar
import org.jetbrains.anko.design.floatingActionButton

class MainUI(val todoAdapter: TodoAdapter) : AnkoComponent<MainActivity> {
    override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {
        return relativeLayout {
            var todoList : ListView? =null

            //textView displayed when there is no task
            val hintListView = textView("What's your Todo List for today?") {
                textSize = 20f
            }.lparams {
                centerInParent()
            }

            //function to show or hide above textView
            fun showHideHintListView(listView: ListView) {
                if (getTotalListItems(listView)>0) {
                    hintListView.visibility = View.GONE
                } else {
                    hintListView.visibility = View.VISIBLE
                }
            }

            //layout to display ListView
            verticalLayout {
                todoList=listView {
                    adapter = todoAdapter
                    onItemLongClick { adapterView, view, i, l ->
                        val options = listOf("Completed","In Progress","Not Started","Delete")
                        selector("Task Options", options) { j ->
                            if (j == 3) {
                                var task=adapter.getItem(i)
                                todoAdapter?.delete(i)
                                showHideHintListView(this@listView)
                                longToast("Task ${task} has been deleted")
                            }else{
                                longToast("Task ${adapter.getItem(i).toString()} has been marked as \"${options[j]}\"")
                            }
                        }
                        true
                    }
                }
            }.lparams {
                    margin = dip(5)
            }

            //Add task FloatingActionButton at bottom right
            floatingActionButton {
                imageResource = android.R.drawable.ic_input_add
                onClick {
                    val adapter = todoList?.adapter as TodoAdapter
                    alert {
                        customView {
                            verticalLayout {
                                toolbar {
                                    id = R.id.dialog_toolbar
                                    lparams(width = matchParent, height = wrapContent)
                                    backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent)
                                    title = "What's your next milestone?"
                                    setTitleTextColor(ContextCompat.getColor(ctx, android.R.color.white))
                                }
                                val task = editText {
                                    hint = "To do task "
                                    padding = dip(20)
                                }
                                positiveButton("Add") {
                                    if(task.text.toString().isEmpty()) {
                                        toast("Oops!! Your task says nothing!")
                                    }
                                    else {
                                        adapter.add(task.text.toString())
                                        showHideHintListView(todoList!!)
                                    }
                                }
                            }
                        }
                    }.show()
                }
            }.lparams {
                //setting button to bottom right of the screen
                margin = dip(10)
                alignParentBottom()
                alignParentEnd()
                alignParentRight()
                gravity = Gravity.BOTTOM or Gravity.END
            }
        }.apply {
            layoutParams = FrameLayout.LayoutParams(matchParent, matchParent)
                    .apply {
                        leftMargin = dip(5)
                        rightMargin = dip(5)
                    }
        }

    }

    //function to get total number of items in list
    fun getTotalListItems(list: ListView?) = list?.adapter?.count ?: 0
}

Final Thoughts

We haven’t used any XML layout resource in developing this To-do app, yet we are able to design the app in a similar style. Anko removes the burden of presenting the data, responding to user interaction, connecting to databases, and much more, from activity or fragments in an app. Also, isolating the UI and Activity classes brings the app closer to MVP(Model-View-Presenter) architecture. You can learn about advanced features of Anko from here.
Though it has a few drawbacks like slower compilation and heavy app size, it packs a great punch when it comes to reusing, maintaining and testing the code. Thus, Kotlin-Anko is all set for Android production apps.

Let me know your views about Anko in the comments section.