2. DEVELOPMENT
● About us
● What is Kotlin Multiplatform?
● Our experience in Kotlin Multiplatform
● moko.icerock.dev – a set of multiplatform libraries
● moko-widgets
2
3. DEVELOPMENT
● 5 years in mobile development
● 80+ mobile projects (iOS & Android)
● 40+ developers in 2 offices
3
5. DEVELOPMENT
5
Code Sharing in IceRock
Flutter
React Native
J2ObjC
New programming language
Integration with OS via middle layer
Non-native UI
Legacy tech stacks
Asynchronous work with OS
Need to adopt Java-code to ObjC
Native UI
Hot reload
6. DEVELOPMENT
6
Kotlin Multiplatform
+
Kotlin/Native
Code Sharing at IceRock
Kotlin looks like Swift
Inner libraries already in Kotlin
Shift to native env at any moment
Familiar programming language
Native UI
Full access to Android OS and iOS features
and SDKs
There are some limitations in Kotlin > Swift direction
Devs worry about integration
7. DEVELOPMENT
7
Kotlin Multiplatform at IceRock
Kotlin MPP and Kotlin/Native since July 2018
1 year achievement
Reduce time and cost for both platforms by 1.5x
1.5x speed
50% of shared code on average
50:50
Easy learning curve for Android and iOS developers
Easy to learn, hard to master
9. DEVELOPMENT
9
expect class Logger(tag: String) {
fun log(string: String)
}
fun main() {
val logger = Logger("MPP")
logger.log("hello world!")
}
commonMain
10. DEVELOPMENT
10
iosMain
import platform.Foundation.NSLog
actual class Logger actual constructor(private val
tag: String) {
actual fun log(string: String) {
NSLog("[$tag] $string")
}
}
import android.util.Log
actual class Logger actual constructor(private val
tag: String) {
actual fun log(string: String) {
Log.v(tag, string)
}
}
androidMain
12. DEVELOPMENT
12
Kotlin Multiplatform - past
View
AuthActivity AuthViewController ProfileViewController ProfileActivity
Presentation
AuthViewModel ProfileViewModel PostsViewModel AddPostViewModel
Domain
UserInteractor PostsInteractor Validator Payment
Data
User Post UserRepository PostRepository
RestUser RestPost UserApi PostApi
13. DEVELOPMENT
13
Kotlin Multiplatform - now
View
AuthScreen ProfileScreen
Presentation
AuthViewModel ProfileViewModel PostsViewModel
Domain
UserInteractor PostsInteractor Validator
Data
User Post UserRepository
RestUser RestPost UserApi
powered by
25. DEVELOPMENT
The basic architecture concepts:
1. compliance with platform rules
2. declare structure, not rendering
3. compile-time safety
4. reactive data handling
25
concepts
26. DEVELOPMENT
26
code
class App : BaseApplication() {
override fun setup() {
val theme = Theme()
registerScreenFactory(MainScreen::class) { MainScreen(theme) }
}
override fun getRootScreen(): KClass<out Screen<Args.Empty>> {
return MainScreen::class
}
}
common
27. DEVELOPMENT
27
code
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
mppApplication = App().apply {
setup()
}
}
companion object {
lateinit var mppApplication: App
}
}
class MainActivity : HostActivity() {
override val application: BaseApplication
get() = MainApplication.mppApplication
}
android
28. DEVELOPMENT
28
code
@UIApplicationMain
class AppDelegate: NSObject, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: ..., didFinishLaunchingWithOptions ...) -> Bool {
let app = App()
app.setup()
let screen = app.createRootScreen()
let rootViewController = screen.createViewController()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = rootViewController
window?.makeKeyAndVisible()
return true
}
}
ios
29. DEVELOPMENT
29
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
30. DEVELOPMENT
30
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
31. DEVELOPMENT
31
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
32. DEVELOPMENT
32
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
33. DEVELOPMENT
33
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
34. DEVELOPMENT
34
code
class MainScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
container(size = WidgetSize.AsParent) {
center {
text(
size = WidgetSize.WrapContent,
text = const(MR.strings.hello_world.desc())
)
}
}
}
}
common
44. DEVELOPMENT
44
code
class LoginScreen(
private val theme: Theme
) : WidgetScreen<Args.Empty>() {
override fun createContentWidget() = with(theme) {
constraint(size = WidgetSize.AsParent) {
// ...
}
}
}
common
45. DEVELOPMENT
45
code
override fun createContentWidget() = with(theme) {
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(
size = WidgetSize.Const(SizeSpec.WrapContent, SizeSpec.WrapContent),
image = const(Image.resource(MR.images.logo))
)
}
}
common
46. DEVELOPMENT
46
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(
size = WidgetSize.WidthAsParentHeightWrapContent,
id = Id.EmailInputId,
label = const("Email".desc() as StringDesc),
field = viewModel.emailField
)
val passwordInput = +input(
size = WidgetSize.WidthAsParentHeightWrapContent,
id = Id.PasswordInputId,
label = const("Password".desc() as StringDesc),
field = viewModel.passwordField
)
}
common
47. DEVELOPMENT
47
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(...)
val passwordInput = +input(...)
val loginButton = +button(
size = WidgetSize.Const(SizeSpec.AsParent, SizeSpec.Exact(50f)),
text = const("Login".desc() as StringDesc),
onTap = viewModel::onLoginPressed
)
}
common
48. DEVELOPMENT
48
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(...)
val passwordInput = +input(...)
val loginButton = +button(...)
constraints {
passwordInput centerYToCenterY root
passwordInput leftRightToLeftRight root
emailInput bottomToTop passwordInput
emailInput leftRightToLeftRight root
loginButton topToBottom passwordInput
loginButton leftRightToLeftRight root
logoImage centerXToCenterX root
logoImage.verticalCenterBetween(
top = root.top,
bottom = emailInput.top
)
}
common
49. DEVELOPMENT
49
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(...)
val passwordInput = +input(...)
val loginButton = +button(...)
constraints {
passwordInput centerYToCenterY root
passwordInput leftRightToLeftRight root
emailInput bottomToTop passwordInput
emailInput leftRightToLeftRight root
loginButton topToBottom passwordInput
loginButton leftRightToLeftRight root
logoImage centerXToCenterX root
logoImage.verticalCenterBetween(
top = root.top,
bottom = emailInput.top
)
}
common
50. DEVELOPMENT
50
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(...)
val passwordInput = +input(...)
val loginButton = +button(...)
constraints {
passwordInput centerYToCenterY root
passwordInput leftRightToLeftRight root
emailInput bottomToTop passwordInput
emailInput leftRightToLeftRight root
loginButton topToBottom passwordInput
loginButton leftRightToLeftRight root
logoImage centerXToCenterX root
logoImage.verticalCenterBetween(
top = root.top,
bottom = emailInput.top
)
}
common
51. DEVELOPMENT
51
code
constraint(size = WidgetSize.AsParent) {
val logoImage = +image(...)
val emailInput = +input(...)
val passwordInput = +input(...)
val loginButton = +button(...)
constraints {
passwordInput centerYToCenterY root
passwordInput leftRightToLeftRight root
emailInput bottomToTop passwordInput
emailInput leftRightToLeftRight root
loginButton topToBottom passwordInput
loginButton leftRightToLeftRight root
logoImage centerXToCenterX root
logoImage.verticalCenterBetween(
top = root.top,
bottom = emailInput.top
)
}
common
76. DEVELOPMENT
The basic architecture concepts:
1. compliance with platform rules
2. declare structure, not rendering
3. compile-time safety
4. reactive data handling
76
concepts
79. DEVELOPMENT
Compile-time safety:
1. Type match of WidgetFactory and Widget
2. Child Widgets sizes compile-time checks
3. Widgets Id match to Widget type
4. Arguments in Screens
79
concepts
80. DEVELOPMENT
80
concepts
Type match of WidgetFactory and Widget
val theme = Theme {
textFactory = DefaultTextWidgetViewFactory()
}
val theme = Theme {
textFactory = DefaultContainerWidgetViewFactory()
}
83. DEVELOPMENT
83
concepts
Widgets Id match to Widget type
setContainerFactory(
DefaultContainerWidgetViewFactory(),
RootContainerId
)
setTextFactory(
DefaultTextWidgetViewFactory(),
RootContainerId
)
object RootContainerId: ContainerWidget.Id
90. DEVELOPMENT
90
current list of widgets
with(theme) {
constraint(...)
container(...)
linear(...)
scroll(...)
stateful(...)
tabs(...)
clickable(...)
image(...)
text(...)
input(...)
button(...)
list(...)
collection(...)
flatAlert(...)
progressBar(...)
singleChoice(...)
switch(...)
}
91. DEVELOPMENT
91
current list of widgets
public abstract class Widget<WS : WidgetSize> public constructor() {
public abstract val size: WS
public abstract fun buildView(viewFactoryContext: ViewFactoryContext): ViewBundle<WS>
}
public interface ViewFactory<W : Widget<out WidgetSize>> {
public abstract fun <WS : WidgetSize> build(widget: W, size: WS,
viewFactoryContext: ViewFactoryContext): ViewBundle<WS>
}
92. DEVELOPMENT
● Widgets declare screens and structure of screens,
but rendering is native
● Jetpack Compose & SwiftUI can be used to render
widgets
92
references
93. DEVELOPMENT
● 1 kotlin developer can create for both platforms
● natively for developer and customer
● no limits with any modifications (feature, screen, …)
● MVP quickly ≠ must redo natively in the future
93
benefits
94. DEVELOPMENT
What’s available today (December, 19):
● Base widgets set
● Base navigation patterns
● Base styles for default widget-views
● A set of samples
● Actual version – 0.1.0-dev-5
94
roadmap
95. DEVELOPMENT
95
December-January:
● Implement in production project
● Check flexibility of API to detect and fix limits – you
can help with it.
Just try self and send feedback to github issues
● Screens actions API design (show toast, alert, route)
● More documentation and samples
roadmap