BUILDING PROGRESSIVE WEB APPS IN KOTLIN Erik Hellman @ErikHellman Copenhagen Denmark
Actual Cross Platform!
Types - They’re pretty great!
Type definitions for JavaScript DOM APIs lib.dom.d.ts
import { LitElement, html, property, customElement } from 'lit-element'; @customElement('simple-greeting') export class SimpleGreeting extends LitElement { @property() name = 'World'; render() { return html`<p>Hello, ${this.name}!*/p>`; } }
class SimpleGreeting : LitElement() { private var name: String = "World" override fun render(): dynamic { return "<p>Hello, $name!*/p>" } companion object { val properties = json("name" to String*:class) } }
JavaScript can be weird... function javaScriptIsWeird(wantNumber) { if (wantNumber) { return 42 } else { return "Here is some text" } }
TypeScript can also be weird! :) function typeScriptExample(wantNumber: boolean): number | string { if (wantNumber) { return 42 } else { return "Here is some text" } }
“It’s complicated…”
Kotlin/JS
Kotlin/JS - build.gradle.kts plugins { id("org.jetbrains.kotlin.js") version "1.3.61" } group = "se.hellsoft" version = "1.0-SNAPSHOT" repositories { mavenCentral() jcenter() } kotlin { target { nodejs() browser() } sourceSets["main"].dependencies { implementation(kotlin("stdlib-js")) } }
Kotlin/JS - Main.kt import kotlin.browser. window val document = window .document fun main() { val button = document .querySelector("#button") *: return button.addEventListener("click", { console .log("Clicked on button!}") } ) }
Kotlin/JS - main.js if (typeof kotlin **= 'undefined') { throw new Error ("Error loading module 'test'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'test'."); } var test = function (_, Kotlin) { 'use strict'; var Unit = Kotlin.kotlin.Unit; var document; function main$lambda(it) { console .log('Clicked on button!}'); return Unit; } function main() { var tmp$; tmp$ = document.querySelector('#button'); if (tmp$ *= null) { return; } var button = tmp$; button.addEventListener('click', main$lambda); } Object .defineProperty(_, 'document', { get: function () { return document; } }); _.main = main; document = window .document; main(); Kotlin.defineModule('test', _); return _; }(typeof test **= 'undefined' ? {} : test , kotlin);
Kotlin/JS - main.js var main = function (_, Kotlin) { **. function main$lambda(it) { console .log('Clicked on button!}'); return Unit; } function main() { var tmp$; tmp$ = document.querySelector('#button'); if (tmp$ *= null) { return; } var button = tmp$; button.addEventListener('click', main$lambda); } **. main(); **. }(typeof main **= 'undefined' ? {} : main , kotlin);
Progressive Web Apps
Reliable - Fast - Engaging https://developers.google.com/web/progressive-web-apps
Web App Manifest Service Worker manifest.json Web UI
Web App Manifest - manifest.json { "short_name": "Maps", "name": "Google Maps", "icons": [ { "src": "/images/icons-192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/images/icons-512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": "/maps/?source=pwa", "background_color": "#3367D6", "display": "standalone", "scope": "/maps/", "theme_color": "#3367D6" }
Service Worker index.html main.js service-worker.js
Service Worker - index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Kotlin/JS PWA Demo*/title> */head> <body> <div id="appContent">*/div> */body> <script src="main.js">*/script> */html>
Service Worker - main.js if ('serviceWorker' in navigator ) { navigator .serviceWorker .register('/service-worker.js') .then(() *> { console .log('Service Worker registered!') }) .catch(error *> { console .error('Service Worker registration failed!', error) }); }
Service Worker - service-worker.js self .addEventListener('install', event *> { console .log('Service Worker installed!') }); self .addEventListener('activate', event *> { console .log('Service Worker is now active!') }); self .addEventListener('fetch', event *> { const url = new URL (event.request.url); if (url.origin **= location .origin *& url.pathname **= '/dog.svg') { event.respondWith( caches .match('/cat.svg')); } });
Service Worker
Kotlin/JS - Service Workers
Kotlin/JS - Main.kt import kotlin.browser. window fun main() { window .addEventListener("load", { window .navigator.serviceWorker .register("/service-worker.js") .then { console .log("Service worker registered!") } .catch { console .error("Service Worker registration failed: $ it ") } } ) }
Kotlin/JS Output Output Input
Kotlin/JS - Main.kt How can we create this file? import kotlin.browser. window fun main() { window .addEventListener("load", { window .navigator.serviceWorker .register("/service-worker.js") .then { console .log("Service worker registered!") } .catch { console .error("Service Worker registration failed: $ it ") } } ) }
2 copies of Kotlin/JS stdlib!!! First solution - 2 Gradle modules!
Second solution - use the same script!
Kotlin/JS - Main.kt Same script as we’re currently running in! import kotlin.browser. window fun main() { window.addEventListener("load", { window.navigator.serviceWorker .register("/kotlin-js-pwa.js") .then { console.log("Service worker registered!") } .catch { console.error("Service Worker registration failed: $it") } } ) }
Kotlin/JS - Main.kt Throws ReferenceError in a Service Worker! external val self : ServiceWorkerGlobalScope fun main() { try { window .addEventListener("load", { window .navigator.serviceWorker.register("/kotlin-js-pwa.js") } ) } catch (t: Throwable) { self .addEventListener("install", { event -> console .log("Service Worker installed!") } ) self .addEventListener("activate", { event -> console .log("Service Worker is now active!") } ) } }
Kotlin/JS - Main.kt Reference to Service Worker scope external val self : ServiceWorkerGlobalScope fun main() { try { window .addEventListener("load", { window .navigator.serviceWorker.register("/kotlin-js-pwa.js") } ) } catch (t: Throwable) { self .addEventListener("install", { event -> console .log("Service Worker installed!") } ) self .addEventListener("activate", { event -> console .log("Service Worker is now active!") } ) } }
Implementing the Service Worker
Kotlin/JS - Installing Service Worker const val CACHE_NAME = "my-site-cache-v1" val urlsToCache = arrayOf ( "/", Reference to Service Worker scope "/styles/main.css", "/images/dog.svg", "/images/cat.cvg" ) external val self : ServiceWorkerGlobalScope fun installServiceWorker() { self .addEventListener("install", { event -> event as InstallEvent event.waitUntil( self .caches.open( CACHE_NAME ) .then { it .addAll( urlsToCache ) } ) } }
Kotlin/JS - Implementing offmine cache self .addEventListener("fetch", { event -> event as FetchEvent self .caches.match(event.request) .then { it as Response? return@then it *: self .fetch(event.request) } } )
Calling your HTTP API with Kotlin/JS
Kotlinx.serialization + ktor client plugins { id("org.jetbrains.kotlin.js") version "1.3.61" id("org.jetbrains.kotlin.plugin.serialization") version "1.3.61" } sourceSets["main"].dependencies { implementation(kotlin("stdlib-js")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.14.0") implementation("io.ktor:ktor-client-json-js:1.2.6") implementation("io.ktor:ktor-client-js:1.2.6") }
Kitten API response { "count": 1, "kittens": [ { "name": "Lucy", "age": 3, "gender": "male", "color": "gray", "race": "siberian", "photoUri": "https:*/kitten.io/images/lucy.png" } ] }
Kotlin data classes @Serializable data class KittensResponse( val count: Int, val kittens: List<Kitten> ) @Serializable data class Kitten( val name: String, val age: Int, val gender: Gender, val color: Color, val race: Race, val photoUri: String )
Recommend
More recommend