Часть 1. TMA на KMP. Пишем кликер на Kotlin/JS

от автора

В этой статье рассмотрим старт проекта как обычное веб-приложение с минимальным функционалом. Остальные функции будут завязаны на Telegram API и веб-приложение сможет запускаться из Telegram.

Навигация по циклу статей

Часть 1. Пишем веб-приложение кликер на Kotlin – текущая статья

Раскрытые темы в цикле

  • Web приложение на Kotlin – часть 1

  • Интеграция приложения с Telegram Mini Apps – часть 2

  • Работа с элементами интерфейса TMA приложения. Тема, MainButtonBackButton – часть 2

  • Поделиться ссылкой на приложение через Telegram. Передача данных через ссылку – часть 2

  • Аутентификации через TMA приложение – часть 2 и 2.5

  • Telegram Payments API– часть 3

Техническое задание. Кратко

Разработать Telegram Mini Apps приложение-кликер с основными механиками:

  • Тапать на коин и увеличивать счётчик

  • Сохранять количество тапов для каждого пользователя

  • Приглашать друзей через ссылку из приложения

  • Просматривать список друзей.

  • Оплата премиум статуса через Telegram

  • В будущем перенести на Android/IOS

Зачем нужен Kotlin в Web. Ещё один .js фреймворк?

Kotlin Multiplaform анонсирован ещё в 2017 с поддержкой JVM, Native и JS таргетов. И именно JS тартет наиболее недооценён как самой JetBrains, так и компаниями, применяющими в разработке язык Kotlin.

Только в середине 2023 года JetBrains заявила о смене аббревиатуры с KMM (Kotlin Multiplatform Mobile) на KMP (Kotlin Multiplatform), что говорит о перспективах развития всех тартетов.

Зачем бизнесу нужен Kotlin Multiplatform, тем более в веб-разработке? В мобильные приложения уже давно интегрируют общий код между платформами, однако JS таргет, хоть и представлен давно, не все разработчики видят в его использовании смысл. Однако причина писать веб-приложения на Kotlin такая же как и для мобильных платформ – это шаринг кода между платформами. Уже имея приложение на KMP под две платформы – Android и iOS – можно третьей добавить браузер и разработать веб приложение, не переписывая бизнес логику, хотя конечно же доступа к системе у веб-приложений меньше, из-за чего придётся уменьшить количество поддерживаемых фичей.

Kotlin/JS тартет способен компилироваться в JavaScript код, вызывать методы из JS модулей или самим быть вызываем, что значительно расширяет возможности приложений на Kotlin/JS и даёт возможность писать на Kotlin с кодовой базовой уже существующих JS приложений.

Главная трудность – это написание UI. Сейчас есть выбор между четырьмя реализациями

  • Написать UI на HTML и CSS. И работать с DOM деревом как в старые добрые – получать элемент по id и изменять его

  • Использовать React in Kotlin (  kotlin-wrappers/kotlin-rea…  @GitHub) из модуля kotlin-wrappers. Однако такой подход выглядит неестественным для языка Kotlin. Повсеместное использование external для props, неоднородные стили с CSS in Kotlin

  • Использовать Compose for Web (пока статус Alpha) из Compose Multiplatform. Можно компилировать под WASM (который использует wasm gc, поддержки которого пока нет в Safari/WebKit) и JS, где уже встречаются баги взаимодействия со скроллом и нестабильной работой.

  • Наиболее перспективный вариант Compose HTML library.

Выбор на чем писать UI

Подробнее о Compose HTML library. Код компилируется в JS и использует compose runtime и html теги с css in kotlin стилями, по идеологии схож с JSX.

Доступен базовый функционал compose runtime: Composable аннотация, Side-effects, states и д.р. из пакета compose-runtime.

Не доступны другие пакеты, как compose-ui, compose-material, из-за чего нельзя использовать привычные ColumnRowBoxScaffoldButton (composable ui функции), MaterialTheme и д.р.

Для написания UI используются функции DivButtonSpanTextArea и д.р. браузерные теги, только вызываемые как функции с заглавной буквы. Стили применяются через СSS in Kotlin через отдельный StyleSheet или блок style в attrs любого тега.

Старт проекта

Для упрощения старта можно использовать шаблон, однако ещё разберём особенности настройки.

Файловая структура – типичная для проекта для kotlin multiplatform.

В build.gradle.kts composeApp модуля подключим плагины (также нужно объявить в корневом модуле 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 } 

Зададим JS таргет и зависимости в блоке kotlin{} модуля composeApp

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

Создаём директории для наших исходников commonMain и jsMain

В resources jsMain нужно создать 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> 

Далее определим точку входа в приложение. Создаём main.kt с функцией main, которая и будет точкой входа в наше приложение

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

Напишем простой экран, который будет сообщать, что это веб-приложение, написанное на Kotlin

Важно: UI находится в jsMain, поскольку это нативный для браузера способа отображения

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

Вызовем функцию с нашим приложением в блоке renderComposable

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

Первоначальная настройка закончена, теперь можно запустить приложение и проверить, что всё работает как надо

./gradlew composeApp:jsRun 

Но страница выглядит просто как текст, Compose HTML library может инжектить в браузер свои стили, которые пишутся на Kotlin. Добавим таблицу стилей в наше приложение

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

Важно, что каждый StyleSheet должен быть явно определён, именно в этот момент он инжектится в браузерную страницу. Применяем таблицу стилей.

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

Далее через поля из AppStyles применим стиль к нашему Div.

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

Запускаем ещё раз и наблюдаем, как наши стили применились и приложение выглядит, как нам нужно.

Добавляем ресурсы

Теперь переходим к созданию полноценного веб-приложения. Каждый этап описан не будет, однако основные моменты будут описаны. Некоторые из доступных, хоть и ограниченных, возможностей Compose runtime и Compose HTML library, при работе с KMP

Для этого используем библиотеку moko-resources. Подключается вместе с gradle плагином в build.gradle.kts модуля 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") } 

Добавим нашу картинку, по которой будем тапать в commonMain модуль.

И получим url до картинки через сгенерированный класс Res и поле Res.images.click_item.

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

Создаём UI нашего кликера

Поскольку это кликер, обязательно нужно добавить счётчик нажатий на наш «подставить на кого будем кликать».

Создадим состояние, значение которого будем увеличивать по клику на картинку.

@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++                 }             }         )     } } 

Итоги

В данной статье мы научились разрабатывать веб-приложения на довольно нестандартном стеке.

Список используемых библиотек на данный момент:

  • Compose Runtime

  • Moko-resources

Дальше – больше, в следующих статьях это просто веб-приложение с одной кнопкой станет полноценным Telegram-кликером со своей реферальной системой.


ссылка на оригинал статьи https://habr.com/ru/articles/830120/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *