Part 1. TMA on KMP. Writing a clicker in Kotlin

In this article, I will consider the project launch as a regular web application with minimal functionality. The remaining functions will be tied to the Telegram API and the web application will be able to be launched from Telegram.

Navigate through the article series:

Part 1. Writing a clicker web app in Kotlin – current article
Part 2. Writing a clicker for Telegram in Kotlin – in development
Part 2.5. User authentication with DRF – in development
Part 3. Adding payment via Telegram Mini Apps on Kotlin – in development

Topics covered in the series

  • Web Application in Kotlin – Part 1

  • Integrating the App with Telegram Mini Apps – Part 2

  • Working with the interface elements of the TMA application. Theme, MainButton, BackButton – part 2

  • Share a link to an application via Telegram. Transferring data via a link – part 2

  • Authentication via TMA application – part 2 and 2.5

  • Telegram Payments API – Part 3

Technical task. Briefly

Develop Telegram Mini Apps – a clicker app with the main mechanics:

  • Tap on the coin and increase the counter

  • Save the number of taps for each user

  • Invite friends via a link from the app

  • View your friends list.

  • Payment for premium status via Telegram

  • In the future, we will transfer it to Android/IOS

Why Kotlin is needed in Web. Another .js framework?

Kotlin Multiplaform announced back in 2017 with support for JVM, Native and JS targets. And it is the JS target that is most underestimated by both JetBrains itself and companies using the Kotlin language in development.

Only in mid-2023, JetBrains announced the change of the abbreviation from KMM (Kotlin Multiplatform Mobile) to KMP (Kotlin Multiplatform), which speaks about the development prospects of all platforms.

Why does a business need Kotlin Multiplatform, especially in web development? Mobile applications have long been integrating common code between platforms, but JS target, although it has been presented for a long time, not all developers see the point in using it. However, the reason to write web applications in Kotlin is the same as for mobile platforms – code sharing between platforms. Already having an application on KMP for two platforms – Android and iOS – you can add a third browser and develop a web application without rewriting the business logic, although of course web applications have less access to the system, which is why you will have to reduce the number of supported features.

The Kotlin/JS target is capable of compiling into JavaScript code, calling methods from JS modules or being called itself, which significantly expands the capabilities of Kotlin/JS applications and makes it possible to write in Kotlin with the code base of existing JS applications.

The main difficulty is writing the UI. There are currently four implementations to choose from:

  • Write UI in HTML and CSS. And work with DOM tree as in good old days – get element by id and change it.

  • Use React in Kotlin ( kotlin-wrappers/kotlin-rea… @GitHub) from the kotlin-wrappers module. However, this approach seems unnatural for the Kotlin language. The widespread use of external for props, inconsistent styles with CSS in Kotlin

  • Use Compose for Web (currently in Alpha status) from Compose Multiplatform. Can be compiled for WASM (which uses wasm gc, which is not yet supported in Safari/WebKit) and JS, where there are already bugs in interaction with scrolling and unstable operation.

  • The most promising option Compose HTML library.

Choosing what to write UI in

More about Compose HTML library. The code is compiled to JS and uses compose runtime and html tags with css in kotlin styles, similar in ideology to JSX.

Available basic functionality of compose runtime: Composable annotation, Side-effects, states, etc. from the compose-runtime package.

Not available other packages like compose-ui, compose-material, which is why you can't use the usual ones Column, Row, Box, Scaffold, Button (composable ui functions), MaterialTheme and etc.

Functions are used to write UI Div, Button, Span, TextArea and other browser tags, only called as functions with a capital letter. Styles are applied via CSS in Kotlin via a separate StyleSheet or block style V attrs any tag.

Project start

To make the start easier, you can use samplebut we will still look at the settings features.

The file structure is typical for a project for kotlin multiplatform.

IN build.gradle.kts composeApp module we connect plugins (also need to declare in the root module build.gradle.kts)

plugins {
    id("org.jetbrains.kotlin.multiplatform")
    id("org.jetbrains.kotlin.plugin.compose") // компилятор, необходимый для сборки модуля с compose на Kotlin 2.X
    id("org.jetbrains.compose") // работа с compose multiplatform
}

Let's set the JS target and dependencies in the block kotlin{} module composeApp

kotlin {
    js(IR) {
        browser()
        binaries.executable()
    }
    sourceSets {
        jsMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.html.core)
        }
    }
}

We create directories for our sources commonMain And jsMain

In resources jsMain need to create index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>EllowBurgerBot</title>
    <script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body id="app" class="app">
<script src="app.js"> </script>
</body>
</html>

Next, we will define the entry point to the application. Create main.kt with function mainwhich will be the entry point to our application

fun main() {
    renderComposable(rootElementId = "app") {
       /* Весь интерфейс здесь */
    }
}

Let's write a simple screen that will tell you that this is a web application written in Kotlin

Important: UI is in jsMainsince this is the browser's native display method.

@Composable
fun App() {
    Div {
        H2 {
            Text("This is my Kotlin web application")
        }
    }
}

Let's call the function with our application in the block renderComposable

renderComposable(rootElementId = "app") {
    App()
}

The initial setup is complete, now you can launch the application and check that everything works as it should.

./gradlew composeApp:jsRun

But the page looks like just text, Compose HTML library can inject its styles, which are written in Kotlin, into the browser. Let's add a style sheet to our application

object AppStyles: StyleSheet() {
    val MainContainer by style {
        display(DisplayStyle.Flex)
        width(100.vw)
        height(100.vh)
        textAlign("center")
        alignItems(AlignItems.Center)
        justifyContent(JustifyContent.Center)
    }
}

It is important that everyone StyleSheet must be explicitly defined, it is at this point that it is injected into the browser page. We apply the style sheet.

renderComposable(rootElementId = "app") {
    Style(AppStyles)
    App()
}

Further through the fields from AppStyles let's apply the style to ours Div.

@Composable
fun App() {
    Div(
        attrs = {
            classes(AppStyles.MainContainer)
        }
    ) {
        H2 {
            Text("This is my Kotlin web application")
        }
    }
}

Let's run it again and see how our styles have been applied and the application looks the way we want it to.

Adding resources

Now we move on to creating a full-fledged web application. Each stage will not be described, but the main points will be described. Some of the available, albeit limited, capabilities of the Compose runtime and Compose HTML library, when working with KMP

For this we use the library moko-resources. Connected together with the gradle plugin in build.gradle.kts module composeApp

plugins {
    // ...
    id("dev.icerock.mobile.multiplatform-resources")
}
kotlin {
//...
sourceSets {
commonMain.dependencies {
implementation("dev.icerock.moko:resources:0.24.1")
}
//...
}
}
multiplatformResources {
resourcesPackage.set("your.package.name")
resourcesClassName.set("Res")
}

Let's add our picture, which we will tap on commonMain module.

And we get url to the image via the generated class Res and the field Res.images.click_item.

@Composable
fun App() {
Div(
attrs = {
classes(AppStyles.MainContainer)
}
) {
Img(
src = Res.images.click_item.fileUrl,
attrs = {
classes(AppStyles.MainImage) // стиль с указанием размера картинки
}
)
}
}

Let's create the UI of our clicker

Since this is a clicker, we definitely need to add a click counter to our “insert who we will click on”.

Let's create a state, the value of which will be increased by clicking on the picture.

@Composable
fun App() {
var score by remember { mutableStateOf(0) }
Div(
attrs = {
classes(AppStyles.MainContainer)
}
) {
H2 {
Text("Score: $score")
}
Img(
src = Res.images.click_item.fileUrl,
attrs = {
classes(AppStyles.ClickImage)
onClick {
score++
}
}
)
}
}

Results

In this article, we learned how to develop web applications on a rather non-standard stack.

List of libraries currently used:

  • Compose Runtime

  • Moko-resources

There's more to come, in the following articles this simple web application with one button will become a full-fledged Telegram clicker with its own referral system.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *