The Hitchhikers Guide Through
The Hitchhikers Guide Through
Kotlin Multiplatform
Kotlin Multiplatform
@cafonsomota
revised and updated edition
revised and updated edition
a brief history of time
“In the beginning the Universe was created. This had made many people
angry and has been widely regarded as a bad move.”
Slide 5
Slide 5 text
android iOS
Slide 6
Slide 6 text
Dark Theme
Slide 7
Slide 7 text
android iOS
Slide 8
Slide 8 text
Photo by Fabian Grohs on Unsplash
Slide 9
Slide 9 text
Photo by Fabian Grohs on Unsplash
a wild application idea
appears!
develop it for Android and iOS.
Slide 10
Slide 10 text
-hire a team of specialised developers
-define requirements
-design mockups for both platforms
-plan features development
-start development/ write unit tests
-write t-specs for SQA validation
-cross-checking validation between both platforms
(or jacks)
Steps
Slide 11
Slide 11 text
~2xthe team
the cost
the status meetings required
the time spent on development
the time spent on testing
native
the time spent on bug fixing
Slide 12
Slide 12 text
Photo by Fabian Grohs on Unsplash
a wild application idea
appears!
develop it for Android and iOS and web.
Slide 13
Slide 13 text
~3xthe team
the cost
the status meetings required
the time spent on development
the time spent on testing
the time spent on bug fixing
Slide 14
Slide 14 text
No content
Slide 15
Slide 15 text
let’s find a solution
that works on all
platforms.
- smaller team
- typically half of the cost needed on native
- small learning curve for web developers
(advantages)
Cross-platform
Slide 18
Slide 18 text
- chained to the framework implementation of UI
- new updates from the OS will take time to adopt
- performance is not the same
- some native code might need to be written
- OS/ device features are dependent on the fw support
- dart (flutter) is not widely used language (for now)
(disadvantages)
Cross-platform
Slide 19
Slide 19 text
how can we have
the best of both
worlds?
Slide 20
Slide 20 text
how can we have
the best of both
worlds?
Slide 21
Slide 21 text
kotlin
“42.”
Slide 22
Slide 22 text
No content
Slide 23
Slide 23 text
- developed by JetBrains
- open source
- concise, safe, interoperable, tool-friendly
- supported/ used by Google for Android Development
- it’s more than “just for Android”
Kotlin
Server-side Android Kotlin JS Native
Slide 24
Slide 24 text
Mutability
etc.
Null safety
Default
values
Type
inference
String
interpolation
when
Collections
> [2, 4, 6, 8]
.filter { it % 2 == 0 }
(collections)
Language features
output
mutableListOf(1, null, 2, null, 3, 4, 5, 6, 7, 8, 9)
.filterNotNull()
Slide 33
Slide 33 text
> [8, 6, 4, 2]
.sortedDescending()
(collections)
Language features
output
.filter { it % 2 == 0 }
mutableListOf(1, null, 2, null, 3, 4, 5, 6, 7, 8, 9)
.filterNotNull()
Slide 34
Slide 34 text
- small learning curve involved
- easily to go from JavaScript/ Swift into Kotlin and back
(from a developers’ point of view)
Easy to learn
Slide 35
Slide 35 text
var variable = 42
variable = 1
let value = 42
var variable = 42
variable = 1
val value = 42
*adapted from http://nilhcem.com/swift-is-like-kotlin/
(variables and constants)
Kotlin vs Swift
Kotlin
Swift
Slide 36
Slide 36 text
fun greet(name: String, day: String): String {
return "Hello $name, today is $day."
}
greet(“Porto", “Saturday")
*updated from http://nilhcem.com/swift-is-like-kotlin/
func greet(_ name: String,_ day: String) -> String {
return "Hello \(name), today is \(day)."
}
greet(“Porto", “Saturday")
Kotlin
Swift
(functions)
Kotlin vs Swift
Slide 37
Slide 37 text
kotlin multiplatform
“‘Resistance is useless!’ How can anyone maintain a positive mental
attitude if you’re saying things like that?”
Slide 38
Slide 38 text
K
otlin multiplatform
using kotlin in projects
that target more than
one platform
Slide 39
Slide 39 text
(advantages)
- language features
- kotlin first!
- low risk
- you decide what’s worth to share across projects
- interoperability
- consistency across platforms
- strong community support
Kotlin Multiplatform
Slide 40
Slide 40 text
first reaction
Slide 41
Slide 41 text
view view view view
model
parser
network
presentation presentation presentation presentation
network network network
parser parser parser
model model model
presentation presentation presentation
desktop
web
iOS
android
Slide 42
Slide 42 text
business logic business logic business logic
model
parser
network
presentation
model
parser
network
presentation
model
parser
network
presentation
model
parser
network
presentation
business logic
view view view view
desktop
web
iOS
android
Slide 43
Slide 43 text
android iOS web desktop
model
parser
network
presentation
common
view view view view
java/kotlin objective-c/ swift (kotlin) JS supported in jvm
declared at common module
expect
declared at android module
actual
declared at iOS module
declared at …
Slide 47
Slide 47 text
we want a platform-specific value for name
Slide 48
Slide 48 text
expect object Platform {
val name: String
}
commonMain
src/commonMain/sample/Platform.kt
Slide 49
Slide 49 text
commonMain
src/commonMain/sample/Platform.kt
- define actual on android
- define actual on iOS
targets: android and iOS
expect object Platform {
val name: String
}
Slide 50
Slide 50 text
src/iOSMain/sample/Platform.kt
actual object Platform {
actual val name: String = "Android"
}
actual object Platform {
actual val name: String = "iOS"
}
src/androidMain/sample/Platform.kt
platform-dependent code
Slide 51
Slide 51 text
IntelliJ will ask you declare the implementations for actual
Slide 52
Slide 52 text
*.kt
common
expect
JVM
actual
*.kt, *.java, *.jar
Native
actual
*.kt, *C, Swift, Framework
JS
actual
*.kt, *.js, NPM
Slide 53
Slide 53 text
medium.com/@cafonsomota
Slide 54
Slide 54 text
show me the code!
Slide 55
Slide 55 text
demo
"For a moment, nothing happened. Then, after a second or so, nothing
continued to happen.”
Slide 56
Slide 56 text
github.com/cmota/droidconLX
Slide 57
Slide 57 text
- user interface
- RecyclerView and more (Android)
- UITableViewController and more (iOS)
- multiple network request
- parse response objects
- store on local database
- notify the UI
- that there’s new content available to be draw
- reload list
project structure
multiplatform
network
database
android
parser
presenter
presenter
view
platform
domain
specific
model
Slide 65
Slide 65 text
class SessionizeAPI(engine: HttpClientEngine) {
private val client = HttpClient(engine) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
suspend fun fetchSpeakers(): List {
val response = client.get{ url ("$URL")}
val json = response.readText()
return Json.parse(SpeakerEntity.serializer().list, json)
}
}
network
src/commonMain/data/SessionizeAPI.kt
ktor
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
common (shared) code
Slide 66
Slide 66 text
kotlinx.serialization
network
src/commonMain/data/SessionizeAPI.kt
class SessionizeAPI(engine: HttpClientEngine) {
private val client = HttpClient(engine) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
suspend fun fetchSpeakers(): List {
val response = client.get{ url ("$URL")}
val json = response.readText()
return Json.parse(SpeakerEntity.serializer().list, json)
}
}
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 67
Slide 67 text
json response
[{
"id": "2bc1e95f-1243-4ad0-8a11-75541fee10e0",
"firstName": "Carlos",
"lastName": "Mota",
"fullName": "Carlos Mota",
"bio": "… GDG Coimbra organizer and Kotlin evangelist, he also has a
huge passion for travel, photography, space and the occasional run.",
"tagLine": "Lead Software Engineer at WIT Software",
"profilePicture": “https://sessionize.com/imag...7ef8707ae.jpg”,
"sessions": [{
"id": 131513,
"name": "The Hitchhikers Guide Through Kotlin Multiplatform"
}],
…
},…
https://sessionize.com/api/v2/3hvwlgcc/view/speakers
Slide 68
Slide 68 text
json response
[{
"id": "2bc1e95f-1243-4ad0-8a11-75541fee10e0",
"firstName": "Carlos",
"lastName": "Mota",
"fullName": "Carlos Mota",
"bio": "… GDG Coimbra organizer and Kotlin evangelist, he also has a
huge passion for travel, photography, space and the occasional run.",
"tagLine": "Lead Software Engineer at WIT Software",
"profilePicture": “https://sessionize.com/imag...7ef8707ae.jpg”,
"sessions": [{
"id": 131513,
"name": "The Hitchhikers Guide Through Kotlin Multiplatform"
}],
…
},…
https://sessionize.com/api/v2/3hvwlgcc/view/speakers
Slide 69
Slide 69 text
kotlinx.serialization
src/commonMain/data/entities/SpeakerEntity.kt multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
parser
@Serializable
data class SpeakerEntity (
val id: String,
val firstName: String,
val lastName: String,
val fullName: String,
val bio: String,
val tagLine: String,
val profilePicture: String,
val sessions: List,
val isTopSpeaker: Boolean,
val links: List,
val questionAnswers: List,
val categories: List)
Slide 70
Slide 70 text
kotlinx.serialization
src/commonMain/data/entities/SpeakerEntity.kt multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
parser
@Serializable
data class SpeakerEntity (
val id: String,
val firstName: String,
val lastName: String,
val fullName: String,
val bio: String,
@SerialName("tagLine") val jobTitle: String,
val profilePicture: String,
val sessions: List,
val isTopSpeaker: Boolean,
val links: List,
val questionAnswers: List,
val categories: List)
Slide 71
Slide 71 text
database
CREATE TABLE SpeakerModel (
id TEXT NOT NULL PRIMARY KEY,
speaker TEXT as Speaker NOT NULL
);
insertOrReplaceSpeaker:
INSERT OR REPLACE INTO SpeakerModel(id, speaker) VALUES (?, ?);
selectAllSpeakers:
SELECT *
FROM SpeakerModel;
SQLDelight
src/commonMain/sqldelight/data/model/SpeakerModel.sq multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 72
Slide 72 text
database
SQLite
SQLDelight
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 73
Slide 73 text
database
SQLite Compiler Generated code
SQLDelight
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 74
Slide 74 text
database
Generated code
SQLDelight
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 75
Slide 75 text
app/build/sqldelight/ScheduleDb/data/app/ScheduleDbImpl.kt
database
class SpeakerModelQueriesImpl(private val db: ScheduleDbImpl,
private val driver: SqlDriver) :
TransacterImpl(driver), SpeakerModelQueries {
…
override fun insertOrReplaceSpeaker(id: String, speaker: Speaker) {
driver.execute(2113668020,
"""INSERT OR REPLACE INTO SpeakerModel(id, speaker)
|VALUES (?1, ?2)""", 2) {
bindString(1, id)
bindString(2, db.SpeakerModelAdapter.speakerAdapter.encode(speaker))
}
notifyQueries(2113668020, {db.speakerModelQueries.selectAllSpeakers})
}
}
generated class
SQLDelight
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 76
Slide 76 text
model
class SpeakerDao(database: ScheduleDb) {
private val db = database.speakerModelQueries
internal fun insertOrReplace(speaker: Speaker) {
db.insertOrReplaceSpeaker(
id = speaker.id,
speaker = speaker)
}
internal fun getAllSpeakers(): List {
val data = db.selectAllSpeakers().executeAsList()
…
src/commonMain/domain/dao/SpeakerDao.kt multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 77
Slide 77 text
model
@Serializable
data class Speaker (
val id: String,
val fullName: String,
val bio: String,
val tagLine: String,
val profilePicture: String,
val sessions: List,
val categories: List)
fun SpeakerEntity.toSpeaker() = Speaker(
id = id,
fullName = fullName,
bio = bio,
…)
src/commonMain/domain/model/Speaker.kt multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 78
Slide 78 text
domain
class GetSpeakers(val api: SessionizeAPI, val dao: SpeakerDao) {
suspend operator fun invoke(onSuccess: (List) -> Unit,
onFailure: (Exception) -> Unit) {
try {
val result = api.fetchSpeakers()
val speakers = Speaker.toSpeaker(result)
dao.insertOrReplace(speakers)
coroutineScope {
launch(uiDispatcher) {
onSuccess(speakers)
}
}
} catch (e: Exception) {
onFailure(e)
}
src/commonMain/domain/GetSpeakers.kt multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 79
Slide 79 text
class SpeakersListPresenter(val speakers: GetSpeakers,
val coroutineContext: CoroutineContext) {
lateinit var view: ISpeakersData
fun attachView(currView: ISpeakersData) {
view = currView
fetchSpeakersList()
}
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/SpeakersListPresenter.kt
Slide 80
Slide 80 text
class SpeakersListPresenter(val speakers: GetSpeakers,
val coroutineContext: CoroutineContext) {
lateinit var view: ISpeakersData
fun attachView(currView: ISpeakersData) {
view = currView
fetchSpeakersList()
}
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/SpeakersListPresenter.kt
onCreate()/ viewDidLoad()
Slide 81
Slide 81 text
class SpeakersListPresenter(val speakers: GetSpeakers,
val coroutineContext: CoroutineContext) {
lateinit var view: ISpeakersData
fun attachView(currView: ISpeakersData) {
view = currView
fetchSpeakersList()
}
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/SpeakersListPresenter.kt
Slide 82
Slide 82 text
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/cb/ISpeakersData.kt
interface ISpeakersData {
fun onSpeakersDataFetched(speakers: List)
fun onSpeakersDataFailed(e: Exception)
}
Slide 83
Slide 83 text
class SpeakersListPresenter(val speakers: GetSpeakers,
val coroutineContext: CoroutineContext) {
lateinit var view: ISpeakersData
fun attachView(currView: ISpeakersData) {
view = currView
fetchSpeakersList()
}
private fun fetchSpeakersList() {
PresenterCoroutineScope(coroutineContext).launch {
speakers(
onSuccess = { view?.onSpeakersDataFetched(it) },
onFailure = { view?.onSpeakersDataFailed(it) })
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/SpeakersListPresenter.kt
Slide 84
Slide 84 text
class SpeakersListPresenter(val speakers: GetSpeakers,
val coroutineContext: CoroutineContext) {
lateinit var view: ISpeakersData
fun attachView(currView: ISpeakersData) {
view = currView
fetchSpeakersList()
}
private fun fetchSpeakersList() {
PresenterCoroutineScope(coroutineContext).launch {
speakers(
onSuccess = { view?.onSpeakersDataFetched(it) },
onFailure = { view?.onSpeakersDataFailed(it) })
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/presentation/SpeakersListPresenter.kt
Slide 85
Slide 85 text
object ServiceLocator {
private val sessionizeAPI by lazy {
SessionizeAPI(PlatformServiceLocator.httpClientEngine)
}
private val getSpeakers: GetSpeakers
get() = GetSpeakers(sessionizeAPI, speakerDao)
private val speakerDao by lazy {
SpeakerDao(PlatformServiceLocator.databaseEngine)
}
val getSpeakersPresenter: SpeakersListPresenter
get() = SpeakersPresenter(getSpeakers)
…
presenter
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/ServiceLocator.kt
Slide 86
Slide 86 text
No content
Slide 87
Slide 87 text
platform specific
expect object PlatformServiceLocator {
val httpClientEngine: HttpClientEngine
val databaseEngine: ScheduleDb
}
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/PlatformServiceLocator.kt
Slide 88
Slide 88 text
platform specific
expect object PlatformServiceLocator {
val httpClientEngine: HttpClientEngine
val databaseEngine: ScheduleDb
}
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonMain/PlatformServiceLocator.kt
Slide 89
Slide 89 text
actual object PlatformServiceLocator {
actual val httpClientEngine: HttpClientEngine by lazy {
OkHttp.create()
}
actual val databaseEngine: SqlDriver by lazy {
AndroidSqliteDriver(ScheduleDb.Schema, context, "droidcon.db")
…
platform specific
androidMain
iOSMain
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 90
Slide 90 text
actual object PlatformServiceLocator {
actual val httpClientEngine: HttpClientEngine by lazy {
OkHttp.create()
}
actual val databaseEngine: SqlDriver by lazy {
AndroidSqliteDriver(ScheduleDb.Schema, context, "droidcon.db")
…
platform specific
androidMain
actual object PlatformServiceLocator {
actual val httpClientEngine: HttpClientEngine by lazy {
Ios.create()
}
actual val databaseEngine: SqlDriver by lazy {
NativeSqliteDriver(ScheduleDb.Schema, "droidcon.db")
…
iOSMain
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 91
Slide 91 text
actual object PlatformServiceLocator {
actual val httpClientEngine: HttpClientEngine by lazy {
OkHttp.create()
}
actual val databaseEngine: SqlDriver by lazy {
AndroidSqliteDriver(ScheduleDb.Schema, context, "droidcon.db")
…
platform specific
androidMain
actual object PlatformServiceLocator {
actual val httpClientEngine: HttpClientEngine by lazy {
Ios.create()
}
actual val databaseEngine: SqlDriver by lazy {
NativeSqliteDriver(ScheduleDb.Schema, "droidcon.db")
…
iOSMain
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 92
Slide 92 text
src/commonAndroid/presenter/activities/MainActivity.kt
android
class MainActivity : AppCompatActivity(), ISpeakersData {
val presenter by lazy { ServiceLocator.getSpeakersPresenter }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter.attachView(this)
}
override fun onSpeakersDataFetched(list: List){
setup(list)
…
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 93
Slide 93 text
src/commonAndroid/presenter/activities/MainActivity.kt
android
class MainActivity : AppCompatActivity(), ISpeakersData {
val presenter by lazy { ServiceLocator.getSpeakersPresenter }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter.attachView(this)
}
override fun onSpeakersDataFetched(list: List){
setup(list)
…
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
src/commonAndroid/presenter/adapters/SpeakersListAdapter.kt
android
override fun onBindViewHolder(viewHolder: SpeakerViewHolder,
position: Int) {
val speaker = speakers[position]
Glide.with(viewHolder.speakerPhoto)
.load(speaker.profilePicture)
.apply(RequestOptions.circleCropTransform())
.into(viewHolder.speakerPhoto)
viewHolder.speakerName.text = speaker.fullName
viewHolder.talkTitle.text = speaker.talkTitle
viewHolder.container.setOnClickListener {
action.onUserClickAction(speaker, it)
}
}
you can keep using your android libraries
multiplatform
network
database
android
parser
view
platform
specific
presenter
presenter
domain
model
Slide 96
Slide 96 text
iosApp/iosApp/SpeakerTableViewController.swift
iOS
multiplatform
network
database
iOS
parser
view
platform
specific
presenter
presenter
domain
model
class SpeakerTableViewController:
UITableViewController, ISpeakersData
lazy var presenter = ServiceLocator.init().getSpeakerPresenter
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presenter.attachView(view: self)
}
func onSpeakersDataFetched(speakers: [Speaker]) {
setup(speakers)
…
Slide 97
Slide 97 text
iosApp/iosApp/SpeakerTableViewController.swift
iOS
multiplatform
network
database
iOS
parser
view
platform
specific
presenter
presenter
domain
model
class SpeakerTableViewController:
UITableViewController, ISpeakersData {
lazy var presenter = ServiceLocator.init().getSpeakersPresenter
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presenter.attachView(view: self)
}
func onSpeakersDataFetched(speakers: [Speaker]) {
setup(speakers)
…
Slide 98
Slide 98 text
iosApp/iosApp/SpeakerTableViewController.swift
iOS
multiplatform
network
database
iOS
parser
view
platform
specific
presenter
presenter
domain
model
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "SpeakerTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier:
cellIdentifier, for: indexPath) as SpeakerTableViewCell
let speaker = speakers[indexPath.row]
cell.talkName.text = speaker.talkTitle
cell.speakerName.text = speaker.fullName
cell.speakerImage.image = speaker.profilePicture
return cell
}
Slide 99
Slide 99 text
conclusions
don’t forget to bring your own towel.
Slide 100
Slide 100 text
team structure
Kotlin Multiplatform
android iOS
Slide 101
Slide 101 text
team structure
Kotlin Multiplatform
android iOS
mobile backend
documentation
tests
clean API
Slide 102
Slide 102 text
(impressions)
Kotlin Multiplatform
I could just focus on doing what I know best - UI. I have
no idea how things are being done in the backend, yet I
know that when I ask for data I receive it.
- iOS Developer
Slide 103
Slide 103 text
- it’s in experimental state
- although there are projects in production
- you can spend some time resolving compilation issues
- specially if you try to target all platforms
- not possible to debug kotlin from Xcode
- there’s a plugin for Xcode from TouchLab
- lookout for plugins/ libraries updates
(in progress)
Kotlin Multiplatform
Slide 104
Slide 104 text
- strong community
- a lot of people are using kotlin nowadays
- Google and JetBrains are pushing Kotlin in
- you can ask questions directly on https://kotlinlang.slack.com
- 2x faster to develop your features business logic
- 2x faster writing unit tests
- one tech stack
- consistency across platforms
(conclusions)
Kotlin Multiplatform
Slide 105
Slide 105 text
- be extremely careful with all updates
- IDEA, jdk, kotlin, libraries - everything.
- start small, don’t try to reach 100% of shared logic
- keep versioning in mind
- remember that’s still on experimental state
(suggestions)
Kotlin Multiplatform
Slide 106
Slide 106 text
- Kotlin Slack
- Android @ Portugal Slack
- Kotlin Official
- Kotlin Training
- Kotlin by:
https://jakewharton.com/presentations/
http://antonioleiva.com/kotlin
(useful links)
more information