Let's talking about KMP

Meet the Team

Harry Hsu
Sr. SWE Engineer

林宇軒
Sr. SWE Engineer
Who am I
Calvin Huang
終於讓我有機會做這件事了
這是我家貓咪
名字是黑妞,可愛的男孩子 ♂






Today, we won't be discussing the traditional methods
為什麼大家都想要 Code once run everywhere?



神代的人類語言統一
創造了無數輝煌
人們建造了巴別塔嘗試達到神的境界而被懲罰
混亂的語言讓世界不再統一
無法再創過往的榮耀

全都圍繞在一個字上:榨取產能







But how we can achieve that goal?



Take a glance at UI




Credit: https://www.learnui.design/blog/ios-vs-android-app-ui-design-complete-guide.html

Look into the sample code
struct LoginView: View {
@StateObject var viewModel = LoginViewModel()
var body: some View {
VStack {
Image("STREAMMARK")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 40)
.padding(.all, 24)
Text("Welcome to Stream Chat")
.font(.title)
.padding(.all, 8)
Text("Select a user to try the iOS SDK:")
.font(.body)
.padding(.all, 8)
.padding(.bottom, 16)
List(viewModel.demoUsers) { user in
Button {
viewModel.demoUserTapped(user)
} label: {
DemoUserView(user: user)
}
.padding(.vertical, 4)
.animation(nil)
}
.listStyle(.plain)
Spacer()
}
.overlay(
viewModel.loading ? ProgressView() : nil
)
}
}
@Composable
private fun LoginScreen() {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(32.dp))
Icon(
modifier = Modifier.size(width = 80.dp, height = 40.dp),
painter = painterResource(id = R.drawable.ic_stream),
contentDescription = null,
tint = ChatTheme.colors.primaryAccent,
)
Spacer(modifier = Modifier.height(28.dp))
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringResource(R.string.login_screen_title),
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = ChatTheme.colors.textHighEmphasis,
)
Spacer(modifier = Modifier.height(12.dp))
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
items(items = LoginUsers.createUsers()) { loginUser ->
UserItem(loginUser = loginUser)
DividerItem()
}
}
}
}

Look into the sample code
struct LoginView: View {
@StateObject var viewModel = LoginViewModel()
var body: some View {
VStack {
Image("STREAMMARK")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 40)
.padding(.all, 24)
Text("Welcome to Stream Chat")
.font(.title)
.padding(.all, 8)
Text("Select a user to try the iOS SDK:")
.font(.body)
.padding(.all, 8)
.padding(.bottom, 16)
List(viewModel.demoUsers) { user in
Button {
viewModel.demoUserTapped(user)
} label: {
DemoUserView(user: user)
}
.padding(.vertical, 4)
.animation(nil)
}
.listStyle(.plain)
Spacer()
}
.overlay(
viewModel.loading ? ProgressView() : nil
)
}
}
@Composable
private fun LoginScreen() {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(32.dp))
Icon(
modifier = Modifier.size(width = 80.dp, height = 40.dp),
painter = painterResource(id = R.drawable.ic_stream),
contentDescription = null,
tint = ChatTheme.colors.primaryAccent,
)
Spacer(modifier = Modifier.height(28.dp))
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringResource(R.string.login_screen_title),
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = ChatTheme.colors.textHighEmphasis,
)
Spacer(modifier = Modifier.height(12.dp))
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
items(items = LoginUsers.createUsers()) { loginUser ->
UserItem(loginUser = loginUser)
DividerItem()
}
}
}
}

Look into the sample code
同場加映 - React???
mport React, { useState, useEffect } from 'react';
const LoginView = () => {
const [viewModel, setViewModel] = useState(new LoginViewModel());
const [loading, setLoading] = useState(viewModel.loading);
useEffect(() => {
// Assuming you have a way to update loading status in your view model
const handleLoadingChange = () => setLoading(viewModel.loading);
viewModel.onLoadingChange = handleLoadingChange;
return () => {
viewModel.onLoadingChange = null;
};
}, [viewModel]);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<img
src="path_to_STREAMMARK_image"
style={{ height: '40px', aspectRatio: 'fit', padding: '24px' }}
alt="STREAMMARK"
/>
<h1 style={{ padding: '8px' }}>Welcome to Stream Chat</h1>
<p style={{ padding: '8px', paddingBottom: '16px' }}>Select a user to try the iOS SDK:</p>
<div style={{ width: '100%' }}>
{viewModel.demoUsers.map(user => (
<button key={user.id} onClick={() => viewModel.demoUserTapped(user)} style={{ padding: '4px', width: '100%', textAlign: 'left' }}>
<DemoUserView user={user} />
</button>
))}
</div>
{loading && <p>Loading...</p>}
</div>
);
};
export default LoginView;

And...architecture

MVP

MVVM

MVI


✅
✅


✅
✅


✅
✅
總結起來為一個字:極度相似








Then things took a different turn



Design
matters


Then things took a different turn



Native matters

How Flutter draws?


The Dart code that paints Flutter’s visuals is compiled into native code, which uses Skia (or, in future, Impeller) for rendering.

How Flutter draws?
Let's take a brief dive into the mechanism


How ReactNative draws?


the React Native renderer communicates with the host platform to mount host views on the screen (create, insert, update or delete of host views) and it listens for events that are generated by the user on the host platform.

How ReactNative draws?
Let's take a brief dive into the mechanism



This is the most common scenario where most of the render pipeline happens on JavaScript thread.

So what is the limitation?
Be cautious of ReactNative performance

JS frame rate (JavaScript thread)
For most React Native applications, your business logic will run on the JavaScript thread.
...
Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it.


So what is the limitation?
It's better in Flutter


Then you know what?


Native UI always works better
FPS:

Native UI always works better
At the very least, don't let it bother you too much.

Ultimately, it doesn't really matter. 60FPS works well for everyone, and most people won't notice if you drop a few frames here and there... except maybe for professional gamers
Due to some other limitations...
Both Flutter and ReactNative


If you want it to appear like a native app, you'll need to create that effect on your own.
Of course the original UI experiences...?



Flutter does the awesome effort to save your time



Due to some other limitations...
Both Flutter and ReactNative

And it still some issues, though

All come to Kotlin Multiplatform mobile




Native Implementation
Shared Module

Base on the flexibility

來說點隨處可見、且無聊的事情吧
- 發包給外部廠商實作,溝通來回之下工期被壓縮

- 案子草創期沒有訂下明確的 spec、加上廠商沒有問清楚就實作,導致做完之後時間更短還因為做錯重改
- 連鎖反應導致工期不足,雪上加霜的是開發人員熟悉的開發模式過時、某些功能直接抄 stackoverflow
- 資安檢測修改項目不熟悉,工期不足 trial & error,但不知道為什麼復測會過


這時候又需要將外包的 code 接回來繼續做下去怎麼辦!?
Shared module
5%
Base on the flexibility

We can integrate the legacy in easy way
...haha
來說點隨處可見、且無聊的事情吧

The different core concept compared to other cross-platform solutions


Provide a cross-platform UI solution
Built the app on top on of that
Try to figure out the all common behaviors and adapt them to shared module
Get limited since all build on top of fundation UI framework

Provide a way to share the logic (perhaps data)
Extract common logic across projects
Discuss if anything else needs to be placed at shared module
Feel free to leave or stay whenever you want

Provide a cross-platform UI solution
Built the app on top on of that
Try to figure out the all common behaviors and adapt them to shared module
Get limited since all build on top of fundation UI framework

極端例子: NFC 靠卡儲值實作
因為 UI 用跨平台方案

讀不同平台的文件,找出 API 共同的行為

根據跨平台方案的接口整合

Potential Risk

Provide a way to share the logic (perhaps data)
Extract common logic across projects
Discuss if anything else needs to be placed at shared module
Feel free to leave or stay whenever you want
極端例子: NFC 靠卡儲值實作

基於不同平台分別實作

討論是不是要將相似的接口整合進 shared module

- 交換完全 native,

- 實作接口,讓 shared-
流程後拋回 UI 連動處理
module 處理交換
Total autonomy


因為 UI 用跨平台方案

讀不同平台的文件,找出 API 共同的行為

根據跨平台方案的接口整合

Different priority, different form of the team
Flutter/ReactNative...etc first
Native platform second


Different priority, different form of the team
Native platform first
KMP second

基於不同平台分別實作

討論是不是要將相似的接口整合進 shared module

- 交換完全 native,
- 實作接口,讓 shared-
流程後拋回 UI 連動處理
module 處理交換

Different priority, different form of the team


November 1, 2023
Kotlin Multiplatofm is stable



Exciting roadmap ahead


Exciting roadmap ahead


Our tips :)
Before





Our tips :)
Before




After
Get in touch
QA time
https://app.sli.do/event/aSqr53HcJnCNPacBdvCCGa
Recurting

https://www.cakeresume.com/companies/i-pass

Code
By Calvin Huang
Code
- 536