We design payments technology that powers the growth of millions of businesses around the world. Engineering the next frontiers in payments technology
Leader in payment and secured transactions
Over 50 billion transactions/year
7000+ engineers in over 40 countries
A huge & diverse tech-stack
Tips
This training is also available in French / Cette formation est aussi disponible en Français
',10)]))}const p=i(l,[["render",s],["__file","index.html.vue"]]),c=JSON.parse(`{"path":"/","title":"Welcome","lang":"en-US","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Get started →","link":"/en/presentation/","type":"primary"}],"features":[{"title":"Language features","details":"null safety, extensions, lambdas, Java interoperability and more"},{"title":"Backend development","details":"With Ktor, spring and NodeJS"},{"title":"Frontend development","details":"Compose multiplatform, Kotlin/JS, Kotlin/WASM and JVM frameworks"},{"title":"Cross-platform development","details":"With KMP and Compose multiplatform"},{"title":"Advanced Kotlin","details":"Coroutines, delegates, Function literal with receiver, DSLs and more"},{"title":"Practical exercises and solutions","details":"All chapters have a set of exercises"}],"footer":"Worldline, 2023"},"headers":[{"level":2,"title":"Who we are","slug":"who-we-are","link":"#who-we-are","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Useful links","slug":"useful-links","link":"#useful-links","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"index.md"}`);export{p as comp,c as data};
diff --git a/assets/index.html-2LK4oqoY.js b/assets/index.html-2LK4oqoY.js
new file mode 100644
index 0000000..fa29d96
--- /dev/null
+++ b/assets/index.html-2LK4oqoY.js
@@ -0,0 +1,13 @@
+import{_ as e,c as a,a as s,o as t}from"./app-Djq7wF8p.js";const i={};function l(c,n){return t(),a("div",null,n[0]||(n[0]=[s(`
Kotlin allows to delegate the getter and setter of a property to another object, which is called a delegate. It is a class that defines the getValue and setValue methods.
Kotlin provides standard delegates such lazy properties and observable properties.
Kotlin provides a high level concurrency model called Coroutines. The developer can delegated the management of threads to the compiler and runtime and using higher level constructs than threads to express asynchronous operations.
Coroutines in Kotlin revolve around these concepts:
A coroutine is an instance of suspendable computation.
Kotlin has many methods for creating a coroutine such as launch.
A coroutine must exist within a coroutine scope.
For example runBlocking creates a coroutine scope whithin which coroutines can be launched.
A coroutine can run suspend functions which can suspend the coroutine but do not block the thread.
For example: the delay suspend the coroutine but does not block the thread on which it is running.
Suspend functions are operations that may take time such http requests and file system calls.
The suspend qualifier defines a suspend function. It runs within a coroutine and can call other suspend functions.
Flow allows to generate a list of asynchronous values.
Deferred and Channel transfer a single value and a stream of values, respectively, between coroutines.
▶️ this code show how to create a coroutine and suspend function and how to use them.
As seen previously, function extension add behavior to existing classes. Inside the definition of the function extension, we can reference the extension receiver (or this) implicitly.
fun String.countCharacters()= length // or this.length
+println("hello".countCharacters())// prints 5
+
We can define this extension with a function literal (or lambda) in instead of a named function (declared with fun).
var extFn: String.()-> Int
+extFn ={ length }// extFn is a function literal
+println("hello".extFn())// prints 5
+println(extFn("hello"))// prints 5
+
extFnis a function literal (lambda) that has access to the receiver (this). That's why it's called a function literal with receiver.
extFn("hello") or extFn("hello") call the extension as expected from extension functions.
The type of a function literal with receiver is funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType and is called with funName(receiverValue, arg1Value, etc.) or receiverValue.funName(arg1Value, etc.). However, this is not the interesting aspect.
The important part is extFn = { length } which can be put as a function argument in a higher order function. The developer that calls the higher order function must define extFn, which in turn has access to the receiver. This allows for a nice style of programming. ▶️ this code shows an example.
Type-safe builders combine well-named builder functions and functions literals with receiver to create type-safe, statically typed builders in Kotlin.
open the java-integration-exercise projects in the materials folder.
Have a look at the Java class we provided you in the src/main/java/com/worldline/learning/kotlin/java2kotlin package. (yes, that's the Pokemon class)
Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at src/main/kotlin/com/worldline/learning/kotlin/java2kotlin)
Have a look at the generated Kotlin code, and note the major differences you spot!
`,28)]))}const p=e(i,[["render",l],["__file","index.html.vue"]]),r=JSON.parse('{"path":"/en/kotlin-features-advanced/","title":"📚 Advanced and other Kotlin features","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Delegated properties","slug":"delegated-properties","link":"#delegated-properties","children":[]},{"level":2,"title":"Concurrency and Coroutines","slug":"concurrency-and-coroutines","link":"#concurrency-and-coroutines","children":[]},{"level":2,"title":"Function literal with receiver and Type-safe builders","slug":"function-literal-with-receiver-and-type-safe-builders","link":"#function-literal-with-receiver-and-type-safe-builders","children":[]},{"level":2,"title":"🧪 Exercises","slug":"🧪-exercises","link":"#🧪-exercises","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/kotlin-features-advanced/README.md"}');export{p as comp,r as data};
diff --git a/assets/index.html-2g5kMIFT.js b/assets/index.html-2g5kMIFT.js
new file mode 100644
index 0000000..9b14f21
--- /dev/null
+++ b/assets/index.html-2g5kMIFT.js
@@ -0,0 +1,7 @@
+import{_ as e,c as I,a,o as g}from"./app-Djq7wF8p.js";const i={};function c(n,l){return g(),I("div",null,l[0]||(l[0]=[a(`
Kotlin est un langage qui support les paradigmes orienté objet et fonctionnel. Ce chapitre couvre les caractéristiques basiques et intermédiaires. Le chapitre suivant couvrira les fonctionnalités avancées.
Appeler une fonction en passant la valeur dans l'ordre de déclaration.
Utilisez des étiquettes d'argument pour plus de clarté, cependant, cela permet également un classement arbitraire des arguments.
Les arguments optionnels ont une valeur par défaut et peuvent être omis lors de l'appel.
Les fonctions sont des éléments de première classe ou citoyens : elles peuvent être affectées à une variable, passées en tant que paramètre de fonction ou renvoyées par une fonction.
💡 Une fonction qui prend une fonction comme argument ou en renvoie une est une fonction d'ordre supérieur.
Un type de fonction peut être exprimé comme suit : (typeOfParam1, typeOfParam2, etc) -> returnType (Le type de retour vide est Unit).
Les fonctions anonymes utilisent la syntaxe suivante { argName1, argName2, etc. -> // code }
Aussi appelées fonctions lambda ou fonctions littérales
Le dernier argument de la fonction peut être mis après la fermeture après la parenthèse fermante compute(9, 5) { x, y -> x * y }
null safety est une fonctionnalité du compilateur qui élimine la fameuse Null pointer exception ou npe. En effet, le compilateur signale des erreurs et des avertissements lorsque nous manipulons des types nullables (également appelées types optionnels) dès qu'il y a un risque de npe à l'exécution. Ainsi, afin de mettre Voici une liste des fonctionnalités de sécurité null fournies par Kotlin :
Tous les types ne sont pas nullables par défaut ; nous ne pouvons pas affecter null à une variable ou à un argument.
Par exemple, ce code échoue var s: String = null.
Un type peut être rendu nullable en le suffixant avec un ?. Par exemple : var s : chaîne ? = nul.
Kotlin interdit d'appeler une méthode ou une propriété de type non nullable, sauf si l'on fait l'une de ces possibilités :
Utilisez le chaînage optionnel avec le suffixe ?.
Fournissez une valeur par défaut avec l'opérateur elvis ?:.
Smart-cast le nullable dans un non-nullable.
Utilisez l'opérateur !! qui élimine les vérifications du compilateur. Cela ne devrait jamais être utilisé.
Ne jamais déballer avec !!
Car cela équivaut à désactiver la null safety. Utilisez les autres possibilités à la place.
▶️ ce code illustrate la null safety et les types optionnels.
La classe \`Optional\` de Java ne fournit aucun protection à la compilation
Ce code lance une npe en Java: Optional<String> s = null; s.isPresent();. Le compilateur Java (au moins à la version version 17) ne propose pas d'équivalent à ce que propose Kotlin comme le smart casting.
Les énumérations permettent de travailler avec un groupe de valeurs de façon cadrée. Contrairement aux énumérations Java, les énumérations Kotlin sont des classes. Les enum class de Kotlin fournissent ces fonctionnalités :
Les expressions when prennent en charge les énumérations.
Une enum class peut définir des méthodes et implémenter des interfaces mais elle ne peut pas dériver d'une classe.
Il existe des méthodes pour lister les constantes d'une enum class.
Chaque constante d'une énumération a des propriétés pour obtenir son nom et sa position (en commençant par 0).
Kotlin permet d'écrire du code Orienté Object concis grâce aux caractéristiques suivantes :
Concepts disponibles : classes, héritage, interfaces et classes abstraites.
Prise en charge possée des propriétés : les getters et les setters sont automatiquement implémentés.
On peut les personnaliser les accesseurs en définissant les fonctions get() et set(value) à côté de la déclaration de la propriété.
Les arguments du constructeur sont définis à côté du nom de la classe class ClassName(arg1, atg2, )
Préfixer les arguments d'un constructeur avec val ou var en fait une propriété (val la rend non ré-assignable).
Le nom du constructeur est init et ne nécessite pas de paramètres.
Le compilateur vérifie que toutes les propriétés non nullables sont initialisées à la fin du constructeur.
⚠️ Le compilateur ne vérifie pas l'initialisation des propriétés lateinit. Ainsi, y accéder avant alors qu'elles ne sont pas initialisés provoque une exception.
Une classe doit être préfixée avec open pour permettre l'héritage.
Kotlin utilise le niveau d'accès public par défaut.
L'opérateur d'égalité == appelle implicitement la méthode equals() (contrairement à Java qui utilise l'égalité de référence).
Un objet compagnon contient des méthodes et des propriétés statiques.
Les extensions ajoutent des fonctions et des propriétés aux classes existantes.
💡 Ils remplacent l'héritage dans de nombreuses situations.
Par exemple, nous pouvons ajouter des fonctions à la classe String au lieu de créer une nouvelle classe StringUtils.
Les classes et interfaces scellées ne peuvent pas être étendues ou implémentées par des tiers.
Ne définir les accesseurs que si vous avez un comportement personnalisé
Kotlin prend en charge les propriétés de façon plus poussée que Java et permet d'ajouter des accesseurs ultérieurement sans refactoriser le code qui appelle ces propriétés. Ainsi, par défaut, il suffit de définir le nom des propriétés sans accesseurs et on peut les utiliser directement.
La programmation fonctionnelle s'articule autour de ces concepts : fonctions pures, récursivité, transparence référentielle, variables immuables, fonctions en tant que citoyens de première classe et fonctions d'ordre supérieur.
Expliquons brièvement ces concepts :
Les variables immuables signifient qu'on ne peut pas changer la valeur d'une variable ou ses propriétés une fois qu'elle a été créée. Si nous voulons le faire, nous devons créer une nouvelle instance avec la nouvelle valeur.
Les fonctions pures sont des fonctions qui n'ont pas d'effets secondaires et renverront donc toujours la même sortie étant donné la même entrée.
Les fonctions sont des citoyennes de première classe : elles peuvent être affectées à une variable ou utilisées dans des fonctions d'ordre supérieur (passées en tant qu'un argument de fonction ou retournées dans un fonction).
Transparence référentielle : signifie qu'une expression peut être remplacée par son résultat sans modifier le comportement du programme.
💡 Les langages fonctionnels purs fournissent ces fonctionnalités de manière native et les appliquent (au moment de la construction).
Kotlin n'est pas un langage fonctionnel pur mais il prend en charge certaines fonctionnalités. En effet, Kotlin ne sait pas dire si une fonction est pures ou non, mais il fournit des collections immuables via la bibliothèque kotlinx.collections.immutable pour nous aider à manipuler des données immuables.
\`listOf\` génère des listes en lecture seule, mais qui sont mutables
Une liste en lecture seule ne peut pas ajouter ou supprimer des éléments, mais elle peut modifier les données sous-jacentes.
La programmation déclarative est un style célèbre dans la programmation fonctionnelle. Il consiste à écrire du code sous la forme d'un enchaînement d'appels de fonction dans ce style val result = f(x).g(y). .... Les fonctions d'ordre supérieur remplacent de nombreuses situations où nous utiliserions des boucles. Cela favorise le code lisible qui est facile à déboguer et à maintenir.
▶️ ce code montre comment manipuler une liste avec la programmation déclarative.
`,77)]))}const s=e(i,[["render",c],["__file","index.html.vue"]]),t=JSON.parse(`{"path":"/fr/kotlin-features/","title":"📚 Fonctionnalités du langage Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Caractéristiques de base","slug":"caracteristiques-de-base","link":"#caracteristiques-de-base","children":[{"level":3,"title":"Constructions de base (variables, flux de contrôle)","slug":"constructions-de-base-variables-flux-de-controle","link":"#constructions-de-base-variables-flux-de-controle","children":[]},{"level":3,"title":"Les fonctions","slug":"les-fonctions","link":"#les-fonctions","children":[]},{"level":3,"title":"Null safety","slug":"null-safety","link":"#null-safety","children":[]},{"level":3,"title":"Énumérations","slug":"enumerations","link":"#enumerations","children":[]},{"level":3,"title":"Exercices","slug":"exercices","link":"#exercices","children":[]}]},{"level":2,"title":"Fonctionnalités intermédiaires","slug":"fonctionnalites-intermediaires","link":"#fonctionnalites-intermediaires","children":[{"level":3,"title":"Programmation orientée objet","slug":"programmation-orientee-objet","link":"#programmation-orientee-objet","children":[]},{"level":3,"title":"Data class","slug":"data-class","link":"#data-class","children":[]},{"level":3,"title":"Programmation fonctionnelle","slug":"programmation-fonctionnelle","link":"#programmation-fonctionnelle","children":[]},{"level":3,"title":"Kotlin and Java interoperability","slug":"kotlin-and-java-interoperability","link":"#kotlin-and-java-interoperability","children":[]},{"level":3,"title":"Exercices","slug":"exercices-1","link":"#exercices-1","children":[]}]},{"level":2,"title":"Plus d'exercices et de lecture","slug":"plus-d-exercices-et-de-lecture","link":"#plus-d-exercices-et-de-lecture","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/kotlin-features/README.md"}`);export{s as comp,t as data};
diff --git a/assets/index.html-B08ZAEqY.js b/assets/index.html-B08ZAEqY.js
new file mode 100644
index 0000000..878b91d
--- /dev/null
+++ b/assets/index.html-B08ZAEqY.js
@@ -0,0 +1,272 @@
+import{_ as c,c as r,a as o,b as e,d as t,r as l,o as u,e as n,f as s}from"./app-Djq7wF8p.js";const d={};function k(m,a){const p=l("CodeGroupItem"),i=l("CodeGroup");return u(),r("div",null,[a[2]||(a[2]=o(`
In addition to that, Kotlin is theoretically compatible with any framework that targets the JVM or JS. For example, this tutorial shows how to use node.js with Kotlin. However, frameworks that do not officially support Kotlin may require some tweaking to use it.
Ktor is a cross-platform Kotlin library for building both HTTP clients and servers. This makes Ktor a useful library to learn for both front-end developers for its HTTP client capabilities and backend-development for its HTTP server capabilities. In the following, we'll create a REST API with Ktor server.
Create a project on start.ktor.io with the following plugins: Content Negotiation, kotlinx.serialization, and Routing.
Click on "Generate project".
Download the archive, unzip it, and open the project with IntelliJ.
Create a models package and add to it a Customer data class with these immutable properties id: String, firstName: String, lastName: String, email: String.
Annotate the class with @Serializable.
Create a new package named routes and add to it a file CustomerRoutes.kt that will contain the code for the /customer endpoint.
The code below provides the implementation of some endpoints. Please implement the remaining ones.
To enable the route call customerRouting() in the routing configuration file located in plugins/Routing.kt.
For simplicity, use a global in-memory list of customers val store = mutableListOf<Customer>().
Run the server by running the main method.
Test the api on the IDE by using an http file or using any other client.
CustomerRoutes.kt
val store = mutableListOf<Customer>()
+
+fun Route.customerRouting(){
+route("/customer"){
+get{
+ call.respond(store)
+}
+get("{id?}"){
+val id = call.parameters["id"]?:return@get call.respondText(
+"Missing id",
+ status = HttpStatusCode.BadRequest
+)
+val customer =
+ store.find{ it.id == id }?:return@get call.respondText(
+"No customer with id $id",
+ status = HttpStatusCode.NotFound
+)
+ call.respond(customer)
+}
+ post {
+val customer = call.receive<Customer>()
+ store.add(customer)
+ call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
+}
+delete("{id?}"){
+
+}
+}
+}
+
plugins/Routing.kt
fun Application.configureRouting(){
+ routing {
+customerRouting()
+}
+}
+
return@label
You can specify which level you want to return with an explicit label using return@lambda.
Spring is a famous framework for developing server-side applications: APIs, server generated web pages, microservices, etc. It relies on the the Java ecosystem to build and run, thus making it compatible with Kotlin. Even better, Spring officially supports Kotlin. It even allows in start a new project with Kotlin and Gradle-Kotlin. In the next section, we'll use this starter to recreate our above REST API with Spring.
Let's go a little bit further by storing data in a database and writing some tests.
We'll use the H2 in-memory database for the sake of simplicity, since it does not require a server to run. Classes will mapped to database tables with JPA annotations. The database API we'll be using is called JPARepository. It is a lightweight API that provides common CRUD features by just defining an interface.
On the testing side, we'll see two different syntaxes. The default one that is more familiar with Java style and the DSL one which is more readable and more familiar with Kotlin developers.
Create a new Spring project using Spring initializr with Kotlin and the following dependencies: Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web
Open the project and add this class in the model package @Entity class Product(@Id @GeneratedValue var id: Long? = null, var name: String, var price: Int). This single defines the class as well as the minimal JPA annotations (@Entity, @Id and @GeneratedValue) to generate the corresponding table.
In the repository package, declare the ProductRepository interface as follows interface ProductRepository: JpaRepository<Product, Long>. This is enough for Spring to generate an implementation with common features as we'll see later.
Next, create a ProductService class which will contain the business logic. In terms of architecture, the controller calls a service which in turn rely on other services or repositories.
ProductService.kt
@Service
+classProductService(@Autowiredval productRepository: ProductRepository){
+fungetAll()= productRepository.findAll()
+
+// use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product?
+fungetById(id: Long)= productRepository.findByIdOrNull(id)
+}
+
In the controller package, create a ProductController class that is mapped to /product and injects the with @Autowired. Reply to @Get as follows.
In addition to that, Spring provides @ControllerAdvice to change the exception message. You can see an example here.
Let's run the project. Before running the project, we need to add a plugin that allows Kotlin classes to generate a default constructor id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10". The plugins should look as follows:
plugins {
+id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
As an exercise, implement these endpoints: POST a single product, DELETE by id (/product/{id}) and GET by id (/product/{id}).
Hint: ProductController already provides the necessary methods.
Spring frameworks helps perform different types of tests by providing different classes out of the box:
Unit testing of services, repositories and the REST API. This is done through mock utilities such as MockMVC.
Integration testing of the REST API using TestRestTemplate. In this situation, a full server is run and tested.
Most, if not all classes provided by Spring provide an elegant syntax for Java developers. Some of them go further by taking advantage of Kotlin specific features. In the following, we're going to focus on parts that provide Kotlin DSLs, namely unit testing the REST API with MockMVC.
Create a test class ProductControllerUnitTests with this initial content. MockMvc allows to unit test the REST API. The @AutoConfigureMockMvc annotation allows spring to configure it automatically
Add these two tests. The first one uses a classic approach while the second take advantage of Kotlin DSL capabilities. In addition to that, we name using a more readable string literal
As an exercise, unit tests for the other endpoints.
The request builder of JpaRepository
Spring repositories implement requests based on the name of their methods. For example, to get all products sorted by name, we can add this method to the interface.
Thanks to Kotlin/JS, we can write apps that target node.js using Kotlin. We can even import npm libraries as long as you declare the JS API surface that you'll be using in Kotlin. This is called external declaration (You can think of it as an equivalent of TypeScript's type definitions) that declares the symbols that we want to access in Kotlin thanks to @JsModule and @JsNonModule annotations.
Defining such external declarations can be a hassle and there seems to be no official automatic generator (dukat has been removed in kotlin 1.8.20). In that case, we have two options, either we write the external declaration ourselves or import it as a dependency if available. Fortunately for express developers, chrisnkrueger/kotlin-express provides declarations for the express library.
There are two gradle plugins that allow to create node.js projects: the kotlin("js") one and the kotlin("multiplatform") one. The difference between the two plugins is that the former only supports JS or WASM while the latter supports more platforms but requires to configure source sets. Thus, the former may seem easier to setup but the latter is better in the long run because it allows us to get more familiar with Kotlin Multiplatform (KMP).
At the time of writing, I didn't find an official wizard or starter project. So we'll create one from scratch using gradle init.
Create a new Gradle project using IntelliJ or by running gradle init in a empty folder (see below for the replies to the gradle init command).
gradle init
gradle init
+Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --statusfor details
+
+Select type of project to generate:
+1: basic
+2: application
+3: library
+4: Gradle plugin
+Enter selection (default: basic)[1..4]1
+
+Select build script DSL:
+1: Kotlin
+2: Groovy
+Enter selection (default: Kotlin)[1..2]1
+
+Project name (default: starter): rest-api-kotlin-nodejs
+
+Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no)[yes, no]yes
+
+
+> Task :init
+To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.3/samples
+
+BUILD SUCCESSFUL in 24s
+2 actionable tasks: 2 executed
+
In build.gradle.kts, add and configure the kotlin("multiplatform") plugin. Also add the express and dev.chriskrueger:kotlin-express dependencies.
create a main.kt file in src/jsMain/kotlin with the following content:
main.kt
dataclassMessage(val id: Int,val message: String)
+
+val messages =mutableListOf(Message(0,"I love Kotlin/JS"))
+
+funmain(){
+val app = express.Express()
+
+// REST API that provides a **GET /hello** endpoint
+ app.get("/hello"){ _, res ->
+ res.send(messages)
+}
+
+// Create a server that listens to port 3000
+ app.listen(3000){
+ console.log("server start at port 3000")
+}
+}
+
Run the task jsRun from IntelliJ of from the command line ./gradlew --console=plain jsRun. The server should start running.
Let's add a post endpoint which reads the body as a json. In order to read the body as json, we must add this possibility to express by importing the npm library body-parser and by calling app.use(bodyParser.json()). Once this setup is complete, req.body will contain the content of the body. However, there is no available external definition for bodyParser as of the time of writing. Thus, we must create or own external definition.
First, add the body-parser dependncy in the build file implementation(npm("body-parser", "> 1.0.0 < 2.0.0"))
Next, we would write: app.use(bodyparser.json()) to activate the library. Let's guess what a minimal definition of bodyparser can be.
BodyParser.kt
// external means that this class is defined in JS
+externalclass BodyParser {
+// we tell Kotlin that we want to use the json() function.
+funjson(): Any
+// It is not required to define all the functions of the module
+}
+
+// @JsModule is used to import the module from the NPM registry
+@JsModule("body-parser")
+externalval bodyParser: BodyParser
+
Finally, we just need to add the BodyParser.kt file into the project and use it in our server.
main.kt
app.use(bodyParser.json())
+app.post("/hello"){ req, res ->
+// Kotlin does not keep the original field name when parsing JSON from JS (you can see it the in get response)
+if(req.body as? Message ==null){
+println("failed to get the body from Kotlin")
+}
+// Thus, we need to use js() to get the the field by its expected name
+// js() calls JS from Kotlin
+println("req.body from JS \${js("req.body.id")} - \${js("req.body.message")}")
+val id =js("req.body.id")as? Int
+val message =js("req.body.message")as? String
+if(message !=null&& id !=null){
+ messages.add(Message(id, message))
+ res.status(201).end()
+}else{
+ res.status(400).send(js("{cause : 'error'}")as Any)
+}
+}
+
`,34))])}const g=c(d,[["render",k],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/en/backend-development/","title":"📚 Backend development","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Ktor","slug":"ktor","link":"#ktor","children":[{"level":3,"title":"🧪 develop an API with Ktor","slug":"🧪-develop-an-api-with-ktor","link":"#🧪-develop-an-api-with-ktor","children":[]}]},{"level":2,"title":"Spring framework","slug":"spring-framework","link":"#spring-framework","children":[{"level":3,"title":"🧪 Spring boot part 1 - develop the same API with Spring Boot","slug":"🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot","link":"#🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot","children":[]},{"level":3,"title":"🧪 Spring boot part 2 - adding a database","slug":"🧪-spring-boot-part-2-adding-a-database","link":"#🧪-spring-boot-part-2-adding-a-database","children":[]},{"level":3,"title":"🧪 Spring boot part 2 - adding tests","slug":"🧪-spring-boot-part-2-adding-tests","link":"#🧪-spring-boot-part-2-adding-tests","children":[]}]},{"level":2,"title":"node.js","slug":"node-js","link":"#node-js","children":[{"level":3,"title":"🧪 Getting started with Kotlin/JS and Express","slug":"🧪-getting-started-with-kotlin-js-and-express","link":"#🧪-getting-started-with-kotlin-js-and-express","children":[]},{"level":3,"title":"🧪 Adding a post endpoint and an external Kotlin/JS definition","slug":"🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition","link":"#🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition","children":[]},{"level":3,"title":"🧪 Adding more endpoints","slug":"🧪-adding-more-endpoints","link":"#🧪-adding-more-endpoints","children":[]}]},{"level":2,"title":"🎯 Solutions","slug":"🎯-solutions","link":"#🎯-solutions","children":[]},{"level":2,"title":"Other frameworks","slug":"other-frameworks","link":"#other-frameworks","children":[]},{"level":2,"title":"📖 Further readings","slug":"📖-further-readings","link":"#📖-further-readings","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/backend-development/README.md"}');export{g as comp,b as data};
diff --git a/assets/index.html-BHDS7_46.js b/assets/index.html-BHDS7_46.js
new file mode 100644
index 0000000..ab0d3ca
--- /dev/null
+++ b/assets/index.html-BHDS7_46.js
@@ -0,0 +1,35 @@
+import{_ as e}from"./kotlin-wasm-webapp-R4_9ho9v.js";import{_ as s,c as a,a as t,o as l}from"./app-Djq7wF8p.js";const o="/learning-kotlin/assets/kotlin-wasm-flag-BKaaN9Pq.png",i="/learning-kotlin/assets/wasm-build-conf-edit-CmamvRv7.png",p="/learning-kotlin/assets/wasm-run-configuration-x_w9-EC1.png",r="/learning-kotlin/assets/compose-multiplaform-web-wH6XfCHb.gif",u="/learning-kotlin/assets/compose-multiplaform-Dfyu_rxB.gif",c={};function d(m,n){return l(),a("div",null,n[0]||(n[0]=[t('
Kotlin supporte une large sélection de frameworks frontaux sur toutes les plateformes : mobile, desktop et web. Vous trouverez ci-dessous un aperçu des possibilités que vous pouvez faire directement à partir d'IntelliJ :
Côté bureau
Grâce au support de la JVM, Kotlin supporte JavaFX. 💡 Il existe même un équivalent en Kotlin appelé tornadofx.
Compose Multiplatform apporte l'API Jetpack Compose sur le bureau, le web et le mobile.
Sur le web
Ktor peut utiliser des moteurs de modèles tels que FreeMarker pour créer des pages de serveur.
Avec KotlinJS, les développeurs peuvent créer des applications React, nodsjs ou vanilla JS en utilisant Kotlin.
Kotlin WASM se compile en Web Assembly. Il peut compléter KotlinJS pour les tâches à forte intensité de calcul.
Compose Multiplatform apporte deux options sur le web: Compose web et Compose for Web Canvas.
Sur les mobiles
Les développeurs Android utilisent Jetpack Compose ou l'ancienne méthode de layout XML.
Compose Multiplatform supporte Android de façon stable et iOS de façon expérimentale.
Comme nous pouvons le voir, Kotlin propose plusieurs options. L'option la plus séduisante en terme de partage de code est Compose Multiplatform. Ceci est possible notamment grâce à KMP
KMP (Kotlin Multiplatform) permet de partager une base de code unique sur plusieurs cibles.
KMP s'appuie sur Kotlin native et d'autres fonctionnalités de Kotlin pour aider les développeurs à créer des projets destinés à plusieurs plates-formes en utilisant une base de code Kotlin commune.
De nombreuses combinaisons de cibles et de cas d'utilisation sont possibles :
Full-Stack web apps : Un projet qui contient un backend et une application web tout en partageant une logique commune.
Kotlin/JS peut également cibler le web et même utiliser des frameworks web (tels que react) dans Kolitn.
Kotlin WASM est une autre possibilité de cibler le web, mais il génère WASM au lieu de code JS pur.
Il peut être utilisé par exemple pour développer des bibliothèques à forte intensité de calcul.
Nous pourrons peut-être faire encore plus à l'avenir grâce à l'évolution de toutes ces technologies (Kotlin, WASM et Kotlin/WASM). - Par exemple, [WASI] (https://wasi.dev/) permet à WASM de communiquer avec le système d'exploitation. - Cela signifie que je pourrais voir des projets Kotlin/WASM à l'avenir qui peuvent cibler à la fois le navigateur et le système d'exploitation.
Les assistants de création de projet Kotlin/WASM et Kotlin/JS sur IntelliJ fonctionnent de manière similaire:
L'IDE génère un fichier Kotlin qui sera compilé par la suite en WASM et/ou JS. Kotlin/JS ne génère que du JS tandis que Kotin/WASM génère à la fois du JS et du WASM.
Dans les deux cas, le point d'entrée du code généré est un fichier JS appelé nom_du_module.js.
L'IDE génère également dans le dossier des ressources un fichier index.html dont le but est de charger le JS généré (le fichier nom_du_module.js).
La tâche wasmBrowserDevelopmentRun ou jsWasmBrowserDevelopmentRun lancera un serveur local qui hébergera à la fois les fichiers index.html et les fichiers JS et WASM générés.
Créons une application Kotlin/WASM. Tout d'abord, activez l'assistant Kotlin/WASM en activant kotlin.wasm.wizard dans le registre d'IntelliJ (ouvrez le registre en appuyant deux fois sur shift et en tapant "registry" dans la boîte de recherche). Alternativement, clonez ce projet.
Vérifiez qu'on est sur la dernière version de Kotlin dans build.gradle.kts (l'assistant peut le configurer à une version antérieure).
Ouvrez src/wasmMain/kotlin/sample.kt et cliquez sur le bouton lancer qui apparaît à côté de la fonction main.
Si la compilation échoue parce que l'IDE a utilisé la mauvaise tâche gradle, veuillez la changer en wasmBrowserDevelopmentRun et essayez de l'exécuter à nouveau.
Le serveur de développement devrait démarrer et vous pouvez ouvrir votre application web sur http://localhost:8080/
⚠️ Il se peut que vous deviez activer certains drapeaux sur votre navigateur pour que l'application fonctionne. Si vous voyez une page blanche, veuillez lire les journaux du navigateur pour vérifier les instructions.
Le fichier wasm généré est disponible dans build/js/packages/nom_du_projet/kotlin
WASM étant un format binaire, nous devons d'abord le convertir au format texte.
Nous pouvons soit installer [WABT (The WebAssembly Binary Toolkit ou wabbit)] (https://github.com/WebAssembly/wabt) et utiliser l'outil wasm2wattoolwasm2wat --enable-all -v .\\kotlin-wasm-demo-wasm.wasm -o wasm.wat,
L'assistant Kotlin/JS crée une application très similaire à celle de Kotlin/WASM. Dans un prochain PW, nous créerons une application complète avec Ktor et Kotlin/JS.
Compose multiplatform est une famille de frameworks d'interface utilisateur déclaratifs pour Android (Jetpack Compose), le bureau (Compose Desktop) et le web (Compose Web). Il dispose d'un support expérimental pour iOS et Web Canvas.
Compose multiplatform vs Jetpack Compose
Bien que très similaire, Compose multiplatform est différent de Jetpack Compose car ce dernier n'est compatible qu'avec Android. Google fournit un JetPack compose tutorial pour le développement Android.
Compose Web vs Compose for Web Canvas
La surface de l'API de Compose Web est différente des autres cibles de Compose car elle travaille directement avec le DOM.
Compose for Web Canvas a la même surface d'API que celle du Desktop, Android et iOS car il dessine sur un Canvas et ne manipule pas le DOM.
Cela signifie que le premier a un meilleur support web et que le second a plus de code réutilisable.
',35)]))}const v=s(c,[["render",d],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/fr/front-development/","title":"📚 Développement frontend","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"KMP","slug":"kmp","link":"#kmp","children":[]},{"level":2,"title":"Kotlin/JS et Kotlin/WASM","slug":"kotlin-js-et-kotlin-wasm","link":"#kotlin-js-et-kotlin-wasm","children":[{"level":3,"title":"🧪 Application web Kotlin/WASM","slug":"🧪-application-web-kotlin-wasm","link":"#🧪-application-web-kotlin-wasm","children":[]},{"level":3,"title":"🧪 Application web KotlinJS","slug":"🧪-application-web-kotlinjs","link":"#🧪-application-web-kotlinjs","children":[]}]},{"level":2,"title":"Compose","slug":"compose","link":"#compose","children":[{"level":3,"title":"🧪 Compose Web","slug":"🧪-compose-web","link":"#🧪-compose-web","children":[]},{"level":3,"title":"🧪 Compose desktop + Android app","slug":"🧪-compose-desktop-android-app","link":"#🧪-compose-desktop-android-app","children":[]}]},{"level":2,"title":"Pour aller plus loin","slug":"pour-aller-plus-loin","link":"#pour-aller-plus-loin","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/front-development/README.md"}');export{v as comp,f as data};
diff --git a/assets/index.html-BZTbG60u.js b/assets/index.html-BZTbG60u.js
new file mode 100644
index 0000000..3439f10
--- /dev/null
+++ b/assets/index.html-BZTbG60u.js
@@ -0,0 +1 @@
+import{_ as t}from"./kmp_codelab-CiTPMWjt.js";import{_ as n,c as r,a as i,o as a}from"./app-Djq7wF8p.js";const o={};function l(s,e){return a(),r("div",null,e[0]||(e[0]=[i('
Connaissance de base du développement en Kotlin (notamment la nullabilité, les fonctions en ligne et les fonctions lambda)
IDE Android Studio avec la version stable la plus récente, version Giraffe ou supérieure
Une bonne connectivité
Conseil
Pour plus d'informations sur votre environnement de développement (DEV) et les installations, veuillez consulter la documentation liée à JetBrains ici.
',7)]))}const u=n(o,[["render",l],["__file","index.html.vue"]]),c=JSON.parse('{"path":"/fr/other-technologies/","title":"🛠 Construisons une app multiplateforme !","lang":"fr-FR","frontmatter":{},"headers":[],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/other-technologies/README.md"}');export{u as comp,c as data};
diff --git a/assets/index.html-BwZborl3.js b/assets/index.html-BwZborl3.js
new file mode 100644
index 0000000..9f02671
--- /dev/null
+++ b/assets/index.html-BwZborl3.js
@@ -0,0 +1 @@
+import{_ as i}from"./kotlin-used-for-Bdlavnqs.js";import{_ as t,c as l,a as r,o as n}from"./app-Djq7wF8p.js";const a={};function o(s,e){return n(),l("div",null,e[0]||(e[0]=[r('
Voici quelques arguments qui motivent le passage de Java (version 17 LTS au moment de la rédaction) à Kotlin.
Kotlin prend en charge plus de cibles que Java.
Kotlin protège des références null à la compilation (les Optional Java sont ne sont pas de protections à la compilation).
Les chaînes de caractères Kotlin prennent en charge l'interpolation.
Les fonctionnalités de programmation fonctionnelle de Kotlin sont meilleures. Il permet même de définir des constructeurs et des DSL (Domain Specific Language) dont le typage est sécurisé (type-safe).
Kotlin peut être mélangé avec du code Java, facilitant ainsi le processus de migration.
Vous pouvez lire plus d'arguments dans ces articles :
[8 raisons pour lesquelles vous devriez passer à Kotlin depuis Java] (https://www.geeksforgeeks.org/8-reasons-why-you-should-switch-to-kotlin-from-java)
',19)]))}const d=t(a,[["render",o],["__file","index.html.vue"]]),c=JSON.parse('{"path":"/fr/presentation/","title":"🚀 Présentation de Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Certaines fonctionnalités","slug":"certaines-fonctionnalites","link":"#certaines-fonctionnalites","children":[]},{"level":2,"title":"Histoire","slug":"histoire","link":"#histoire","children":[]},{"level":2,"title":"Quelques chiffres et faits","slug":"quelques-chiffres-et-faits","link":"#quelques-chiffres-et-faits","children":[]},{"level":2,"title":"Pourquoi passer de Java à Kotlin","slug":"pourquoi-passer-de-java-a-kotlin","link":"#pourquoi-passer-de-java-a-kotlin","children":[]},{"level":2,"title":"Sources et plus de lecture","slug":"sources-et-plus-de-lecture","link":"#sources-et-plus-de-lecture","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/presentation/README.md"}');export{d as comp,c as data};
diff --git a/assets/index.html-CIUQNmed.js b/assets/index.html-CIUQNmed.js
new file mode 100644
index 0000000..ba2f1b9
--- /dev/null
+++ b/assets/index.html-CIUQNmed.js
@@ -0,0 +1 @@
+import{_ as a}from"./Kotlin-Beyond-Android-CYulNy7n.js";import{_ as d,c as s,a as r,e as l,b as o,d as i,f as n,r as p,o as u}from"./app-Djq7wF8p.js";const m={};function f(k,e){const t=p("RouteLink");return u(),s("div",null,[e[41]||(e[41]=r('
',7)),l("ul",null,[l("li",null,[o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[0]||(e[0]=[n("Backend development: Rest API with Ktor")])),_:1})]),l("li",null,[e[2]||(e[2]=n("Optional: ")),o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[1]||(e[1]=[n("Backend development: Rest API with node.js and Kotlin/JS")])),_:1})]),l("li",null,[o(t,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:i(()=>e[3]||(e[3]=[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")])),_:1})]),l("li",null,[o(t,{to:"/en/front-development/#compose"},{default:i(()=>e[4]||(e[4]=[n("Front-end development: cross-platform Hello world App with Compose multiplatform")])),_:1})]),e[6]||(e[6]=l("li",null,[l("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1)),l("li",null,[o(t,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:i(()=>e[5]||(e[5]=[n("Full-stack development: Quiz App with Compose multiplatform and Ktor server")])),_:1})])]),e[42]||(e[42]=r('
',5)),l("ul",null,[l("li",null,[o(t,{to:"/en/presentation/#prerequisites"},{default:i(()=>e[7]||(e[7]=[n("Prerequisutes")])),_:1})]),l("li",null,[e[10]||(e[10]=n("Selection of Kotlin's languages features: ")),o(t,{to:"/en/kotlin-features/#null-safety"},{default:i(()=>e[8]||(e[8]=[n("null safety")])),_:1}),e[11]||(e[11]=n(" and ")),o(t,{to:"/en/kotlin-features/#functions"},{default:i(()=>e[9]||(e[9]=[n("functions")])),_:1})]),l("li",null,[o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[12]||(e[12]=[n("Backend development: Rest API with Ktor")])),_:1})]),l("li",null,[e[14]||(e[14]=n("Optional: ")),o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[13]||(e[13]=[n("Backend development: Rest API with node.js and Kotlin/JS")])),_:1})]),l("li",null,[o(t,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:i(()=>e[15]||(e[15]=[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")])),_:1})]),l("li",null,[o(t,{to:"/en/front-development/#compose"},{default:i(()=>e[16]||(e[16]=[n("Front-end development: cross-platform Hello world App with Compose multiplatform")])),_:1})]),e[18]||(e[18]=l("li",null,[l("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1)),l("li",null,[o(t,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:i(()=>e[17]||(e[17]=[n("Full-stack development: Quiz App with Compose multiplatform and Ktor server")])),_:1})])]),e[43]||(e[43]=r('
',4)),l("ul",null,[l("li",null,[o(t,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:i(()=>e[19]||(e[19]=[n("Initiate a cross-platform app")])),_:1})]),l("li",null,[e[23]||(e[23]=n("Live examples of other Kotlin possibilities: ")),l("ul",null,[l("li",null,[o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[20]||(e[20]=[n("Backend development: Rest API with Ktor")])),_:1})]),l("li",null,[o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[21]||(e[21]=[n("Backend development: Rest API with node.js and Kotlin/JS")])),_:1})]),l("li",null,[o(t,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:i(()=>e[22]||(e[22]=[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")])),_:1})])])])]),e[44]||(e[44]=l("h2",{id:"_2024-mixit",tabindex:"-1"},[l("a",{class:"header-anchor",href:"#_2024-mixit"},[l("span",null,"(2024) MiXit")])],-1)),e[45]||(e[45]=l("ul",null,[l("li",null,[n("Titre : "),l("strong",null,"Développement front et back en Kotlin. Une visite guidée de KMP")])],-1)),e[46]||(e[46]=l("p",null,"![qr code](../../assets/qrcode-mixtit24.svg =200x)",-1)),e[47]||(e[47]=l("h3",{id:"agenda-3",tabindex:"-1"},[l("a",{class:"header-anchor",href:"#agenda-3"},[l("span",null,"Agenda")])],-1)),l("ul",null,[l("li",null,[o(t,{to:"/en/presentation/#prerequisites"},{default:i(()=>e[24]||(e[24]=[n("Prérequis")])),_:1})]),l("li",null,[e[27]||(e[27]=n("Fonctionnalités notables: ")),o(t,{to:"/en/kotlin-features/#null-safety"},{default:i(()=>e[25]||(e[25]=[n("null safety")])),_:1}),e[28]||(e[28]=n(" et ")),o(t,{to:"/en/kotlin-features/#functions"},{default:i(()=>e[26]||(e[26]=[n("les fonctions")])),_:1})]),l("li",null,[e[32]||(e[32]=l("strong",null,"Développement backend",-1)),l("ul",null,[e[31]||(e[31]=l("li",null,[l("a",{href:"https://speakerdeck.com/yostane/kotlin-pour-le-developpement-backend",target:"_blank",rel:"noopener noreferrer"},"Présentation d'introduction à Kotlin pour le développement backend")],-1)),l("li",null,[o(t,{to:"/en/backend-development/#spring-framework"},{default:i(()=>e[29]||(e[29]=[n("API Rest avec Spring boot")])),_:1})]),l("li",null,[o(t,{to:"/en/backend-development/#ktor"},{default:i(()=>e[30]||(e[30]=[n("API Rest avec Ktor")])),_:1})])])]),l("li",null,[e[34]||(e[34]=l("strong",null,"Développement frontend",-1)),l("ul",null,[l("li",null,[o(t,{to:"/en/front-development/#compose"},{default:i(()=>e[33]||(e[33]=[n('Application "Hello World" avec Compose Multiplatform')])),_:1})])])]),l("li",null,[e[36]||(e[36]=l("strong",null,"Développement fullstack",-1)),l("ul",null,[l("li",null,[o(t,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:i(()=>e[35]||(e[35]=[n("Application de quiz avec Ktor + Compose Multiplatform")])),_:1})])])]),l("li",null,[e[40]||(e[40]=l("strong",null,"Autres fonctionnalités",-1)),l("ul",null,[l("li",null,[o(t,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:i(()=>e[37]||(e[37]=[n("Kotlin/WASM")])),_:1})]),l("li",null,[o(t,{to:"/en/backend-development/#node.js"},{default:i(()=>e[38]||(e[38]=[n("Développement node.js en Kotlin")])),_:1})]),l("li",null,[o(t,{to:"/en/kotlin-features-advanced/#concurrency-and-coroutines"},{default:i(()=>e[39]||(e[39]=[n("Coroutines")])),_:1})])])])])])}const b=d(m,[["render",f],["__file","index.html.vue"]]),A=JSON.parse(`{"path":"/en/workshops/","title":"📅 Workshops","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"(2023) Android makers : Kotlin Beyond Android","slug":"_2023-android-makers-kotlin-beyond-android","link":"#_2023-android-makers-kotlin-beyond-android","children":[{"level":3,"title":"Link","slug":"link","link":"#link","children":[]},{"level":3,"title":"Agenda","slug":"agenda","link":"#agenda","children":[]}]},{"level":2,"title":"(2023) JNation : Let's discover the possibilities of Kotlin","slug":"_2023-jnation-let-s-discover-the-possibilities-of-kotlin","link":"#_2023-jnation-let-s-discover-the-possibilities-of-kotlin","children":[{"level":3,"title":"Link","slug":"link-1","link":"#link-1","children":[]},{"level":3,"title":"Agenda","slug":"agenda-1","link":"#agenda-1","children":[]}]},{"level":2,"title":"(2023) Mobile DevOps summit","slug":"_2023-mobile-devops-summit","link":"#_2023-mobile-devops-summit","children":[{"level":3,"title":"Agenda","slug":"agenda-2","link":"#agenda-2","children":[]}]},{"level":2,"title":"(2024) MiXit","slug":"_2024-mixit","link":"#_2024-mixit","children":[{"level":3,"title":"Agenda","slug":"agenda-3","link":"#agenda-3","children":[]}]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/workshops/README.md"}`);export{b as comp,A as data};
diff --git a/assets/index.html-COrAJfz9.js b/assets/index.html-COrAJfz9.js
new file mode 100644
index 0000000..dc544f2
--- /dev/null
+++ b/assets/index.html-COrAJfz9.js
@@ -0,0 +1,7 @@
+import{_ as I,c as g,a,o as c}from"./app-Djq7wF8p.js";const i={};function e(n,l){return c(),g("div",null,l[0]||(l[0]=[a(`
Kotlin is an object oriented language with functional features. This chapter covers important and relevant features of the language slit into basic and intermediate. Another chapter covers advanced features.
In a nutshell, null safety is a compiler feature that eliminates the infamous Null pointer exception or npe. The Kotlin compiler reports errors and warnings when we manipulate nullable (also called optional) values. Here is a list of null safety features provided by Kotlin:
All types are non-nullable by default; we cannot assign null to a variable or an argument.
For example, this code fails var s: String = null.
A type can be made nullable by suffixing it with a ?. For example: var s: String? = null.
Kotlin forbids calling a method or a property of a non-nullable type, unless we do one of these possibilities:
Use optional chaining with the ? suffix.
Provide a default value with the elvis ?: operator.
Smart-cast the nullable into a non-nullable.
Use the !! operator that eliminates compiler checks. This should never be used.
Never unwrap with !!
Use other safe techniques instead.
▶️ this code illustrates null safety and how to use optional types.
Java \`Optional\` does not provide compile time null checks
Optional wrap null values on runtime. The Java compiler (as of version 17) does not provide unwrapping features such as smart casting. It is still possible to have a npe like this: Optional<String> s = null; s.isPresent();
Enumerations allow to work with a group of values in a type-safe fashion. Unlike Java enums, Kotlin enums are classes. Kotlin enum classes provide these features:
when statements support enumerations.
Enum constants can declare their own anonymous classes with their corresponding methods, as well as with overriding base methods.
An enum class can implement an interface but it cannot derive from a class
There are methods for listing the defined enum constants and getting an enum constant by its name.
Every enum constant has properties for obtaining its name and position (starting with 0).
Kotlin allows to write concise OOP code and has the following features:
Available common features: classes, inheritance, interfaces, and abstract classes.
Native support of properties: do not define getters and setters unless needed.
Just add get() and set(value) functions next to the property declaration.
Constructor arguments are defined next to the class name class ClassName(arg1, atg2, )
Prefixing a constructor arguments with val or var makes it a property (val makes it read-only).
The constructor name is init and does not require parameters.
The compiler checks that all non-nullable properties are initialized by the end of the constructor.
⚠️ The compiler does not check the initialization of lateinit properties. Thus, accessing them before while uninitialized causes an exception.
Prefix classes with open to allow inheritance.
Kotlin enables the public access level by default.
The equality operator == calls equals() (as opposed to Java which uses reference equality).
A companion object contains static methods and properties.
Extensions add function and properties to existing classes.
💡 They replace inheritance in many situations.
For example, we can add functions to the String class instead of creating a new StringUtils class.
Sealed classes and interfaces cannot be extended or implemented by third parties.
Do not define accessors unless needed
As opposed to Java, Kotlin supports properties and allows to add accessors later without refactoring the code that calls these properties. Thus, by default, just define the name of properties without accessors and use them directly.
Functional programming revolves around these concepts: pure functions, recursion, referential transparency, immutable variables, functions as first-class citizens, and higher-order functions.
Let's briefly explain these concepts:
Immutable variables means that we cannot change the value of a variable or its properties once it has been created. If we want to do so, we must create a new instance with the new value.
Pure functions are functions that do not have side effects and will thus return always the same output given the same input.
Functions are first class citizens: they can be assigned to a variable or used in higher-order functions (passed as a function parameter to another function or returned from a function).
Referential transparency: means that an expression can be replaced by its result without changing the behavior of the program. Transparency refers to the fact that the implementation of the expression is irrelevant.
💡 Pure functional languages provide these features natively and enforces them (at build time).
Kotlin is not a pure functional languages but it supports some features. For example, Kotlin does not have compile time verification of pure functions, but it provides immutable collections through the kotlinx.collections.immutable library.
listOf generates read-only lists, which are not immutable
A read-only list cannot add or remove elements, but it can change the underlying data.
Declarative programming is a famous style within functional programming. It consists of writing code as a chaining of function calls in this style val result = f(x).g(y). .... Higher order functions replace many situation where we would use loops. This favors readable code which is easy to debug an maintain.
▶️ this code show an example of list manipulation using declarative programming.
`,77)]))}const s=I(i,[["render",e],["__file","index.html.vue"]]),t=JSON.parse('{"path":"/en/kotlin-features/","title":"📚 Kotlin language features","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Basic features","slug":"basic-features","link":"#basic-features","children":[{"level":3,"title":"Basic constructs (variables, control flow)","slug":"basic-constructs-variables-control-flow","link":"#basic-constructs-variables-control-flow","children":[]},{"level":3,"title":"Functions","slug":"functions","link":"#functions","children":[]},{"level":3,"title":"Null safety","slug":"null-safety","link":"#null-safety","children":[]},{"level":3,"title":"Enumerations","slug":"enumerations","link":"#enumerations","children":[]},{"level":3,"title":"🧪 Exercises","slug":"🧪-exercises","link":"#🧪-exercises","children":[]}]},{"level":2,"title":"Intermediate features","slug":"intermediate-features","link":"#intermediate-features","children":[{"level":3,"title":"Object oriented programming","slug":"object-oriented-programming","link":"#object-oriented-programming","children":[]},{"level":3,"title":"Data class","slug":"data-class","link":"#data-class","children":[]},{"level":3,"title":"Functional programming","slug":"functional-programming","link":"#functional-programming","children":[]},{"level":3,"title":"Kotlin and Java interoperability","slug":"kotlin-and-java-interoperability","link":"#kotlin-and-java-interoperability","children":[]},{"level":3,"title":"🧪 Exercises","slug":"🧪-exercises-1","link":"#🧪-exercises-1","children":[]}]},{"level":2,"title":"📖 Further reading","slug":"📖-further-reading","link":"#📖-further-reading","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/kotlin-features/README.md"}');export{s as comp,t as data};
diff --git a/assets/index.html-CcuJhM9n.js b/assets/index.html-CcuJhM9n.js
new file mode 100644
index 0000000..7da026c
--- /dev/null
+++ b/assets/index.html-CcuJhM9n.js
@@ -0,0 +1 @@
+import{_ as t}from"./kmp_codelab-CiTPMWjt.js";import{_ as a,c as o,a as r,o as i}from"./app-Djq7wF8p.js";const n={};function l(s,e){return i(),o("div",null,e[0]||(e[0]=[r('
',7)]))}const c=a(n,[["render",l],["__file","index.html.vue"]]),h=JSON.parse(`{"path":"/en/other-technologies/","title":"🛠 Let's make a cross-plaform app !","lang":"en-US","frontmatter":{},"headers":[],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/other-technologies/README.md"}`);export{c as comp,h as data};
diff --git a/assets/index.html-CeMqYfna.js b/assets/index.html-CeMqYfna.js
new file mode 100644
index 0000000..77358e1
--- /dev/null
+++ b/assets/index.html-CeMqYfna.js
@@ -0,0 +1 @@
+import{_ as t,c as i,a as n,o as r}from"./app-Djq7wF8p.js";const l={};function a(s,e){return r(),i("div",null,e[0]||(e[0]=[n('
Conseil
This training is also available in English / Cette formation est aussi disponible en Anglais
',5)]))}const m=t(l,[["render",a],["__file","index.html.vue"]]),p=JSON.parse(`{"path":"/fr/","title":"","lang":"fr-FR","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Débuter →","link":"/fr/presentation/","type":"primary"}],"features":[{"title":"Bien commencer avec Kotlin","details":"lorem"},{"title":"Syntaxe de base","details":"lorem"},{"title":"Exemples","details":"lorem"}],"footer":"Worldline, 2021"},"headers":[{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Liens utiles","slug":"liens-utiles","link":"#liens-utiles","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/index.md"}`);export{m as comp,p as data};
diff --git a/assets/index.html-D-c6bFFq.js b/assets/index.html-D-c6bFFq.js
new file mode 100644
index 0000000..d375f50
--- /dev/null
+++ b/assets/index.html-D-c6bFFq.js
@@ -0,0 +1 @@
+import{_ as t}from"./kotlin-used-for-Bdlavnqs.js";import{_ as i,c as o,a as n,o as r}from"./app-Djq7wF8p.js";const a="/learning-kotlin/assets/kotlin-decision-tree-4i7nEr1Z.svg",l={};function s(h,e){return r(),o("div",null,e[0]||(e[0]=[n('
',23)]))}const p=i(l,[["render",s],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/en/presentation/","title":"🚀 Presentation of Kotlin","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Some features","slug":"some-features","link":"#some-features","children":[]},{"level":2,"title":"History","slug":"history","link":"#history","children":[]},{"level":2,"title":"Some numbers and facts","slug":"some-numbers-and-facts","link":"#some-numbers-and-facts","children":[]},{"level":2,"title":"Why switch from Java to Kotlin","slug":"why-switch-from-java-to-kotlin","link":"#why-switch-from-java-to-kotlin","children":[]},{"level":2,"title":"A decision tree to help you decide if you should use Kotlin","slug":"a-decision-tree-to-help-you-decide-if-you-should-use-kotlin","link":"#a-decision-tree-to-help-you-decide-if-you-should-use-kotlin","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"📖 Further reading","slug":"📖-further-reading","link":"#📖-further-reading","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/presentation/README.md"}');export{p as comp,m as data};
diff --git a/assets/index.html-DI1rj2RX.js b/assets/index.html-DI1rj2RX.js
new file mode 100644
index 0000000..1c3c62f
--- /dev/null
+++ b/assets/index.html-DI1rj2RX.js
@@ -0,0 +1,13 @@
+import{_ as n,c as s,a,o as t}from"./app-Djq7wF8p.js";const l={};function c(i,e){return t(),s("div",null,e[0]||(e[0]=[a(`
Kotlin permet de déléguer le getter et le setter d'une propriété à un autre objet, appelé délégué. C'est une classe qui définit les méthodes getValue et setValue.
Kotlin fournit des délégués standard tels que des propriétés paresseuses et des propriétés observables.
Kotlin fournit un modèle de concurrence de haut niveau appelé Coroutines. Le développeur peut déléguer la gestion des threads au compilateur et à l'exécution et utiliser des constructions de niveau supérieur aux threads pour exprimer des opérations asynchrones.
Les coroutines de Kotlin tournent autour de ces concepts :
Une coroutine est une instance de calcul suspendable.
Kotlin a de nombreuses méthodes pour créer une coroutine telle que launch.
Une coroutine doit exister dans une portée de coroutine.
Par exemple, runBlocking crée une portée de coroutine dans laquelle les coroutines peuvent être lancées.
Une coroutine peut exécuter des fonctions de suspension qui peuvent suspendre la coroutine mais ne bloquent pas le thread.
Par exemple : le delay suspend la coroutine mais ne bloque pas le thread sur lequel elle s'exécute.
Les fonctions de suspension sont des opérations qui peuvent prendre du temps telles que les requêtes http et les appels au système de fichiers.
Le qualificateur suspend définit une fonction de suspension. Il s'exécute dans une coroutine et peut appeler d'autres fonctions de suspension.
Flow permet de générer une liste de valeurs asynchrones.
Deferred et Channel transfèrent respectivement une valeur unique et un flux de valeurs entre coroutines.
▶️ this code show how to create a coroutine and suspend function and how to use them.
Comme vu précédemment, les extensions de fonctions ajoute du comportement à des classes existantes sans utiliser l'héritage. À l'intérieur de la définition de l'extension de fonction, nous pouvons référencer implicitement le récepteur d'extension (this).
fun String.countCharacters()= length // or this.length
+println("hello".countCharacters())// prints 5
+
Nous pouvons définir cette extension avec une fonction littérale (ou lambda) au lieu d'une fonction classique (déclarée avec fun).
var extFn: String.()-> Int
+extFn ={ length }// extFn is a function literal
+println("hello".extFn())// prints 5
+println(extFn("hello"))// prints 5
+
extFnest une fonction littérale (lambda) qui a accès au récepteur (this). C'est pourquoi on l'appelle une fonction littérale avec récepteur.
extFn("hello") ou extFn("hello") appelle l'extension comme prévu par les fonctions d'extension.
Le type d'une fonction littérale avec récepteur est funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType et est appelé avec funName(receiverValue, arg1Value, etc.) ou receiverValue.funName(arg1Value, etc.) .). Cependant, ce n'est pas l'aspect le plus intéressant.
La partie importante est extFn = { length } qui peut être placée comme argument de fonction dans une fonction d'ordre supérieur. Le développeur qui appelle la fonction d'ordre supérieur doit définir extFn, qui à son tour a accès au récepteur. Cela permet un style de programmation assez intéressant.
Les Type-safe builders combinent les monteurs bien nommées et les fonctions littérales avec récepteur pour créer des monteur avec un typage statique et sécurisé. La syntaxe particulière possible avec technique permet de définir une sorte de sous-langage aussi appelé DSL (domain specific language).
open the java-integration-exercise projects in the materials folder.
Have a look at the Java class we provided you in the src/main/java/com/worldline/learning/kotlin/java2kotlin package. (yes, that's the Pokemon class)
Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at src/main/kotlin/com/worldline/learning/kotlin/java2kotlin)
Have a look at the generated Kotlin code, and note the major differences you spot!
`,29)]))}const p=n(l,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/fr/kotlin-features-advanced/","title":"📚 Fonctionnalités avancées de Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Propriétés déléguées","slug":"proprietes-deleguees","link":"#proprietes-deleguees","children":[]},{"level":2,"title":"Concurrence et coroutines","slug":"concurrence-et-coroutines","link":"#concurrence-et-coroutines","children":[]},{"level":2,"title":"Littéral de fonction avec récepteur et constructeurs de type sécurisé","slug":"litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise","link":"#litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise","children":[]},{"level":2,"title":"Exercises","slug":"exercises","link":"#exercises","children":[{"level":3,"title":"Exercise 1","slug":"exercise-1","link":"#exercise-1","children":[]}]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/kotlin-features-advanced/README.md"}');export{p as comp,u as data};
diff --git a/assets/index.html-DgauC6Dk.js b/assets/index.html-DgauC6Dk.js
new file mode 100644
index 0000000..378c867
--- /dev/null
+++ b/assets/index.html-DgauC6Dk.js
@@ -0,0 +1,195 @@
+import{_ as c,c as r,a as l,b as e,d as t,r as o,o as u,e as n,f as s}from"./app-Djq7wF8p.js";const d={};function k(m,a){const i=o("CodeGroupItem"),p=o("CodeGroup");return u(),r("div",null,[a[2]||(a[2]=l(`
De nombreux frameworks supportent officiellement Kotlin comme Spring, Quarkus et Ktor, parmi d'autres listés ici.
En outre, Kotlin est théoriquement compatible avec tout framework qui cible la JVM ou JS. Cependant, les frameworks qui ne supportent pas officiellement Kotlin peuvent nécessiter quelques ajustements pour l'utiliser.
Ktor est une bibliothèque Kotlin multiplateforme permettant de développer des clients et des serveurs HTTP. Cela fait de Ktor une bibliothèque utile à la fois aux développeurs frontend, pour la partie client HTTP, ainsi qu'aux développeurs backend, pour la partie serveur HTTP. Dans ce qui suit, nous allons créer une API REST avec le serveur Ktor.
Créez un projet sur start.ktor.io avec les plugins suivants : Content Negotiation, kotlinx.serialization, et Routing.
Cliquez sur "Generate project".
Téléchargez l'archive, décompressez-la et ouvrez le projet avec votre IDE préféré.
Créez un package models et ajoutez-y une classe de données Customer avec ces propriétés immuables id : String, firstName : String, lastName : Chaîne, email : Chaîne.
Annotez la classe avec @Serializable.
Créez un nouveau package nommé routes et ajoutez-y un fichier CustomerRoutes.kt qui contiendra le code pour l'endpoint /customer.
Le code ci-dessous fournit l'implémentation de certains endpoints. Veuillez implémenter les autres.
Pour activer la route, appelez customerRouting() dans le fichier de configuration du routage situé dans plugins/Routing.kt.
Pour plus de simplicité, utilisez une liste globale de clients en mémoire val store = mutableListOf<Customer>().
Lancer le serveur en exécutant la méthode main.
Tester l'API sur l'IDE en utilisant un fichier http ou en utilisant n'importe quel autre client.
CustomerRoutes.kt
val store = mutableListOf<Customer>()
+
+fun Route.customerRouting(){
+route("/customer"){
+get{
+ call.respond(store)
+}
+get("{id?}"){
+val id = call.parameters["id"]?:return@get call.respondText(
+"Missing id",
+ status = HttpStatusCode.BadRequest
+)
+val customer =
+ store.find{ it.id == id }?:return@get call.respondText(
+"Pas de client avec l'id $id",
+ status = HttpStatusCode.NotFound
+)
+ call.respond(customer)
+}
+ post {
+val customer = call.receive<Customer>()
+ store.add(customer)
+ call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
+}
+delete("{id?}"){
+// TODO
+}
+}
+}
+
+
plugins/Routing.kt
fun Application.configureRouting(){
+ routing {
+customerRouting()
+}
+}
+
return@label
Vous pouvez spécifier le niveau que vous voulez retourner avec un label explicite en utilisant return@lambda.
Grâce à Kotlin/JS, nous pouvons écrire des applications qui ciblent node.js en utilisant Kotlin.
On peut même importer des librairies npm à condition de déclarer les API JS que l'on va utiliser en Kotlin. C'est ce qu'on appelle une déclaration externe (vous pouvez la considérer comme un équivalent des définitions de type de TypeScript) qui déclare les symboles auxquels nous voulons accéder en Kotlin grâce aux annotations @JsModule et @JsNonModule. Définir de telles déclarations externes peut s'avérer fastidieux et il ne semble pas y avoir de générateur automatique officiel et stable (dukat a été supprimé dans kotlin 1.8.20). Dans ce cas, nous avons deux options, soit écrire la déclaration externe nous-même, soit l'importer en tant que dépendance si elle est disponible.
Heureusement pour nous, le prochain TP utilise la librairie Express pour laquelle nous pouvons trouver une déclaration de type externe.
Modifiez main.kt comme suit. Cela crée un serveur API REST qui écoute le port 3000 et fournit une route GET /hello.
dataclassMessage(val id : Int,val message : String)
+
+funmain(){
+val messages =listOf(Message(0,"I love Kotlin/JS"))
+val app = express.Express()
+ app.get("/hello"){ req, res ->
+ res.send(messages)
+}
+
+ app.listen(3000){
+ console.log("server start at port 3000")
+}
+}
+
Exécutez la tâche nodeRun depuis votre IDE ou depuis la ligne de commande (si vous avez installé Gradle).
Si vous rencontrez une erreur avec Yarn lock, exécutez la tâche kotlinUpgradeYarnLock puis réessayez.
Ajouter des routes en POST, PUT et DELETE
En ce qui concerne le corps du POST, Express positionne req.body à undefined à moins que nous ne spécifions un body parser.
Pour un corps en JSON, nous devons appeler app.use(bodyParser.json()).
bodyParser est une bibliothèque npm et malheureusement, chrisnkrueger/kotlin-express ne fournit pas de définition externe pour bodyParser au moment de l'écriture de ces lignes (chrisnkrueger/kotlin-express en version 1.2.0).
Spring est un framework célèbre pour le développement d'applications côté serveur : API REST, pages web générées par le serveur, microservices, etc. Il s'appuie sur l'écosystème Java pour la compilation et l'exécution, ce qui le rend compatible avec Kotlin. Mieux encore, Spring supporte officiellement Kotlin. On peut même démarrer un nouveau projet avec Kotlin et Gradle-Kotlin. Dans la prochaine section, nous utiliserons ce projet pour recréer notre API REST plus haut avec Spring.
Vérifiez que la partie plugins build.gradle.kts utilise la dernière version de Kotlin. Voici à quoi cela devrait ressembler avec Kotlin 1.8.10 :
plugins {
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
Créez la data class Customer dans le package model (sans l'annotation @Serializable).
Créez un paquetage controller qui contient une classe CustomerController qui fournit un CRUD en utilisant une liste globale.
Vous pouvez trouver un squelette ci-dessous.
💡 Dans Spring, les contrôleurs Rest servent de routes Ktor, où un contrôleur définit une ressource REST.
Définissez les mêmes routes que dans le TP précédent.
Démarrez le serveur de l'API REST en exécutant :
Sur Powershell : .\\gradlew.bat bootRun
Tout shell Unix : .\\gradlew bootRun
Ou bien, vérifiez si votre IDE fournit déjà des configurations d'exécution pour les projets Spring Boot.
Allons un peu plus loin en stockant des données dans une base de données et en écrivant quelques tests.
Nous utiliserons la base de données en mémoire H2 pour des raisons de simplicité, puisqu'elle ne nécessite pas de serveur pour fonctionner. Les classes seront mappées aux tables de la base de données avec des annotations JPA. L'API de base de données que nous utiliserons s'appelle JPARepository. C'est une API légère qui fournit des fonctionnalités CRUD communes à partir d'une simple une interface.
Créez un nouveau projet Spring en utilisant Spring initializr avec Kotlin et les dépendances suivantes : Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web.
Ouvrez le projet et ajoutez cette classe dans le package model@Entity class Product(@Id @GeneratedValue var id : Long ? = null, var name : String, var price : Int). Ceci définit la classe ainsi que les annotations JPA minimales (@Entity, @Id et @GeneratedValue) pour générer la table correspondante.
Dans le package repository, déclarez l'interface ProductRepository comme suit interface ProductRepository : JpaRepository<Produit, Long>. C'est suffisant pour que Spring génère une implémentation avec des caractéristiques communes comme nous le verrons plus tard.
Ensuite, créez une classe ProductService qui contiendra la logique métier. En termes d'architecture, le contrôleur appelle un service qui, à son tour, s'appuie sur d'autres services ou référentiels.
ProductService.kt
@Service
+classProductService(@Autowiredval productRepository: ProductRepository){
+fungetAll()= productRepository.findAll()
+
+// use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product?
+fungetById(id: Long)= productRepository.findByIdOrNull(id)
+}
+
Dans le package controller, créez une classe ProductController qui est mappée à /product et injectée avec @Autowired. Répondez à @Get comme suit.
Exécutons le projet. Avant de lancer le projet, nous devons ajouter un plugin qui permet aux classes Kotlin de générer un constructeur par défaut id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10". Les plugins devraient ressembler à ce qui suit :
plugins {
+id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
+
En guise d'exercice, implémentez ces routes : POST d'un seul produit, DELETE par id (/produit/{id}) et GET par id (/produit/{id}).
Indice : ProductController fournit déjà les méthodes nécessaires.
Appelez les différents points de terminaison avec un client REST.
Les frameworks Spring permettent d'effectuer différents types de tests en fournissant différentes classes dès le départ :
Tests unitaires/de composants des services et de l'API REST. Cela se fait par le biais d'utilitaires de bouchonnage tels que MockMVC.
Tests d'intégration de l'API REST en utilisant TestRestTemplate. Dans ce cas, un serveur complet est exécuté et testé.
La plupart des classes fournies par Spring, si ce n'est toutes, offrent une syntaxe élégante pour les développeurs Java. Certaines d'entre elles vont plus loin en tirant parti des caractéristiques spécifiques de Kotlin. Dans ce qui suit, nous allons nous concentrer sur les parties qui fournissent des DSLs Kotlin, à savoir le test unitaire de l'API REST avec MockMVC.
Créer une classe de test ProductControllerUnitTests avec le contenu initial ci-dessous. MockMvc permet de tester unitairement l'API REST. L'annotation @AutoConfigureMockMvc permet à Spring de la configurer automatiquement.
Ajoutez les deux tests ci-dessous. Le premier utilise une approche classique tandis que le second tire parti des capacités du DSL de Kotlin. De plus, nous utilisons une chaîne littérale plus lisible.
En guise d'exercice, écrire des tests pour les autres points d'accès.
Le constructeur de requêtes de JpaRepository
Les repository Spring implémentent des requêtes basées sur le nom de leurs méthodes. Par exemple, pour obtenir tous les produits triés par nom, nous pouvons ajouter cette méthode à l'interface.
`,9))])}const g=c(d,[["render",k],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/fr/backend-development/","title":"📚 Développement du backend","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Ktor","slug":"ktor","link":"#ktor","children":[{"level":3,"title":"TP : développer une API avec Ktor","slug":"tp-developper-une-api-avec-ktor","link":"#tp-developper-une-api-avec-ktor","children":[]}]},{"level":2,"title":"node.js","slug":"node-js","link":"#node-js","children":[{"level":3,"title":"TP : API Rest avec Kotlin/JS et Express","slug":"tp-api-rest-avec-kotlin-js-et-express","link":"#tp-api-rest-avec-kotlin-js-et-express","children":[]}]},{"level":2,"title":"Spring framework","slug":"spring-framework","link":"#spring-framework","children":[{"level":3,"title":"TP : Spring boot part 1 - développer la même API avec Spring Boot","slug":"tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot","link":"#tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot","children":[]},{"level":3,"title":"TP : Spring boot partie 2 - ajouter une base de données","slug":"tp-spring-boot-partie-2-ajouter-une-base-de-donnees","link":"#tp-spring-boot-partie-2-ajouter-une-base-de-donnees","children":[]},{"level":3,"title":"TP : Spring boot partie 3 - ajouter des tests","slug":"tp-spring-boot-partie-3-ajouter-des-tests","link":"#tp-spring-boot-partie-3-ajouter-des-tests","children":[]},{"level":3,"title":"Projets terminés","slug":"projets-termines","link":"#projets-termines","children":[]}]},{"level":2,"title":"Aller plus loin","slug":"aller-plus-loin","link":"#aller-plus-loin","children":[]},{"level":2,"title":"Lien et références","slug":"lien-et-references","link":"#lien-et-references","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/backend-development/README.md"}');export{g as comp,b as data};
diff --git a/assets/index.html-Dz4P2gMg.js b/assets/index.html-Dz4P2gMg.js
new file mode 100644
index 0000000..f1bda5f
--- /dev/null
+++ b/assets/index.html-Dz4P2gMg.js
@@ -0,0 +1 @@
+import{_ as t}from"./logo_worldline-dinT9MYm.js";import{_ as n,c as i,a,o as r}from"./app-Djq7wF8p.js";const l={};function s(o,e){return r(),i("div",null,e[0]||(e[0]=[a('
',10)]))}const p=n(l,[["render",s],["__file","index.html.vue"]]),c=JSON.parse(`{"path":"/en/","title":"Welcome","lang":"en-US","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Get started →","link":"/en/presentation/","type":"primary"}],"features":[{"title":"Language features","details":"null safety, extensions, lambdas, Java interoperability and more"},{"title":"Backend development","details":"With Ktor, spring and node.js"},{"title":"Frontend development","details":"Compose multiplatform, Kotlin/JS, Kotlin/WASM and JVM frameworks"},{"title":"Cross-platform development","details":"With KMP and Compose multiplatform"},{"title":"Advanced Kotlin","details":"Coroutines, delegates, Function literal with receiver, DSLs and more"},{"title":"Practical exercises and solutions","details":"All chapters have a set of exercises"}],"footer":"Worldline, 2023"},"headers":[{"level":2,"title":"Who we are","slug":"who-we-are","link":"#who-we-are","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Useful links","slug":"useful-links","link":"#useful-links","children":[]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/index.md"}`);export{p as comp,c as data};
diff --git a/assets/index.html-jwVfesBb.js b/assets/index.html-jwVfesBb.js
new file mode 100644
index 0000000..7f5e3c6
--- /dev/null
+++ b/assets/index.html-jwVfesBb.js
@@ -0,0 +1 @@
+import{_ as s}from"./Kotlin-Beyond-Android-CYulNy7n.js";import{_ as d,c as a,a as i,e,b as o,d as r,f as n,r as p,o as u}from"./app-Djq7wF8p.js";const f="/learning-kotlin/assets/Androidmakers2023Kotlinshortlink-7XGfMd7N.svg",m={};function k(v,l){const t=p("RouteLink");return u(),a("div",null,[l[42]||(l[42]=i('
',7)),e("ul",null,[e("li",null,[o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[0]||(l[0]=[n("Backend development: Rest API with Ktor")])),_:1})]),e("li",null,[l[2]||(l[2]=n("Optional: ")),o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[1]||(l[1]=[n("Backend development: Rest API with node.js and Kotlin/JS")])),_:1})]),e("li",null,[o(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:r(()=>l[3]||(l[3]=[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")])),_:1})]),e("li",null,[o(t,{to:"/fr/front-development/#compose"},{default:r(()=>l[4]||(l[4]=[n("Front-end development: cross-platform Hello world App with Compose multiplatform")])),_:1})]),l[6]||(l[6]=e("li",null,[e("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1)),e("li",null,[o(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:r(()=>l[5]||(l[5]=[n("Full-stack development: Quiz App with Compose multiplatform and Ktor server")])),_:1})])]),l[43]||(l[43]=i('
',4)),e("ul",null,[e("li",null,[o(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:r(()=>l[7]||(l[7]=[n("Développement d'une application multi-plateformes")])),_:1})]),e("li",null,[l[11]||(l[11]=n("Démonstration des autres possibilités: ")),e("ul",null,[e("li",null,[o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[8]||(l[8]=[n("API Rest avec Ktor")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[9]||(l[9]=[n("API Rest avec node.js et Kotlin/JS")])),_:1})]),e("li",null,[o(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:r(()=>l[10]||(l[10]=[n("Webapp Kotlin/WASM et Kotlin/JS")])),_:1})])])])]),l[44]||(l[44]=i('
',4)),e("ul",null,[e("li",null,[o(t,{to:"/fr/presentation/#prerequisites"},{default:r(()=>l[12]||(l[12]=[n("Prérequis")])),_:1})]),e("li",null,[l[15]||(l[15]=n("Caractéristiques notables de Kotlin: ")),o(t,{to:"/fr/kotlin-features/#null-safety"},{default:r(()=>l[13]||(l[13]=[n("le null safety")])),_:1}),l[16]||(l[16]=n(" et ")),o(t,{to:"/fr/kotlin-features/#functions"},{default:r(()=>l[14]||(l[14]=[n("les fonctions")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#spring-framework"},{default:r(()=>l[17]||(l[17]=[n("API Rest avec Spring boot")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[18]||(l[18]=[n("API Rest avec Ktor")])),_:1})]),e("li",null,[o(t,{to:"/fr/front-development/#compose"},{default:r(()=>l[19]||(l[19]=[n(`Développement d'une application multi-plateformes type "Hello World" avec Compose Multiplatform`)])),_:1})]),e("li",null,[o(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:r(()=>l[20]||(l[20]=[n("Dévelopment sullstack d'une application de Quiz avec with Compose multiplatform en front et Ktor server en backend")])),_:1})]),e("li",null,[l[24]||(l[24]=n("Autres fonctionnalités et possibilités: ")),e("ul",null,[e("li",null,[o(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:r(()=>l[21]||(l[21]=[n("Kotlin/WASM")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#nodejs"},{default:r(()=>l[22]||(l[22]=[n("Serveur node.js avec Kotlin/JS")])),_:1})]),e("li",null,[o(t,{to:"/fr/kotlin-features-advanced/#concurrency-and-coroutines"},{default:r(()=>l[23]||(l[23]=[n("Coroutines")])),_:1})])])])]),l[45]||(l[45]=e("h2",{id:"_2024-mixit",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2024-mixit"},[e("span",null,"(2024) MiXit")])],-1)),l[46]||(l[46]=e("ul",null,[e("li",null,[n("Titre : "),e("strong",null,"Développement front et back en Kotlin. Une visite guidée de KMP")])],-1)),l[47]||(l[47]=e("p",null,"![qr code](../../assets/qrcode-mixtit24.svg =200x)",-1)),l[48]||(l[48]=e("h3",{id:"agenda-3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#agenda-3"},[e("span",null,"Agenda")])],-1)),e("ul",null,[e("li",null,[o(t,{to:"/fr/presentation/#prerequisites"},{default:r(()=>l[25]||(l[25]=[n("Prérequis")])),_:1})]),e("li",null,[l[28]||(l[28]=n("Fonctionnalités notables: ")),o(t,{to:"/fr/kotlin-features/#null-safety"},{default:r(()=>l[26]||(l[26]=[n("null safety")])),_:1}),l[29]||(l[29]=n(" et ")),o(t,{to:"/fr/kotlin-features/#functions"},{default:r(()=>l[27]||(l[27]=[n("les fonctions")])),_:1})]),e("li",null,[l[33]||(l[33]=e("strong",null,"Développement backend",-1)),e("ul",null,[l[32]||(l[32]=e("li",null,[e("a",{href:"https://speakerdeck.com/yostane/kotlin-pour-le-developpement-backend",target:"_blank",rel:"noopener noreferrer"},"Présentation d'introduction à Kotlin pour le développement backend")],-1)),e("li",null,[o(t,{to:"/fr/backend-development/#spring-framework"},{default:r(()=>l[30]||(l[30]=[n("API Rest avec Spring boot")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#ktor"},{default:r(()=>l[31]||(l[31]=[n("API Rest avec Ktor")])),_:1})])])]),e("li",null,[l[35]||(l[35]=e("strong",null,"Développement frontend",-1)),e("ul",null,[e("li",null,[o(t,{to:"/fr/front-development/#compose"},{default:r(()=>l[34]||(l[34]=[n('Application "Hello World" avec Compose Multiplatform')])),_:1})])])]),e("li",null,[l[37]||(l[37]=e("strong",null,"Développement fullstack",-1)),e("ul",null,[e("li",null,[o(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:r(()=>l[36]||(l[36]=[n("Application de quiz avec Ktor + Compose Multiplatform")])),_:1})])])]),e("li",null,[l[41]||(l[41]=e("strong",null,"Autres fonctionnalités",-1)),e("ul",null,[e("li",null,[o(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:r(()=>l[38]||(l[38]=[n("Kotlin/WASM")])),_:1})]),e("li",null,[o(t,{to:"/fr/backend-development/#nodejs"},{default:r(()=>l[39]||(l[39]=[n("Développement node.js en Kotlin")])),_:1})]),e("li",null,[o(t,{to:"/fr/kotlin-features-advanced/#concurrency-and-coroutines"},{default:r(()=>l[40]||(l[40]=[n("Coroutines")])),_:1})])])])])])}const x=d(m,[["render",k],["__file","index.html.vue"]]),A=JSON.parse('{"path":"/fr/workshops/","title":"📅 Workshops","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Android makers 2023: Kotlin Beyond Android","slug":"android-makers-2023-kotlin-beyond-android","link":"#android-makers-2023-kotlin-beyond-android","children":[{"level":3,"title":"Liens","slug":"liens","link":"#liens","children":[]},{"level":3,"title":"Agenda","slug":"agenda","link":"#agenda","children":[]}]},{"level":2,"title":"Mobile DevOps summit 2023","slug":"mobile-devops-summit-2023","link":"#mobile-devops-summit-2023","children":[{"level":3,"title":"Agenda","slug":"agenda-1","link":"#agenda-1","children":[]}]},{"level":2,"title":"Devoxx Morocco 2023","slug":"devoxx-morocco-2023","link":"#devoxx-morocco-2023","children":[{"level":3,"title":"Agenda","slug":"agenda-2","link":"#agenda-2","children":[]}]},{"level":2,"title":"(2024) MiXit","slug":"_2024-mixit","link":"#_2024-mixit","children":[{"level":3,"title":"Agenda","slug":"agenda-3","link":"#agenda-3","children":[]}]}],"git":{"updatedTime":1729869737000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/workshops/README.md"}');export{x as comp,A as data};
diff --git a/assets/index.html-zXMBiwyM.js b/assets/index.html-zXMBiwyM.js
new file mode 100644
index 0000000..0bb2b5f
--- /dev/null
+++ b/assets/index.html-zXMBiwyM.js
@@ -0,0 +1,24 @@
+import{_ as n}from"./kotlin-wasm-webapp-R4_9ho9v.js";import{_ as t,c as a,a as o,o as s}from"./app-Djq7wF8p.js";const l="/learning-kotlin/assets/launch-android-app-BS4WBFFP.png",i="/learning-kotlin/assets/hello-compose-demo-B4DIIuDy.gif",r={};function p(c,e){return s(),a("div",null,e[0]||(e[0]=[o('
"The Kotlin Multiplatform technology is designed to simplify the development of cross-platform projects. It reduces time spent writing and maintaining the same code for different platforms while retaining the flexibility and benefits of native programming." ₁
KMP relies on Kotlin native and other Kotlin features to help developers create projects that target multiple platforms using a common Kotlin code-base.
Many combinations of targets and use cases are possible:
Full-Stack web apps: A project that contains a backend and a web app while sharing common logic.
Kotlin/JS can also target the web and even use web frameworks (such as react) in Kolitn.
Kotlin WASM is another possibility to target the web but this will generate WASM instead of pure JS code.
It can be used for example to develop computation intensive libraries.
Maybe we can do even more in the future with as all these technologies (Kotlin, WASM and Kotlin/WASM) evolve. - For example, WASI allows WASM to communicate with the operating system. - This means that me may see Kotlin/WASM project projects in the future that can target both the browser and the OS.
Let's create a Kotlin/WASM app. By cloning git clone git@github.com:Kotlin/kotlin-wasm-examples.git and opening the browser-example folder in your IDE.
Open the project and run the wasmJsBrowserRun task.
The development server should start and you can open your WASM powered webapp on http://localhost:8080/
⚠️ You may need to activate some flags on your browser for the app to work. If you see a blank page, please read the browser logs to check for the instructions.
Please check the contents of src/wasmJsMain/kotlin/Simple.kt to understand how the page is coded.
Next, let's check the generated wasm file which is available in build/js/packages/project_name/kotlin
WASM being a binary format, we need to convert it first to text format.
Both Kotlin/WASM and Kotlin/JS IntelliJ work somewhat similarly.
Both rely on the KMP plugin
Kotlin/WASM is enabled by adding a wasmjs section in the build.gradle.kts file, while Kotlin/JS is enabled by adding a js section.
The Kotlin code will compile to WASM and / or JS. Kotlin/JS generates only JS while Kotin/WASM generates both JS and WASM.
In both cases, the entry point of the generated code is a JS file called module_name.js.
The index.html in the resources folder loads the generated JS explained above (the one named module_name.js).
The task wasmBrowserDevelopmentRun or jsWasmBrowserDevelopmentRun run a local server that hosts both the index.html files and the generated JS and WASM files.
"Compose Multiplatform simplifies and accelerates UI development for Desktop and Web applications, and allows extensive UI code sharing between Android, iOS, Desktop and Web. It's a modern toolkit for building native UI. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs."
While very similar, Compose multiplatform is different from Jetpack Compose as the latter is only compatible with Android. Google provides a JetPack compose tutorial for Android development.
Compose HTML is not cross-platform
Compose HTML is UI a library targeting Kotlin/JS which is not compatible with Compose Multiplatform (it is a different API). For cross-platform UI development with Compose Multiplatform, compose Web is the choice.
We'll create a multiplatform app using the official template. At the time of writing, this template does not include a compose web target.
Please check that your environment is correctly setup as explained here.
On Windows and Linux, we don't need to install iOS/macOS related tools but and we won't be able to run iOS/macOS targets.
If we don't want to install Android Studio, we need at least to install the Android SDK either through the official installer or from the "Languages and Framework -> Android SDK" menu in the settings.
Open the official template and either download a zip or use the "use this template" options on GitHub.
Open the downloaded projet. You'll note that it contains these modules:
a shared module (or subproject) that contains common code as well as
and another module for earch targeted platform: androidApp, iOSApp and desktopApp (When web will be included in the template, we should also see a webApp project). These contain the source code of the apps itself (such as the main activity in Android, the @main App in iOS and the main function in desktopJVM) and well as platform specific resources that cannot be placed in the shared module. Some examples of such files are the AndroidManifest.xml for android and the info.plist in iOS.
In order to run the desktopApp, open a terminal on the project root folder and launch this command: ./gradlew desktopApp:run.
In order to run the Android App, the simplest way is to launch it from IntelliJ . It is also possible define a gradle task that installs the app on the device and issues a command to the device to launch it.
In order to run the iOS App, the simplest way is to run it on the simulator using IntelliJ. In order to run it on a real device, the TramID needs to be defined as explained here
Compose multiplatform is a component based declarative UI framework. Each component is called a Composable and is defined as a function annotated with @Composable.
In compose multiplatform, the main component (the component at the root of the App) is usually found in shared/src/commonMain/Kotlin/App.kt.
Take a look at shared/src/commonMain/Kotlin/App.kt, run the app and try to understand how compose works.
Let's create a new composable called RandomNumberList.
@Composable
+funRandomNumberList(){
+// Generate a list of random numbers
+val myRandomValues =List(5){ Random.nextInt(0,30)}
+// LazyColumn is a vertically scrolling list that renders items on demand
+ LazyColumn {
+items(myRandomValues.size){
+Text(text ="$it")
+}
+}
+}
+
Place this composable below AnimatedVisibility and Button and run the app.
In addition to that, Kotlin is theoretically compatible with any framework that targets the JVM or JS. For example, this tutorial shows how to use node.js with Kotlin. However, frameworks that do not officially support Kotlin may require some tweaking to use it.
Ktor is a cross-platform Kotlin library for building both HTTP clients and servers. This makes Ktor a useful library to learn for both front-end developers for its HTTP client capabilities and backend-development for its HTTP server capabilities. In the following, we'll create a REST API with Ktor server.
Create a project on start.ktor.io with the following plugins: Content Negotiation, kotlinx.serialization, and Routing.
Click on "Generate project".
Download the archive, unzip it, and open the project with IntelliJ.
Create a models package and add to it a Customer data class with these immutable properties id: String, firstName: String, lastName: String, email: String.
Annotate the class with @Serializable.
Create a new package named routes and add to it a file CustomerRoutes.kt that will contain the code for the /customer endpoint.
The code below provides the implementation of some endpoints. Please implement the remaining ones.
To enable the route call customerRouting() in the routing configuration file located in plugins/Routing.kt.
For simplicity, use a global in-memory list of customers val store = mutableListOf<Customer>().
Run the server by running the main method.
Test the api on the IDE by using an http file or using any other client.
CustomerRoutes.kt
val store = mutableListOf<Customer>()
+
+fun Route.customerRouting(){
+route("/customer"){
+get{
+ call.respond(store)
+}
+get("{id?}"){
+val id = call.parameters["id"]?:return@get call.respondText(
+"Missing id",
+ status = HttpStatusCode.BadRequest
+)
+val customer =
+ store.find{ it.id == id }?:return@get call.respondText(
+"No customer with id $id",
+ status = HttpStatusCode.NotFound
+)
+ call.respond(customer)
+}
+ post {
+val customer = call.receive<Customer>()
+ store.add(customer)
+ call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
+}
+delete("{id?}"){
+
+}
+}
+}
+
plugins/Routing.kt
fun Application.configureRouting(){
+ routing {
+customerRouting()
+}
+}
+
return@label
You can specify which level you want to return with an explicit label using return@lambda.
Spring is a famous framework for developing server-side applications: APIs, server generated web pages, microservices, etc. It relies on the the Java ecosystem to build and run, thus making it compatible with Kotlin. Even better, Spring officially supports Kotlin. It even allows in start a new project with Kotlin and Gradle-Kotlin. In the next section, we'll use this starter to recreate our above REST API with Spring.
Let's go a little bit further by storing data in a database and writing some tests.
We'll use the H2 in-memory database for the sake of simplicity, since it does not require a server to run. Classes will mapped to database tables with JPA annotations. The database API we'll be using is called JPARepository. It is a lightweight API that provides common CRUD features by just defining an interface.
On the testing side, we'll see two different syntaxes. The default one that is more familiar with Java style and the DSL one which is more readable and more familiar with Kotlin developers.
Create a new Spring project using Spring initializr with Kotlin and the following dependencies: Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web
Open the project and add this class in the model package @Entity class Product(@Id @GeneratedValue var id: Long? = null, var name: String, var price: Int). This single defines the class as well as the minimal JPA annotations (@Entity, @Id and @GeneratedValue) to generate the corresponding table.
In the repository package, declare the ProductRepository interface as follows interface ProductRepository: JpaRepository<Product, Long>. This is enough for Spring to generate an implementation with common features as we'll see later.
Next, create a ProductService class which will contain the business logic. In terms of architecture, the controller calls a service which in turn rely on other services or repositories.
ProductService.kt
@Service
+classProductService(@Autowiredval productRepository: ProductRepository){
+fungetAll()= productRepository.findAll()
+
+// use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product?
+fungetById(id: Long)= productRepository.findByIdOrNull(id)
+}
+
In the controller package, create a ProductController class that is mapped to /product and injects the with @Autowired. Reply to @Get as follows.
In addition to that, Spring provides @ControllerAdvice to change the exception message. You can see an example here.
Let's run the project. Before running the project, we need to add a plugin that allows Kotlin classes to generate a default constructor id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10". The plugins should look as follows:
plugins {
+id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
As an exercise, implement these endpoints: POST a single product, DELETE by id (/product/{id}) and GET by id (/product/{id}).
Hint: ProductController already provides the necessary methods.
Spring frameworks helps perform different types of tests by providing different classes out of the box:
Unit testing of services, repositories and the REST API. This is done through mock utilities such as MockMVC.
Integration testing of the REST API using TestRestTemplate. In this situation, a full server is run and tested.
Most, if not all classes provided by Spring provide an elegant syntax for Java developers. Some of them go further by taking advantage of Kotlin specific features. In the following, we're going to focus on parts that provide Kotlin DSLs, namely unit testing the REST API with MockMVC.
Create a test class ProductControllerUnitTests with this initial content. MockMvc allows to unit test the REST API. The @AutoConfigureMockMvc annotation allows spring to configure it automatically
Add these two tests. The first one uses a classic approach while the second take advantage of Kotlin DSL capabilities. In addition to that, we name using a more readable string literal
As an exercise, unit tests for the other endpoints.
The request builder of JpaRepository
Spring repositories implement requests based on the name of their methods. For example, to get all products sorted by name, we can add this method to the interface.
Thanks to Kotlin/JS, we can write apps that target node.js using Kotlin. We can even import npm libraries as long as you declare the JS API surface that you'll be using in Kotlin. This is called external declaration (You can think of it as an equivalent of TypeScript's type definitions) that declares the symbols that we want to access in Kotlin thanks to @JsModule and @JsNonModule annotations.
Defining such external declarations can be a hassle and there seems to be no official automatic generator (dukat has been removed in kotlin 1.8.20). In that case, we have two options, either we write the external declaration ourselves or import it as a dependency if available. Fortunately for express developers, chrisnkrueger/kotlin-express provides declarations for the express library.
There are two gradle plugins that allow to create node.js projects: the kotlin("js") one and the kotlin("multiplatform") one. The difference between the two plugins is that the former only supports JS or WASM while the latter supports more platforms but requires to configure source sets. Thus, the former may seem easier to setup but the latter is better in the long run because it allows us to get more familiar with Kotlin Multiplatform (KMP).
At the time of writing, I didn't find an official wizard or starter project. So we'll create one from scratch using gradle init.
Create a new Gradle project using IntelliJ or by running gradle init in a empty folder (see below for the replies to the gradle init command).
gradle init
gradle init
+Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --statusfor details
+
+Select type of project to generate:
+1: basic
+2: application
+3: library
+4: Gradle plugin
+Enter selection (default: basic)[1..4]1
+
+Select build script DSL:
+1: Kotlin
+2: Groovy
+Enter selection (default: Kotlin)[1..2]1
+
+Project name (default: starter): rest-api-kotlin-nodejs
+
+Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no)[yes, no]yes
+
+
+> Task :init
+To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.3/samples
+
+BUILD SUCCESSFUL in 24s
+2 actionable tasks: 2 executed
+
In build.gradle.kts, add and configure the kotlin("multiplatform") plugin. Also add the express and dev.chriskrueger:kotlin-express dependencies.
create a main.kt file in src/jsMain/kotlin with the following content:
main.kt
dataclassMessage(val id: Int,val message: String)
+
+val messages =mutableListOf(Message(0,"I love Kotlin/JS"))
+
+funmain(){
+val app = express.Express()
+
+// REST API that provides a **GET /hello** endpoint
+ app.get("/hello"){ _, res ->
+ res.send(messages)
+}
+
+// Create a server that listens to port 3000
+ app.listen(3000){
+ console.log("server start at port 3000")
+}
+}
+
Run the task jsRun from IntelliJ of from the command line ./gradlew --console=plain jsRun. The server should start running.
Let's add a post endpoint which reads the body as a json. In order to read the body as json, we must add this possibility to express by importing the npm library body-parser and by calling app.use(bodyParser.json()). Once this setup is complete, req.body will contain the content of the body. However, there is no available external definition for bodyParser as of the time of writing. Thus, we must create or own external definition.
First, add the body-parser dependncy in the build file implementation(npm("body-parser", "> 1.0.0 < 2.0.0"))
Next, we would write: app.use(bodyparser.json()) to activate the library. Let's guess what a minimal definition of bodyparser can be.
BodyParser.kt
// external means that this class is defined in JS
+externalclass BodyParser {
+// we tell Kotlin that we want to use the json() function.
+funjson(): Any
+// It is not required to define all the functions of the module
+}
+
+// @JsModule is used to import the module from the NPM registry
+@JsModule("body-parser")
+externalval bodyParser: BodyParser
+
Finally, we just need to add the BodyParser.kt file into the project and use it in our server.
main.kt
app.use(bodyParser.json())
+app.post("/hello"){ req, res ->
+// Kotlin does not keep the original field name when parsing JSON from JS (you can see it the in get response)
+if(req.body as? Message ==null){
+println("failed to get the body from Kotlin")
+}
+// Thus, we need to use js() to get the the field by its expected name
+// js() calls JS from Kotlin
+println("req.body from JS ${js("req.body.id")} - ${js("req.body.message")}")
+val id =js("req.body.id")as? Int
+val message =js("req.body.message")as? String
+if(message !=null&& id !=null){
+ messages.add(Message(id, message))
+ res.status(201).end()
+}else{
+ res.status(400).send(js("{cause : 'error'}")as Any)
+}
+}
+
"The Kotlin Multiplatform technology is designed to simplify the development of cross-platform projects. It reduces time spent writing and maintaining the same code for different platforms while retaining the flexibility and benefits of native programming." ₁
KMP relies on Kotlin native and other Kotlin features to help developers create projects that target multiple platforms using a common Kotlin code-base.
Many combinations of targets and use cases are possible:
Full-Stack web apps: A project that contains a backend and a web app while sharing common logic.
Kotlin/JS can also target the web and even use web frameworks (such as react) in Kolitn.
Kotlin WASM is another possibility to target the web but this will generate WASM instead of pure JS code.
It can be used for example to develop computation intensive libraries.
Maybe we can do even more in the future with as all these technologies (Kotlin, WASM and Kotlin/WASM) evolve. - For example, WASI allows WASM to communicate with the operating system. - This means that me may see Kotlin/WASM project projects in the future that can target both the browser and the OS.
Let's create a Kotlin/WASM app. By cloning git clone git@github.com:Kotlin/kotlin-wasm-examples.git and opening the browser-example folder in your IDE.
Open the project and run the wasmJsBrowserRun task.
The development server should start and you can open your WASM powered webapp on http://localhost:8080/
⚠️ You may need to activate some flags on your browser for the app to work. If you see a blank page, please read the browser logs to check for the instructions.
Please check the contents of src/wasmJsMain/kotlin/Simple.kt to understand how the page is coded.
Next, let's check the generated wasm file which is available in build/js/packages/project_name/kotlin
WASM being a binary format, we need to convert it first to text format.
Both Kotlin/WASM and Kotlin/JS IntelliJ work somewhat similarly.
Both rely on the KMP plugin
Kotlin/WASM is enabled by adding a wasmjs section in the build.gradle.kts file, while Kotlin/JS is enabled by adding a js section.
The Kotlin code will compile to WASM and / or JS. Kotlin/JS generates only JS while Kotin/WASM generates both JS and WASM.
In both cases, the entry point of the generated code is a JS file called module_name.js.
The index.html in the resources folder loads the generated JS explained above (the one named module_name.js).
The task wasmBrowserDevelopmentRun or jsWasmBrowserDevelopmentRun run a local server that hosts both the index.html files and the generated JS and WASM files.
"Compose Multiplatform simplifies and accelerates UI development for Desktop and Web applications, and allows extensive UI code sharing between Android, iOS, Desktop and Web. It's a modern toolkit for building native UI. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs."
While very similar, Compose multiplatform is different from Jetpack Compose as the latter is only compatible with Android. Google provides a JetPack compose tutorial for Android development.
Compose HTML is not cross-platform
Compose HTML is UI a library targeting Kotlin/JS which is not compatible with Compose Multiplatform (it is a different API). For cross-platform UI development with Compose Multiplatform, compose Web is the choice.
We'll create a multiplatform app using the official template. At the time of writing, this template does not include a compose web target.
Please check that your environment is correctly setup as explained here.
On Windows and Linux, we don't need to install iOS/macOS related tools but and we won't be able to run iOS/macOS targets.
If we don't want to install Android Studio, we need at least to install the Android SDK either through the official installer or from the "Languages and Framework -> Android SDK" menu in the settings.
Open the official template and either download a zip or use the "use this template" options on GitHub.
Open the downloaded projet. You'll note that it contains these modules:
a shared module (or subproject) that contains common code as well as
and another module for earch targeted platform: androidApp, iOSApp and desktopApp (When web will be included in the template, we should also see a webApp project). These contain the source code of the apps itself (such as the main activity in Android, the @main App in iOS and the main function in desktopJVM) and well as platform specific resources that cannot be placed in the shared module. Some examples of such files are the AndroidManifest.xml for android and the info.plist in iOS.
In order to run the desktopApp, open a terminal on the project root folder and launch this command: ./gradlew desktopApp:run.
In order to run the Android App, the simplest way is to launch it from IntelliJ . It is also possible define a gradle task that installs the app on the device and issues a command to the device to launch it.
In order to run the iOS App, the simplest way is to run it on the simulator using IntelliJ. In order to run it on a real device, the TramID needs to be defined as explained here
Compose multiplatform is a component based declarative UI framework. Each component is called a Composable and is defined as a function annotated with @Composable.
In compose multiplatform, the main component (the component at the root of the App) is usually found in shared/src/commonMain/Kotlin/App.kt.
Take a look at shared/src/commonMain/Kotlin/App.kt, run the app and try to understand how compose works.
Let's create a new composable called RandomNumberList.
@Composable
+funRandomNumberList(){
+// Generate a list of random numbers
+val myRandomValues =List(5){ Random.nextInt(0,30)}
+// LazyColumn is a vertically scrolling list that renders items on demand
+ LazyColumn {
+items(myRandomValues.size){
+Text(text ="$it")
+}
+}
+}
+
Place this composable below AnimatedVisibility and Button and run the app.
Kotlin allows to delegate the getter and setter of a property to another object, which is called a delegate. It is a class that defines the getValue and setValue methods.
Kotlin provides standard delegates such lazy properties and observable properties.
Kotlin provides a high level concurrency model called Coroutines. The developer can delegated the management of threads to the compiler and runtime and using higher level constructs than threads to express asynchronous operations.
Coroutines in Kotlin revolve around these concepts:
A coroutine is an instance of suspendable computation.
Kotlin has many methods for creating a coroutine such as launch.
A coroutine must exist within a coroutine scope.
For example runBlocking creates a coroutine scope whithin which coroutines can be launched.
A coroutine can run suspend functions which can suspend the coroutine but do not block the thread.
For example: the delay suspend the coroutine but does not block the thread on which it is running.
Suspend functions are operations that may take time such http requests and file system calls.
The suspend qualifier defines a suspend function. It runs within a coroutine and can call other suspend functions.
Flow allows to generate a list of asynchronous values.
Deferred and Channel transfer a single value and a stream of values, respectively, between coroutines.
▶️ this code show how to create a coroutine and suspend function and how to use them.
As seen previously, function extension add behavior to existing classes. Inside the definition of the function extension, we can reference the extension receiver (or this) implicitly.
fun String.countCharacters()= length // or this.length
+println("hello".countCharacters())// prints 5
+
We can define this extension with a function literal (or lambda) in instead of a named function (declared with fun).
var extFn: String.()-> Int
+extFn ={ length }// extFn is a function literal
+println("hello".extFn())// prints 5
+println(extFn("hello"))// prints 5
+
extFnis a function literal (lambda) that has access to the receiver (this). That's why it's called a function literal with receiver.
extFn("hello") or extFn("hello") call the extension as expected from extension functions.
The type of a function literal with receiver is funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType and is called with funName(receiverValue, arg1Value, etc.) or receiverValue.funName(arg1Value, etc.). However, this is not the interesting aspect.
The important part is extFn = { length } which can be put as a function argument in a higher order function. The developer that calls the higher order function must define extFn, which in turn has access to the receiver. This allows for a nice style of programming. ▶️ this code shows an example.
Type-safe builders combine well-named builder functions and functions literals with receiver to create type-safe, statically typed builders in Kotlin.
open the java-integration-exercise projects in the materials folder.
Have a look at the Java class we provided you in the src/main/java/com/worldline/learning/kotlin/java2kotlin package. (yes, that's the Pokemon class)
Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at src/main/kotlin/com/worldline/learning/kotlin/java2kotlin)
Have a look at the generated Kotlin code, and note the major differences you spot!
Kotlin is an object oriented language with functional features. This chapter covers important and relevant features of the language slit into basic and intermediate. Another chapter covers advanced features.
In a nutshell, null safety is a compiler feature that eliminates the infamous Null pointer exception or npe. The Kotlin compiler reports errors and warnings when we manipulate nullable (also called optional) values. Here is a list of null safety features provided by Kotlin:
All types are non-nullable by default; we cannot assign null to a variable or an argument.
For example, this code fails var s: String = null.
A type can be made nullable by suffixing it with a ?. For example: var s: String? = null.
Kotlin forbids calling a method or a property of a non-nullable type, unless we do one of these possibilities:
Use optional chaining with the ? suffix.
Provide a default value with the elvis ?: operator.
Smart-cast the nullable into a non-nullable.
Use the !! operator that eliminates compiler checks. This should never be used.
Never unwrap with !!
Use other safe techniques instead.
▶️ this code illustrates null safety and how to use optional types.
Java `Optional` does not provide compile time null checks
Optional wrap null values on runtime. The Java compiler (as of version 17) does not provide unwrapping features such as smart casting. It is still possible to have a npe like this: Optional<String> s = null; s.isPresent();
Enumerations allow to work with a group of values in a type-safe fashion. Unlike Java enums, Kotlin enums are classes. Kotlin enum classes provide these features:
when statements support enumerations.
Enum constants can declare their own anonymous classes with their corresponding methods, as well as with overriding base methods.
An enum class can implement an interface but it cannot derive from a class
There are methods for listing the defined enum constants and getting an enum constant by its name.
Every enum constant has properties for obtaining its name and position (starting with 0).
Kotlin allows to write concise OOP code and has the following features:
Available common features: classes, inheritance, interfaces, and abstract classes.
Native support of properties: do not define getters and setters unless needed.
Just add get() and set(value) functions next to the property declaration.
Constructor arguments are defined next to the class name class ClassName(arg1, atg2, )
Prefixing a constructor arguments with val or var makes it a property (val makes it read-only).
The constructor name is init and does not require parameters.
The compiler checks that all non-nullable properties are initialized by the end of the constructor.
⚠️ The compiler does not check the initialization of lateinit properties. Thus, accessing them before while uninitialized causes an exception.
Prefix classes with open to allow inheritance.
Kotlin enables the public access level by default.
The equality operator == calls equals() (as opposed to Java which uses reference equality).
A companion object contains static methods and properties.
Extensions add function and properties to existing classes.
💡 They replace inheritance in many situations.
For example, we can add functions to the String class instead of creating a new StringUtils class.
Sealed classes and interfaces cannot be extended or implemented by third parties.
Do not define accessors unless needed
As opposed to Java, Kotlin supports properties and allows to add accessors later without refactoring the code that calls these properties. Thus, by default, just define the name of properties without accessors and use them directly.
Functional programming revolves around these concepts: pure functions, recursion, referential transparency, immutable variables, functions as first-class citizens, and higher-order functions.
Let's briefly explain these concepts:
Immutable variables means that we cannot change the value of a variable or its properties once it has been created. If we want to do so, we must create a new instance with the new value.
Pure functions are functions that do not have side effects and will thus return always the same output given the same input.
Functions are first class citizens: they can be assigned to a variable or used in higher-order functions (passed as a function parameter to another function or returned from a function).
Referential transparency: means that an expression can be replaced by its result without changing the behavior of the program. Transparency refers to the fact that the implementation of the expression is irrelevant.
💡 Pure functional languages provide these features natively and enforces them (at build time).
Kotlin is not a pure functional languages but it supports some features. For example, Kotlin does not have compile time verification of pure functions, but it provides immutable collections through the kotlinx.collections.immutable library.
listOf generates read-only lists, which are not immutable
A read-only list cannot add or remove elements, but it can change the underlying data.
Declarative programming is a famous style within functional programming. It consists of writing code as a chaining of function calls in this style val result = f(x).g(y). .... Higher order functions replace many situation where we would use loops. This favors readable code which is easy to debug an maintain.
▶️ this code show an example of list manipulation using declarative programming.
De nombreux frameworks supportent officiellement Kotlin comme Spring, Quarkus et Ktor, parmi d'autres listés ici.
En outre, Kotlin est théoriquement compatible avec tout framework qui cible la JVM ou JS. Cependant, les frameworks qui ne supportent pas officiellement Kotlin peuvent nécessiter quelques ajustements pour l'utiliser.
Ktor est une bibliothèque Kotlin multiplateforme permettant de développer des clients et des serveurs HTTP. Cela fait de Ktor une bibliothèque utile à la fois aux développeurs frontend, pour la partie client HTTP, ainsi qu'aux développeurs backend, pour la partie serveur HTTP. Dans ce qui suit, nous allons créer une API REST avec le serveur Ktor.
Créez un projet sur start.ktor.io avec les plugins suivants : Content Negotiation, kotlinx.serialization, et Routing.
Cliquez sur "Generate project".
Téléchargez l'archive, décompressez-la et ouvrez le projet avec votre IDE préféré.
Créez un package models et ajoutez-y une classe de données Customer avec ces propriétés immuables id : String, firstName : String, lastName : Chaîne, email : Chaîne.
Annotez la classe avec @Serializable.
Créez un nouveau package nommé routes et ajoutez-y un fichier CustomerRoutes.kt qui contiendra le code pour l'endpoint /customer.
Le code ci-dessous fournit l'implémentation de certains endpoints. Veuillez implémenter les autres.
Pour activer la route, appelez customerRouting() dans le fichier de configuration du routage situé dans plugins/Routing.kt.
Pour plus de simplicité, utilisez une liste globale de clients en mémoire val store = mutableListOf<Customer>().
Lancer le serveur en exécutant la méthode main.
Tester l'API sur l'IDE en utilisant un fichier http ou en utilisant n'importe quel autre client.
CustomerRoutes.kt
val store = mutableListOf<Customer>()
+
+fun Route.customerRouting(){
+route("/customer"){
+get{
+ call.respond(store)
+}
+get("{id?}"){
+val id = call.parameters["id"]?:return@get call.respondText(
+"Missing id",
+ status = HttpStatusCode.BadRequest
+)
+val customer =
+ store.find{ it.id == id }?:return@get call.respondText(
+"Pas de client avec l'id $id",
+ status = HttpStatusCode.NotFound
+)
+ call.respond(customer)
+}
+ post {
+val customer = call.receive<Customer>()
+ store.add(customer)
+ call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
+}
+delete("{id?}"){
+// TODO
+}
+}
+}
+
+
plugins/Routing.kt
fun Application.configureRouting(){
+ routing {
+customerRouting()
+}
+}
+
return@label
Vous pouvez spécifier le niveau que vous voulez retourner avec un label explicite en utilisant return@lambda.
Grâce à Kotlin/JS, nous pouvons écrire des applications qui ciblent node.js en utilisant Kotlin.
On peut même importer des librairies npm à condition de déclarer les API JS que l'on va utiliser en Kotlin. C'est ce qu'on appelle une déclaration externe (vous pouvez la considérer comme un équivalent des définitions de type de TypeScript) qui déclare les symboles auxquels nous voulons accéder en Kotlin grâce aux annotations @JsModule et @JsNonModule. Définir de telles déclarations externes peut s'avérer fastidieux et il ne semble pas y avoir de générateur automatique officiel et stable (dukat a été supprimé dans kotlin 1.8.20). Dans ce cas, nous avons deux options, soit écrire la déclaration externe nous-même, soit l'importer en tant que dépendance si elle est disponible.
Heureusement pour nous, le prochain TP utilise la librairie Express pour laquelle nous pouvons trouver une déclaration de type externe.
Modifiez main.kt comme suit. Cela crée un serveur API REST qui écoute le port 3000 et fournit une route GET /hello.
dataclassMessage(val id : Int,val message : String)
+
+funmain(){
+val messages =listOf(Message(0,"I love Kotlin/JS"))
+val app = express.Express()
+ app.get("/hello"){ req, res ->
+ res.send(messages)
+}
+
+ app.listen(3000){
+ console.log("server start at port 3000")
+}
+}
+
Exécutez la tâche nodeRun depuis votre IDE ou depuis la ligne de commande (si vous avez installé Gradle).
Si vous rencontrez une erreur avec Yarn lock, exécutez la tâche kotlinUpgradeYarnLock puis réessayez.
Ajouter des routes en POST, PUT et DELETE
En ce qui concerne le corps du POST, Express positionne req.body à undefined à moins que nous ne spécifions un body parser.
Pour un corps en JSON, nous devons appeler app.use(bodyParser.json()).
bodyParser est une bibliothèque npm et malheureusement, chrisnkrueger/kotlin-express ne fournit pas de définition externe pour bodyParser au moment de l'écriture de ces lignes (chrisnkrueger/kotlin-express en version 1.2.0).
Spring est un framework célèbre pour le développement d'applications côté serveur : API REST, pages web générées par le serveur, microservices, etc. Il s'appuie sur l'écosystème Java pour la compilation et l'exécution, ce qui le rend compatible avec Kotlin. Mieux encore, Spring supporte officiellement Kotlin. On peut même démarrer un nouveau projet avec Kotlin et Gradle-Kotlin. Dans la prochaine section, nous utiliserons ce projet pour recréer notre API REST plus haut avec Spring.
Vérifiez que la partie plugins build.gradle.kts utilise la dernière version de Kotlin. Voici à quoi cela devrait ressembler avec Kotlin 1.8.10 :
plugins {
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
Créez la data class Customer dans le package model (sans l'annotation @Serializable).
Créez un paquetage controller qui contient une classe CustomerController qui fournit un CRUD en utilisant une liste globale.
Vous pouvez trouver un squelette ci-dessous.
💡 Dans Spring, les contrôleurs Rest servent de routes Ktor, où un contrôleur définit une ressource REST.
Définissez les mêmes routes que dans le TP précédent.
Démarrez le serveur de l'API REST en exécutant :
Sur Powershell : .\gradlew.bat bootRun
Tout shell Unix : .\gradlew bootRun
Ou bien, vérifiez si votre IDE fournit déjà des configurations d'exécution pour les projets Spring Boot.
Allons un peu plus loin en stockant des données dans une base de données et en écrivant quelques tests.
Nous utiliserons la base de données en mémoire H2 pour des raisons de simplicité, puisqu'elle ne nécessite pas de serveur pour fonctionner. Les classes seront mappées aux tables de la base de données avec des annotations JPA. L'API de base de données que nous utiliserons s'appelle JPARepository. C'est une API légère qui fournit des fonctionnalités CRUD communes à partir d'une simple une interface.
Créez un nouveau projet Spring en utilisant Spring initializr avec Kotlin et les dépendances suivantes : Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web.
Ouvrez le projet et ajoutez cette classe dans le package model@Entity class Product(@Id @GeneratedValue var id : Long ? = null, var name : String, var price : Int). Ceci définit la classe ainsi que les annotations JPA minimales (@Entity, @Id et @GeneratedValue) pour générer la table correspondante.
Dans le package repository, déclarez l'interface ProductRepository comme suit interface ProductRepository : JpaRepository<Produit, Long>. C'est suffisant pour que Spring génère une implémentation avec des caractéristiques communes comme nous le verrons plus tard.
Ensuite, créez une classe ProductService qui contiendra la logique métier. En termes d'architecture, le contrôleur appelle un service qui, à son tour, s'appuie sur d'autres services ou référentiels.
ProductService.kt
@Service
+classProductService(@Autowiredval productRepository: ProductRepository){
+fungetAll()= productRepository.findAll()
+
+// use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product?
+fungetById(id: Long)= productRepository.findByIdOrNull(id)
+}
+
Dans le package controller, créez une classe ProductController qui est mappée à /product et injectée avec @Autowired. Répondez à @Get comme suit.
Exécutons le projet. Avant de lancer le projet, nous devons ajouter un plugin qui permet aux classes Kotlin de générer un constructeur par défaut id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10". Les plugins devraient ressembler à ce qui suit :
plugins {
+id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
+id("org.springframework.boot") version "3.0.4"
+id("io.spring.dependency-management") version "1.1.0"
+kotlin("jvm") version "1.8.10"
+kotlin("plugin.spring") version "1.8.10"
+}
+
+
En guise d'exercice, implémentez ces routes : POST d'un seul produit, DELETE par id (/produit/{id}) et GET par id (/produit/{id}).
Indice : ProductController fournit déjà les méthodes nécessaires.
Appelez les différents points de terminaison avec un client REST.
Les frameworks Spring permettent d'effectuer différents types de tests en fournissant différentes classes dès le départ :
Tests unitaires/de composants des services et de l'API REST. Cela se fait par le biais d'utilitaires de bouchonnage tels que MockMVC.
Tests d'intégration de l'API REST en utilisant TestRestTemplate. Dans ce cas, un serveur complet est exécuté et testé.
La plupart des classes fournies par Spring, si ce n'est toutes, offrent une syntaxe élégante pour les développeurs Java. Certaines d'entre elles vont plus loin en tirant parti des caractéristiques spécifiques de Kotlin. Dans ce qui suit, nous allons nous concentrer sur les parties qui fournissent des DSLs Kotlin, à savoir le test unitaire de l'API REST avec MockMVC.
Créer une classe de test ProductControllerUnitTests avec le contenu initial ci-dessous. MockMvc permet de tester unitairement l'API REST. L'annotation @AutoConfigureMockMvc permet à Spring de la configurer automatiquement.
Ajoutez les deux tests ci-dessous. Le premier utilise une approche classique tandis que le second tire parti des capacités du DSL de Kotlin. De plus, nous utilisons une chaîne littérale plus lisible.
En guise d'exercice, écrire des tests pour les autres points d'accès.
Le constructeur de requêtes de JpaRepository
Les repository Spring implémentent des requêtes basées sur le nom de leurs méthodes. Par exemple, pour obtenir tous les produits triés par nom, nous pouvons ajouter cette méthode à l'interface.
Kotlin supporte une large sélection de frameworks frontaux sur toutes les plateformes : mobile, desktop et web. Vous trouverez ci-dessous un aperçu des possibilités que vous pouvez faire directement à partir d'IntelliJ :
Côté bureau
Grâce au support de la JVM, Kotlin supporte JavaFX. 💡 Il existe même un équivalent en Kotlin appelé tornadofx.
Compose Multiplatform apporte l'API Jetpack Compose sur le bureau, le web et le mobile.
Sur le web
Ktor peut utiliser des moteurs de modèles tels que FreeMarker pour créer des pages de serveur.
Avec KotlinJS, les développeurs peuvent créer des applications React, nodsjs ou vanilla JS en utilisant Kotlin.
Kotlin WASM se compile en Web Assembly. Il peut compléter KotlinJS pour les tâches à forte intensité de calcul.
Compose Multiplatform apporte deux options sur le web: Compose web et Compose for Web Canvas.
Sur les mobiles
Les développeurs Android utilisent Jetpack Compose ou l'ancienne méthode de layout XML.
Compose Multiplatform supporte Android de façon stable et iOS de façon expérimentale.
Comme nous pouvons le voir, Kotlin propose plusieurs options. L'option la plus séduisante en terme de partage de code est Compose Multiplatform. Ceci est possible notamment grâce à KMP
KMP (Kotlin Multiplatform) permet de partager une base de code unique sur plusieurs cibles.
KMP s'appuie sur Kotlin native et d'autres fonctionnalités de Kotlin pour aider les développeurs à créer des projets destinés à plusieurs plates-formes en utilisant une base de code Kotlin commune.
De nombreuses combinaisons de cibles et de cas d'utilisation sont possibles :
Full-Stack web apps : Un projet qui contient un backend et une application web tout en partageant une logique commune.
Kotlin/JS peut également cibler le web et même utiliser des frameworks web (tels que react) dans Kolitn.
Kotlin WASM est une autre possibilité de cibler le web, mais il génère WASM au lieu de code JS pur.
Il peut être utilisé par exemple pour développer des bibliothèques à forte intensité de calcul.
Nous pourrons peut-être faire encore plus à l'avenir grâce à l'évolution de toutes ces technologies (Kotlin, WASM et Kotlin/WASM). - Par exemple, [WASI] (https://wasi.dev/) permet à WASM de communiquer avec le système d'exploitation. - Cela signifie que je pourrais voir des projets Kotlin/WASM à l'avenir qui peuvent cibler à la fois le navigateur et le système d'exploitation.
Les assistants de création de projet Kotlin/WASM et Kotlin/JS sur IntelliJ fonctionnent de manière similaire:
L'IDE génère un fichier Kotlin qui sera compilé par la suite en WASM et/ou JS. Kotlin/JS ne génère que du JS tandis que Kotin/WASM génère à la fois du JS et du WASM.
Dans les deux cas, le point d'entrée du code généré est un fichier JS appelé nom_du_module.js.
L'IDE génère également dans le dossier des ressources un fichier index.html dont le but est de charger le JS généré (le fichier nom_du_module.js).
La tâche wasmBrowserDevelopmentRun ou jsWasmBrowserDevelopmentRun lancera un serveur local qui hébergera à la fois les fichiers index.html et les fichiers JS et WASM générés.
Créons une application Kotlin/WASM. Tout d'abord, activez l'assistant Kotlin/WASM en activant kotlin.wasm.wizard dans le registre d'IntelliJ (ouvrez le registre en appuyant deux fois sur shift et en tapant "registry" dans la boîte de recherche). Alternativement, clonez ce projet.
Vérifiez qu'on est sur la dernière version de Kotlin dans build.gradle.kts (l'assistant peut le configurer à une version antérieure).
Ouvrez src/wasmMain/kotlin/sample.kt et cliquez sur le bouton lancer qui apparaît à côté de la fonction main.
Si la compilation échoue parce que l'IDE a utilisé la mauvaise tâche gradle, veuillez la changer en wasmBrowserDevelopmentRun et essayez de l'exécuter à nouveau.
Le serveur de développement devrait démarrer et vous pouvez ouvrir votre application web sur http://localhost:8080/
⚠️ Il se peut que vous deviez activer certains drapeaux sur votre navigateur pour que l'application fonctionne. Si vous voyez une page blanche, veuillez lire les journaux du navigateur pour vérifier les instructions.
Le fichier wasm généré est disponible dans build/js/packages/nom_du_projet/kotlin
WASM étant un format binaire, nous devons d'abord le convertir au format texte.
Nous pouvons soit installer [WABT (The WebAssembly Binary Toolkit ou wabbit)] (https://github.com/WebAssembly/wabt) et utiliser l'outil wasm2wattoolwasm2wat --enable-all -v .\kotlin-wasm-demo-wasm.wasm -o wasm.wat,
L'assistant Kotlin/JS crée une application très similaire à celle de Kotlin/WASM. Dans un prochain PW, nous créerons une application complète avec Ktor et Kotlin/JS.
Compose multiplatform est une famille de frameworks d'interface utilisateur déclaratifs pour Android (Jetpack Compose), le bureau (Compose Desktop) et le web (Compose Web). Il dispose d'un support expérimental pour iOS et Web Canvas.
Compose multiplatform vs Jetpack Compose
Bien que très similaire, Compose multiplatform est différent de Jetpack Compose car ce dernier n'est compatible qu'avec Android. Google fournit un JetPack compose tutorial pour le développement Android.
Compose Web vs Compose for Web Canvas
La surface de l'API de Compose Web est différente des autres cibles de Compose car elle travaille directement avec le DOM.
Compose for Web Canvas a la même surface d'API que celle du Desktop, Android et iOS car il dessine sur un Canvas et ne manipule pas le DOM.
Cela signifie que le premier a un meilleur support web et que le second a plus de code réutilisable.
Kotlin permet de déléguer le getter et le setter d'une propriété à un autre objet, appelé délégué. C'est une classe qui définit les méthodes getValue et setValue.
Kotlin fournit des délégués standard tels que des propriétés paresseuses et des propriétés observables.
Kotlin fournit un modèle de concurrence de haut niveau appelé Coroutines. Le développeur peut déléguer la gestion des threads au compilateur et à l'exécution et utiliser des constructions de niveau supérieur aux threads pour exprimer des opérations asynchrones.
Les coroutines de Kotlin tournent autour de ces concepts :
Une coroutine est une instance de calcul suspendable.
Kotlin a de nombreuses méthodes pour créer une coroutine telle que launch.
Une coroutine doit exister dans une portée de coroutine.
Par exemple, runBlocking crée une portée de coroutine dans laquelle les coroutines peuvent être lancées.
Une coroutine peut exécuter des fonctions de suspension qui peuvent suspendre la coroutine mais ne bloquent pas le thread.
Par exemple : le delay suspend la coroutine mais ne bloque pas le thread sur lequel elle s'exécute.
Les fonctions de suspension sont des opérations qui peuvent prendre du temps telles que les requêtes http et les appels au système de fichiers.
Le qualificateur suspend définit une fonction de suspension. Il s'exécute dans une coroutine et peut appeler d'autres fonctions de suspension.
Flow permet de générer une liste de valeurs asynchrones.
Deferred et Channel transfèrent respectivement une valeur unique et un flux de valeurs entre coroutines.
▶️ this code show how to create a coroutine and suspend function and how to use them.
Comme vu précédemment, les extensions de fonctions ajoute du comportement à des classes existantes sans utiliser l'héritage. À l'intérieur de la définition de l'extension de fonction, nous pouvons référencer implicitement le récepteur d'extension (this).
fun String.countCharacters()= length // or this.length
+println("hello".countCharacters())// prints 5
+
Nous pouvons définir cette extension avec une fonction littérale (ou lambda) au lieu d'une fonction classique (déclarée avec fun).
var extFn: String.()-> Int
+extFn ={ length }// extFn is a function literal
+println("hello".extFn())// prints 5
+println(extFn("hello"))// prints 5
+
extFnest une fonction littérale (lambda) qui a accès au récepteur (this). C'est pourquoi on l'appelle une fonction littérale avec récepteur.
extFn("hello") ou extFn("hello") appelle l'extension comme prévu par les fonctions d'extension.
Le type d'une fonction littérale avec récepteur est funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType et est appelé avec funName(receiverValue, arg1Value, etc.) ou receiverValue.funName(arg1Value, etc.) .). Cependant, ce n'est pas l'aspect le plus intéressant.
La partie importante est extFn = { length } qui peut être placée comme argument de fonction dans une fonction d'ordre supérieur. Le développeur qui appelle la fonction d'ordre supérieur doit définir extFn, qui à son tour a accès au récepteur. Cela permet un style de programmation assez intéressant.
Les Type-safe builders combinent les monteurs bien nommées et les fonctions littérales avec récepteur pour créer des monteur avec un typage statique et sécurisé. La syntaxe particulière possible avec technique permet de définir une sorte de sous-langage aussi appelé DSL (domain specific language).
open the java-integration-exercise projects in the materials folder.
Have a look at the Java class we provided you in the src/main/java/com/worldline/learning/kotlin/java2kotlin package. (yes, that's the Pokemon class)
Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at src/main/kotlin/com/worldline/learning/kotlin/java2kotlin)
Have a look at the generated Kotlin code, and note the major differences you spot!
Kotlin est un langage qui support les paradigmes orienté objet et fonctionnel. Ce chapitre couvre les caractéristiques basiques et intermédiaires. Le chapitre suivant couvrira les fonctionnalités avancées.
Appeler une fonction en passant la valeur dans l'ordre de déclaration.
Utilisez des étiquettes d'argument pour plus de clarté, cependant, cela permet également un classement arbitraire des arguments.
Les arguments optionnels ont une valeur par défaut et peuvent être omis lors de l'appel.
Les fonctions sont des éléments de première classe ou citoyens : elles peuvent être affectées à une variable, passées en tant que paramètre de fonction ou renvoyées par une fonction.
💡 Une fonction qui prend une fonction comme argument ou en renvoie une est une fonction d'ordre supérieur.
Un type de fonction peut être exprimé comme suit : (typeOfParam1, typeOfParam2, etc) -> returnType (Le type de retour vide est Unit).
Les fonctions anonymes utilisent la syntaxe suivante { argName1, argName2, etc. -> // code }
Aussi appelées fonctions lambda ou fonctions littérales
Le dernier argument de la fonction peut être mis après la fermeture après la parenthèse fermante compute(9, 5) { x, y -> x * y }
null safety est une fonctionnalité du compilateur qui élimine la fameuse Null pointer exception ou npe. En effet, le compilateur signale des erreurs et des avertissements lorsque nous manipulons des types nullables (également appelées types optionnels) dès qu'il y a un risque de npe à l'exécution. Ainsi, afin de mettre Voici une liste des fonctionnalités de sécurité null fournies par Kotlin :
Tous les types ne sont pas nullables par défaut ; nous ne pouvons pas affecter null à une variable ou à un argument.
Par exemple, ce code échoue var s: String = null.
Un type peut être rendu nullable en le suffixant avec un ?. Par exemple : var s : chaîne ? = nul.
Kotlin interdit d'appeler une méthode ou une propriété de type non nullable, sauf si l'on fait l'une de ces possibilités :
Utilisez le chaînage optionnel avec le suffixe ?.
Fournissez une valeur par défaut avec l'opérateur elvis ?:.
Smart-cast le nullable dans un non-nullable.
Utilisez l'opérateur !! qui élimine les vérifications du compilateur. Cela ne devrait jamais être utilisé.
Ne jamais déballer avec !!
Car cela équivaut à désactiver la null safety. Utilisez les autres possibilités à la place.
▶️ ce code illustrate la null safety et les types optionnels.
La classe `Optional` de Java ne fournit aucun protection à la compilation
Ce code lance une npe en Java: Optional<String> s = null; s.isPresent();. Le compilateur Java (au moins à la version version 17) ne propose pas d'équivalent à ce que propose Kotlin comme le smart casting.
Les énumérations permettent de travailler avec un groupe de valeurs de façon cadrée. Contrairement aux énumérations Java, les énumérations Kotlin sont des classes. Les enum class de Kotlin fournissent ces fonctionnalités :
Les expressions when prennent en charge les énumérations.
Une enum class peut définir des méthodes et implémenter des interfaces mais elle ne peut pas dériver d'une classe.
Il existe des méthodes pour lister les constantes d'une enum class.
Chaque constante d'une énumération a des propriétés pour obtenir son nom et sa position (en commençant par 0).
Kotlin permet d'écrire du code Orienté Object concis grâce aux caractéristiques suivantes :
Concepts disponibles : classes, héritage, interfaces et classes abstraites.
Prise en charge possée des propriétés : les getters et les setters sont automatiquement implémentés.
On peut les personnaliser les accesseurs en définissant les fonctions get() et set(value) à côté de la déclaration de la propriété.
Les arguments du constructeur sont définis à côté du nom de la classe class ClassName(arg1, atg2, )
Préfixer les arguments d'un constructeur avec val ou var en fait une propriété (val la rend non ré-assignable).
Le nom du constructeur est init et ne nécessite pas de paramètres.
Le compilateur vérifie que toutes les propriétés non nullables sont initialisées à la fin du constructeur.
⚠️ Le compilateur ne vérifie pas l'initialisation des propriétés lateinit. Ainsi, y accéder avant alors qu'elles ne sont pas initialisés provoque une exception.
Une classe doit être préfixée avec open pour permettre l'héritage.
Kotlin utilise le niveau d'accès public par défaut.
L'opérateur d'égalité == appelle implicitement la méthode equals() (contrairement à Java qui utilise l'égalité de référence).
Un objet compagnon contient des méthodes et des propriétés statiques.
Les extensions ajoutent des fonctions et des propriétés aux classes existantes.
💡 Ils remplacent l'héritage dans de nombreuses situations.
Par exemple, nous pouvons ajouter des fonctions à la classe String au lieu de créer une nouvelle classe StringUtils.
Les classes et interfaces scellées ne peuvent pas être étendues ou implémentées par des tiers.
Ne définir les accesseurs que si vous avez un comportement personnalisé
Kotlin prend en charge les propriétés de façon plus poussée que Java et permet d'ajouter des accesseurs ultérieurement sans refactoriser le code qui appelle ces propriétés. Ainsi, par défaut, il suffit de définir le nom des propriétés sans accesseurs et on peut les utiliser directement.
La programmation fonctionnelle s'articule autour de ces concepts : fonctions pures, récursivité, transparence référentielle, variables immuables, fonctions en tant que citoyens de première classe et fonctions d'ordre supérieur.
Expliquons brièvement ces concepts :
Les variables immuables signifient qu'on ne peut pas changer la valeur d'une variable ou ses propriétés une fois qu'elle a été créée. Si nous voulons le faire, nous devons créer une nouvelle instance avec la nouvelle valeur.
Les fonctions pures sont des fonctions qui n'ont pas d'effets secondaires et renverront donc toujours la même sortie étant donné la même entrée.
Les fonctions sont des citoyennes de première classe : elles peuvent être affectées à une variable ou utilisées dans des fonctions d'ordre supérieur (passées en tant qu'un argument de fonction ou retournées dans un fonction).
Transparence référentielle : signifie qu'une expression peut être remplacée par son résultat sans modifier le comportement du programme.
💡 Les langages fonctionnels purs fournissent ces fonctionnalités de manière native et les appliquent (au moment de la construction).
Kotlin n'est pas un langage fonctionnel pur mais il prend en charge certaines fonctionnalités. En effet, Kotlin ne sait pas dire si une fonction est pures ou non, mais il fournit des collections immuables via la bibliothèque kotlinx.collections.immutable pour nous aider à manipuler des données immuables.
`listOf` génère des listes en lecture seule, mais qui sont mutables
Une liste en lecture seule ne peut pas ajouter ou supprimer des éléments, mais elle peut modifier les données sous-jacentes.
La programmation déclarative est un style célèbre dans la programmation fonctionnelle. Il consiste à écrire du code sous la forme d'un enchaînement d'appels de fonction dans ce style val result = f(x).g(y). .... Les fonctions d'ordre supérieur remplacent de nombreuses situations où nous utiliserions des boucles. Cela favorise le code lisible qui est facile à déboguer et à maintenir.
▶️ ce code montre comment manipuler une liste avec la programmation déclarative.
Connaissance de base du développement en Kotlin (notamment la nullabilité, les fonctions en ligne et les fonctions lambda)
IDE Android Studio avec la version stable la plus récente, version Giraffe ou supérieure
Une bonne connectivité
Conseil
Pour plus d'informations sur votre environnement de développement (DEV) et les installations, veuillez consulter la documentation liée à JetBrains ici.
Voici quelques arguments qui motivent le passage de Java (version 17 LTS au moment de la rédaction) à Kotlin.
Kotlin prend en charge plus de cibles que Java.
Kotlin protège des références null à la compilation (les Optional Java sont ne sont pas de protections à la compilation).
Les chaînes de caractères Kotlin prennent en charge l'interpolation.
Les fonctionnalités de programmation fonctionnelle de Kotlin sont meilleures. Il permet même de définir des constructeurs et des DSL (Domain Specific Language) dont le typage est sécurisé (type-safe).
Kotlin peut être mélangé avec du code Java, facilitant ainsi le processus de migration.
Vous pouvez lire plus d'arguments dans ces articles :
[8 raisons pour lesquelles vous devriez passer à Kotlin depuis Java] (https://www.geeksforgeeks.org/8-reasons-why-you-should-switch-to-kotlin-from-java)
We design payments technology that powers the growth of millions of businesses around the world. Engineering the next frontiers in payments technology
Leader in payment and secured transactions
Over 50 billion transactions/year
7000+ engineers in over 40 countries
A huge & diverse tech-stack
Tips
This training is also available in French / Cette formation est aussi disponible en Français