0% found this document useful (0 votes)
120 views

Android Summit Learn Jetpack Compose by Example

Uploaded by

mrnovoa
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
120 views

Android Summit Learn Jetpack Compose by Example

Uploaded by

mrnovoa
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 156

Learn Jetpack Compose

by Example
Vinay Gaba
@vinaygaba

Join the conversation in #session-chat-d2-s2-t1


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.
Why do we need
Compose?
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
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
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
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
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
Disclaimer
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
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
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
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
Examples
Hello World
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {

}
}
}
fun ComponentActivity.setContent(
recomposer: Recomposer = Recomposer.current(),
content: @Composable () "-> Unit
): Composition {
"// Some magic ✨🦄
}
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {

}
}
}
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "Hello World")
}
}
}
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "Hello World")
}
}
}
class HelloWorldActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "Hello World")
}
}
}
@Composable
fun CustomTextComponent() {
Text(text = "Hello World")
}
@Composable
fun CustomTextComponent(displayText: String) {
Text(
text = displayText,
style = TextStyle(
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
)
)
}
@Composable
fun CustomTextComponent(displayText: String) {
Text(
text = displayText,
style = TextStyle(
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
)
)
}
@Composable
fun CustomTextComponent(displayText: String) {
Text(
text = displayText,
style = TextStyle(
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
)
)
}

@Preview
@Composable
fun CustomTextComponentPreview() {
CustomTextComponent("Hello World")
}
Display Image
@Composable
fun DrawableImage() {

}
@Composable
fun DrawableImage() {
val image = loadImageResource(R.drawable.landscape)
}
@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!


@Composable
fun DrawableImage(@DrawableRes resId: Int) {
val image = loadImageResource(resId)
image.resource.resource"?.let {
Image(
asset = it,
modifier = Modifier.preferredSize(200.dp)
)
}
}
@Composable
fun DrawableImage(@DrawableRes resId: Int) {
val image = loadImageResource(resId)
image.resource.resource"?.let {
Image(
asset = it,
modifier = Modifier.preferredSize(200.dp)
)
}
}
Modifiers
Text(
text = "Hello",
modifier = Modifier.padding(16.dp)
)
Text(
text = "Hello",
modifier = Modifier.padding(16.dp)
.background(color = Color.Red)
)
Text(
text = "Hello",
modifier = Modifier.padding(16.dp)
.background(color = Color.Red)
)
Text(
text = "Hello",
modifier = Modifier.padding(16.dp) Hello
.background(color = Color.Red)
)
Text(
text = "Hello",
modifier = Modifier.background(color = Color.Red)
.padding(16.dp)
)
Text(
text = "Hello",
modifier = Modifier.background(color = Color.Red)
.padding(16.dp)
)
Text(
text = "Hello",
modifier = Modifier.background(color = Color.Red) Hello
.padding(16.dp)
)

The order of a Modifier has an impact on the behavior


Alert Dialog
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.
How vs What
How What
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()
}
How What
// Classic Android
val alertDialog = AlertDialog.Builder(context)
.setTitle("Android Summit!”)
.setMessage("Isn't this conference amazing?")

@Composable
// Somewhere else in code fun AlertDialogComponent() {
if (some_condition_is_met()) {
alertDialog.show()
} }

// Somewhere else in code


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

Button(onClick = { showPopup = true }) {


Text(text = "Click Me")
}
}
@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")
}
}
)
}
}
@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")
}
}
)
}
}
@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")
}
}
)
}
}
Recomposition
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.
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
(user: User)

(name: String) (age: Int) (user: User)

[counter: Int] (user: User)

(address: String)
(imageURL: String)
[scale: Float]
Rules of
Recomposition
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
@Composable
fun ParentComposable() {
"// Don’t write logic that always depends
"// on the execution of all the composable
Child1Composable()
Child2Composable()
Child3Composable()
}
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
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
"// 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)
}
"// When this component is called from inside an animation,
"// it will be called on every frame.
@Composable
fun ComponentCalledFromAnimation() {
launchInComposition
"// expensiveOperation takes 2 seconds to run
val result = expensiveOperation()

Text(result.message)
}
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
@Composable
fun ParentComposable() {
"// Can be called in any order
Child1Composable()
Child2Composable()
Child3Composable()
}
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
Simple Layouts
Row
Row

1 2
Row

1
2

Column
@Composable
fun ImageWithTitleSubtitleComponent() {

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

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

}
}
}
@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")
}
}
}
@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)
}
}
}
@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)
}
}
}
Display List
@Composable
fun ListComponent(superheroList: List<Person>) {

}
@Composable
fun ListComponent(superheroList: List<Person>) {
ScrollableColumn {
for(person in superheroList) {
SimpleRowComponent(
person.name,
person.age,
person.profilePictureUrl
)
}
}
}
www.JetpackCompose.app
@Composable
fun ListComponent(superheroList: List<Person>) {

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

}
}
@Composable
fun ListComponent(superheroList: List<Person>) {
LazyColumnFor(items = superheroList) { person "->
SimpleRowComponent(
person.name,
person.age,
person.profilePictureUrl
)
}
}
@Composable
fun ListComponent(superheroList: List<Person>) {
LazyColumnFor(items = superheroList) { person "->
SimpleRowComponent(
person.name,
person.age,
person.profilePictureUrl
)
}
}
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.
Click Gesture
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp),
shape = RoundedCornerShape(4.dp)
) {
""...
""...
""...
}
}
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String,
viewModel: SuperheroViewModel
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp)
.cl
shape = RoundedCornerShape(4.dp)
) {
....
....
}
}
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String,
viewModel: SuperheroViewModel
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp)
.cl
shape =clip
RoundedCornerShape(4.dp)
) {
.... clickable
.... clipToBounds
}
}
@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)
) {
....
....
}
}
@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)
) {
....
....
}
}
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String,
onClick: () "-> Unit
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp)
.clickable {
onClick()
},
shape = RoundedCornerShape(4.dp)
) {
....
....
}
}
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String,
onClick: () "-> Unit
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp)
.clickable {
onClick()
},
shape = RoundedCornerShape(4.dp)
) {
....
....
}
}
Pinch-to-Zoom & Drag
@Composable
fun ZoomableImageComponent(imageUrl: String) {

}
@Composable
fun ZoomableImageComponent(imageUrl: String) {
var scale by state { 1f }
var panOffset by state { Offset(0f, 0f) }
}
@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()
)
}
}
@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
)
)
}
}
@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
)
)
}
}
@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
)
)
}
}
Compose in Classic Android
activity_compose_in_classic_android.xml

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http:"//schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" "/>

<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" "/>
"</LinearLayout>
activity_compose_in_classic_android.xml

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http:"//schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" "/>

<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" "/>
"</LinearLayout>
class ComposeInClassicAndroidActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_compose_in_classic_android)
}
}
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()
}
}
}
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()
}
}
}
Classic Android in Compose
@Composable
fun ClassAndroidInComposeComponent() {

}
@Composable
fun ClassAndroidInComposeComponent() {
val context = ContextAmbient.current
val classicTextView = remember { TextView(context) }
}
@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
}
}
ViewModel
@Composable
fun MoviesComponent() {
val viewModel: MoviesViewModel = viewModel()

"// or

val viewModel: MoviesViewModel = viewModel(ViewModelProvider.Factory)


}
LiveData
@Composable
fun MoviesComponent() {
val viewModel: MoviesViewModel = viewModel()
}
@Composable
fun MoviesComponent() {
val viewModel: MoviesViewModel = viewModel()
val movieList = viewModel.movieListLiveData
}
@Composable
fun MoviesComponent() {
val viewModel: MoviesViewModel = viewModel()
val movieList = viewModel.movieListLiveData.observeAsState()
}
@Composable
fun MoviesComponent() {
‘X’AsState()
val viewModel: MoviesViewModel = viewModel()
val movieList = viewModel.movieListLiveData.observeAsState()
}
Testing
@Composable
fun SimpleRowComponent(
titleText: String,
subtitleText: String,
imageUrl: String
) {
Card(
modifier = Modifier.fillMaxWidth()
.padding(8.dp),
shape = RoundedCornerShape(4.dp)
) {
""...
""...
}
}
@RunWith(JUnit4"::class)
class SimpleRowComponentTest {

}
@RunWith(JUnit4"::class)
class SimpleRowComponentTest {
@get:Rule
val composeTestRule = createComposeRule(disableTransitions = true)
}
@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"
)
}
}
}
@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")
}
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title")
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithSubstring("Ti")
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithTag("TitleTag")
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithTag("TitleTag")
}

"// In the component


@Composable
fun SimpleRowComponent(""...) {
Card(
modifier = Modifier.testTag("TitleTag")
) {
""...
}
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title")
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").assertIsDisplayed()
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").assertIsHidden()
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").assertHasClickAction()
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").assertHeightIsAtLeast(100.dp)
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").performClick()
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").performGesture { swipeDown() }
}
@Test
fun check_if_card_is_displayed() {
composeTestRule.onNodeWithText("Title").assertIsDisplayed()
}
Resources
Christmas came early"!!
https:"//bit.ly/ComposeByExample
https:"//www.JetpackCompose.app/
https:"//www.JetpackCompose.app/FAQ
@vinaygaba
https:"//www.JetpackCompose.app/Quick-Bites
https:"//github.com/airbnb/Showkase
Learn Jetpack Compose
by Example
Vinay Gaba
https://bit.ly/ComposeByExample
@vinaygaba

You might also like