Save 37% off PRO during our Black Friday Sale! »

Jetpack Compose vs SwiftUI (Android Worldwide 2021)

B3f560d34c14a9113e5024bc34ac26a0?s=47 Mohit S
October 26, 2021

Jetpack Compose vs SwiftUI (Android Worldwide 2021)

B3f560d34c14a9113e5024bc34ac26a0?s=128

Mohit S

October 26, 2021
Tweet

Transcript

  1. Mohit Sarveiya Jetpack Compose vs SwiftUI @heyitsmohit

  2. Jetpack Compose vs SwiftUI • Concurrency • Managing State •

    Navigation
  3. Benefits • Understand different approaches in Compose & SwiftUI •

    Collaborate with iOS Dev on architecture.
  4. Building Android & iOS App Example App

  5. iOS Android

  6. • Display Images • Search

  7. • Like an Image • Display location, tags

  8. Jetpack Compose • MutableStateOf • Remember • Side Effects •

    @State • @ObservedObject • @Published SwiftUI
  9. Jetpack Compose Views, Managing State

  10. • Display Images • Search

  11. Images List Screen View Model State

  12. Images List Screen View Model State

  13. val uiState = MutableStateFlow<ImagesUiState>()

  14. val uiState = MutableStateFlow<ImagesUiState>()

  15. sealed class ImagesUiState { 
 
 object Loading: ImagesUiState() }

  16. sealed class ImagesUiState { 
 data class Success( val images:

    List<ImageData> ): ImagesUiState() }
  17. sealed class ImagesUiState { 
 data class Error( val errorMessage:

    String ): ImagesUiState() }
  18. val uiState = MutableStateFlow(ImageUiState.Loading)

  19. Images List Screen View Model State

  20. init { viewModelScope.launch { val images = repository.getImages() _uiState.value =

    ImagesUiState.Success(images = images) } }
  21. init { viewModelScope.launch { val images = repository.getImages() _uiState.value =

    ImagesUiState.Success(images = images) } }
  22. init { viewModelScope.launch { val images = repository.getImages() _uiState.value =

    ImagesUiState.Success(images = images) } }
  23. init { viewModelScope.launch { val images = repository.getImages() uiState.value =

    ImagesUiState.Success(images = images) } }
  24. init { viewModelScope.launch { val images = repository.getImages() uiState.value =

    ImagesUiState.Success(images = images) } }
  25. Images List Screen View Model State

  26. @Composable fun ImagesListScreen() { )

  27. Images List Screen View Model State

  28. Streams of data • LiveData -> observeAsState • Flow ->

    collectAsState • Observable -> subscribeAsState
  29. @Composable fun ImagesListScreen() { val uiState = uiState.collectAsState().value )

  30. @Composable fun ImagesListScreen() { when (uiState) { } )

  31. @Composable fun ImagesListScreen() { when (uiState) { ImagesUiState.Loading ImagesUiState.Success 


    ImagesUiState.Error } )
  32. ImagesUiState.Loading -> { CircularProgressIndicator() }

  33. ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
  34. ImagesUiState.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator() } }
  35. 
 Grid Search

  36. ImagesUiState.Success -> { }

  37. ImagesUiState.Success -> { Column { } }

  38. Column { SearchView { } LazyVerticalGrid() { } }

  39. Column { SearchView { } LazyVerticalGrid() { } }

  40. LazyVerticalGrid( cells = GridCells.Fixed(3) ) { }

  41. LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) { 
 


    
 } }
  42. LazyVerticalGrid( cells = GridCells.Fixed(3) ) { items(uiState.images) { Image( painter

    = rememberImagePainter(it.url) ) }
  43. Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale

    = ContentScale.Crop )
  44. Image( painter = rememberImagePainter(it.url), modifier = Modifier .size(128.dp) .padding(8.dp) .contentScale

    = ContentScale.Crop )
  45. • Search for images • Update grid with results

  46. Search View View Model Event

  47. Search View View Model State

  48. 
 Grid Search

  49. Search View • Remember and update search text • Perform

    Search
  50. @Composable fun SearchView() { OutlinedTextField( value = , onValueChange =

    { } ) }
  51. OutlinedTextField( value = , onValueChange = { } )

  52. OutlinedTextField( value = , onValueChange = { } )

  53. val queryState = remember { mutableStateOf("") }

  54. val queryState = remember { mutableStateOf("") } OutlinedTextField( value =

    queryState.value, onValueChange = { queryState.value = it } )
  55. val queryState = remember { mutableStateOf("") } OutlinedTextField( value =

    queryState.value, onValueChange = { queryState.value = it } )
  56. Search View • Remember and update search text • Perform

    Search
  57. Search View View Model Event

  58. @Composable fun SearchView( onSearch: (String) -> Unit ) { }

  59. OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } )

    )
  60. OutlinedTextField( keyboardActions = KeyboardActions( onDone = { onSearch(queryState.value) } )

    )
  61. Column { SearchView { imagesViewModel.searchImages(query) } }

  62. class ImagesViewModel() fun searchImages(query: String) { } }

  63. viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repository.searchImages(query =

    query) uiState.value = ImagesUiState.Success(images = images) }
  64. viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query =

    query) uiState.value = ImagesUiState.Success(images = images) }
  65. viewModelScope.launch { uiState.value = ImagesUiState.Loading val images = repo.searchImages(query =

    query) uiState.value = ImagesUiState.Success(images = images) }
  66. None
  67. • Display location, tags • Like an Image

  68. @Composable fun ImageDetailsScreen() { }

  69. @Composable fun ImageDetailsScreen(imageId: Int?) { }

  70. Image Details Screen View Model Side Effect

  71. @Composable fun ImageDetailsScreen(imageId: Int?) { }

  72. LaunchedEffect(key1 = imageId) { }

  73. LaunchedEffect(key1 = imageId) { viewModel.getImage(imageId) }

  74. val uiState = uiState.collectAsState().value

  75. when (uiState) { ImageDetailsUiState.Loading ImageDetailsUiState.Success ImageDetailsUiState.Error }

  76. • Display location, tags • Like an Image

  77. 
 Image Toolbar

  78. Scaffold( topBar = { 
 }, content = { }

    )
  79. Scaffold( topBar = { TopAppBar( title = { Text(text =

    "Images") } } )
  80. Scaffold( actions = { Icon( Icons.Default.FavoriteBorder ) } )

  81. • Like Image -> • Unlike Image ->

  82. class ImageDetailsViewModel { var isLiked = mutableStateOf(false) }

  83. fun likeImage(imageId: Int) { }

  84. fun likeImage(imageId: Int) { isLiked.value = true }

  85. fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val

    result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
  86. fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val

    result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
  87. fun likeImage(imageId: Int) { isLiked.value = true viewModelScope.launch { val

    result = repo.likeImage(imageId) result.onFailure { isLiked.value = false } } }
  88. val isLikeState = remember { viewModel.isLiked }

  89. Icon( if (isLikeState.value.not()) Icons.Default.FavoriteBorder else Icons.Filled.Favorite )

  90. Icon( modifier = Modifier.clickable { viewModel.likeImage(imageId) } )

  91. • Display location, tags • Like an Image

  92. None
  93. Nav Host Images List View Images Details View

  94. Images List View Images Details View

  95. val navController = rememberNavController()

  96. val navController = rememberNavController() NavHost(navController) { }

  97. NavHost(navController) { 
 composable("images_list") { } composable(“images_details") { } }

  98. NavHost(navController) { 
 composable("images_list") { ImagesListScreen() } composable(“images_details") { ImageDetailsScreen()

    } }
  99. composable("images_list") { ImagesListScreen { } }

  100. composable("images_list") { ImagesListScreen { navController.navigate(“images_details/{imageId}“) } }

  101. composable(“images_details") { ImagesDetailsScreen( backStackEntry.arguments ?. getString(“imageId") ?. toInt(), ... )

    }
  102. None
  103. Jetpack Compose • MutableStateOf • Remember • Side Effects •

    @State • @ObservedObject • @Published SwiftUI
  104. Jetpack Compose • MutableStateOf • Remember • Side Effects

  105. SwiftUI Views, Concurrency, Managing State, Navigation

  106. • Display Images • Search

  107. Observed Object View Observe

  108. Observed Object View Update

  109. class ImagesRepository: ObservableObject { }

  110. class ImagesRepository: ObservableObject { @Published var images: [ImageData] = []

    }
  111. Swift Concurrency • Async/Await • Tasks

  112. func getImages() async { }

  113. func getImages() async { images = apiService.getImages() }

  114. func getImages() async { images = apiService.getImages() } Publishing changes

    from background threads is not allowed; make sure to publish values from the main thread !
  115. @MainActor func getImages() async { images = apiService.getImages() }

  116. Observed Object View Observe

  117. struct ContentView: View { }

  118. struct ContentView: View { }

  119. struct ContentView: View { var body: some View { }

    }
  120. struct ContentView: View { @StateObject var repository = ImagesRepository() }

  121. struct ContentView: View { @StateObject var repository = ImagesRepository() }

    Best Practice Use StateObject when creating objects.
  122. struct ContentView: View { 
 @StateObject var repository = ImagesRepository()

    
 }.task { }
  123. struct ContentView: View { 
 @StateObject var repository = ImagesRepository()

    
 }.task { await repository.getImages() }
  124. struct ContentView: View { 
 @State var showProgressBar = true

    
 }
  125. ZStack { }

  126. ZStack { if (showProgressBar) { } }

  127. ZStack { if (showProgressBar) { ProgressView() .progressViewStyle( CircularProgressViewStyle(tint: .blue) )

    } }
  128. struct ContentView: View { 
 @State var showProgressBar = true

    
 }.task { await repository.getImages() showProgressBar = false }
  129. struct ContentView: View { 


  130. • Display Images • Search • Like an image

  131. LazyVGrid(spacing: 10) { }

  132. LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in }

    }
  133. LazyVGrid(spacing: 10) { ForEach(repo.images, id: \.id) { item in Image(item.url)

    .resizable() .frame(minWidth: 0, maxWidth: .infinity) } }
  134. • Display Images • Search • Like an image

  135. @State var searchText = ""

  136. LazyVGrid(spacing: 10) { }.searchable()

  137. LazyVGrid(spacing: 10) { }.searchable(text: $searchText)

  138. LazyVGrid(spacing: 10) { }.searchable(text: $searchText) Two way binding

  139. @State var searchText = “" var searchResults: [ImageData] { if

    searchText.isEmpty { return repository.images } else { return filterImages(searchText) }
  140. • Display Images • Search • Like an image

  141. struct ImageDetailsView: View { let imageData: ImageData }

  142. struct ImageDetailsView: View { @State var isLiked: Bool = false

    }
  143. struct ImageDetailsView: View { 
 @State var isLiked: Bool =

    false 
 var body: some View { 
 }.onAppear { } }
  144. struct ImageDetailsView: View { 
 @State var isLiked: Bool =

    false 
 var body: some View { 
 }.onAppear { isLiked = imageData.isLiked } }
  145. struct ImageDetailsView: View { 


  146. Button(action: { isLiked.toggle() }) { Image(systemName: isLiked ? "heart.fill" :

    "heart") }
  147. struct ImageDetailsView: View { 
 @State var isLiked: Bool =

    false 
 var body: some View { 
 }.onChange(of: isLiked) { } }
  148. @State var likeTask: Task<Void, Never>? = nil

  149. .onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.

    cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
  150. .onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.

    cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
  151. .onChange(of: isLiked) { newValue in if (newValue) { likeTask ?.

    cancel() likeTask = Task { await repository.likeImage(id: imageData.id) } } }
  152. • Display Images • Search • Like an image

  153. None
  154. NavigationView { }

  155. NavigationView { NavigationLink( destination: ImageDetailsView( repository: repository, imageData: item )

    ) }
  156. None
  157. toolbar { ToolbarItem { Button(action: { 
 }) { Image(systemName:

    "square.grid.2x2") } } }
  158. Jetpack Compose • MutableStateOf • Remember • Side Effects •

    @State • @ObservedObject • @Published SwiftUI
  159. Resources • github.com/msya/ImagesComposeApp • github.com/msya/ImagesSwiftUIApp

  160. Thank You! www.codingwithmohit.com @heyitsmohit