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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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.

Note: If you haven’t seen the MVP pattern before, you can check out the tutorial Getting Started with MVP on Android.

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:

  1. Show a list of books provided to it.
  2. Show a loading indicator while the app is fetching the book data from the server.
  3. Hide the loading indicator.

As for the presenter, it can:

  1. Display results on any view that it’s provided.
  2. 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:

  1. Implement the BookStoreContract.Presenter interface.
  2. Add a lateinit property to keep a reference to the view.
  3. Implement the attach() method from the BookStoreContract.Presenter interface, and initialize the view property with the received parameter.
  4. Implement the loadBooks() method required by the BookStoreContract.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.

  1. 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.
  2. Create a new XMLHttpRequest instance.
  3. Set this request up so that it sends an HTTP GET to the given URL.
  4. Set a callback which will be invoked when the request completes.
  5. Check if the request is in a done (4) state, and if it has an OK (200) status code.
  6. Call the callback function received as a parameter, and pass it the contents of the network response as a single string.
  7. 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:

  1. Ask the view to show a loading indicator before you start loading the data.
  2. Make the asynchronous request to get the books’ data.
  3. Parse the JSON response received as an array of instances of the Book data class.
  4. Ask the view to hide the loading indicator, since you’ve finished loading and parsing.
  5. 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:

Books in Dev Console

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.

Note: If you try printing the books themselves (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.

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).

Ahmed Tarek

Contributors

Ahmed Tarek

Author

Márton Braun

Tech Editor

Ryan Dube

Editor

Pankaj Kumar

Illustrator

Joe Howard

Final Pass Editor

Eric Soto

Team Lead

Over 300 content creators. Join our team.