Kunafa: Easy to use, high level framework in Kotlin for front-end web-development

Islam Abdalla
The Kotlin Chronicle
5 min readMar 15, 2019

Here at Narbase we use Kunafa for all of our front-end web development. Since its first release in February 2018, Kunafa has evolved to become very flexible and extremely fun to use. And now, Kunafa 0.2.x has finally been released. There are many changes to the the 0.1.x releases. Here are the main changes. But before we go into details, you can join us at Slack by clicking this link.

Styles

Kunafa 0.2.0 has revamped the styles and styles sheets. As the philosophy of Kunafa is to make web development easy, and allow developers to do everything with Kotlin, kunafa now allows you to use almost all of CSS directly in Kotlin.
First, you can add a style to any view simply by balling the `style { }` function. For example:

textView {
text = "Failed to connect to the internet"
style {
width = 290.px
height = wrapContent
textAlign = TextAlign.Center
backgroundColor = Color.white
borderRadius = 4.px.toString()
border = "1px solid #d4d4d4"
}
}

The `style { }` function creates on-the-fly a new CSS class rule set and adds it to the `textView` view above. To reuse the same style for multiple views, you can use the following syntax.

val errorStyle = classRuleSet {
width = 290.px
height = wrapContent
textAlign = TextAlign.Center
backgroundColor = Color.white
borderRadius = 4.px.toString()
border = "1px solid #d4d4d4"
}

and then to add it to a view:

textView {
text = "Failed to connect to the internet"
addRuleSet(errorStyle)
}

The `classRuleSet()` function creates a ruleset with a CSS class selector. If you want to create a custom css selector you can use

val customSelectorRuleSet = stringRuleSet("ul #id") {
color = Color("323243")
}

And then add it to a view the same way as above.

Components

Kunafa has two concepts, views and component. The view is the basic building block of the UI. It is basically a wrapper around HTML DOM elements. Every view contains an HTML element, but adds many functionalities around it. It has helper functions for easy ui creation. Unless you want to add new functionality to the core of Kunafa, you shouldn’t extend the view classes. To build complicated UI blocks, you can use the views DSL or Components.

For example, this a reusable ui block of user details row with an image and a name:

fun View.userDetailsView(imageUrl: String, userFullName: String) {
horizontalLayout {
imageView {
element.src = imageUrl
style {
width = 50.px
height = 50.px
}
}
textView {
text = userFullName
}
}
}

and then it can be called like this:

page {
verticalLayout {
userDetailsView("/image/1.png", "First user name")
userDetailsView("/image/2.png", "Second user name")
userDetailsView("/image/3.png", "Third user name")
}
}

So what are components? The view itself is what appears on the screen. But how do you listen to its lifecycle events? Enter components. Components are wrappers around a view to make it easier to listen to the view lifecycle events, and to make it easy to have a detached view that is not yet added to the DOM. For example, if we wan’t to wrap `userDetailsView` inside a components, it will be like this:

class UserDetailsView(val imageUrl: String, val userFullName: String) : Component() {
override fun View?.getView(): View {
return horizontalLayout {
style {
width = matchParent
}
imageView {
element.src = imageUrl
style {
width = 50.px
height = 50.px
}
}
textView {
text = userFullName
style {
width = weightOf(1)
}
}
}
}
}

The function `View?.getView()` is the only function that needs to be implemented, and it should return only one view.

Listening to view lifecycle

Now that we have a component, we can listen to the view lifecycle events:

class UserDetailsView(val imageUrl: String, val userFullName: String) : Component() {
override fun onViewMounted(lifecycleOwner: LifecycleOwner) {
console.log("View mounted")
}
override fun onViewRemoved(lifecycleOwner: LifecycleOwner) {
console.log("View removed")
}
override fun View?.getView(): View {
return horizontalLayout {
style {
width = matchParent
}
imageView {
element.src = imageUrl
style {
width = 50.px
height = 50.px
}
}
textView {
text = userFullName
style {
width = weightOf(1)
}
}
}
}
}

The view has four lifecycle events: `viewWillMount`, `onViewMounted`, `viewWillBeRemoved`, and `onViewRemoved`

Routing

Routing should be easy and intuitive. In Kunafa it feels very natural. To specify that a certain view should be mounted at a certain path, simply wrap it with a `route()` function. For example:

page {
verticalLayout {
link("/") { text = "Home" }
link("/about") { text = "About" }
route("/", isExact = true) {
textView {
text = "Home page"
}
}
route("/about") {
textView {
text = "About page"
}
}
}
}

The `link` is a wrapper around the anchor `a` view. The view inside the `route` function will be mounted when the browser path matches the path given to the route. Notice the `isExact` in the home route so that the home page will only be matched when the browser path is exactly `/`.

Nested routes

You can have nested routes be calling the `route` function inside another `route` function. Fo example to have a `/about/contact` and `/about/team` routes, it can be as follows:

route("/about") {
verticalLayout {
textView {
text = "About page"
}
route("/contact") {
textView {
text = "Contact us"
}
}
route("/team") {
textView {
text = "Our awesome team"
}
}
}
}

Notice that we don’t need to pass the `/about` in the nested routes. as the route path will be relative to its parent route. But what if we need it to be absoulte? Simple, set the `isAbsolute` flag to true as follows.

route("/about/team", isAbsolute = true) {
textView {
text = "Our awesome team"
}
}

There are more to routing, such as path parameters and getting parent route path. But that will be in a detailed tutorial.

Conclusion

So that’s Kunafa 0.2.0. We like the way it has evolved until now, and we find it very enjoyable to work with. We will keep on writing the documentation for it, and will try to publish complete tutorials and example to make it easier to get started with Kunafa.
We would really like to hear from you. Give it a try and let us know what you think. Also, don’t forget to join us at Slack here, and leave us a star in Github.

--

--