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
- 445