Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Intro to Web Dev with Kotlin

Zachary Smith
February 13, 2019

Intro to Web Dev with Kotlin

With front-end web apps becoming larger and more complex, and Node.js allowing Javascript to be used on the server side, the language has had a rapid rise in popularity. But for many JVM/Android developers, the thought of getting into front end web development and the Javascript ecosystem can seem quite unpleasant. However with Kotlin's ability to target JS, we can leverage the language features we've come to love, to make the experience a bit more fun. In this talk, we'll explore how we can build a Kotlin front-end application and even use Kotlin to interact with Node.js on the server.

Zachary Smith

February 13, 2019
Tweet

More Decks by Zachary Smith

Other Decks in Technology

Transcript

  1. allprojects { buildscript { ext.kotlin_version = '1.3.21' ext.kotlinx_html_version = "0.6.12"

    repositories { mavenCentral() maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.44" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } } Root - build.gradle
  2. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' repositories { jcenter() }

    dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" } compileKotlin2Js { kotlinOptions.moduleKind = 'commonjs' kotlinOptions.outputFile = "$buildDir/js/index.js" } Backend - build.gradle
  3. { "name": "backend", "version": "1.0.0", "description": "", "main": "index.js", "scripts":

    { "test": "echo \"Error: no test specified\" && exit 1", "start": "node ./build/js/index.js" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4", "kotlin": "^1.3.21", "request": "^2.88.0" } } Backend - package.json
  4. { "name": "backend", "version": "1.0.0", "description": "", "main": "index.js", "scripts":

    { "test": "echo \"Error: no test specified\" && exit 1", "start": "node ./build/js/index.js" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4", "kotlin": "^1.3.21", "request": "^2.88.0" } } Backend - package.json
  5. external fun require(module: String): dynamic fun main() { val express

    = require("express") val request = require("request") val app = express() ... }
  6. external fun require(module: String): dynamic fun main() { val express

    = require("express") val request = require("request") val app = express() ... } Declare externally defined API returning dynamic type.
  7. Dynamic • The dynamic type basically turns off Kotlin's type

    checker. • A dynamic value can be assigned to any variable or passed anywhere as a parameter. • Any value can be assigned to dynamic variable • Any value can be passed to a function that takes dynamic as a parameter. • Null-checks are disabled for dynamic values.
  8. external fun require(module: String): dynamic fun main() { ... app.use(express.static("public"))

    app.get("/") { req, res -> res.send("public") } app.get("/todos") { req, res -> request.get("https://jsonplaceholder.typicode.com/todos") { err, response, body -> res.type("text/json") res.send(response.body) } } app.listen(3000) { println("Listening on port 3000") } }
  9. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  10. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  11. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  12. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  13. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  14. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  15. apply plugin: 'kotlin2js' apply plugin: 'kotlin-dce-js' apply plugin: 'org.jetbrains.kotlin.frontend' apply

    plugin: 'kotlinx-serialization' repositories { jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-dev" } maven { url "http://dl.bintray.com/kotlinx/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "http://dl.bintray.com/kotlin/kotlin-js-wrappers" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.6.12" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.10.0" implementation 'org.jetbrains:kotlin-react:16.6.0-pre.68-kotlin-1.3.20' implementation "org.jetbrains:kotlin-react-dom:16.6.0-pre.68-kotlin-1.3.20" } Frontend - build.gradle
  16. kotlinFrontend { downloadNodeJsVersion = 'latest' sourceMaps = true npm {

    dependency "style-loader" dependency "react" dependency "react-dom" devDependency "karma" } webpackBundle { bundleName = "main" contentPath = file('src/main/web') } } Frontend - build.gradle
  17. compileKotlin2Js { kotlinOptions { metaInfo = true outputFile = "$project.buildDir.path/js/${project.name}.js"

    sourceMap = true moduleKind = 'commonjs' main = "call" } } Frontend - build.gradle
  18. React • A JavaScript library for building user interfaces •

    Uses the concept of Components • Components can manage their own state, and can be composed make complex UIs.
  19. Components • Components have a “render()” method. • Method takes

    input data and returns “descriptions” of what to display. • “Descriptions” are defined in objects call React Elements
  20. React Elements • React Elements are cheap to create and

    maintain. • React uses these to model the state of the UI. • When component state changes, render is called, and new React Elements are produced that reflect updated state. • Can diff this ui model with current DOM state and will only update the DOM nodes necessary to bring DOM to correct state
  21. fun render(container: Element?, callback: () -> Unit = {}, handler:

    RBuilder.() -> Unit) = render(buildElements(handler), container, callback)
  22. fun render(container: Element?, callback: () -> Unit = {}, handler:

    RBuilder.() -> Unit) = render(buildElements(handler), container, callback)
  23. fun render(container: Element?, callback: () -> Unit = {}, handler:

    RBuilder.() -> Unit) = render(buildElements(handler), container, callback)
  24. fun render(container: Element?, callback: () -> Unit = {}, handler:

    RBuilder.() -> Unit) = render(buildElements(handler), container, callback) Invokes external render function in React JS library.
  25. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {} Render method for defining UI
  26. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {}
  27. package react import kotlinext.js.* // Props external interface RProps val

    RProps.children: Any get() = asDynamic().children var RProps.key: String get() = error("key cannot be read from props") set(value) { asDynamic().key = value }
  28. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {}
  29. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {}
  30. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {}
  31. inline fun RBuilder.div(..., block: RDOMBuilder<DIV>.() -> Unit): ReactElement: { return

    tag(block) { ... } } fun <P:RProps, C:Component<P, *>> child( klazz: KClass<C>, handler: RHandler<P> ): ReactElement { val rClass = klazz.js as RClass<P> return rClass(handler) }
  32. class AppComponent : RComponent<RProps, RState>() { override fun RBuilder.render() {

    div("container") { div("row") { h2("display-1") { +"Welcome to React with Kotlin" } } todoListComponent() } } } fun RBuilder.appComponent() = child(AppComponent::class) {}
  33. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { override fun TodoListState.init(props:

    TodoListProps) {...} override fun RBuilder.render() {...} private fun addTodo() {...} private fun deleteTodo(selectedTodo: TodoItem) {...} private fun fetchTodosFromNetwork() {...} } fun RBuilder.todoListComponent(items: List<TodoItem> = listOf()) { return child(TodoListComponent::class) { attrs.initialItems = items } }
  34. interface TodoListProps : RProps { var initialItems: List<TodoItem> } interface

    TodoListState : RState { var todos: List<TodoItem> var text: String }
  35. @Serializable data class TodoItem( val userId: Int, val id: Int,

    val title: String, val completed: Boolean )
  36. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { override fun TodoListState.init(props:

    TodoListProps) { ... } override fun RBuilder.render() { div("row justify-content-center") { div("input-group mb-3") { input(type = InputType.text, name = "itemText", classes = "form-control") { key = "itemText" attrs { value = state.text placeholder = "Add a to-do item" onChangeFunction = { val target = it.target as HTMLInputElement setState { text = target.value } } } } ... } } ...
  37. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { override fun TodoListState.init(props:

    TodoListProps) { ... } override fun RBuilder.render() { div("row justify-content-center") { div("input-group mb-3") { input(type = InputType.text, name = "itemText", classes = "form-control") { key = "itemText" attrs { value = state.text placeholder = "Add a to-do item" onChangeFunction = { val target = it.target as HTMLInputElement setState { text = target.value } } } } ... } } ...
  38. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { div("input-group mb-3") { ... div("input-group-append") { primaryButton("Add", ::addTodo) primaryButton("Get Todos From Network", ::fetchTodosFromNetwork) } } } ...
  39. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { div("input-group mb-3") { ... div("input-group-append") { primaryButton("Add", ::addTodo) primaryButton("Get Todos From Network", ::fetchTodosFromNetwork) } } } ...
  40. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { div("input-group mb-3") { ... div("input-group-append") { primaryButton("Add", ::addTodo) primaryButton("Get Todos From Network", ::fetchTodosFromNetwork) } } } ...
  41. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { ... } div("row justify-content-center") { ul("list-group") { state.todos.forEachIndexed { index, todo -> li("list-group-item d-flex justify-content-between align-items-center") { key = index.toString() +todo.title deleteButton("DELETE") { deleteTodo(todo) } } } } } } ...
  42. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { ... } div("row justify-content-center") { ul("list-group") { state.todos.forEachIndexed { index, todo -> li("list-group-item d-flex justify-content-between align-items-center") { key = index.toString() +todo.title deleteButton("DELETE") { deleteTodo(todo) } } } } } } ...
  43. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... override fun

    RBuilder.render() { div("row justify-content-center") { ... } div("row justify-content-center") { ul("list-group") { state.todos.forEachIndexed { index, todo -> li("list-group-item d-flex justify-content-between align-items-center") { key = index.toString() +todo.title deleteButton("DELETE") { deleteTodo(todo) } } } } } } ...
  44. fun RDOMBuilder<*>.primaryButton(btnText: String, onClick: () -> Unit): ReactElement { return

    button(classes = "btn btn-outline-secondary") { +btnText attrs { onClickFunction = { onClick() } } } } fun RDOMBuilder<*>.deleteButton(btnText: String, onClick: () -> Unit): ReactElement { return button(classes = "badge badge-danger badge-pill") { +btnText attrs { onClickFunction = { onClick() } } } }
  45. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... private fun

    addTodo() { if (state.text.isNotEmpty()) { setState { todos += TodoItem( userId = 1, id = generateId(), title = text, completed = false ) text = "" } } } ...
  46. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... private fun

    deleteTodo(selectedTodo: TodoItem) { setState { todos = todos.filter { todoItem -> todoItem.id != selectedTodo.id } } } ...
  47. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { ... private fun

    fetchTodosFromNetwork() { xhrGet("https://jsonplaceholder.typicode.com/todos") { response -> setState { todos += Json.parse(TodoItem.serializer().list, response) } } } }
  48. fun xhrGet(url: String, callback: (String) -> Unit) { val request

    = XMLHttpRequest() request.open("GET", url) request.onload = { if (request.readyState == 4.toShort() && request.status == 200.toShort()) { callback.invoke(request.responseText) } } request.send() }
  49. class TodoListComponent(props: TodoListProps) : RComponent<TodoListProps, TodoListState>(props) { override fun TodoListState.init(props:

    TodoListProps) {...} override fun RBuilder.render() {...} private fun addTodo() {...} private fun deleteTodo(selectedTodo: TodoItem) {...} private fun fetchTodosFromNetwork() {...} }
  50. Advantages Backend • Can interact with existing node systems. •

    Compile kotlin programs to deploy on AWS systems running node. • Isomorphic JS -> Sharing code between frontend and backend • Kotlin Common -> Shared code with other platforms (iOS & Android)
  51. Advantages Frontend • Statically-typed front end web development • Isomorphic

    Kotlin/JS -> Sharing code between frontend and backend • Existing React wrapper libraries facilitate reactive programming. • Kotlin's language support for DSL's and extension allow for rich, fluid ui composition
  52. Disadvantages • Consuming JavaScript returns “dynamic” types. • 3rd party

    wrappers are still limited. • Integration with popular libs would require writing your own wrappers or forgoing type safety. • Debugging can be difficult.
  53. • Your first Node.js app with Kotlin ◦ https://medium.com/@Miqubel/your-first-node-js-app-with-kotlin-30e07baa0bf7 •

    Kotlin and Javascript ◦ https://www.baeldung.com/kotlin-javascript • Web App With Kotlin.js: Getting Started ◦ https://www.raywenderlich.com/201669-web-app-with-kotlin-js-getting-started • Kotlin/JS configuration made simple ◦ https://blog.kotlin-academy.com/kotlin-js-configuration-made-simple-ef0e361 fcd4 • Kotlin wrappers for popular JavaScript libraries ◦ https://github.com/JetBrains/kotlin-wrappers • Create React apps using Kotlin with no build configuration ◦ https://github.com/JetBrains/create-react-kotlin-app • Kotlin Full-stack Application Example ◦ https://github.com/Kotlin/kotlin-fullstack-sample • KotlinConf 2017 - How to Build a React App in Kotlin by Dave Ford ◦ https://www.youtube.com/watch?v=FDOECr-sT6U