Slide 1

Slide 1 text

BY SERGIO SASTRE TDD-FYING YOUR UI WITH EASE! fun Driven_Development @Composable @Preview { } /* */ TDD

Slide 2

Slide 2 text

@Gio_Sastre CPDD TDD + =

Slide 3

Slide 3 text

How screenshot testing @Gio_Sastre works ?

Slide 4

Slide 4 text

record AND verify @Gio_Sastre

Slide 5

Slide 5 text

CI @Gio_Sastre + @Preview @Composable JVM

Slide 6

Slide 6 text

CI @Gio_Sastre + @Preview @Composable = test JVM

Slide 7

Slide 7 text

CI @Gio_Sastre + @Preview @Composable record JVM

Slide 8

Slide 8 text

CI @Gio_Sastre + @Preview @Composable refactoring verify JVM

Slide 9

Slide 9 text

CI @Gio_Sastre + @Preview @Composable refactoring verify JVM

Slide 10

Slide 10 text

@Gio_Sastre + @Preview @Composable JVM refactoring verify

Slide 11

Slide 11 text

@Gio_Sastre + @Preview @Composable JVM refactoring verify

Slide 12

Slide 12 text

Roborazzi Paparazzi Compose Preview Jvm-based screenshot testing libraries @Gio_Sastre

Slide 13

Slide 13 text

Roborazzi Paparazzi Compose Preview Jvm-based screenshot testing libraries library Layoutlib Robolectric based on @Gio_Sastre

Slide 14

Slide 14 text

Jvm-based screenshot testing libraries @Gio_Sastre Paparazzi Compose Preview Roborazzi @Preview Launch Effect

Slide 15

Slide 15 text

@Gio_Sastre to CPDD ? What is TDD and how it relates

Slide 16

Slide 16 text

@Gio_Sastre TDD basics TDD R E D T E S T G R E E
 N T E S T R E F A
 C T O R I N G Write a failing test 1 Write minimal code to make the test pass 2 Refactor code Verify code changes by running tests 3

Slide 17

Slide 17 text

@Gio_Sastre CPDD basics TDD

Slide 18

Slide 18 text

@Gio_Sastre CPDD basics TDD

Slide 19

Slide 19 text

@Gio_Sastre CPDD basics CPDD TDD

Slide 20

Slide 20 text

@Gio_Sastre CPDD basics Write @Previews 1 Refactor code Verify UI screenshots 3 Write code to make the UI look as expected 2 Record UI screenshot CPDD P R E I E W V R E C O
 R D R E F A
 C T O R I N G

Slide 21

Slide 21 text

@Gio_Sastre TDD & CPDD CPDD P R E I E W V R E C O
 R D R E F A
 C T O R I N G TDD R E D T E S T G R E E
 N T E S T R E F A
 C T O R I N G

Slide 22

Slide 22 text

@Gio_Sastre @Previews last vs. @Previews first CPDD

Slide 23

Slide 23 text

@Gio_Sastre Screen designs Loading Error Content

Slide 24

Slide 24 text

@Gio_Sastre @Previews last Content

Slide 25

Slide 25 text

@Gio_Sastre @Previews last Content fun SubscriptionScreen ( @Composable } subscriptions : ImmutableList , ) {

Slide 26

Slide 26 text

@Gio_Sastre @Previews last Content fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) {

Slide 27

Slide 27 text

@Gio_Sastre @Previews last Content fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , SubscriptionsLazyColumn ( items = subscriptions ) { … } ) {

Slide 28

Slide 28 text

@Gio_Sastre @Previews last Content fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( enabled = ) ) { true

Slide 29

Slide 29 text

@Gio_Sastre @Previews last Content fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( enabled = ) ) { true

Slide 30

Slide 30 text

enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , ) fun SubscriptionScreen ( @Composable true

Slide 31

Slide 31 text

enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , ) fun SubscriptionScreen ( @Composable true

Slide 32

Slide 32 text

enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , } else } if ( isLoading ) { ShimmerRows ( … ) ) { fun SubscriptionScreen ( @Composable true

Slide 33

Slide 33 text

enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , } else } if ( isLoading ) { ShimmerRows ( … ) ) { fun SubscriptionScreen ( @Composable true

Slide 34

Slide 34 text

! isLoading enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , } else } if ( isLoading ) { ShimmerRows ( … ) ) { fun SubscriptionScreen ( @Composable

Slide 35

Slide 35 text

! isLoading enabled = @Gio_Sastre @Previews last Loading Header ( … ) } subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( isLoading : Boolean , } else } if ( isLoading ) { ShimmerRows ( … ) ) { fun SubscriptionScreen ( @Composable

Slide 36

Slide 36 text

{ ! isLoading @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , Error

Slide 37

Slide 37 text

{ ! isLoading @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , Error

Slide 38

Slide 38 text

{ if ! isLoading ( ! isError ) @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , Error

Slide 39

Slide 39 text

{ if ! isLoading ( ! isError ) @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , Error

Slide 40

Slide 40 text

{ if ! isLoading ( ! isError ) @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , ErrorButton ( ) if ( isError ) { } else { } Error

Slide 41

Slide 41 text

{ if ! isLoading ( ! isError ) @Gio_Sastre @Previews last fun SubscriptionScreen ( @Composable Header ( … ) } subscriptions : ImmutableList , ) { isLoading : Boolean , SubscriptionsLazyColumn ( items = subscriptions ) { … } if ( isLoading ) { } else } ShimmerRows ( … ) BuySubscriptionButton ( enabled = ) isError : Boolean , ErrorButton ( ) if ( isError ) { } else { } Error

Slide 42

Slide 42 text

@Gio_Sastre - Too much logic in UI code Issues with “@Previews last” development 1 @Composables code is harder to understand 2 @Preview cannot render due to “smart @Composables” - No clear separation of UI states - Big or deeply nested objects - ViewModels i.e. those having as arguments

Slide 43

Slide 43 text

in practice CPDD @Gio_Sastre

Slide 44

Slide 44 text

@Gio_Sastre One Preview per Screen UI State One Composable per Screen UI State Don’t avoid code duplication CPDD Preview Prerecord Multi-Previews over a single Preview

Slide 45

Slide 45 text

@Gio_Sastre ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview CPDD Preview Prerecord

Slide 46

Slide 46 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } CPDD Preview Prerecord

Slide 47

Slide 47 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { } CPDD Preview Prerecord

Slide 48

Slide 48 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { } CPDD Preview Prerecord

Slide 49

Slide 49 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } Header ( … ) fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { } CPDD Preview Prerecord

Slide 50

Slide 50 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { } CPDD Preview Prerecord

Slide 51

Slide 51 text

@Gio_Sastre Content ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( enabled = true ) fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { } CPDD Preview Prerecord

Slide 52

Slide 52 text

@Gio_Sastre ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } Loading CPDD Preview Prerecord

Slide 53

Slide 53 text

@Gio_Sastre ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } fun LoadingSubscriptionScreen ( @Composable ) { } Loading CPDD Preview Prerecord

Slide 54

Slide 54 text

@Gio_Sastre LoadingSubscriptionScreen ( ) ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } fun LoadingSubscriptionScreen ( @Composable ) { } Loading CPDD Preview Prerecord

Slide 55

Slide 55 text

@Gio_Sastre LoadingSubscriptionScreen ( ) ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } Header ( … ) fun LoadingSubscriptionScreen ( @Composable ) { } Loading CPDD Preview Prerecord

Slide 56

Slide 56 text

@Gio_Sastre LoadingSubscriptionScreen ( ) ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } Header ( … ) fun LoadingSubscriptionScreen ( @Composable ) { } Loading ShimmerRows ( … ) CPDD Preview Prerecord

Slide 57

Slide 57 text

@Gio_Sastre LoadingSubscriptionScreen ( ) ) { @Preview fun LoadingSubscriptionScreenPreview ( @Composable } AppTheme { } Header ( … ) BuySubscriptionButton ( enabled = false ) fun LoadingSubscriptionScreen ( @Composable ) { } Loading ShimmerRows ( … ) CPDD Preview Prerecord

Slide 58

Slide 58 text

@Gio_Sastre ) { fun ErrorSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } CPDD Preview Prerecord Error

Slide 59

Slide 59 text

@Gio_Sastre ) { fun ErrorSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } fun ErrorSubscriptionScreen ( @Composable ) { } CPDD Preview Prerecord Error

Slide 60

Slide 60 text

@Gio_Sastre ErrorSubscriptionScreen ( ) ) { fun ErrorSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } fun ErrorSubscriptionScreen ( @Composable ) { } CPDD Preview Prerecord Error

Slide 61

Slide 61 text

@Gio_Sastre ErrorSubscriptionScreen ( ) ) { fun ErrorSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } Header ( … ) fun ErrorSubscriptionScreen ( @Composable ) { } CPDD Preview Prerecord Error

Slide 62

Slide 62 text

@Gio_Sastre ErrorSubscriptionScreen ( ) ) { fun ErrorSubscriptionScreenPreview ( @Composable } @Preview AppTheme { } Header ( … ) ErrorButton () fun ErrorSubscriptionScreen ( @Composable ) { } CPDD Preview Prerecord Error

Slide 63

Slide 63 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false )

Slide 64

Slide 64 text

data class Content ( ) sealed class ScreenUiState ( subscriptions : ImmutableList , ) { : ScreenUiState : ScreenUiState data object Loading ( ) : ScreenUiState data object Error ( ) } @Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false )

Slide 65

Slide 65 text

data class Content ( ) sealed class ScreenUiState ( subscriptions : ImmutableList , ) { : ScreenUiState : ScreenUiState data object Loading ( ) : ScreenUiState data object Error ( ) } @Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false ) fun SubscriptionScreen () @Composable } val screenUiState = viewModel.screenUiState.collectAsState() when ( screenUiState ) { is Content -> ContentSubscriptionScreen ( screenUiState.subscriptions ) Loading -> LoadingSubscriptionScreen ( ) Error -> ErrorSubscriptionScreen ( ) }

Slide 66

Slide 66 text

data class Content ( ) sealed class ScreenUiState ( subscriptions : ImmutableList , ) { : ScreenUiState : ScreenUiState data object Loading ( ) : ScreenUiState data object Error ( ) } @Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false ) fun SubscriptionScreen () @Composable } val screenUiState = viewModel.screenUiState.collectAsState() when ( screenUiState ) { is Content -> ContentSubscriptionScreen ( screenUiState.subscriptions ) Loading -> LoadingSubscriptionScreen ( ) Error -> ErrorSubscriptionScreen ( ) }

Slide 67

Slide 67 text

data class Content ( ) sealed class ScreenUiState ( subscriptions : ImmutableList , ) { : ScreenUiState : ScreenUiState data object Loading ( ) : ScreenUiState data object Error ( ) } @Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false ) fun SubscriptionScreen () @Composable } val screenUiState = viewModel.screenUiState.collectAsState() when ( screenUiState ) { is Content -> ContentSubscriptionScreen ( screenUiState.subscriptions ) Loading -> LoadingSubscriptionScreen ( ) Error -> ErrorSubscriptionScreen ( ) }

Slide 68

Slide 68 text

data class Content ( ) sealed class ScreenUiState ( subscriptions : ImmutableList , ) { : ScreenUiState : ScreenUiState data object Loading ( ) : ScreenUiState data object Error ( ) } @Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable Header ( … ) ErrorButton () } fun LoadingSubscriptionScreen ( @Composable Header ( … ) BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false ) fun SubscriptionScreen () @Composable } val screenUiState = viewModel.screenUiState.collectAsState() when ( screenUiState ) { is Content -> ContentSubscriptionScreen ( screenUiState.subscriptions ) Loading -> LoadingSubscriptionScreen ( ) Error -> ErrorSubscriptionScreen ( ) }

Slide 69

Slide 69 text

@Gio_Sastre ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } CPDD Preview Prerecord

Slide 70

Slide 70 text

@Gio_Sastre ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } @Preview ( fontScale = 1.3f ) CPDD Preview Prerecord

Slide 71

Slide 71 text

@Gio_Sastre ) { fun ContentSubscriptionScreenPreview ( @Composable } @Preview ContentSubscriptionScreen ( subscriptions ) val subscriptions = … AppTheme { } @Preview ( fontScale = 1.3f ) S C R O L L A B L E CPDD Preview Prerecord

Slide 72

Slide 72 text

@Gio_Sastre CPDD Record Don’t write screenshot tests Auto-generate them from @Previews

Slide 73

Slide 73 text

@Gio_Sastre Auto-generate tests from @Previews @Previews source location Fully automated screenshotTest only + main only main only Fully automated Roborazzi Paparazzi Compose Preview

Slide 74

Slide 74 text

Composable Preview Scanner https://github.com/sergio-sastre/ComposablePreviewScanner @Gio_Sastre Compose Multiplatform: supports @Previews in common

Slide 75

Slide 75 text

@Gio_Sastre Verify must execute fast Verify on every significant change CPDD Refactoring

Slide 76

Slide 76 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) TopBar ( … ) Terms( … ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … )

Slide 77

Slide 77 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) TopBar ( … ) Terms( … ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … )

Slide 78

Slide 78 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) TopBar ( … ) Terms( … ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … )

Slide 79

Slide 79 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { Header ( … ) SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) TopBar ( … ) Terms( … ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( )

Slide 80

Slide 80 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( ) ScaffoldSubscriptionScreen { }

Slide 81

Slide 81 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( ) ScaffoldSubscriptionScreen { } verify

Slide 82

Slide 82 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( ) ScaffoldSubscriptionScreen { } verify

Slide 83

Slide 83 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) Header ( … ) Terms( … ) TopBar ( … ) Header ( … ) TopBar ( … ) Terms( … ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( ) ScaffoldSubscriptionScreen { } verify

Slide 84

Slide 84 text

@Gio_Sastre fun ContentSubscriptionScreen ( @Composable subscriptions : ImmutableList , ) { SubscriptionsLazyColumn ( items = subscriptions ) { … } BuySubscriptionButton ( } fun ErrorSubscriptionScreen ( @Composable ErrorButton () } fun LoadingSubscriptionScreen ( @Composable BuySubscriptionButton ( } ShimmerRows ( … ) enabled = false) enabled = true ) ) { fun ScaffoldSubscriptionScreen ( @Composable } Header ( … ) TopBar ( … ) Terms( … ) @Composable subscriptionsPlaceholder : ( ) -> Unit, subscriptionsPlaceholder( ) ScaffoldSubscriptionScreen { } ScaffoldSubscriptionScreen { } ScaffoldSubscriptionScreen { } verify

Slide 85

Slide 85 text

@Gio_Sastre

Slide 86

Slide 86 text

@Gio_Sastre

Slide 87

Slide 87 text

@Gio_Sastre

Slide 88

Slide 88 text

@Gio_Sastre

Slide 89

Slide 89 text

@Gio_Sastre Original New

Slide 90

Slide 90 text

@Gio_Sastre Original New Diff Verify

Slide 91

Slide 91 text

Bonus Section

Slide 92

Slide 92 text

@Gio_Sastre + CPDD TDD + CPDD TDD

Slide 93

Slide 93 text

@Gio_Sastre + CPDD TDD define UiStates assert UiStates + CPDD TDD

Slide 94

Slide 94 text

@Gio_Sastre + CPDD TDD define UiStates assert UiStates + WHEN fetching at least 1 subscription, THEN show only the first one selected CPDD TDD

Slide 95

Slide 95 text

@Gio_Sastre + CPDD TDD define UiStates assert UiStates + ScreenUiState. Content ImmutableList WHEN fetching at least 1 subscription, THEN show only the first one selected CPDD TDD

Slide 96

Slide 96 text

@Gio_Sastre + CPDD TDD define UiStates assert UiStates + ScreenUiState. Content ImmutableList WHEN fetching at least 1 subscription, THEN show only the first one selected uiState. subscriptions. fi rst ( ) . selected uiState. subscriptions. fi lter { it.selected } . size == 1 CPDD TDD

Slide 97

Slide 97 text

). apply { CardViewHolder ( val layout = LayoutIn fl ater.from (context).in fl ate (R. card_layout, null) bind ( CardItem( ) ) }. itemView … container = layout @Gio_Sastre “Android View” Previews

Slide 98

Slide 98 text

). apply { CardViewHolder ( val layout = LayoutIn fl ater.from (context).in fl ate (R. card_layout, null) bind ( CardItem( ) ) }. itemView … container = layout @Gio_Sastre AndroidView ( modifier = Modifier.fillMaxSize() factory = { context -> ) } private fun CardViewHolderPreview() { @Composable @Preview CardViewHolderPreview “Android View” Previews

Slide 99

Slide 99 text

). apply { CardViewHolder ( val layout = LayoutIn fl ater.from (context).in fl ate (R. card_layout, null) bind ( CardItem( ) ) }. itemView … container = layout @Gio_Sastre AndroidView ( modifier = Modifier.fillMaxSize() factory = { context -> ) } private fun CardViewHolderPreview() { @Composable @Preview CardViewHolderPreview CardViewHolderPreview - Dark @Preview (name = “Dark”, uiMode = UI_MODE_NIGHT_YES) “Android View” Previews

Slide 100

Slide 100 text

@Gio_Sastre - Single responsibility: 1 UI State -> 1 Composable Advantages of CPDD 1 @Composables code is understandable & maintainable 3 TDD & Testing behaviour becomes more intuitive - Defines Ui states you can assert in unit tests 2 Ensure no visual regression bugs in @Composables - Screenshot tests to automatically verify ALL other screens - @Previews for instant feedback on the current screen changes

Slide 101

Slide 101 text

attention for your Thanks

Slide 102

Slide 102 text

Linkedin @SergioSastre sergio-sastre @Gio_Sastre Hashnode blogs Sergio Sastre Flórez GDE for Android Lead Android developer appdev.de Github sergio-sastre-florez