Slide 1

Slide 1 text

Gerard paligot

Slide 2

Slide 2 text

PLAYER SELECT

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

PLAYER SELECT

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Gerard paligot INSERT COIN.

Slide 7

Slide 7 text

COMPOSE SwiftUI

Slide 8

Slide 8 text

Similarities Counter example

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

@Composable fun CounterScreen() { }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

@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 = { }) { ... } } } }

Slide 15

Slide 15 text

@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 = { }) { ... } } } }

Slide 16

Slide 16 text

@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++ }) { ... } } } }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

struct Counter: View { var body: some View { } }

Slide 19

Slide 19 text

struct Counter: View { var body: some View { HStack { Button { // Action } label: { Image(systemName: "minus") } Button { // Action } label: { Image(systemName: "plus") } } } }

Slide 20

Slide 20 text

struct Counter: View { var body: some View { HStack { Button { // Action } label: { Image(systemName: "minus") } Text("Count:") Button { // Action } label: { Image(systemName: "plus") } } } }

Slide 21

Slide 21 text

struct Counter: View { var body: some View { HStack { Button { // Action } label: { Image(systemName: "minus") } .padding() Text("Count:") Button { // Action } label: { Image(systemName: "plus") } .padding() } } }

Slide 22

Slide 22 text

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() } } }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

COMPOSE SwiftUI Hit Layout

Slide 25

Slide 25 text

COMPOSE SwiftUI Hit ARRANGEMENT

Slide 26

Slide 26 text

COMPOSE SwiftUI Hit ALIGNMENT

Slide 27

Slide 27 text

COMPOSE SwiftUI Hit less code

Slide 28

Slide 28 text

COMPOSE SwiftUI Vote for Jetpack Compose Vote for SwiftUI

Slide 29

Slide 29 text

COMPOSE SwiftUI

Slide 30

Slide 30 text

Packaging How to use the declarative UI Toolkit?

Slide 31

Slide 31 text

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)

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

COMPOSE SwiftUI Hit PACKAGING

Slide 34

Slide 34 text

COMPOSE SwiftUI Vote for Jetpack Compose Vote for SwiftUI

Slide 35

Slide 35 text

COMPOSE SwiftUI

Slide 36

Slide 36 text

Previews Check the render without running your application

Slide 37

Slide 37 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 40

Slide 40 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 41

Slide 41 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 42

Slide 42 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 43

Slide 43 text

struct Counter_Previews: PreviewProvider { static var previews: some View { Counter() } }

Slide 44

Slide 44 text

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 ) } }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

@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 )

Slide 49

Slide 49 text

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" }

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

@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() } } }

Slide 52

Slide 52 text

@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 )

Slide 53

Slide 53 text

@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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

@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) ) }

Slide 57

Slide 57 text

@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) ) } }

Slide 58

Slide 58 text

@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) ) }

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

COMPOSE SwiftUI Hit multi previews

Slide 61

Slide 61 text

COMPOSE SwiftUI Hit customizable

Slide 62

Slide 62 text

COMPOSE SwiftUI Hit wysiwyg

Slide 63

Slide 63 text

COMPOSE SwiftUI Hit not limited TO UI

Slide 64

Slide 64 text

COMPOSE SwiftUI Vote for Jetpack Compose Vote for SwiftUI

Slide 65

Slide 65 text

COMPOSE SwiftUI

Slide 66

Slide 66 text

Bottom bar navigation Navigate in your application

Slide 67

Slide 67 text

struct TabScreen: View { var body: some View { } }

Slide 68

Slide 68 text

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") } } } }

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

@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 ) } } ) { } }

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

NavHost NavController NavGraph Composable Composable Composable

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

@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") } }

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

@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"

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

fun NavController.bottomBarNavigate(route: String) { }

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

@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 ) }

Slide 85

Slide 85 text

@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 ) }

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

@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" ) { // ...

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

COMPOSE SwiftUI Hit less code

Slide 90

Slide 90 text

COMPOSE SwiftUI MISS navigation

Slide 91

Slide 91 text

COMPOSE SwiftUI hit customization

Slide 92

Slide 92 text

COMPOSE SwiftUI Hit easy to use

Slide 93

Slide 93 text

COMPOSE SwiftUI hit refactoring

Slide 94

Slide 94 text

COMPOSE SwiftUI Vote for Jetpack Compose Vote for SwiftUI

Slide 95

Slide 95 text

COMPOSE SwiftUI

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

COMPOSE SwiftUI hit customization Hit NO WRONG UI

Slide 100

Slide 100 text

COMPOSE SwiftUI

Slide 101

Slide 101 text

COMPOSE SwiftUI

Slide 102

Slide 102 text

Jetpack Compose SwiftUI

Slide 103

Slide 103 text

Jetpack Compose SwiftUI

Slide 104

Slide 104 text

Business Logic and Core iOS Specific API Android Specific API Jetpack Compose SwiftUi

Slide 105

Slide 105 text

COMPOSE SwiftUI

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

Soundtracks 
 Title Theme - Street Fighter 2 Character Select - Street Fighter 2 Start Battle - Street Fighter 2 Guile’s Theme - Street Fighter 2 Ryu’s Theme - Street Fighter 2 Ken’s Theme - Street Fighter 2 Zangief’s Theme - Street Fighter 2 End Battle - Street Fighter 2 Credits - Street Fighter 
 Cosmonkey - Champion

Slide 108

Slide 108 text

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