Android Summit Learn Jetpack Compose by Example
Android Summit Learn Jetpack Compose by Example
by Example
Vinay Gaba
@vinaygaba
}
}
}
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))
}
}
@Composable
// Somewhere else in code fun AlertDialogComponent() {
if (some_condition_is_met()) {
alertDialog.show()
} }
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) {
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
(address: String)
(imageURL: String)
[scale: Float]
(user: User)
(address: String)
(imageURL: String)
[scale: Float]
(user: User)
(address: String)
(imageURL: String)
[scale: Float]
(user: User)
(address: String)
(imageURL: String)
[scale: Float]
(user: User)
(address: String)
(imageURL: String)
[scale: Float]
(user: User)
(address: String)
(imageURL: String)
[scale: Float]
Rules of
Recomposition
Rules of
Recomposition
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
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
<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
<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) }
"// or
}
@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")
}