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
870
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
98
Building Shared UIs across Platforms with Compose
heyitsmohit
1
440
Building Multiplatform Apps with Compose
heyitsmohit
2
380
Building StateFlows with Jetpack Compose
heyitsmohit
6
1.6k
Building Android Testing Infrastructure
heyitsmohit
1
360
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
630
Using Square Workflow for Android & iOS
heyitsmohit
1
340
Building Android Infrastructure Teams at Scale
heyitsmohit
3
260
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
480
Other Decks in Programming
See All in Programming
RAGの回答精度評価用のQAデータセットを生成AIに作らせた話
kurahara
0
240
労務ドメインを快適に開発する方法 / How to Comfortably Develop in the Labor Domain
yuki21
1
250
デザインシステムとコンポーネント指向によるフロントエンド開発プロセスの革新 / Innovation in Frontend Development Processes through Design Systems and Component-Oriented Architecture
nrslib
8
5.3k
Rechartsで楽にゴリゴリにカスタマイズする!
10tera
1
160
[DroidKaigi 2024] Android ViewからJetpack Composeへ 〜Jetpack Compose移行のすゝめ〜 / From Android View to Jetpack Compose: A Guide to Migration
syarihu
1
240
A New Era of Testing
mannodermaus
2
150
Our Websites Need a Lifestyle Change, Not a Diet
ryantownsend
0
120
『ドメイン駆動設計をはじめよう』中核の業務領域
masuda220
PRO
5
960
Modular Monolith Go Server with GraphQL Federation + gRPC
110y
1
580
rails_girls_is_my_gate_to_join_the_ruby_commuinty
maimux2x
0
190
Scala アプリケーションのビルドを改善してデプロイ時間を 1/4 にした話 | How I improved the build of my Scala application and reduced deployment time by 4x
nomadblacky
1
150
LangGraphでのHuman-in-the-Loopの実装
os1ma
3
980
Featured
See All Featured
Web development in the modern age
philhawksworth
204
10k
Unsuck your backbone
ammeep
667
57k
How to name files
jennybc
75
98k
YesSQL, Process and Tooling at Scale
rocio
167
14k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
354
29k
KATA
mclloyd
27
13k
WebSockets: Embracing the real-time Web
robhawkes
59
7.3k
Writing Fast Ruby
sferik
623
60k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
23
1.7k
Web Components: a chance to create the future
zenorocha
308
41k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
45
4.8k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
26
1.9k
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