Slide 1

Slide 1 text

Learn Jetpack Compose by Example Vinay Gaba @vinaygaba Join the conversation in #session-chat-d2-s2-t1

Slide 2

Slide 2 text

Jetpack Compose / jet·pak kuhm·powz / noun Jetpack Compose is a declarative & modern toolkit for building native Android UI. It simplifies and accelerates UI development on Android.

Slide 3

Slide 3 text

Why do we need Compose?

Slide 4

Slide 4 text

Why do we need Compose? UI Toolkit is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code

Slide 5

Slide 5 text

Why do we need Compose? UI Toolkit is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code

Slide 6

Slide 6 text

Why do we need Compose? UI Toolkit is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code

Slide 7

Slide 7 text

Why do we need Compose? UI Toolkit is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code

Slide 8

Slide 8 text

Why do we need Compose? UI Toolkit is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code

Slide 9

Slide 9 text

Disclaimer

Slide 10

Slide 10 text

Disclaimer Examples are based on 1.0.0-alpha03 Compose is in alpha so API’s can still change Beta release expected in the next few months

Slide 11

Slide 11 text

Disclaimer Examples are based on 1.0.0-alpha03 Compose is in alpha so API’s can still change Beta release expected in the next few months

Slide 12

Slide 12 text

Disclaimer Examples are based on 1.0.0-alpha03 Compose is in alpha so API’s can still change Beta release expected in the next few months

Slide 13

Slide 13 text

Disclaimer Examples are based on 1.0.0-alpha03 Compose is in alpha so API’s can still change Beta release expected in the next few months

Slide 14

Slide 14 text

Examples

Slide 15

Slide 15 text

Hello World

Slide 16

Slide 16 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } }

Slide 17

Slide 17 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { } } }

Slide 18

Slide 18 text

fun ComponentActivity.setContent( recomposer: Recomposer = Recomposer.current(), content: @Composable () "-> Unit ): Composition { "// Some magic ✨ }

Slide 19

Slide 19 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { } } }

Slide 20

Slide 20 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text(text = "Hello World") } } }

Slide 21

Slide 21 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text(text = "Hello World") } } }

Slide 22

Slide 22 text

class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text(text = "Hello World") } } }

Slide 23

Slide 23 text

@Composable fun CustomTextComponent() { Text(text = "Hello World") }

Slide 24

Slide 24 text

@Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) }

Slide 25

Slide 25 text

@Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) }

Slide 26

Slide 26 text

@Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) } @Preview @Composable fun CustomTextComponentPreview() { CustomTextComponent("Hello World") }

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Display Image

Slide 30

Slide 30 text

@Composable fun DrawableImage() { }

Slide 31

Slide 31 text

@Composable fun DrawableImage() { val image = loadImageResource(R.drawable.landscape) }

Slide 32

Slide 32 text

@Composable fun DrawableImage() { val image = loadImageResource(R.drawable.landscape) image.resource.resource"?.let { Image(asset = it, modifier = Modifier.preferredSize(200.dp)) } } Modifiers are your best friends!

Slide 33

Slide 33 text

@Composable fun DrawableImage(@DrawableRes resId: Int) { val image = loadImageResource(resId) image.resource.resource"?.let { Image( asset = it, modifier = Modifier.preferredSize(200.dp) ) } }

Slide 34

Slide 34 text

@Composable fun DrawableImage(@DrawableRes resId: Int) { val image = loadImageResource(resId) image.resource.resource"?.let { Image( asset = it, modifier = Modifier.preferredSize(200.dp) ) } }

Slide 35

Slide 35 text

Modifiers

Slide 36

Slide 36 text

Text( text = "Hello", modifier = Modifier.padding(16.dp) )

Slide 37

Slide 37 text

Text( text = "Hello", modifier = Modifier.padding(16.dp) .background(color = Color.Red) )

Slide 38

Slide 38 text

Text( text = "Hello", modifier = Modifier.padding(16.dp) .background(color = Color.Red) )

Slide 39

Slide 39 text

Text( text = "Hello", modifier = Modifier.padding(16.dp) .background(color = Color.Red) ) Hello

Slide 40

Slide 40 text

Text( text = "Hello", modifier = Modifier.background(color = Color.Red) .padding(16.dp) )

Slide 41

Slide 41 text

Text( text = "Hello", modifier = Modifier.background(color = Color.Red) .padding(16.dp) )

Slide 42

Slide 42 text

Text( text = "Hello", modifier = Modifier.background(color = Color.Red) .padding(16.dp) ) Hello The order of a Modifier has an impact on the behavior

Slide 43

Slide 43 text

Alert Dialog

Slide 44

Slide 44 text

Jetpack Compose / jet·pak kuhm·powz / noun Jetpack Compose is a declarative & modern toolkit for building native Android UI. It simplifies and accelerates UI development on Android.

Slide 45

Slide 45 text

How What vs

Slide 46

Slide 46 text

How What

Slide 47

Slide 47 text

How What // Classic Android val alertDialog = AlertDialog.Builder(context) .setTitle("Android Summit!”) .setMessage("Isn't this conference amazing?") // Somewhere else in code if (some_condition_is_met()) { alertDialog.show() } // Somewhere else in code if (some_other_condition_is_met()) { alertDialog.dismiss() }

Slide 48

Slide 48 text

How What // Classic Android val alertDialog = AlertDialog.Builder(context) .setTitle("Android Summit!”) .setMessage("Isn't this conference amazing?") // Somewhere else in code if (some_condition_is_met()) { alertDialog.show() } // Somewhere else in code if (some_other_condition_is_met()) { alertDialog.dismiss() } @Composable fun AlertDialogComponent() { }

Slide 49

Slide 49 text

How What // Classic Android val alertDialog = AlertDialog.Builder(context) .setTitle("Android Summit!”) .setMessage("Isn't this conference amazing?") // Somewhere else in code if (some_condition_is_met()) { alertDialog.show() } // Somewhere else in code if (some_other_condition_is_met()) { alertDialog.dismiss() } @Composable fun AlertDialogComponent() { if (some_condition_is_met()) { } }

Slide 50

Slide 50 text

How What // Classic Android val alertDialog = AlertDialog.Builder(context) .setTitle("Android Summit!”) .setMessage("Isn't this conference amazing?") // Somewhere else in code if (some_condition_is_met()) { alertDialog.show() } // Somewhere else in code if (some_other_condition_is_met()) { alertDialog.dismiss() } @Composable fun AlertDialogComponent() { if (some_condition_is_met()) { AlertDialog( title = { Text("Android Summit!") }, text = { Text(text = "Isn't this amazing?") } ) } }

Slide 51

Slide 51 text

State

Slide 52

Slide 52 text

@Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false) } }

Slide 53

Slide 53 text

@Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false) } Button(onClick = { showPopup = true }) { Text(text = "Click Me") } }

Slide 54

Slide 54 text

@Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false) } Button(onClick = { showPopup = true }) { Text(text = "Click Me") } if (showPopup) { AlertDialog( onCloseRequest = { showPopup = false }, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }

Slide 55

Slide 55 text

@Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false) } Button(onClick = { showPopup = true }) { Text(text = "Click Me") } if (showPopup) { AlertDialog( onCloseRequest = { showPopup = false }, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }

Slide 56

Slide 56 text

@Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false) } if (!showPopup) { Button(onClick = { showPopup = true }) { Text(text = "Click Me") } } else { AlertDialog( onCloseRequest = { showPopup = false }, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }

Slide 57

Slide 57 text

Recomposition

Slide 58

Slide 58 text

Recompose / re·kuhm·powz / verb In an imperative UI model, to change a widget, you call a setter on the widget to change its internal state. In Compose, you call the composable function again with new data. Doing so causes the function to be recomposed--the widgets emitted by the function are redrawn, if necessary, with new data. The Compose framework can intelligently recompose only the components that changed.

Slide 59

Slide 59 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 60

Slide 60 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 61

Slide 61 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 62

Slide 62 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 63

Slide 63 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 64

Slide 64 text

(user: User) (name: String) (age: Int) (user: User) [counter: Int] (user: User) (imageURL: String) [scale: Float] (address: String)

Slide 65

Slide 65 text

Rules of Recomposition

Slide 66

Slide 66 text

Rules of Recomposition Some composable functions could be skipped Composable functions can be called frequently Composable functions can execute in any order Composable functions can run in parallel

Slide 67

Slide 67 text

@Composable fun ParentComposable() { "// Don’t write logic that always depends "// on the execution of all the composable Child1Composable() Child2Composable() Child3Composable() }

Slide 68

Slide 68 text

Rules of Recomposition Some composable functions could be skipped Composable functions can be called frequently Composable functions can execute in any order Composable functions can run in parallel

Slide 69

Slide 69 text

Rules of Recomposition Some composable functions could be skipped Composable functions can be called frequently Composable functions can execute in any order Composable functions can run in parallel

Slide 70

Slide 70 text

"// When this component is called from inside an animation, "// it will be called on every frame. @Composable fun ComponentCalledFromAnimation() { "// expensiveOperation takes 2 seconds to run val result = expensiveOperation() Text(result.message) }

Slide 71

Slide 71 text

"// When this component is called from inside an animation, "// it will be called on every frame. @Composable fun ComponentCalledFromAnimation() { "// expensiveOperation takes 2 seconds to run val result = expensiveOperation() Text(result.message) } launchInComposition

Slide 72

Slide 72 text

Rules of Recomposition Some composable functions could be skipped Composable functions can be called frequently Composable functions can execute in any order Composable functions can run in parallel

Slide 73

Slide 73 text

@Composable fun ParentComposable() { "// Can be called in any order Child1Composable() Child2Composable() Child3Composable() }

Slide 74

Slide 74 text

Rules of Recomposition Composable functions could be skipped Composable functions can be called frequently Composable functions can execute in any order Composable functions can run in parallel

Slide 75

Slide 75 text

Simple Layouts

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

Row

Slide 78

Slide 78 text

Row 1 2

Slide 79

Slide 79 text

1 Column 1 Row 2

Slide 80

Slide 80 text

@Composable fun ImageWithTitleSubtitleComponent() { }

Slide 81

Slide 81 text

@Composable fun ImageWithTitleSubtitleComponent() { Row() { Column() { } } }

Slide 82

Slide 82 text

@Composable fun ImageWithTitleSubtitleComponent() { Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) { Column(modifier = Modifier.padding(start = 16.dp)) { } } }

Slide 83

Slide 83 text

@Composable fun ImageWithTitleSubtitleComponent() { Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) { DrawableImage(R.drawable.landscape) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = "Title") CustomTextComponent(displayText = "Subtitle") } } }

Slide 84

Slide 84 text

@Composable fun ImageWithTitleSubtitleComponent( title: String, subtitle: String, imageUrl: String ) { Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) { NetworkImage(imageUrl) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = title) CustomTextComponent(displayText = subtitle) } } }

Slide 85

Slide 85 text

@Composable fun ImageWithTitleSubtitleComponent( title: String, subtitle: String, imageUrl: String ) { Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) { NetworkImage(imageUrl) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = title) CustomTextComponent(displayText = subtitle) } } }

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Display List

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

@Composable fun ListComponent(superheroList: List) { }

Slide 91

Slide 91 text

@Composable fun ListComponent(superheroList: List) { ScrollableColumn { for(person in superheroList) { SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } } }

Slide 92

Slide 92 text

www.JetpackCompose.app

Slide 93

Slide 93 text

@Composable fun ListComponent(superheroList: List) { }

Slide 94

Slide 94 text

@Composable fun ListComponent(superheroList: List) { LazyColumnFor(items = superheroList) { person "-> } }

Slide 95

Slide 95 text

@Composable fun ListComponent(superheroList: List) { LazyColumnFor(items = superheroList) { person "-> SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } }

Slide 96

Slide 96 text

@Composable fun ListComponent(superheroList: List) { LazyColumnFor(items = superheroList) { person "-> SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } }

Slide 97

Slide 97 text

Thank You Droid God, For this new day. I will rest in your promises(coroutines) of a world free of fragments. Guide me with compile-time checks and help me in every @SuppressWarnings that I add.

Slide 98

Slide 98 text

Click Gesture

Slide 99

Slide 99 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp), shape = RoundedCornerShape(4.dp) ) { ""... ""... ""... } }

Slide 100

Slide 100 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel: SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) .cl shape = RoundedCornerShape(4.dp) ) { .... .... } }

Slide 101

Slide 101 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel: SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) .cl shape = RoundedCornerShape(4.dp) ) { .... .... } } clip clickable clipToBounds

Slide 102

Slide 102 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel: SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) + .clickable { viewModel.updateSelectedSuperhero() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }

Slide 103

Slide 103 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel: SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) + .clickable { viewModel.updateSelectedSuperhero() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }

Slide 104

Slide 104 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, onClick: () "-> Unit ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) .clickable { onClick() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }

Slide 105

Slide 105 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, onClick: () "-> Unit ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp) .clickable { onClick() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }

Slide 106

Slide 106 text

Pinch-to-Zoom & Drag

Slide 107

Slide 107 text

@Composable fun ZoomableImageComponent(imageUrl: String) { }

Slide 108

Slide 108 text

@Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state { 1f } var panOffset by state { Offset(0f, 0f) } }

Slide 109

Slide 109 text

@Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state { 1f } var panOffset by state { Offset(0f, 0f) } Box(gravity = Alignment.Center) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize() ) } }

Slide 110

Slide 110 text

@Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state { 1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize().drawLayer( scaleX = scale, scaleY = scale ) ) } }

Slide 111

Slide 111 text

@Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state { 1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }).rawDragGestureFilter( object : DragObserver { override fun onDrag(dragDistance: Offset): Offset { panOffset = panOffset.plus(dragDistance) return super.onDrag(dragDistance) } }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize().drawLayer( scaleX = scale, scaleY = scale, translationX = panOffset.x, translationY = panOffset.y ) ) } }

Slide 112

Slide 112 text

@Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state { 1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }).rawDragGestureFilter( object : DragObserver { override fun onDrag(dragDistance: Offset): Offset { panOffset = panOffset.plus(dragDistance) return super.onDrag(dragDistance) } }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize().drawLayer( scaleX = scale, scaleY = scale, translationX = panOffset.x, translationY = panOffset.y ) ) } }

Slide 113

Slide 113 text

No content

Slide 114

Slide 114 text

Compose in Classic Android

Slide 115

Slide 115 text

" activity_compose_in_classic_android.xml

Slide 116

Slide 116 text

" activity_compose_in_classic_android.xml

Slide 117

Slide 117 text

class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) } }

Slide 118

Slide 118 text

class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) val composeView = findViewById(R.id.compose_view) composeView.setContent { SimpleRowComponent() } } }

Slide 119

Slide 119 text

class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) val composeView = findViewById(R.id.compose_view) composeView.setContent { SimpleRowComponent() } } }

Slide 120

Slide 120 text

Classic Android in Compose

Slide 121

Slide 121 text

@Composable fun ClassAndroidInComposeComponent() { }

Slide 122

Slide 122 text

@Composable fun ClassAndroidInComposeComponent() { val context = ContextAmbient.current val classicTextView = remember { TextView(context) } }

Slide 123

Slide 123 text

@Composable fun ClassAndroidInComposeComponent() { val context = ContextAmbient.current val classicTextView = remember { TextView(context) } AndroidView(viewBlock = { classicTextView }) { view "-> "// view is inflated here. Do anything if your logic requires it } }

Slide 124

Slide 124 text

ViewModel

Slide 125

Slide 125 text

@Composable fun MoviesComponent() { val viewModel: MoviesViewModel = viewModel() "// or val viewModel: MoviesViewModel = viewModel(ViewModelProvider.Factory) }

Slide 126

Slide 126 text

LiveData

Slide 127

Slide 127 text

@Composable fun MoviesComponent() { val viewModel: MoviesViewModel = viewModel() }

Slide 128

Slide 128 text

@Composable fun MoviesComponent() { val viewModel: MoviesViewModel = viewModel() val movieList = viewModel.movieListLiveData }

Slide 129

Slide 129 text

@Composable fun MoviesComponent() { val viewModel: MoviesViewModel = viewModel() val movieList = viewModel.movieListLiveData.observeAsState() }

Slide 130

Slide 130 text

@Composable fun MoviesComponent() { val viewModel: MoviesViewModel = viewModel() val movieList = viewModel.movieListLiveData.observeAsState() } ‘X’AsState()

Slide 131

Slide 131 text

Testing

Slide 132

Slide 132 text

@Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String ) { Card( modifier = Modifier.fillMaxWidth() .padding(8.dp), shape = RoundedCornerShape(4.dp) ) { ""... ""... } }

Slide 133

Slide 133 text

@RunWith(JUnit4"::class) class SimpleRowComponentTest { }

Slide 134

Slide 134 text

@RunWith(JUnit4"::class) class SimpleRowComponentTest { @get:Rule val composeTestRule = createComposeRule(disableTransitions = true) }

Slide 135

Slide 135 text

@RunWith(JUnit4"::class) class SimpleRowComponentTest { @get:Rule val composeTestRule = createComposeRule(disableTransitions = true) @Before fun setUp() { composeTestRule.setContent { SimpleRowComponent( titleText = "Title", subtitleText = "Subtitle", imageUrl = "https:"//www.google.com/demo.jpg" ) } } }

Slide 136

Slide 136 text

@RunWith(JUnit4"::class) class SimpleRowComponentTest { @get:Rule val composeTestRule = createComposeRule(disableTransitions = true) @Before fun setUp() { composeTestRule.setContent { SimpleRowComponent( titleText = "Title", subtitleText = "Subtitle", imageUrl = "https:"//www.google.com/demo.jpg" ) } } @Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title") } }

Slide 137

Slide 137 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title") }

Slide 138

Slide 138 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithSubstring("Ti") }

Slide 139

Slide 139 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithTag("TitleTag") }

Slide 140

Slide 140 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithTag("TitleTag") } "// In the component @Composable fun SimpleRowComponent(""...) { Card( modifier = Modifier.testTag("TitleTag") ) { ""... } }

Slide 141

Slide 141 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title") }

Slide 142

Slide 142 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").assertIsDisplayed() }

Slide 143

Slide 143 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").assertIsHidden() }

Slide 144

Slide 144 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").assertHasClickAction() }

Slide 145

Slide 145 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").assertHeightIsAtLeast(100.dp) }

Slide 146

Slide 146 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").performClick() }

Slide 147

Slide 147 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").performGesture { swipeDown() } }

Slide 148

Slide 148 text

@Test fun check_if_card_is_displayed() { composeTestRule.onNodeWithText("Title").assertIsDisplayed() }

Slide 149

Slide 149 text

Resources

Slide 150

Slide 150 text

Christmas came early"!!

Slide 151

Slide 151 text

https:"//bit.ly/ComposeByExample

Slide 152

Slide 152 text

https:"//www.JetpackCompose.app/

Slide 153

Slide 153 text

https:"//www.JetpackCompose.app/FAQ

Slide 154

Slide 154 text

https:"//www.JetpackCompose.app/Quick-Bites @vinaygaba

Slide 155

Slide 155 text

https:"//github.com/airbnb/Showkase

Slide 156

Slide 156 text

Learn Jetpack Compose by Example Vinay Gaba @vinaygaba https://bit.ly/ComposeByExample