Web App With Kotlin.js: Getting Started
In this tutorial, you’ll learn how to create a web app using Kotlin.js. This will include manipulating the DOM and fetching data from a server, all in Kotlin! By Ahmed Tarek.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Web App With Kotlin.js: Getting Started
25 mins
- Why Kotlin.js?
- Getting Started
- Creating the Main Function
- Calling JavaScript Code From Kotlin
- Representing Books
- Architecting the App
- Fetching Data From the Server
- Building the UI
- Changing the Loader Visibility
- Building Book Elements
- Binding the Data
- Applying CSS
- Creating Cards
- Showing the Book Store Page
- Where to Go from Here?
Architecting the App
You’ll use a basic MVP architecture in this app. A presenter class will contain all of the business logic, while a page class will act as the view. Before you create these classes, you’ll create the contract between them.
Create a new Kotlin interface called BookStoreContract
(as usual, in its own file in the src folder) which defines the connection between the view and the presenter. Add the following code to it:
interface BookStoreContract {
interface View {
fun showBooks(books: List<Book>) // 1
fun showLoader() // 2
fun hideLoader() // 3
}
interface Presenter {
fun attach(view: View) // 4
fun loadBooks() // 5
}
}
The view will be able to:
- Show a list of books provided to it.
- Show a loading indicator while the app is fetching the book data from the server.
- Hide the loading indicator.
As for the presenter, it can:
- Display results on any view that it’s provided.
- Start loading the book data from the data source. In this case, that’s a remote server.
With that done, you can now create a BookStorePage
class, and add the following code:
class BookStorePage(private val presenter: BookStoreContract.Presenter) : BookStoreContract.View {
override fun showBooks(books: List<Book>) {
}
override fun showLoader() {
}
override fun hideLoader() {
}
}
This class has a constructor with a BookStoreContract.Presenter
parameter. It implements the BookStoreContract.View
interface with three required methods (empty, for now).
Create a BookStorePresenter
class and add the following code:
// 1
class BookStorePresenter : BookStoreContract.Presenter {
// 2
private lateinit var view: BookStoreContract.View
// 3
override fun attach(view: BookStoreContract.View) {
this.view = view
}
// 4
override fun loadBooks() {
}
}
In this class, you:
- Implement the
BookStoreContract.Presenter
interface. - Add a
lateinit
property to keep a reference to the view. - Implement the
attach()
method from theBookStoreContract.Presenter
interface, and initialize theview
property with the received parameter. - Implement the
loadBooks()
method required by theBookStoreContract.Presenter
interface (empty, for now).
Fetching Data From the Server
You need a way to fetch data from the server. To do this, add the following method to the BookStorePresenter
class.
// 1
private fun getAsync(url: String, callback: (String) -> Unit) {
// 2
val xmlHttp = XMLHttpRequest()
// 3
xmlHttp.open("GET", url)
// 4
xmlHttp.onload = {
// 5
if (xmlHttp.readyState == 4.toShort() && xmlHttp.status == 200.toShort()) {
// 6
callback.invoke(xmlHttp.responseText)
}
}
// 7
xmlHttp.send()
}
Hit option+return on Mac or Alt+Enter on PC to add in an import for the XMLHttpRequest
class.
Let’s go over what you’re doing here, step-by-step.
- Create a new method that makes a network request. It takes a URL to fetch from, as well as a function with a
String
parameter, which it will pass the result of the network call to. - Create a new
XMLHttpRequest
instance. - Set this request up so that it sends an HTTP GET to the given URL.
- Set a callback which will be invoked when the request completes.
- Check if the request is in a done (4) state, and if it has an OK (200) status code.
- Call the
callback
function received as a parameter, and pass it the contents of the network response as a single string. - Invoke
send()
to fire off the HTTP request you’ve set up.
With that done, you can now use this helper method to implement loadBooks()
:
override fun loadBooks() {
//1
view.showLoader()
//2
getAsync(API_URL) { response ->
//3
val books = JSON.parse<Array<Book>>(response)
//4
view.hideLoader()
//5
view.showBooks(books.toList())
}
}
In this code, you:
- Ask the view to show a loading indicator before you start loading the data.
- Make the asynchronous request to get the books’ data.
- Parse the JSON response received as an array of instances of the
Book
data class. - Ask the view to hide the loading indicator, since you’ve finished loading and parsing.
- Ask the view to show the list of books.
You can iterate the books
array and print each book.title
to the console to be sure that everything works correctly. To do this, add the following lines of code after the books have been parsed:
books.forEach { book ->
println(book.title)
}
In order to test out the presenter code, update the main()
function to read:
fun main(args: Array<String>) {
val bookStorePresenter = BookStorePresenter()
val bookStorePage = BookStorePage(bookStorePresenter)
bookStorePresenter.attach(bookStorePage)
bookStorePresenter.loadBooks()
}
Here, you create a new instance of BookStorePresenter
, and then an instance of BookStorePage
, passing the page the presenter instance via its constructor. You then attach the page to the presenter and call loadBooks()
on the presenter directly.
Build and run the project and refresh index.html. You should see a log like the following screenshot:
When done with testing, remove the forEach
loop with the print statement inside loadBooks()
.
This means you’ll be able to read its properties, but any methods you’d expect the class to have will be missing – including the auto-generated toString()
implementation. If you need a more robust parsing solution that will do this correctly, you can take a look at the kotlinx.serialization library.
println(book)
), it’s normal to just see [object Object]
repeated over and over in the output. This is because the JSON.parse
call constructs pure JavaScript objects instead of calling the constructor of the Kotlin Book
class.
This means you’ll be able to read its properties, but any methods you’d expect the class to have will be missing – including the auto-generated toString()
implementation. If you need a more robust parsing solution that will do this correctly, you can take a look at the kotlinx.serialization library.
Building the UI
The index.html file contains two <div>
tags with IDs, namely "loader"
and "content"
. The former is a loading indicator that you can show while your app is loading data, and hide when it’s done loading. The latter one is a container that all of the book cards will be added to.
To access these DOM elements in your Kotlin code, add two new properties to the BookStorePage
class as shown below.
private val loader = document.getElementById("loader") as HTMLDivElement
private val content = document.getElementById("content") as HTMLDivElement
You can always get an element in the DOM by its ID, using the document
object and the getElementById()
method, just like you would in JavaScript.
The getElementById()
method returns a generic Element
, which you can cast to the more specific element type if you need (similar to how the findViewById()
method used to work on Android).