Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
Mohit Sarveiya Jetpack Compose vs SwiftUI @heyitsmohit
Slide 2
Slide 2 text
Jetpack Compose vs SwiftUI ● Concurrency ● Managing State ● Navigation
Slide 3
Slide 3 text
Benefits ● Understand different approaches in Compose & SwiftUI ● Collaborate with iOS Dev on architecture.
Slide 4
Slide 4 text
Building Android & iOS App Example App
Slide 5
Slide 5 text
iOS Android
Slide 6
Slide 6 text
• Display Images • Search
Slide 7
Slide 7 text
• Like an Image • Display location, tags
Slide 8
Slide 8 text
Jetpack Compose ● MutableStateOf ● Remember ● Side Effects ● @State ● @ObservedObject ● @Published SwiftUI
Slide 9
Slide 9 text
Jetpack Compose Views, Managing State
Slide 10
Slide 10 text
• Display Images • Search
Slide 11
Slide 11 text
Images List Screen View Model State
Slide 12
Slide 12 text
Images List Screen View Model State
Slide 13
Slide 13 text
val uiState = MutableStateFlow()
Slide 14
Slide 14 text
val uiState = MutableStateFlow()
Slide 15
Slide 15 text
sealed class ImagesUiState { object Loading: ImagesUiState() }
Slide 16
Slide 16 text
sealed class ImagesUiState { data class Success( val images: List ): ImagesUiState() }
Slide 17
Slide 17 text
sealed class ImagesUiState { data class Error( val errorMessage: String ): ImagesUiState() }
Slide 18
Slide 18 text
val uiState = MutableStateFlow(ImageUiState.Loading)
Slide 19
Slide 19 text
Images List Screen View Model State
Slide 20
Slide 20 text
init { viewModelScope.launch { val images = repository.getImages() _uiState.value = ImagesUiState.Success(images = images) } }
Slide 21
Slide 21 text
init { viewModelScope.launch { val images = repository.getImages() _uiState.value = ImagesUiState.Success(images = images) } }
Slide 22
Slide 22 text
init { viewModelScope.launch { val images = repository.getImages() _uiState.value = ImagesUiState.Success(images = images) } }
Slide 23
Slide 23 text
init { viewModelScope.launch { val images = repository.getImages() uiState.value = ImagesUiState.Success(images = images) } }
Slide 24
Slide 24 text
init { viewModelScope.launch { val images = repository.getImages() uiState.value = ImagesUiState.Success(images = images) } }
Slide 25
Slide 25 text
Images List Screen View Model State
Slide 26
Slide 26 text
@Composable fun ImagesListScreen() { )
Slide 27
Slide 27 text
Images List Screen View Model State
Slide 28
Slide 28 text
Streams of data ● LiveData -> observeAsState ● Flow -> collectAsState ● Observable -> subscribeAsState
Slide 29
Slide 29 text
@Composable fun ImagesListScreen() { val uiState = uiState.collectAsState().value )
Slide 30
Slide 30 text
@Composable fun ImagesListScreen() { when (uiState) { } )
Slide 31
Slide 31 text
@Composable fun ImagesListScreen() { when (uiState) { ImagesUiState.Loading ImagesUiState.Success ImagesUiState.Error } )
Slide 32
Slide 32 text
ImagesUiState.Loading -> { CircularProgressIndicator() }
Slide 33
Slide 33 text
ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
Slide 34
Slide 34 text
ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
Slide 35
Slide 35 text
Grid Search
Slide 36
Slide 36 text
ImagesUiState.Success -> { }
Slide 37
Slide 37 text
ImagesUiState.Success -> { Column { } }
Slide 38
Slide 38 text
Column { SearchView { } LazyVerticalGrid() { } }
Slide 39
Slide 39 text
Column { SearchView { } LazyVerticalGrid() { } }
Slide 40
Slide 40 text
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { }
Slide 41
Slide 41 text
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) { } }
Slide 42
Slide 42 text
LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) { Image( painter = rememberImagePainter(it.url) ) }
Slide 43
Slide 43 text
Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale = ContentScale.Crop )
Slide 44
Slide 44 text
Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale = ContentScale.Crop )
Slide 45
Slide 45 text
• Search for images • Update grid with results
Slide 46
Slide 46 text
Search View View Model Event
Slide 47
Slide 47 text
Search View View Model State
Slide 48
Slide 48 text
Grid Search
Slide 49
Slide 49 text
Search View ● Remember and update search text ● Perform Search
Slide 50
Slide 50 text
@Composable fun SearchView() { OutlinedTextField( value = , onValueChange = { } ) }
Slide 51
Slide 51 text
OutlinedTextField( value = , onValueChange = { } )
Slide 52
Slide 52 text
OutlinedTextField( value = , onValueChange = { } )
Slide 53
Slide 53 text
val queryState = remember { mutableStateOf("") }
Slide 54
Slide 54 text
val queryState = remember { mutableStateOf("") } OutlinedTextField( value = queryState.value, onValueChange = { queryState.value = it } )
Slide 55
Slide 55 text
val queryState = remember { mutableStateOf("") } OutlinedTextField( value = queryState.value, onValueChange = { queryState.value = it } )
Slide 56
Slide 56 text
Search View ● Remember and update search text ● Perform Search
Slide 57
Slide 57 text
Search View View Model Event
Slide 58
Slide 58 text
@Composable fun SearchView( onSearch: (String) -> Unit ) { }
Slide 59
Slide 59 text
OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } ) )
Slide 60
Slide 60 text
OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } ) )
Slide 61
Slide 61 text
Column { SearchView { imagesViewModel.searchImages(query) } }
Slide 62
Slide 62 text
class ImagesViewModel() fun searchImages(query: String) { } }
Slide 63
Slide 63 text
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repository.searchImages(query = query) uiState.value = ImagesUiState.Success(images = images) }
Slide 64
Slide 64 text
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query = query) uiState.value = ImagesUiState.Success(images = images) }
Slide 65
Slide 65 text
viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query = query) uiState.value = ImagesUiState.Success(images = images) }
Slide 66
Slide 66 text
No content
Slide 67
Slide 67 text
• Display location, tags • Like an Image
Slide 68
Slide 68 text
@Composable fun ImageDetailsScreen() { }
Slide 69
Slide 69 text
@Composable fun ImageDetailsScreen(imageId: Int?) { }
Slide 70
Slide 70 text
Image Details Screen View Model Side Effect
Slide 71
Slide 71 text
@Composable fun ImageDetailsScreen(imageId: Int?) { }
Slide 72
Slide 72 text
LaunchedEffect(key1 = imageId) { }
Slide 73
Slide 73 text
LaunchedEffect(key1 = imageId) { viewModel.getImage(imageId) }
Slide 74
Slide 74 text
val uiState = uiState.collectAsState().value
Slide 75
Slide 75 text
when (uiState) { ImageDetailsUiState.Loading ImageDetailsUiState.Success ImageDetailsUiState.Error }
Slide 76
Slide 76 text
• Display location, tags • Like an Image
Slide 77
Slide 77 text
Image Toolbar
Slide 78
Slide 78 text
Scaffold( topBar = { }, content = { } )
Slide 79
Slide 79 text
Scaffold( topBar = { TopAppBar( title = { Text(text = "Images") } } )
Slide 80
Slide 80 text
Scaffold( actions = { Icon( Icons.Default.FavoriteBorder ) } )
Slide 81
Slide 81 text
• Like Image -> • Unlike Image ->
Slide 82
Slide 82 text
class ImageDetailsViewModel { var isLiked = mutableStateOf(false) }
Slide 83
Slide 83 text
fun likeImage(imageId: Int) { }
Slide 84
Slide 84 text
fun likeImage(imageId: Int) { isLiked.value = true }
Slide 85
Slide 85 text
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
Slide 86
Slide 86 text
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
Slide 87
Slide 87 text
fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
Slide 88
Slide 88 text
val isLikeState = remember { viewModel.isLiked }
Slide 89
Slide 89 text
Icon( if (isLikeState.value.not()) Icons.Default.FavoriteBorder else Icons.Filled.Favorite )
Slide 90
Slide 90 text
Icon( modifier = Modifier.clickable { viewModel.likeImage(imageId) } )
Slide 91
Slide 91 text
• Display location, tags • Like an Image
Slide 92
Slide 92 text
No content
Slide 93
Slide 93 text
Nav Host Images List View Images Details View
Slide 94
Slide 94 text
Images List View Images Details View
Slide 95
Slide 95 text
val navController = rememberNavController()
Slide 96
Slide 96 text
val navController = rememberNavController() NavHost(navController) { }
Slide 97
Slide 97 text
NavHost(navController) { composable("images_list") { } composable(“images_details") { } }
Slide 98
Slide 98 text
NavHost(navController) { composable("images_list") { ImagesListScreen() } composable(“images_details") { ImageDetailsScreen() } }
Slide 99
Slide 99 text
composable("images_list") { ImagesListScreen { } }
Slide 100
Slide 100 text
composable("images_list") { ImagesListScreen { navController.navigate(“images_details/{imageId}“) } }
Slide 101
Slide 101 text
composable(“images_details") { ImagesDetailsScreen( backStackEntry.arguments ?. getString(“imageId") ?. toInt(), ... ) }
Slide 102
Slide 102 text
No content
Slide 103
Slide 103 text
Jetpack Compose ● MutableStateOf ● Remember ● Side Effects ● @State ● @ObservedObject ● @Published SwiftUI
Slide 104
Slide 104 text
Jetpack Compose ● MutableStateOf ● Remember ● Side Effects
Slide 105
Slide 105 text
SwiftUI Views, Concurrency, Managing State, Navigation
Slide 106
Slide 106 text
• Display Images • Search
Slide 107
Slide 107 text
Observed Object View Observe
Slide 108
Slide 108 text
Observed Object View Update
Slide 109
Slide 109 text
class ImagesRepository: ObservableObject { }
Slide 110
Slide 110 text
class ImagesRepository: ObservableObject { @Published var images: [ImageData] = [] }
Slide 111
Slide 111 text
Swift Concurrency ● Async/Await ● Tasks
Slide 112
Slide 112 text
func getImages() async { }
Slide 113
Slide 113 text
func getImages() async { images = apiService.getImages() }
Slide 114
Slide 114 text
func getImages() async { images = apiService.getImages() } Publishing changes from background threads is not allowed; make sure to publish values from the main thread !
Slide 115
Slide 115 text
@MainActor func getImages() async { images = apiService.getImages() }
Slide 116
Slide 116 text
Observed Object View Observe
Slide 117
Slide 117 text
struct ContentView: View { }
Slide 118
Slide 118 text
struct ContentView: View { }
Slide 119
Slide 119 text
struct ContentView: View { var body: some View { } }
Slide 120
Slide 120 text
struct ContentView: View { @StateObject var repository = ImagesRepository() }
Slide 121
Slide 121 text
struct ContentView: View { @StateObject var repository = ImagesRepository() } Best Practice Use StateObject when creating objects.
Slide 122
Slide 122 text
struct ContentView: View { @StateObject var repository = ImagesRepository() }.task { }
Slide 123
Slide 123 text
struct ContentView: View { @StateObject var repository = ImagesRepository() }.task { await repository.getImages() }
Slide 124
Slide 124 text
struct ContentView: View { @State var showProgressBar = true }
Slide 125
Slide 125 text
ZStack { }
Slide 126
Slide 126 text
ZStack { if (showProgressBar) { } }
Slide 127
Slide 127 text
ZStack { if (showProgressBar) { ProgressView() .progressViewStyle( CircularProgressViewStyle(tint: .blue) ) } }
Slide 128
Slide 128 text
struct ContentView: View { @State var showProgressBar = true }.task { await repository.getImages() showProgressBar = false }
Slide 129
Slide 129 text
struct ContentView: View {
Slide 130
Slide 130 text
• Display Images • Search • Like an image
Slide 131
Slide 131 text
LazyVGrid(spacing: 10) { }
Slide 132
Slide 132 text
LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in } }
Slide 133
Slide 133 text
LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in Image(item.url) .resizable() .frame(minWidth: 0, maxWidth: .infinity) } }
Slide 134
Slide 134 text
• Display Images • Search • Like an image
Slide 135
Slide 135 text
@State var searchText = ""
Slide 136
Slide 136 text
LazyVGrid(spacing: 10) { }.searchable()
Slide 137
Slide 137 text
LazyVGrid(spacing: 10) { }.searchable(text: $searchText)
Slide 138
Slide 138 text
LazyVGrid(spacing: 10) { }.searchable(text: $searchText) Two way binding
Slide 139
Slide 139 text
@State var searchText = “" var searchResults: [ImageData] { if searchText.isEmpty { return repository.images } else { return filterImages(searchText) }
Slide 140
Slide 140 text
• Display Images • Search • Like an image
Slide 141
Slide 141 text
struct ImageDetailsView: View { let imageData: ImageData }
Slide 142
Slide 142 text
struct ImageDetailsView: View { @State var isLiked: Bool = false }
Slide 143
Slide 143 text
struct ImageDetailsView: View { @State var isLiked: Bool = false var body: some View { }.onAppear { } }
Slide 144
Slide 144 text
struct ImageDetailsView: View { @State var isLiked: Bool = false var body: some View { }.onAppear { isLiked = imageData.isLiked } }
Slide 145
Slide 145 text
struct ImageDetailsView: View {
Slide 146
Slide 146 text
Button(action: { isLiked.toggle() }) { Image(systemName: isLiked ? "heart.fill" : "heart") }
Slide 147
Slide 147 text
struct ImageDetailsView: View { @State var isLiked: Bool = false var body: some View { }.onChange(of: isLiked) { } }
Slide 148
Slide 148 text
@State var likeTask: Task? = nil
Slide 149
Slide 149 text
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?. cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
Slide 150
Slide 150 text
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?. cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
Slide 151
Slide 151 text
.onChange(of: isLiked) { newValue in if (newValue) { likeTask ?. cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
Slide 152
Slide 152 text
• Display Images • Search • Like an image
Slide 153
Slide 153 text
No content
Slide 154
Slide 154 text
NavigationView { }
Slide 155
Slide 155 text
NavigationView { NavigationLink( destination: ImageDetailsView( repository: repository, imageData: item ) ) }
Slide 156
Slide 156 text
No content
Slide 157
Slide 157 text
toolbar { ToolbarItem { Button(action: { }) { Image(systemName: "square.grid.2x2") } } }
Slide 158
Slide 158 text
Jetpack Compose ● MutableStateOf ● Remember ● Side Effects ● @State ● @ObservedObject ● @Published SwiftUI
Slide 159
Slide 159 text
Resources ● github.com/msya/ImagesComposeApp ● github.com/msya/ImagesSwiftUIApp
Slide 160
Slide 160 text
Thank You! www.codingwithmohit.com @heyitsmohit