Use Kotlin Anko DSL and Say No to Android Layouts Written in XML

We would like to introduce an application that serves as our lunch menu.

Every day at 13:37 we eat lunch together at our cool office, catered by the lovely couple Hanna and Tadeo. The only problem we have with lunch right now is ordering. The menus for the upcoming week are provided in a Google Sheet which we have to fill out before each Friday. It’s inconvenient and requires a lot of time on everyone’s part. We already have an iOS app that gives us access to the lunch menus, so we decided to write a similar one for our Android users.

ss1 (1)ss2

Our Minimal Viable Product (MVP) is dead simple. It contains five tabs with lunch menus for each given day, a list with the number of meals ordered by the logged in user, and the total number of orders for every given meal. We’re using the Google Sheet API as our backend.

We decided to choose Kotlin as our programming language. There is some good stuff out there about learning Kotlin, so we decided to share with you a less documented but still very interesting topic — creating Android layouts in code with the help of the Kotlin Anko library. Our post assumes that you have at least a basic working knowledge of developing Android applications and have at least skimmed through the Kotlin documentation. Reading through this this piece will give you a basic knowledge of creating Android Layouts directly in code using Kotlin Anko.

The Kotlin Anko DSL

Let’s start with the simplest possible example. The Login screen contains only one image which is on top and centered horizontally.

login

This layout can be represented in an Android layout XML file by putting ImageView with the source image into RelativeLayout. Also, we have to set the centerHorizontal attribute and padding. The syntax is a bit verbose due to the XML format and Android layout requirements. We have to define the android name space, width and height for each view, and declare file encoding.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="32dip">

   <ImageView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:src="@drawable/ic_logo"
       android:layout_centerHorizontal="true"/>

</RelativeLayout>

The same layout can be achieved completely in code thanks to Anko’s DSL and Kotlin Extensions. Anko defines the extension methods for each view available in the Android Framework and support libraries.

In our example, RecyclerView maps to the relativeLayout method. Anko extension methods are defined for Activity, Context, and ViewManager. So in the case of Activity, we can call the Anko DSL in any method, but the most appropriate place to do so is in the onCreate method. It is worth mentioning that the width and height layout parameters are optional and set by default to wrap_content, so if this fits our needs then we do not have to declare it or have to declare only one dimension. We are able to define the sizes of layout elements in DIP, SP, or Pixels.

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   relativeLayout {
       padding = dip(32)
       id = R.id.login_main_container

       imageView(R.drawable.ic_logo).lparams {
           centerHorizontally()
       }
   }

   setupPresenter()
}

Extracting the Kotlin Anko Layout to a Separate Class

The previous example mixes view layout with business logic. Fortunately for us, it is fairly easy to extract the Anko layout to a separate class. There is a dedicated interface, AnkoComponent, which contains a single method: createView. Anko layout methods are not available in AnkoComponent, that’s why we have to pass AnkoContext, and we can create a layout by calling methods on this object. We can call multiple methods on the object using the with syntax.

class LoginUI: AnkoComponent<LoginActivity> {
   override fun createView(ui: AnkoContext<LoginActivity>) = with(ui){
       relativeLayout {
           padding = dip(32)
           id = R.id.login_main_container
           backgroundColor = ContextCompat.getColor(ctx, R.color.colorRed)

           imageView(R.drawable.ic_logo).lparams {
               centerHorizontally()
           }
       }
   }
}

The extracted layout can be set to Activity using the setContentView extension method which is defined for AnkoComponent.

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   LoginUI().setContentView(this)
   setupPresenter()
}

Creating RecyclerView list with Kotlin Anko

Let’s move on to more advanced subjects and take a closer look at implementing simple lists with the help of RecyclerView.

recycler

We’ll be using ViewPager with FragmentPagerAdapter that creates a fragment for each tab. In LunchMenuFragment, we define the layout with a single RecyclerView and LunchMenuAdapter. In order to create the layout in Fragment, we have to pass AnkoContext to LunchMenuUI.

class LunchMenuFragment : Fragment(), LunchMenuView, AnkoLogger {

   val listAdapter: LunchMenuAdapter = LunchMenuAdapter(emptyList<Meal>())

   override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
       lunchMenuPresenter = LaunchMenuPresenter(createMealService(), this, arguments.getInt(ARG_SECTION_NUMBER))
       lunchMenuPresenter.createView()
       return LunchMenuUI(listAdapter).createView(AnkoContext.create(ctx))
   }
   ...
}

LunchMenuUI contains only one view, that is RecyclerView. Both the layout manager and adapter can be defined directly in the layout code, which is very convenient.

class LunchMenuUI(val listAdapter: LunchMenuAdapter) : AnkoComponent<Fragment> {
   override fun createView(ui: AnkoContext<Fragment>): View {
       return with(ui) {
           frameLayout() {
               lparams(width = matchParent, height = matchParent)
               recyclerView {
                   id = R.id.lunch_menu_list
                   lparams(width = matchParent, height = matchParent)
                   layoutManager = LinearLayoutManager(ctx)
                   adapter = listAdapter
               }
           }
       }
   }
}

LunchMenuAdapter is very simple, it only instantiates views and bind data to them. In order to create a view in Adapter, we have to create AnkoContext and pass it to the createView method with the layout definition.

class LunchMenuAdapter(var mealList: List<Meal>) : RecyclerView.Adapter<LaunchMenuItemViewHolder>() {
   override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): LaunchMenuItemViewHolder? {
       return LaunchMenuItemViewHolder(LunchMenuItemUI().createView(AnkoContext.create(parent!!.context)))
   }

   override fun onBindViewHolder(holder: LaunchMenuItemViewHolder?, position: Int) {
       val meal = mealList[position]
       holder!!.bind(meal)
   }

   override fun getItemCount(): Int {
       return mealList.size
   }
}

The view for each item is also defined using the Anko layout DSL. It contains two TextViews and one ImageView, which are laid out using the horizontal LinearLayout. In the beginning we set up horizontal padding to 16 DIP, using the helper method horizontalPadding, unavailable in XML layouts, makes it very convenient.

The first TextView with the id lunch_menu_name is responsible for displaying meal names, we set its attributes in a way very similar to the one used in XML layouts. View-specific attributes are defined inside of its block, and layout-related attributes are set inside the lparams block; in this case we set the layout weight to 1 and the layout gravity to center vertical.

The next ImageView displays the lock icon with a size of 20×20 DIP and a resource drawable with id ic_lock.

The last TextView with the id lunch_menu_count displays the meal counter and has got a bigger font size of 18 SP. Font size is defined as a float value and by default has the unit set to SP; naturally, we can change the unit by passing it as a first argument to textSize.

class LunchMenuItemUI : AnkoComponent<ViewGroup> {
   override fun createView(ui: AnkoContext<ViewGroup>): View {
       return with(ui) {

           linearLayout {
               lparams(width = matchParent, height = dip(48))
               orientation = LinearLayout.HORIZONTAL
               horizontalPadding = dip(16)

               textView {
                   lparams {
                       gravity = Gravity.CENTER_VERTICAL
                       weight = 1.0f
                   }
                   id = R.id.lunch_menu_name
                   singleLine = true
                   ellipsize = TextUtils.TruncateAt.END
                   textSize = 16f
               }

               imageView {
                   lparams(width = dip(20), height = dip(20)) {
                       gravity = Gravity.CENTER_VERTICAL
                       marginStart = dip(8)
                   }
                   imageResource = R.drawable.ic_lock
               }

               textView {
                   lparams(width = dip(40)) {
                       gravity = Gravity.CENTER_VERTICAL
                       marginStart = dip(8)
                   }
                   id = R.id.lunch_menu_count
                   textSize = 18f
                   gravity = Gravity.CENTER_HORIZONTAL
               }
           }
       }
   }
}

Now, all layout-related stuff is done and we can bind item views using RecyclerView.ViewHolder. Holder has references to text views responsible for displaying the meal name and the meal counter. To find text views in item view, we’ll be using the Anko find method which is a more concise equivalent of findViewById.

class LaunchMenuItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
   val name: TextView = itemView.find(R.id.lunch_menu_name)
   val count: TextView = itemView.find(R.id.lunch_menu_count)

   fun bind(meal: Meal) {
       name.text = meal.name
       count.text = meal.count.toString() + "/" + meal.totalCount
   }
}

Styling and Theming Kotlin Anko Layouts

The Android SDK allows developers to easily style all views with a common theme, like Theme.AppCompat.Light.DarkActionBar. Let’s look at the example of the Toolbar used in our application. We define the theme for an activity in styles.xml. Typically, the layout with the Toolbar in XML would look like this:

<?xml version="1.0" encoding="utf-8"?>
...

<android.support.v7.widget.Toolbar
    android:id="@+id/lunch_toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize">

...

The result of this code would be the following themed Toolbar:

Screen Shot 2016-05-25 at 10.55.39

We can define an identical layout in Anko DSL like this:

...
toolbar {
   id = R.id.lunch_toolbar
   lparams (width = matchParent, height = wrapContent)
}
...

But the result is far from what we would expect:

Screen Shot 2016-05-25 at 11.13.45

Because Anko unfortunately doesn’t support theming yet (as of version 0.8.3), the next thing we’ll try is manually adding the background color and using a style extension function.

...
toolbar {
   id = R.id.lunch_toolbar
   lparams (width = matchParent, height = wrapContent)
   backgroundColor = ContextCompat.getColor(ctx, R.color.colorRed)

}.style { view ->
   when (view) {
       is TextView -> view.textColor = Color.WHITE
   }
}
...

The background is now set, but the Toolbar title (which is a TextView) color is still black.

Screen Shot 2016-05-25 at 10.56.28

After debugging the style function, it turns out that during execution all fields of Toolbar are null. That is because Toolbar initialize its content Views in a lazy way. We have two options. Treat Toolbar as any other container and set our own TextView inside of it.

...
toolbar {
   id = R.id.lunch_toolbar
   lparams (width = matchParent, height = wrapContent)
   backgroundColor = ContextCompat.getColor(ctx, R.color.colorRed)
   textView {
       text = "Lunch"
   }
}.style { view ->
   when (view) {
       is TextView -> view.textColor = Color.WHITE
   }
}
...

The second option is using the Toolbar setTitleTextColor method because it is a bit less verbose.

...
toolbar {
   id = R.id.lunch_toolbar
   lparams (width = matchParent, height = wrapContent)
   backgroundColor = ContextCompat.getColor(ctx, R.color.colorRed)
   setNavigationIcon(R.drawable.ic_nav)
   setTitleTextColor(ContextCompat.getColor(ctx, android.R.color.white))
}
...

This finally gives us the styling we wanted to achieve.

Screen Shot 2016-05-25 at 11.15.30

The style function is very useful as it allows to quickly set a common style for all the children of ViewGroup. But when using more complex widgets, like Toolbar, it fails, forcing us to set each attribute manually. It turns out, however, that the Theming functionality in Kotlin is currently in testing and should be available fairly soon.

Conclusion

Writing Android layouts in code instead of XML is a very promising idea. It was theoretically possible from day one, but Kotlin Anko moved it to a whole new level. It removes all of the unnecessary boilerplate code and makes writing UI a pleasant experience.

Moreover, at this moment the library works pretty well, although it still needs some work done, especially in areas like theming and documentation. We also weren’t able to work with the layout Preview directly in Android Studio, because the plugin kept crashing. We are waiting for the final 1.0 version and will instantly switch to layouts in code when the library is finally released. Hopefully, it will happen sooner rather than later, but seeing that the project is actively maintained gives us hope that it’s the former. We only wish we had had a tool this cool since the start of the Android platform.

You can find the application presented in this article on our GitHub.

Got inspired? E-mail us and we’ll get in touch to find out how our design and development services can drive business value for you.

Related Resources

https://medium.com/@BhaskerShrestha/kotlin-and-anko-for-your-android-1c11054dd255 https://realm.io/news/getting-started-with-kotlin-and-anko/ http://kotlinlang.org/docs/reference/basic-syntax.html https://kotlinlang.org/docs/tutorials/koans.html http://fragmentedpodcast.com/episodes/20/

Drawing a GCB Watch Face with Android Wear
Using the Zephyr Add-on in Quality Assurance