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

SwiftUI vs Jetpack Compose by an Android Engineer

Gerard
December 27, 2022

SwiftUI vs Jetpack Compose by an Android Engineer

As an Android Engineer, Jetpack Compose is a revolution. Build and maintain user interface and be compatible with older Android versions are daily challenges for engineers. These points are some of why Jetpack Compose is now an essential library. But in Apple side, SwiftUI is a great alternative in the development of user interfaces for Apple products.

I suggest you to organize a fight between SwiftUI to me right and Jetpack Compose to my left. We'll compare their design, usage, differences and next steps to define who is the best and win this battle!

Take pop corn and trolls, there will be blood!

Gerard

December 27, 2022
Tweet

More Decks by Gerard

Other Decks in Technology

Transcript

  1. Gerard paligot

    View Slide

  2. PLAYER SELECT

    View Slide

  3. Jetpack compose
    100% Kotlin
    Compiler Plugin
    Open source
    Multiplatform
    Developed by Google and Jetbrains

    View Slide

  4. PLAYER SELECT

    View Slide

  5. SwiftUI
    100% Swift
    Embedded in the system
    Developed by Apple
    Cross platform with Apple platforms

    View Slide

  6. Gerard paligot

    View Slide

  7. COMPOSE SwiftUI

    View Slide

  8. Similarities
    Counter example

    View Slide

  9. View Slide

  10. @Composable
    fun CounterScreen() {
    }

    View Slide

  11. @Composable
    fun CounterScreen() {
    Row {
    FloatingActionButton(onClick = { }) {
    Icon(
    imageVector = Icons.Default.Remove,
    contentDescription = "Decrement"
    )
    }
    FloatingActionButton(onClick = { }) {
    Icon(
    imageVector = Icons.Default.Add,
    contentDescription = "Increment"
    )
    }
    }
    }

    View Slide

  12. @Composable
    fun CounterScreen() {
    Row {
    FloatingActionButton(onClick = { }) { ... }
    FloatingActionButton(onClick = { }) { ... }
    }
    }

    View Slide

  13. @Composable
    fun CounterScreen() {
    Row(
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalAlignment = Alignment.CenterVertically
    ) {
    FloatingActionButton(onClick = { }) { ... }
    Text(text = "Count:")
    FloatingActionButton(onClick = { }) { ... }
    }
    }

    View Slide

  14. @Composable
    fun CounterScreen() {
    Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
    ) {
    Row(
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalAlignment = Alignment.CenterVertically
    ) {
    FloatingActionButton(onClick = { }) { ... }
    Text(text = "Count:")
    FloatingActionButton(onClick = { }) { ... }
    }
    }
    }

    View Slide

  15. @Composable
    fun CounterScreen(
    modifier: Modifier = Modifier
    ) {
    Box(
    modifier = modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
    ) {
    Row(
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalAlignment = Alignment.CenterVertically
    ) {
    FloatingActionButton(onClick = { }) { ... }
    Text(text = "Count:")
    FloatingActionButton(onClick = { }) { ... }
    }
    }
    }

    View Slide

  16. @Composable
    fun CounterScreen(
    modifier: Modifier = Modifier
    ) {
    var counter by remember { mutableStateOf(0) }
    Box(
    modifier = modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
    ) {
    Row(
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalAlignment = Alignment.CenterVertically
    ) {
    FloatingActionButton(onClick = { counter-- }) { ... }
    Text(text = "Count: $counter")
    FloatingActionButton(onClick = { counter++ }) { ... }
    }
    }
    }

    View Slide

  17. Jetpack compose
    Declare a component with @Composable
    Box, Row and Column to place elements
    Arrangement and alignment parameters
    State with remember mutableStateOf
    Modifier to decorate components
    Accessibility first

    View Slide

  18. struct Counter: View {


    var body: some View {


    }


    }


    View Slide

  19. struct Counter: View {




    var body: some View {


    HStack {


    Button {


    // Action


    } label: {


    Image(systemName: "minus")


    }


    Button {


    // Action


    } label: {


    Image(systemName: "plus")


    }


    }


    }


    }


    View Slide

  20. struct Counter: View {




    var body: some View {


    HStack {


    Button {


    // Action


    } label: {


    Image(systemName: "minus")


    }


    Text("Count:")


    Button {


    // Action


    } label: {


    Image(systemName: "plus")


    }


    }


    }


    }


    View Slide

  21. struct Counter: View {




    var body: some View {


    HStack {


    Button {


    // Action


    } label: {


    Image(systemName: "minus")


    }


    .padding()


    Text("Count:")


    Button {


    // Action


    } label: {


    Image(systemName: "plus")


    }


    .padding()


    }


    }


    }


    View Slide

  22. struct Counter: View {


    @State var counter: Int = 0




    var body: some View {


    HStack {


    Button {


    counter = counter - 1


    } label: {


    Image(systemName: "minus")


    }


    .padding()


    Text("Count: \(counter)")


    Button {


    counter = counter + 1


    } label: {


    Image(systemName: "plus")


    }


    .padding()


    }


    }


    }


    View Slide

  23. SwiftUI
    Declare a component with protocol impl
    ZStack, HStack and VStack to place elements
    Custom components decorated automatically
    State with @State property

    View Slide

  24. COMPOSE SwiftUI
    Hit Layout

    View Slide

  25. COMPOSE SwiftUI
    Hit ARRANGEMENT

    View Slide

  26. COMPOSE SwiftUI
    Hit ALIGNMENT

    View Slide

  27. COMPOSE SwiftUI
    Hit less code

    View Slide

  28. COMPOSE SwiftUI
    Vote for Jetpack Compose Vote for SwiftUI

    View Slide

  29. COMPOSE SwiftUI

    View Slide

  30. Packaging
    How to use the declarative UI Toolkit?

    View Slide

  31. SwiftUI
    SwiftUI is packaged with the OS
    Because embedded in the system, runtime performance is better
    Need to wait new Apple system versions for new features
    SwiftUI available since iOS 13 (2019) in experimental
    First stable version since iOS 14 (2020)

    View Slide

  32. Jetpack compose
    Jetpack Compose is packaged in a library
    New features available since API 21 (2014)
    In theory, runtime performance is worst than SwiftUI (or XML)
    But the release version make a lot of optimisation

    View Slide

  33. COMPOSE SwiftUI
    Hit PACKAGING

    View Slide

  34. COMPOSE SwiftUI
    Vote for Jetpack Compose Vote for SwiftUI

    View Slide

  35. COMPOSE SwiftUI

    View Slide

  36. Previews
    Check the render without running your application

    View Slide

  37. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  38. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    Pin a preview
    Preview name
    Live preview
    Selectable preview
    Variants
    Canvas Device Settings
    Run on device

    View Slide

  39. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  40. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  41. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  42. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  43. struct Counter_Previews: PreviewProvider {


    static var previews: some View {


    Counter()


    }


    }


    View Slide

  44. struct SpeakerAvatarView: View {


    var url: String




    var body: some View {


    AsyncImageView(


    url: URL(string: url)!,


    placeholder: {


    Text("...")


    }


    )


    .clipShape(Circle())


    }


    }


    struct SpeakerAvatarView_Previews: PreviewProvider {


    static var previews: some View {


    SpeakerAvatarView(


    url: SpeakerUi.companion.fake.url


    )


    }


    }


    View Slide

  45. SwiftUI
    Wysiwyg customization for previews
    Running the preview in the simulator
    Customization configurable as code
    Preview not limited to UI

    View Slide

  46. @Preview
    @Composable
    internal fun CounterScreenPreview() {
    SwiftuiVsComposeTheme {
    CounterScreen()
    }
    }

    View Slide

  47. @Preview
    @Composable
    internal fun CounterScreenPreview() {
    SwiftuiVsComposeTheme {
    CounterScreen()
    }
    }
    Interactive mode
    Run on device

    View Slide

  48. @MustBeDocumented
    @Retention(AnnotationRetention.SOURCE)
    @Target(
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.FUNCTION
    )
    @Repeatable
    annotation class Preview(
    val name: String = "",
    val group: String = "",
    @IntRange(from = 1) val apiLevel: Int = -1,
    val widthDp: Int = -1,
    val heightDp: Int = -1,
    val locale: String = "",
    @FloatRange(from = 0.01) val fontScale: Float = 1f,
    val showSystemUi: Boolean = false,
    val showBackground: Boolean = false,
    val backgroundColor: Long = 0,
    @UiMode val uiMode: Int = 0,
    @Device val device: String = Devices.DEFAULT
    )

    View Slide

  49. object Devices {
    const val DEFAULT = ""
    const val PIXEL_3 = "id:pixel_3"
    const val PIXEL_3_XL = "id:pixel_3_xl"
    const val PIXEL_3A = "id:pixel_3a"
    const val PIXEL_3A_XL = "id:pixel_3a_xl"
    const val PIXEL_4 = "id:pixel_4"
    const val PIXEL_4_XL = "id:pixel_4_xl"
    const val AUTOMOTIVE_1024p = "id:automotive_1024p_landscape"
    const val WEAR_OS_LARGE_ROUND = "id:wearos_large_round"
    const val WEAR_OS_SMALL_ROUND = "id:wearos_small_round"
    const val WEAR_OS_SQUARE = "id:wearos_square"
    const val WEAR_OS_RECT = "id:wearos_rect"
    // Reference devices
    const val PHONE =
    "spec:id=reference_phone,shape=Normal,width=411,height=891,unit=dp,
    dpi=420"
    const val FOLDABLE =
    "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480"
    const val TABLET =
    "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=420"
    const val DESKTOP =
    "spec:shape=Normal,width=1920,height=1080,unit=dp,dpi=420"
    }

    View Slide

  50. @Preview(
    showBackground = true,
    showSystemUi = true,
    device = Devices.PIXEL_XL,
    name = "Counter",
    fontScale = 2f,
    uiMode = UI_MODE_NIGHT_YES,
    )
    @Composable
    internal fun CounterScreenPreview() {
    SwiftuiVsComposeTheme {
    CounterScreen()
    }
    }

    View Slide

  51. @Preview(
    showBackground = true,
    showSystemUi = true,
    device = Devices.PIXEL_XL,
    name = "Counter",
    fontScale = 2f,
    uiMode = UI_MODE_NIGHT_YES,
    )
    @Composable
    internal fun CounterScreenPreview() {
    SwiftuiVsComposeTheme {
    Scaffold {
    CounterScreen()
    }
    }
    }

    View Slide

  52. @Composable
    fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    containerColor: Color = MaterialTheme.colorScheme.background,
    contentColor: Color = contentColorFor(containerColor),
    content: @Composable (PaddingValues) -> Unit
    )

    View Slide

  53. @Preview(
    group = "Devices",
    name = "Pixel 4",
    showSystemUi = true,
    device = Devices.PIXEL_4
    )
    @Preview(
    group = "Devices",
    name = "Tablet",
    showSystemUi = true,
    device = Devices.TABLET
    )
    @Preview(
    group = "Devices",
    name = "Desktop",
    showSystemUi = true,
    device = Devices.DESKTOP
    )
    annotation class DevicesPreview

    View Slide

  54. @DevicesPreview
    @Composable
    internal fun CounterScreenPreview() {
    SwiftuiVsComposeTheme {
    Scaffold {
    CounterScreen()
    }
    }
    }

    View Slide

  55. @Composable
    fun SpeakerAvatar(
    url: String,
    displayName: String,
    modifier: Modifier = Modifier
    ) {
    }

    View Slide

  56. @Composable
    fun SpeakerAvatar(
    url: String,
    displayName: String,
    modifier: Modifier = Modifier
    ) {
    Image(
    painter = rememberAsyncImagePainter(
    model = url,
    imageLoader = LocalContext.current.imageLoader
    ),
    contentDescription = displayName,
    contentScale = ContentScale.FillWidth,
    modifier = modifier.clip(CircleShape)
    )
    }

    View Slide

  57. @Composable
    fun SpeakerAvatar(
    url: String,
    displayName: String,
    modifier: Modifier = Modifier
    ) {
    if (LocalInspectionMode.current) {
    Box(
    modifier = modifier
    .background(
    color = MaterialTheme.colors.secondary,
    shape = CircleShape
    )
    )
    } else {
    Image(
    painter = rememberAsyncImagePainter(
    model = url,
    imageLoader = LocalContext.current.imageLoader
    ),
    contentDescription = displayName,
    contentScale = ContentScale.FillWidth,
    modifier = modifier.clip(CircleShape)
    )
    }
    }

    View Slide

  58. @Composable
    fun SpeakerAvatar(
    url: String,
    displayName: String,
    modifier: Modifier = Modifier
    ) {
    Image(
    painter = rememberAsyncImagePainter(
    model = url,
    placeholder = ColorPainter(MaterialTheme.colors.secondary),
    imageLoader = LocalContext.current.imageLoader
    ),
    contentDescription = displayName,
    contentScale = ContentScale.FillWidth,
    modifier = modifier.clip(CircleShape)
    )
    }

    View Slide

  59. Jetpack compose
    Configure preview with @Preview parameters
    Fully customizable for all kind of devices
    Custom preview annotations
    LocalInspectionMode to keep previews
    Limited to UI elements

    View Slide

  60. COMPOSE SwiftUI
    Hit multi previews

    View Slide

  61. COMPOSE SwiftUI
    Hit customizable

    View Slide

  62. COMPOSE SwiftUI
    Hit wysiwyg

    View Slide

  63. COMPOSE SwiftUI
    Hit not limited TO UI

    View Slide

  64. COMPOSE SwiftUI
    Vote for Jetpack Compose Vote for SwiftUI

    View Slide

  65. COMPOSE SwiftUI

    View Slide

  66. Bottom bar navigation
    Navigate in your application

    View Slide

  67. struct TabScreen: View {


    var body: some View {


    }


    }

    View Slide

  68. struct TabScreen: View {


    var body: some View {


    TabView {


    Counter()


    .tabItem {


    Label("screenAgenda", systemImage: "calendar")


    }


    Counter()


    .tabItem {


    Label("screenSpeakers", systemImage: "person")


    }


    Counter()


    .tabItem {


    Label("screenInfo", systemImage: "info")


    }


    }


    }


    }

    View Slide

  69. SwiftUI
    Just easy to use!
    By default, the UI match the Design System
    But the customization can be more tricky

    View Slide

  70. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    }

    View Slide

  71. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    }
    }
    ) {
    }
    }

    View Slide

  72. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    selected = true,
    icon = {
    Icon(
    imageVector = Icons.Default.CalendarToday,
    contentDescription = null
    )
    },
    onClick = {
    },
    label = { Text(text = "Agenda") },
    alwaysShowLabel = false
    )
    }
    }
    ) {
    }
    }

    View Slide

  73. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    )
    }
    }
    ) {
    }
    }

    View Slide

  74. dependencies {
    // Other dependencies
    implementation("androidx.navigation:navigation-compose:$version")
    }

    View Slide

  75. NavHost NavController
    NavGraph
    Composable
    Composable
    Composable

    View Slide

  76. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    )
    }
    }
    ) {
    }
    }

    View Slide

  77. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    )
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"
    ) {
    composable(route = "agenda") {
    Text(text = "Agenda")
    }
    composable(route = "speakers") {
    Text(text = "Speakers")
    }
    composable(route = "info") {
    Text(text = "Info")
    }
    }

    View Slide

  78. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    )
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"
    ) {
    // ...
    }
    }
    }

    View Slide

  79. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    onClick = {
    navController.navigate("agenda") {
    popUpTo(
    navController.graph.findStartDestination().id
    ) {
    saveState = true
    }
    launchSingleTop = true
    restoreState = true
    }
    },
    )
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"

    View Slide

  80. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    NavigationBarItem(
    // ...
    )
    NavigationBarItem(
    // ...
    )
    NavigationBarItem(
    // ...
    )
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"
    ) {
    // ...
    }
    }
    }

    View Slide

  81. fun NavController.bottomBarNavigate(route: String) {
    }

    View Slide

  82. fun NavController.bottomBarNavigate(route: String) {
    navigate(route) {
    popUpTo(graph.findStartDestination().id) {
    saveState = true
    }
    launchSingleTop = true
    restoreState = true
    }
    }

    View Slide

  83. @Composable
    fun RowScope.BottomNavigationItem(
    label: String,
    icon: ImageVector,
    route: String,
    navController: NavController,
    modifier: Modifier = Modifier,
    ) {
    }

    View Slide

  84. @Composable
    fun RowScope.BottomNavigationItem(
    label: String,
    icon: ImageVector,
    route: String,
    navController: NavController,
    modifier: Modifier = Modifier,
    ) {
    NavigationBarItem(
    modifier = modifier,
    selected = false,
    icon = {
    Icon(
    imageVector = icon,
    contentDescription = null
    )
    },
    onClick = {
    navController.bottomBarNavigate(route)
    },
    label = { Text(text = label) },
    alwaysShowLabel = false
    )
    }

    View Slide

  85. @Composable
    fun RowScope.BottomNavigationItem(
    label: String,
    icon: ImageVector,
    route: String,
    navController: NavController,
    modifier: Modifier = Modifier,
    ) {
    val navBackStackEntry by
    navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination
    val screen = currentDestination?.route
    NavigationBarItem(
    modifier = modifier,
    selected = screen == route,
    icon = {
    Icon(
    imageVector = icon,
    contentDescription = null
    )
    },
    onClick = {
    navController.bottomBarNavigate(route)
    },
    label = { Text(text = label) },
    alwaysShowLabel = false
    )
    }

    View Slide

  86. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"
    ) {
    // ...
    }
    }
    }

    View Slide

  87. @Composable
    fun BottomNavigationScreen(
    modifier: Modifier = Modifier
    ) {
    val navController = rememberNavController()
    Scaffold(
    modifier = modifier,
    bottomBar = {
    NavigationBar {
    BottomNavigationItem(
    route = "agenda",
    label = "Agenda",
    icon = Icons.Default.CalendarToday,
    navController = navController
    )
    BottomNavigationItem(
    // ...
    )
    BottomNavigationItem(
    // ...
    )
    }
    }
    ) {
    NavHost(
    navController = navController, startDestination = "agenda"
    ) {
    // ...

    View Slide

  88. Jetpack compose
    Need to take care about the navigation
    Need to take care about the selected tab
    Need so much code compared to SwiftUI!
    Easy to refactor for something more concise
    But the customization is easier

    View Slide

  89. COMPOSE SwiftUI
    Hit less code

    View Slide

  90. COMPOSE SwiftUI
    MISS navigation

    View Slide

  91. COMPOSE SwiftUI
    hit customization

    View Slide

  92. COMPOSE SwiftUI
    Hit easy to use

    View Slide

  93. COMPOSE SwiftUI
    hit refactoring

    View Slide

  94. COMPOSE SwiftUI
    Vote for Jetpack Compose Vote for SwiftUI

    View Slide

  95. COMPOSE SwiftUI

    View Slide

  96. Platform design system
    How to create UI which respect platform design system?

    View Slide

  97. Jetpack compose
    Material artifacts follow perfectly the Material design specifications
    Need more code than SwiftUI to build correct UI
    Partially fixed by Material 3 artifact and dynamic colors
    Already compatible with Desktop, Web and iOS
    Design decision made by Google allow Compose to add multiplatform support

    View Slide

  98. SwiftUI
    Easy to use, with a bias for the Apple Design System in all components
    Hard to write an application with a wrong UI
    Compatible with all Apple OS platforms
    But can’t use SwiftUI for external platforms

    View Slide

  99. COMPOSE SwiftUI
    hit customization
    Hit NO WRONG UI

    View Slide

  100. COMPOSE SwiftUI

    View Slide

  101. COMPOSE SwiftUI

    View Slide

  102. Jetpack Compose SwiftUI

    View Slide

  103. Jetpack Compose SwiftUI

    View Slide

  104. Business Logic and Core
    iOS


    Specific API
    Android


    Specific API
    Jetpack Compose SwiftUi

    View Slide

  105. COMPOSE SwiftUI

    View Slide

  106. References

    SwiftUI vs Jetpack Compose by an Android Engineer by Gérard Paligot

    https://proandroiddev.com/swiftui-vs-jetpack-compose-by-an-android-engineer-6b48415f36b3


    SwiftUI vs. Jetpack Compose: Why Android Wins Hands Down by Michael Long

    https://michaellong.medium.com/swiftui-vs-jetpack-compose-why-android-wins-hands-down-b5f849b730db


    An iOS Engineer learns about Android’s Jetpack Compose and loves it. by Dimitri James Tsi
    fl
    itzis

    https://medium.com/@tsif/an-ios-engineer-learns-about-androids-jetpack-compose-and-loves-it-c04fc6a53f10


    SwiftUI vs. Jetpack Compos by Jeulian Bissekkou

    https://quickbirdstudios.com/blog/swiftui-vs-android-jetpack-compose/


    Conferences4Hall GitHub project

    https://github.com/GerardPaligot/conferences4hall

    View Slide

  107. Thank you!
    @GerardPaligot
    @GerardPaligot
    Gérard Paligot
    androiddev.social/@GerardPaligot

    View Slide