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
250
Building Shared UIs across Platforms with Compose
heyitsmohit
1
650
Building Multiplatform Apps with Compose
heyitsmohit
2
530
Building StateFlows with Jetpack Compose
heyitsmohit
6
1.9k
Building Android Testing Infrastructure
heyitsmohit
1
510
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
800
Using Square Workflow for Android & iOS
heyitsmohit
1
440
Building Android Infrastructure Teams at Scale
heyitsmohit
3
340
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
580
Other Decks in Programming
See All in Programming
MCPサーバー「モディフィウス」で変更容易性の向上をスケールする / modifius
minodriven
3
250
マンガアプリViewerの大画面対応を考える
kk__777
0
440
Swift Concurrency 年表クイズ
omochi
3
220
CSC509 Lecture 07
javiergs
PRO
0
250
When Dependencies Fail: Building Antifragile Applications in a Fragile World
selcukusta
0
120
KoogではじめるAIエージェント開発
hiroaki404
1
260
Inside of Swift Export
giginet
PRO
1
310
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
670
React Nativeならぬ"Vue Native"が実現するかも?_新世代マルチプラットフォーム開発フレームワークのLynxとLynxのVue.js対応を追ってみよう_Vue Lynx
yut0naga1_fa
2
2k
NIKKEI Tech Talk#38
cipepser
0
350
EMこそClaude Codeでコード調査しよう
shibayu36
0
550
pnpm に provenance のダウングレード を検出する PR を出してみた
ryo_manba
1
170
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
15k
Docker and Python
trallard
46
3.6k
Building Flexible Design Systems
yeseniaperezcruz
329
39k
How to Think Like a Performance Engineer
csswizardry
27
2.2k
Git: the NoSQL Database
bkeepers
PRO
431
66k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.7k
Large-scale JavaScript Application Architecture
addyosmani
514
110k
Product Roadmaps are Hard
iamctodd
PRO
55
11k
Designing for humans not robots
tammielis
254
26k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
31
2.7k
What's in a price? How to price your products and services
michaelherold
246
12k
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