Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack Compose vs SwiftUI (Android Worldwide 2021)

Mohit S
October 26, 2021

Jetpack Compose vs SwiftUI (Android Worldwide 2021)

Mohit S

October 26, 2021
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Mohit Sarveiya
    Jetpack Compose vs SwiftUI
    @heyitsmohit

    View full-size slide

  2. Jetpack Compose vs SwiftUI
    ● Concurrency

    ● Managing State

    ● Navigation

    View full-size slide

  3. Benefits
    ● Understand different approaches in Compose & SwiftUI

    ● Collaborate with iOS Dev on architecture.

    View full-size slide

  4. Building Android & iOS App
    Example App

    View full-size slide

  5. • Display Images

    • Search

    View full-size slide

  6. • Like an Image

    • Display location, tags

    View full-size slide

  7. Jetpack Compose
    ● MutableStateOf

    ● Remember

    ● Side Effects
    ● @State

    ● @ObservedObject

    ● @Published
    SwiftUI

    View full-size slide

  8. Jetpack Compose
    Views, Managing State

    View full-size slide

  9. • Display Images

    • Search

    View full-size slide

  10. Images List
    Screen
    View Model
    State

    View full-size slide

  11. Images List
    Screen
    View Model
    State

    View full-size slide

  12. val uiState = MutableStateFlow()

    View full-size slide

  13. val uiState = MutableStateFlow()

    View full-size slide

  14. sealed class ImagesUiState {


    object Loading: ImagesUiState()

    }

    View full-size slide

  15. sealed class ImagesUiState {


    data class Success(

    val images: List

    ): ImagesUiState()

    }

    View full-size slide

  16. sealed class ImagesUiState {


    data class Error(

    val errorMessage: String

    ): ImagesUiState()

    }

    View full-size slide

  17. val uiState = MutableStateFlow(ImageUiState.Loading)

    View full-size slide

  18. Images List
    Screen
    View Model
    State

    View full-size slide

  19. init {

    viewModelScope.launch {

    val images = repository.getImages()

    _uiState.value = ImagesUiState.Success(images = images)

    }

    }

    View full-size slide

  20. init {

    viewModelScope.launch {

    val images = repository.getImages()

    _uiState.value = ImagesUiState.Success(images = images)

    }

    }

    View full-size slide

  21. init {

    viewModelScope.launch {

    val images = repository.getImages()

    _uiState.value = ImagesUiState.Success(images = images)

    }

    }

    View full-size slide

  22. init {

    viewModelScope.launch {

    val images = repository.getImages()

    uiState.value = ImagesUiState.Success(images = images)

    }

    }

    View full-size slide

  23. init {

    viewModelScope.launch {

    val images = repository.getImages()

    uiState.value = ImagesUiState.Success(images = images)

    }

    }

    View full-size slide

  24. Images List
    Screen
    View Model
    State

    View full-size slide

  25. @Composable

    fun ImagesListScreen() {

    )

    View full-size slide

  26. Images List
    Screen
    View Model
    State

    View full-size slide

  27. Streams of data
    ● LiveData
    ->
    observeAsState

    ● Flow
    ->
    collectAsState

    ● Observable
    ->
    subscribeAsState

    View full-size slide

  28. @Composable

    fun ImagesListScreen() {

    val uiState = uiState.collectAsState().value

    )

    View full-size slide

  29. @Composable

    fun ImagesListScreen() {

    when (uiState) {

    }

    )

    View full-size slide

  30. @Composable

    fun ImagesListScreen() {

    when (uiState) {

    ImagesUiState.Loading

    ImagesUiState.Success

    ImagesUiState.Error

    }

    )

    View full-size slide

  31. ImagesUiState.Loading
    ->
    {

    CircularProgressIndicator()

    }

    View full-size slide

  32. ImagesUiState.Loading
    ->
    {

    Column(

    modifier = Modifier.fillMaxSize(),

    verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally

    ) {

    CircularProgressIndicator()

    }

    }

    View full-size slide

  33. ImagesUiState.Loading
    ->
    {

    Column(

    modifier = Modifier.fillMaxSize(),

    verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally

    ) {

    CircularProgressIndicator()

    }

    }

    View full-size slide


  34. Grid
    Search

    View full-size slide

  35. ImagesUiState.Success
    ->
    {

    }

    View full-size slide

  36. ImagesUiState.Success
    ->
    {

    Column {

    }

    }

    View full-size slide

  37. Column {

    SearchView { }

    LazyVerticalGrid() { }

    }

    View full-size slide

  38. Column {

    SearchView { }

    LazyVerticalGrid() { }

    }

    View full-size slide

  39. LazyVerticalGrid(

    cells = GridCells.Fixed(3)

    ) {

    }

    View full-size slide

  40. LazyVerticalGrid(

    cells = GridCells.Fixed(3)

    ) {

    items(uiState.images) {




    }

    }

    View full-size slide

  41. LazyVerticalGrid(

    cells = GridCells.Fixed(3)

    ) {

    items(uiState.images) {

    Image(

    painter = rememberImagePainter(it.url)

    )

    }

    View full-size slide

  42. Image(

    painter = rememberImagePainter(it.url),

    modifier = Modifier

    .size(128.dp)

    .padding(8.dp)

    .contentScale = ContentScale.Crop

    )

    View full-size slide

  43. Image(

    painter = rememberImagePainter(it.url),

    modifier = Modifier

    .size(128.dp)

    .padding(8.dp)

    .contentScale = ContentScale.Crop

    )

    View full-size slide

  44. • Search for images

    • Update grid with results

    View full-size slide

  45. Search View View Model
    Event

    View full-size slide

  46. Search View View Model
    State

    View full-size slide


  47. Grid
    Search

    View full-size slide

  48. Search View
    ● Remember and update search text

    ● Perform Search

    View full-size slide

  49. @Composable

    fun SearchView() {

    OutlinedTextField(

    value = ,

    onValueChange = {

    }

    )

    }

    View full-size slide

  50. OutlinedTextField(

    value = ,

    onValueChange = {

    }

    )

    View full-size slide

  51. OutlinedTextField(

    value = ,

    onValueChange = {

    }

    )

    View full-size slide

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

    View full-size slide

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

    OutlinedTextField(

    value = queryState.value,

    onValueChange = {

    queryState.value = it

    }

    )

    View full-size slide

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

    OutlinedTextField(

    value = queryState.value,

    onValueChange = {

    queryState.value = it

    }

    )

    View full-size slide

  55. Search View
    ● Remember and update search text

    ● Perform Search

    View full-size slide

  56. Search View View Model
    Event

    View full-size slide

  57. @Composable

    fun SearchView(

    onSearch: (String)
    ->
    Unit

    ) {

    }

    View full-size slide

  58. OutlinedTextField(

    keyboardActions = KeyboardActions(

    onDone = {

    onSearch(queryState.value)

    }

    )

    )

    View full-size slide

  59. OutlinedTextField(

    keyboardActions = KeyboardActions(

    onDone = {

    onSearch(queryState.value)

    }

    )

    )

    View full-size slide

  60. Column {

    SearchView {

    imagesViewModel.searchImages(query)

    }

    }

    View full-size slide

  61. class ImagesViewModel()

    fun searchImages(query: String) {

    }

    }

    View full-size slide

  62. viewModelScope.launch {

    uiState.value = ImagesUiState.Loading

    val images = repository.searchImages(query = query)

    uiState.value = ImagesUiState.Success(images = images)

    }

    View full-size slide

  63. viewModelScope.launch {

    uiState.value = ImagesUiState.Loading

    val images = repo.searchImages(query = query)

    uiState.value = ImagesUiState.Success(images = images)

    }

    View full-size slide

  64. viewModelScope.launch {

    uiState.value = ImagesUiState.Loading

    val images = repo.searchImages(query = query)

    uiState.value = ImagesUiState.Success(images = images)

    }

    View full-size slide

  65. • Display location, tags

    • Like an Image

    View full-size slide

  66. @Composable

    fun ImageDetailsScreen() {

    }

    View full-size slide

  67. @Composable

    fun ImageDetailsScreen(imageId: Int?) {

    }

    View full-size slide

  68. Image
    Details
    Screen
    View Model
    Side Effect

    View full-size slide

  69. @Composable

    fun ImageDetailsScreen(imageId: Int?) {

    }

    View full-size slide

  70. LaunchedEffect(key1 = imageId) {

    }

    View full-size slide

  71. LaunchedEffect(key1 = imageId) {

    viewModel.getImage(imageId)

    }

    View full-size slide

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

    View full-size slide

  73. when (uiState) {

    ImageDetailsUiState.Loading

    ImageDetailsUiState.Success

    ImageDetailsUiState.Error

    }

    View full-size slide

  74. • Display location, tags

    • Like an Image

    View full-size slide


  75. Image
    Toolbar

    View full-size slide

  76. Scaffold(

    topBar = {

    },

    content = {

    }

    )

    View full-size slide

  77. Scaffold(

    topBar = {

    TopAppBar(

    title = {

    Text(text = "Images")

    }

    }

    )

    View full-size slide

  78. Scaffold(

    actions = {

    Icon(

    Icons.Default.FavoriteBorder

    )

    }

    )

    View full-size slide

  79. • Like Image
    ->


    • Unlike Image
    ->

    View full-size slide

  80. class ImageDetailsViewModel {

    var isLiked = mutableStateOf(false)

    }

    View full-size slide

  81. fun likeImage(imageId: Int) {

    }

    View full-size slide

  82. fun likeImage(imageId: Int) {

    isLiked.value = true

    }

    View full-size slide

  83. fun likeImage(imageId: Int) {

    isLiked.value = true

    viewModelScope.launch {

    val result = repo.likeImage(imageId)

    result.onFailure {

    isLiked.value = false

    }

    }

    }

    View full-size slide

  84. fun likeImage(imageId: Int) {

    isLiked.value = true

    viewModelScope.launch {

    val result = repo.likeImage(imageId)

    result.onFailure {

    isLiked.value = false

    }

    }

    }

    View full-size slide

  85. fun likeImage(imageId: Int) {

    isLiked.value = true

    viewModelScope.launch {

    val result = repo.likeImage(imageId)

    result.onFailure {

    isLiked.value = false

    }

    }

    }

    View full-size slide

  86. val isLikeState = remember { viewModel.isLiked }

    View full-size slide

  87. Icon(

    if (isLikeState.value.not())

    Icons.Default.FavoriteBorder

    else

    Icons.Filled.Favorite

    )

    View full-size slide

  88. Icon(

    modifier = Modifier.clickable {

    viewModel.likeImage(imageId)

    }

    )

    View full-size slide

  89. • Display location, tags

    • Like an Image

    View full-size slide

  90. Nav Host
    Images List View Images Details View

    View full-size slide

  91. Images List View Images Details View

    View full-size slide

  92. val navController = rememberNavController()

    View full-size slide

  93. val navController = rememberNavController()

    NavHost(navController) {

    }

    View full-size slide

  94. NavHost(navController) {


    composable("images_list") {



    }

    composable(“images_details") {



    }

    }

    View full-size slide

  95. NavHost(navController) {

    composable("images_list") {

    ImagesListScreen()

    }

    composable(“images_details") {

    ImageDetailsScreen()

    }

    }

    View full-size slide

  96. composable("images_list") {

    ImagesListScreen {

    }

    }

    View full-size slide

  97. composable("images_list") {

    ImagesListScreen {

    navController.navigate(“images_details/{imageId}“)

    }

    }

    View full-size slide

  98. composable(“images_details") {

    ImagesDetailsScreen(

    backStackEntry.arguments
    ?.
    getString(“imageId")
    ?.
    toInt(),

    ...


    )

    }

    View full-size slide

  99. Jetpack Compose
    ● MutableStateOf

    ● Remember

    ● Side Effects
    ● @State

    ● @ObservedObject

    ● @Published
    SwiftUI

    View full-size slide

  100. Jetpack Compose
    ● MutableStateOf

    ● Remember

    ● Side Effects

    View full-size slide

  101. SwiftUI
    Views, Concurrency, Managing State, Navigation

    View full-size slide

  102. • Display Images

    • Search

    View full-size slide

  103. Observed Object
    View
    Observe

    View full-size slide

  104. Observed Object
    View
    Update

    View full-size slide

  105. class ImagesRepository: ObservableObject {

    }

    View full-size slide

  106. class ImagesRepository: ObservableObject {

    @Published var images: [ImageData] = []

    }

    View full-size slide

  107. Swift Concurrency
    ● Async/Await

    ● Tasks

    View full-size slide

  108. func getImages() async {

    }

    View full-size slide

  109. func getImages() async {

    images = apiService.getImages()

    }

    View full-size slide

  110. func getImages() async {

    images = apiService.getImages()

    }
    Publishing changes from background threads is not allowed;

    make sure to publish values from the main thread
    !

    View full-size slide

  111. @MainActor

    func getImages() async {

    images = apiService.getImages()

    }

    View full-size slide

  112. Observed Object
    View
    Observe

    View full-size slide

  113. struct ContentView: View {

    }

    View full-size slide

  114. struct ContentView: View {

    }

    View full-size slide

  115. struct ContentView: View {

    var body: some View {

    }

    }

    View full-size slide

  116. struct ContentView: View {

    @StateObject var repository = ImagesRepository()

    }

    View full-size slide

  117. struct ContentView: View {

    @StateObject var repository = ImagesRepository()

    }
    Best Practice
    Use StateObject when creating objects.

    View full-size slide

  118. struct ContentView: View {

    @StateObject var repository = ImagesRepository()

    }.task {

    }

    View full-size slide

  119. struct ContentView: View {

    @StateObject var repository = ImagesRepository()

    }.task {

    await repository.getImages()

    }

    View full-size slide

  120. struct ContentView: View {

    @State var showProgressBar = true

    }

    View full-size slide

  121. ZStack {

    if (showProgressBar) {

    }

    }

    View full-size slide

  122. ZStack {

    if (showProgressBar) {

    ProgressView()

    .progressViewStyle(

    CircularProgressViewStyle(tint: .blue)

    )

    }

    }

    View full-size slide

  123. struct ContentView: View {

    @State var showProgressBar = true

    }.task {

    await repository.getImages()

    showProgressBar = false

    }

    View full-size slide

  124. struct ContentView: View {

    View full-size slide

  125. • Display Images

    • Search

    • Like an image

    View full-size slide

  126. LazyVGrid(spacing: 10) {

    }

    View full-size slide

  127. LazyVGrid(spacing: 10) {

    ForEach(repo.images, id: \.id) { item in

    }

    }

    View full-size slide

  128. LazyVGrid(spacing: 10) {

    ForEach(repo.images, id: \.id) { item in

    Image(item.url)

    .resizable()

    .frame(minWidth: 0, maxWidth: .infinity)

    }

    }

    View full-size slide

  129. • Display Images

    • Search

    • Like an image

    View full-size slide

  130. @State var searchText = ""

    View full-size slide

  131. LazyVGrid(spacing: 10) {

    }.searchable()

    View full-size slide

  132. LazyVGrid(spacing: 10) {

    }.searchable(text: $searchText)

    View full-size slide

  133. LazyVGrid(spacing: 10) {

    }.searchable(text: $searchText)
    Two way binding

    View full-size slide

  134. @State var searchText = “"

    var searchResults: [ImageData] {

    if searchText.isEmpty {

    return repository.images

    } else {

    return filterImages(searchText)

    }

    View full-size slide

  135. • Display Images

    • Search

    • Like an image

    View full-size slide

  136. struct ImageDetailsView: View {

    let imageData: ImageData


    }

    View full-size slide

  137. struct ImageDetailsView: View {

    @State var isLiked: Bool = false

    }

    View full-size slide

  138. struct ImageDetailsView: View {


    @State var isLiked: Bool = false


    var body: some View {


    }.onAppear {

    }

    }

    View full-size slide

  139. struct ImageDetailsView: View {


    @State var isLiked: Bool = false


    var body: some View {


    }.onAppear {

    isLiked = imageData.isLiked

    }

    }

    View full-size slide

  140. struct ImageDetailsView: View {


    View full-size slide

  141. Button(action: {

    isLiked.toggle()

    }) {

    Image(systemName: isLiked ? "heart.fill" : "heart")

    }

    View full-size slide

  142. struct ImageDetailsView: View {


    @State var isLiked: Bool = false


    var body: some View {


    }.onChange(of: isLiked) {

    }

    }

    View full-size slide

  143. @State var likeTask: Task? = nil

    View full-size slide

  144. .onChange(of: isLiked) { newValue in

    if (newValue) {

    likeTask
    ?.
    cancel()

    likeTask = Task {

    await repository.likeImage(id: imageData.id)

    }

    }

    }

    View full-size slide

  145. .onChange(of: isLiked) { newValue in

    if (newValue) {

    likeTask
    ?.
    cancel()

    likeTask = Task {

    await repository.likeImage(id: imageData.id)

    }

    }

    }

    View full-size slide

  146. .onChange(of: isLiked) { newValue in

    if (newValue) {

    likeTask
    ?.
    cancel()

    likeTask = Task {

    await repository.likeImage(id: imageData.id)

    }

    }

    }

    View full-size slide

  147. • Display Images

    • Search

    • Like an image

    View full-size slide

  148. NavigationView {

    }

    View full-size slide

  149. NavigationView {

    NavigationLink(

    destination: ImageDetailsView(

    repository: repository,

    imageData: item

    )

    )

    }

    View full-size slide

  150. toolbar {

    ToolbarItem {

    Button(action: {


    }) {

    Image(systemName: "square.grid.2x2")

    }

    }

    }

    View full-size slide

  151. Jetpack Compose
    ● MutableStateOf

    ● Remember

    ● Side Effects
    ● @State

    ● @ObservedObject

    ● @Published
    SwiftUI

    View full-size slide

  152. Resources
    ● github.com/msya/ImagesComposeApp

    ● github.com/msya/ImagesSwiftUIApp

    View full-size slide

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

    View full-size slide