Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Jetpack Compose vs SwiftUI (Android Worldwide 2...
Search
Mohit S
October 26, 2021
Programming
2
1k
Jetpack Compose vs SwiftUI (Android Worldwide 2021)
Mohit S
October 26, 2021
Tweet
Share
More Decks by Mohit S
See All by Mohit S
Guide to Improving Compose Performance
heyitsmohit
0
220
Building Shared UIs across Platforms with Compose
heyitsmohit
1
620
Building Multiplatform Apps with Compose
heyitsmohit
2
500
Building StateFlows with Jetpack Compose
heyitsmohit
6
1.8k
Building Android Testing Infrastructure
heyitsmohit
1
470
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
780
Using Square Workflow for Android & iOS
heyitsmohit
1
420
Building Android Infrastructure Teams at Scale
heyitsmohit
3
310
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
550
Other Decks in Programming
See All in Programming
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
130
iOSアプリ開発で 関数型プログラミングを実現する The Composable Architectureの紹介
yimajo
2
210
関数型まつり2025登壇資料「関数プログラミングと再帰」
taisontsukada
2
850
Composerが「依存解決」のためにどんな工夫をしているか #phpcon
o0h
PRO
1
230
Java on Azure で LangGraph!
kohei3110
0
170
AIエージェントはこう育てる - GitHub Copilot Agentとチームの共進化サイクル
koboriakira
0
380
「Cursor/Devin全社導入の理想と現実」のその後
saitoryc
0
160
Go1.25からのGOMAXPROCS
kuro_kurorrr
1
810
第9回 情シス転職ミートアップ 株式会社IVRy(アイブリー)の紹介
ivry_presentationmaterials
1
240
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
260
Team topologies and the microservice architecture: a synergistic relationship
cer
PRO
0
1.1k
今ならAmazon ECSのサービス間通信をどう選ぶか / Selection of ECS Interservice Communication 2025
tkikuc
20
3.6k
Featured
See All Featured
Git: the NoSQL Database
bkeepers
PRO
430
65k
Documentation Writing (for coders)
carmenintech
72
4.9k
Code Reviewing Like a Champion
maltzj
524
40k
The Cost Of JavaScript in 2023
addyosmani
51
8.5k
BBQ
matthewcrist
89
9.7k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.5k
The World Runs on Bad Software
bkeepers
PRO
69
11k
Why Our Code Smells
bkeepers
PRO
337
57k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.3k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
5
210
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
790
Transcript
Mohit Sarveiya Jetpack Compose vs SwiftUI @heyitsmohit
Jetpack Compose vs SwiftUI • Concurrency • Managing State •
Navigation
Benefits • Understand different approaches in Compose & SwiftUI •
Collaborate with iOS Dev on architecture.
Building Android & iOS App Example App
iOS Android
• Display Images • Search
• Like an Image • Display location, tags
Jetpack Compose • MutableStateOf • Remember • Side Effects •
@State • @ObservedObject • @Published SwiftUI
Jetpack Compose Views, Managing State
• Display Images • Search
Images List Screen View Model State
Images List Screen View Model State
val uiState = MutableStateFlow<ImagesUiState>()
val uiState = MutableStateFlow<ImagesUiState>()
sealed class ImagesUiState { object Loading: ImagesUiState() }
sealed class ImagesUiState { data class Success( val images:
List<ImageData> ): ImagesUiState() }
sealed class ImagesUiState { data class Error( val errorMessage:
String ): ImagesUiState() }
val uiState = MutableStateFlow(ImageUiState.Loading)
Images List Screen View Model State
init { viewModelScope.launch { val images = repository.getImages() _uiState.value =
ImagesUiState.Success(images = images) } }
init { viewModelScope.launch { val images = repository.getImages() _uiState.value =
ImagesUiState.Success(images = images) } }
init { viewModelScope.launch { val images = repository.getImages() _uiState.value =
ImagesUiState.Success(images = images) } }
init { viewModelScope.launch { val images = repository.getImages() uiState.value =
ImagesUiState.Success(images = images) } }
init { viewModelScope.launch { val images = repository.getImages() uiState.value =
ImagesUiState.Success(images = images) } }
Images List Screen View Model State
@Composable fun ImagesListScreen() { )
Images List Screen View Model State
Streams of data • LiveData -> observeAsState • Flow ->
collectAsState • Observable -> subscribeAsState
@Composable fun ImagesListScreen() { val uiState = uiState.collectAsState().value )
@Composable fun ImagesListScreen() { when (uiState) { } )
@Composable fun ImagesListScreen() { when (uiState) { ImagesUiState.Loading ImagesUiState.Success
ImagesUiState.Error } )
ImagesUiState.Loading -> { CircularProgressIndicator() }
ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
Grid Search
ImagesUiState.Success -> { }
ImagesUiState.Success -> { Column { } }
Column { SearchView { } LazyVerticalGrid() { } }
Column { SearchView { } LazyVerticalGrid() { } }
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { }
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) {
} }
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) { Image( painter
= rememberImagePainter(it.url) ) }
Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale
= ContentScale.Crop )
Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale
= ContentScale.Crop )
• Search for images • Update grid with results
Search View View Model Event
Search View View Model State
Grid Search
Search View • Remember and update search text • Perform
Search
@Composable fun SearchView() { OutlinedTextField( value = , onValueChange =
{ } ) }
OutlinedTextField( value = , onValueChange = { } )
OutlinedTextField( value = , onValueChange = { } )
val queryState = remember { mutableStateOf("") }
val queryState = remember { mutableStateOf("") } OutlinedTextField( value =
queryState.value, onValueChange = { queryState.value = it } )
val queryState = remember { mutableStateOf("") } OutlinedTextField( value =
queryState.value, onValueChange = { queryState.value = it } )
Search View • Remember and update search text • Perform
Search
Search View View Model Event
@Composable fun SearchView( onSearch: (String) -> Unit ) { }
OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } )
)
OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } )
)
Column { SearchView { imagesViewModel.searchImages(query) } }
class ImagesViewModel() fun searchImages(query: String) { } }
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repository.searchImages(query =
query) uiState.value = ImagesUiState.Success(images = images) }
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query =
query) uiState.value = ImagesUiState.Success(images = images) }
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query =
query) uiState.value = ImagesUiState.Success(images = images) }
None
• Display location, tags • Like an Image
@Composable fun ImageDetailsScreen() { }
@Composable fun ImageDetailsScreen(imageId: Int?) { }
Image Details Screen View Model Side Effect
@Composable fun ImageDetailsScreen(imageId: Int?) { }
LaunchedEffect(key1 = imageId) { }
LaunchedEffect(key1 = imageId) { viewModel.getImage(imageId) }
val uiState = uiState.collectAsState().value
when (uiState) { ImageDetailsUiState.Loading ImageDetailsUiState.Success ImageDetailsUiState.Error }
• Display location, tags • Like an Image
Image Toolbar
Scaffold( topBar = { }, content = { }
)
Scaffold( topBar = { TopAppBar( title = { Text(text =
"Images") } } )
Scaffold( actions = { Icon( Icons.Default.FavoriteBorder ) } )
• Like Image -> • Unlike Image ->
class ImageDetailsViewModel { var isLiked = mutableStateOf(false) }
fun likeImage(imageId: Int) { }
fun likeImage(imageId: Int) { isLiked.value = true }
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val
result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val
result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val
result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
val isLikeState = remember { viewModel.isLiked }
Icon( if (isLikeState.value.not()) Icons.Default.FavoriteBorder else Icons.Filled.Favorite )
Icon( modifier = Modifier.clickable { viewModel.likeImage(imageId) } )
• Display location, tags • Like an Image
None
Nav Host Images List View Images Details View
Images List View Images Details View
val navController = rememberNavController()
val navController = rememberNavController() NavHost(navController) { }
NavHost(navController) { composable("images_list") { } composable(“images_details") { } }
NavHost(navController) { composable("images_list") { ImagesListScreen() } composable(“images_details") { ImageDetailsScreen()
} }
composable("images_list") { ImagesListScreen { } }
composable("images_list") { ImagesListScreen { navController.navigate(“images_details/{imageId}“) } }
composable(“images_details") { ImagesDetailsScreen( backStackEntry.arguments ?. getString(“imageId") ?. toInt(), ... )
}
None
Jetpack Compose • MutableStateOf • Remember • Side Effects •
@State • @ObservedObject • @Published SwiftUI
Jetpack Compose • MutableStateOf • Remember • Side Effects
SwiftUI Views, Concurrency, Managing State, Navigation
• Display Images • Search
Observed Object View Observe
Observed Object View Update
class ImagesRepository: ObservableObject { }
class ImagesRepository: ObservableObject { @Published var images: [ImageData] = []
}
Swift Concurrency • Async/Await • Tasks
func getImages() async { }
func getImages() async { images = apiService.getImages() }
func getImages() async { images = apiService.getImages() } Publishing changes
from background threads is not allowed; make sure to publish values from the main thread !
@MainActor func getImages() async { images = apiService.getImages() }
Observed Object View Observe
struct ContentView: View { }
struct ContentView: View { }
struct ContentView: View { var body: some View { }
}
struct ContentView: View { @StateObject var repository = ImagesRepository() }
struct ContentView: View { @StateObject var repository = ImagesRepository() }
Best Practice Use StateObject when creating objects.
struct ContentView: View { @StateObject var repository = ImagesRepository()
}.task { }
struct ContentView: View { @StateObject var repository = ImagesRepository()
}.task { await repository.getImages() }
struct ContentView: View { @State var showProgressBar = true
}
ZStack { }
ZStack { if (showProgressBar) { } }
ZStack { if (showProgressBar) { ProgressView() .progressViewStyle( CircularProgressViewStyle(tint: .blue) )
} }
struct ContentView: View { @State var showProgressBar = true
}.task { await repository.getImages() showProgressBar = false }
struct ContentView: View {
• Display Images • Search • Like an image
LazyVGrid(spacing: 10) { }
LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in }
}
LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in Image(item.url)
.resizable() .frame(minWidth: 0, maxWidth: .infinity) } }
• Display Images • Search • Like an image
@State var searchText = ""
LazyVGrid(spacing: 10) { }.searchable()
LazyVGrid(spacing: 10) { }.searchable(text: $searchText)
LazyVGrid(spacing: 10) { }.searchable(text: $searchText) Two way binding
@State var searchText = “" var searchResults: [ImageData] { if
searchText.isEmpty { return repository.images } else { return filterImages(searchText) }
• Display Images • Search • Like an image
struct ImageDetailsView: View { let imageData: ImageData }
struct ImageDetailsView: View { @State var isLiked: Bool = false
}
struct ImageDetailsView: View { @State var isLiked: Bool =
false var body: some View { }.onAppear { } }
struct ImageDetailsView: View { @State var isLiked: Bool =
false var body: some View { }.onAppear { isLiked = imageData.isLiked } }
struct ImageDetailsView: View {
Button(action: { isLiked.toggle() }) { Image(systemName: isLiked ? "heart.fill" :
"heart") }
struct ImageDetailsView: View { @State var isLiked: Bool =
false var body: some View { }.onChange(of: isLiked) { } }
@State var likeTask: Task<Void, Never>? = nil
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.
cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.
cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.
cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
• Display Images • Search • Like an image
None
NavigationView { }
NavigationView { NavigationLink( destination: ImageDetailsView( repository: repository, imageData: item )
) }
None
toolbar { ToolbarItem { Button(action: { }) { Image(systemName:
"square.grid.2x2") } } }
Jetpack Compose • MutableStateOf • Remember • Side Effects •
@State • @ObservedObject • @Published SwiftUI
Resources • github.com/msya/ImagesComposeApp • github.com/msya/ImagesSwiftUIApp
Thank You! www.codingwithmohit.com @heyitsmohit