KEMBAR78
Jetpack Compose a new way to implement UI on Android | PDF
Jetpack
Compose
a new way to implement UI on Android
Nelson Glauber
@nglauber
Jetpack Compose is a
modern declarative UI
Toolkit to simplify and
accelerate native Android
UI development with less
code, powerful tools, and
intuitive Kotlin APIs.
Motivation
• It’s not easy (or simple) to create a custom view…
• Current toolkit was created in 2008, but UIs are much more complex
nowadays…
• Declarative UI approach becomes popular among mobile developers
thanks to frameworks like SwiftUI, ReactNative and Flutter.
Jetpack Compose
• A new way of thinking the UI development: components over screens.
• Compatible with existing Android apps, so you can adopt it progressively.
• EXPERIMENTAL! Currently in Alpha stage! Don’t use it in production!
Getting started
with Compose
Android Studio 4.2
(Preview)
Compose Preview
Interactive Mode
Launch
Composable
@Preview
setContent
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
setContent
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
setContent
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
setContent
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
setContent
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(stringResource(R.string.hello, name))
}
Material Theme
• Define application’s theme with its respective colors, fonts, shapes, …
• Often is the root element of the screen (but you can nested themes).
setContent {
AppTheme {
Greeting("Android")
}
}
Material Theme
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content
)
}
private val DarkColorPalette =
darkColors(
primary = purple200,
primaryVariant = purple700,
secondary = teal200
)
private val LightColorPalette =
lightColors(
primary = purple500,
primaryVariant = purple700,
secondary = teal200
)
Modifiers
• Decorate an element
• Provide layout parameters
• Assign behavior
• They’re chained and the order is significant!
val shape = CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp)
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
val shape = CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp)
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
val shape = CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp)
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
val shape = CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp)
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
val shape = RoundedCornerShape(8.dp)
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
val shape = CircleShape
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.padding(1.dp)
.background(MaterialTheme.colors.primary, shape)
.clickable(onClick = {
// Click event
})
.padding(16.dp)
)
Layouts
Column Row Box Constraint
Layout
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("Column Text 1")
Text("Column Text 2")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Row Text 1")
Text(text = "Row Text 2")
}
}
Text(
"Stack Text",
modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 16.dp, top = 16.dp)
)
}
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("Column Text 1")
Text("Column Text 2")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Row Text 1")
Text(text = "Row Text 2")
}
}
Text(
"Stack Text",
modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 16.dp, top = 16.dp)
)
}
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("Column Text 1")
Text("Column Text 2")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Row Text 1")
Text(text = "Row Text 2")
}
}
Text(
"Stack Text",
modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 16.dp, top = 16.dp)
)
}
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("Column Text 1")
Text("Column Text 2")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Row Text 1")
Text(text = "Row Text 2")
}
}
Text(
"Stack Text",
modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 16.dp, top = 16.dp)
)
}
ConstraintLayout
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
👇
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
👇
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
👇
ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)) {
val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs()
Text("Nome", modifier = Modifier.constrainAs(text1Ref) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
TextField(modifier = Modifier.padding(top = 8.dp)
.constrainAs(edit1Ref) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(text1Ref.bottom)
})
Button(onClick = {}, modifier = Modifier.padding(top = 8.dp)
.constrainAs(btn1Ref) {
end.linkTo(edit1Ref.end)
top.linkTo(edit1Ref.bottom)
}
)
TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp)
.constrainAs(btn2Ref) {
end.linkTo(btn1Ref.start)
baseline.linkTo(btn1Ref.baseline)
}
)
}
👇
More
components…
Button
Button(
content = { Text("Button") },
onClick = {}
)
OutlinedButton(
content = { Text("OutlinedButton") },
onClick = {}
)
TextButton(
content = { Text("TextButton") },
onClick = {}
)
Button
Button(
content = {
if (isLoading) {
CircularProgressIndicator(color = Color.White)
} else {
Text("Button")
}
},
onClick = {}
)
Image
Image(
asset = imageResource(R.drawable.recife),
contentScale = ContentScale.FillHeight
)
Image(
asset = vectorResource(id = R.drawable.ic_android),
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(Color.Cyan)
)
CoilImage(
modifier = Modifier
.size(96.dp)
.clip(CircleShape),
data = photoUrl
)
Image
Image(
asset = imageResource(R.drawable.recife),
contentScale = ContentScale.FillHeight
)
Image(
asset = vectorResource(id = R.drawable.ic_android),
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(Color.Cyan)
)
PicassoImage(
modifier = Modifier
.size(96.dp)
.clip(CircleShape),
data = photoUrl
)
ScrollableColumn
ScrollableColumn {
for (i in 0..200) {
Text(
"Item: $i",
modifier = Modifier.padding(8.dp).fillMaxWidth()
)
}
}
ScrollableRow
ScrollableRow {
for (i in 0..200) {
Text(
"Item: $i",
modifier = Modifier.padding(8.dp)
)
}
}
Lists
@Composable
fun UserList(users: List<User>) {
LazyColumnFor(
items = users,
modifier = Modifier.fillMaxSize()) {
UserItem(user = it)
}
}
Lists
@Composable
fun UserList(users: List<User>) {
LazyColumnForIndexed(
items = users,
itemContent = { index, item ->
UserItem(user = item, index = index)
}
)
}
Scaffold
Scaffold(
topBar = {...},
drawerContent = {...},
bodyContent = {...},
floatingActionButton = {...},
bottomBar = {...}
)
TopAppBar(
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.Yellow,
title = { Text(text = "Compose") },
actions = {
IconButton(
onClick = {},
icon = { Icon(Icons.Default.Search) }
)
DropdownMenu(…)
}
)
FloatingActionButton(
onClick = {},
icon = { Icon(Icons.Filled.Add) },
backgroundColor = Color.Red,
contentColor = Color.White
)
BottomAppBar(
backgroundColor = MaterialTheme.colors.primary,
content = {
BottomNavigationItem(
icon = { Icon(Icons.Filled.Home) },
selected = selectedTab == 0,
onClick = { selectedTab = 0 },
selectedContentColor = Color.White,
unselectedContentColor = Color.DarkGray,
label = { Text(text = "Home") }
)
BottomNavigationItem(…)
}
)
BottomAppBar(
backgroundColor = MaterialTheme.colors.primary,
content = {
BottomNavigationItem(
icon = { Icon(Icons.Filled.Home) },
selected = selectedTab == 0,
onClick = { selectedTab = 0 },
selectedContentColor = Color.White,
unselectedContentColor = Color.DarkGray,
label = { Text(text = "Home") }
)
BottomNavigationItem(…)
}
)
State
• State in an app is any value that can change over time. 
• Component is updated when state has changed
var nameState by remember { mutableStateOf("") }
TextField(
value = nameState,
label = { Text("Digite seu nome") },
onValueChange = { s: String ->
nameState = s
}
)
State
data class Score(
var team: String,
var score: Int
)
State
class Score(
team: String,
score: Int
) {
var team by mutableStateOf(team)
var score by mutableStateOf(score)
}
class Score(
team: String,
score: Int
) {
var team by mutableStateOf(team)
var score by mutableStateOf(score)
}
@Composable
fun TeamScore(score: Score) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = score.team, style = MaterialTheme.typography.h6)
Button(
content = { Text("+") },
onClick = { score.score += 1 }
)
Text(text = score.score.toString(), style = MaterialTheme.typography.h5)
Button(
content = { Text("-") },
onClick = { score.score = max(score.score - 1, 0) }
)
}
}
@Composable
fun ScoreScreen(homeScore: Score, visitorScore: Score) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row {
TeamScore(score = homeScore)
Text(text = "x",
modifier = Modifier.padding(horizontal = 8.dp),
style = MaterialTheme.typography.h6)
TeamScore(score = visitorScore)
}
OutlinedButton(
modifier = Modifier.padding(top = 16.dp),
content = { Text("Reset") },
onClick = {
homeScore.score = 0
visitorScore.score = 0
}
)
}
}
Compose in your
MVVM app
Observing state
• LiveData.observeAsState
• Flow.collectAsState
• Observable.subscribeAsState
View Model
UI
state event
LiveData
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) {
val users by usersLiveData.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
onSaveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = onDeleteUser
)
}
}
LiveData
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) {
val users by usersLiveData.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
onSaveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = onDeleteUser
)
}
}
LiveData
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) {
val users by usersLiveData.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
onSaveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = onDeleteUser
)
}
}
LiveData
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) {
val users by usersLiveData.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
onSaveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = onDeleteUser
)
}
}
LiveData
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) {
val users by usersLiveData.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
onSaveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = onDeleteUser
)
}
}
LiveData
UserScreen(
usersLiveData = viewModel.allUsers,
onSaveUser = { user ->
viewModel.saveUser(user)
},
onDeleteUser = { user ->
viewModel.deleteUser(user)
}
)
@Composable
fun UserScreen(
usersLiveData: LiveData<List<UserBinding>>,
onSaveUser: (UserBinding) -> Unit,
onDeleteUser: (UserBinding) -> Unit
) { … }
ViewModel + LiveData + Compose
@Composable
fun UserScreen(
viewModel: UsersViewModel
) {
val users by viewModel.allUsers.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
InputPanel(currentUser, onInsertUser = { user ->
viewModel.saveUser(user)
})
UserList(
users = users ?: emptyList(),
onDeleteUser = { user ->
viewModel.deleteUser(user)
}
)
}
}
Interoperability
In fragments…
class MyFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ComposeView(requireContext()).apply {
setContent {
AppTheme {
YourComposable()
}
}
}
}
}
In fragments…
class MyFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ComposeView(requireContext()).apply {
setContent {
AppTheme {
YourComposable()
}
}
}
}
}
In layout files
<androidx.compose.ui.platform.ComposeView
android:id="@+id/my_composable"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
findViewById<ComposeView>(R.id.my_composable).setContent {
MaterialTheme {
Surface {
Text(text = "Hello!")
}
}
}
@Composable
fun MyCalendar(onDateUpdate: (Date) -> Unit) {
AndroidView(
viewBlock = { context: Context ->
val view =
LayoutInflater.from(context).inflate(R.layout.my_layout, null, false)
val textView = view.findViewById<TextView>(R.id.txtDate)
val calendarView = view.findViewById<CalendarView>(R.id.calendarView)
calendarView?.setOnDateChangeListener { cv, year, month, day ->
val date = Calendar.getInstance().apply {
set(year, month, day)
}.time
textView?.text = date.toString()
onDateUpdate(date)
}
view
},
update = { view ->
// Update view
}
)
}
@Composable
fun MyCalendar(onDateUpdate: (Date) -> Unit) {
AndroidView(
viewBlock = { context: Context ->
val view =
LayoutInflater.from(context).inflate(R.layout.my_layout, null, false)
val textView = view.findViewById<TextView>(R.id.txtDate)
val calendarView = view.findViewById<CalendarView>(R.id.calendarView)
calendarView?.setOnDateChangeListener { cv, year, month, day ->
val date = Calendar.getInstance().apply {
set(year, month, day)
}.time
textView?.text = date.toString()
onDateUpdate(date)
}
view
},
update = { view ->
// Update view
}
)
}
@Composable
fun MyCalendar(onDateUpdate: (Date) -> Unit) {
AndroidView(
viewBlock = { context: Context ->
val view =
LayoutInflater.from(context).inflate(R.layout.my_layout, null, false)
val textView = view.findViewById<TextView>(R.id.txtDate)
val calendarView = view.findViewById<CalendarView>(R.id.calendarView)
calendarView?.setOnDateChangeListener { cv, year, month, day ->
val date = Calendar.getInstance().apply {
set(year, month, day)
}.time
textView?.text = date.toString()
onDateUpdate(date)
}
view
},
update = { view ->
// Update view
}
)
}
AndroidViewBinding
android {
...
viewBinding {
enabled = true
}
}
implementation "androidx.compose.ui:ui-viewbinding:$compose_version"
AndroidViewBinding(bindingBlock = MyBindingLayoutBinding::inflate) {
textView.text = "My Text"
seekBar.progress = 50
}
Resource files
• stringResources(R.string.your_string)
• dimensionResource(R.dimen.padding_small)
• colorResource(R.color.blue)
• …
On config changes…
Unlike the current UI toolkit, the state is not saved automatically across
configuration changes.
val nameState = savedInstanceState { "" }
Roadmap
Wrap up
• The way of Compose works seems very interesting, once it is following
the same modern paradigm of other UI toolkits.
• Can we cheer up to see library in other platforms?
• It’s in alpha stage, so DON’T USE IN PRODUCTION!
• Be prepared, because unlearn is harder than learn 😉
References
• Página oficial do Jetpack Compose
https://developer.android.com/jetpack/compose
• Codelab Jetpack Compose
https://codelabs.developers.google.com/codelabs/jetpack-compose-
basics/#0
• Jetpack Compose Samples
https://github.com/android/compose-samples
• Romain Guy Sample
https://github.com/romainguy/sample-materials-shop
References
• Lista de classes do Compose
https://developer.android.com/reference/kotlin/androidx/ui/classes
• Compose Academy
https://compose.academy/
• Classic Android to Jetpack (by Vinay Gaba)
https://jetpackcompose.app/
• Canal #Compose no Slack do Kotlin
slack.kotlinlang.org (#compose)
References
• Understanding Compose (Android Dev Summit 2019)
https://www.youtube.com/watch?v=Q9MtlmmN4Q0
• What’s new in Jetpack Compose (Android Dev Summit 2019)
https://www.youtube.com/watch?v=dtm2h-_sNDQ
• Jetpack Compose (#Android11 - 2020)
https://www.youtube.com/watch?v=U5BwfqBpiWU
• Jetpack Compose - Next Gen Kotlin UI Toolkit for Android (Right?)
https://www.youtube.com/watch?v=I5zRmCheVVg
References
• Thinking in Compose
https://www.youtube.com/watch?v=SMOhl9RK0BA
• Repositório do Jetpack Compose
https://android.googlesource.com/platform/frameworks/support/+/refs/
heads/androidx-master-dev/compose/
• Request Features & Bug Tracker
https://issuetracker.google.com/issues/new?component=612128
References
• goo.gle/compose-samples
• goo.gle/compose-codelabs
• goo.gle/compose-docs
• goo.gle/compose-feedback
• goo.gle/compose-slack
Thank you!
Nelson Glauber
@nglauber

Jetpack Compose a new way to implement UI on Android

  • 1.
    Jetpack Compose a new wayto implement UI on Android Nelson Glauber @nglauber
  • 2.
    Jetpack Compose isa modern declarative UI Toolkit to simplify and accelerate native Android UI development with less code, powerful tools, and intuitive Kotlin APIs.
  • 3.
    Motivation • It’s noteasy (or simple) to create a custom view… • Current toolkit was created in 2008, but UIs are much more complex nowadays… • Declarative UI approach becomes popular among mobile developers thanks to frameworks like SwiftUI, ReactNative and Flutter.
  • 4.
    Jetpack Compose • Anew way of thinking the UI development: components over screens. • Compatible with existing Android apps, so you can adopt it progressively. • EXPERIMENTAL! Currently in Alpha stage! Don’t use it in production!
  • 5.
  • 6.
  • 7.
  • 8.
    setContent class MainActivity :AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
  • 9.
    setContent class MainActivity :AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
  • 10.
    setContent class MainActivity :AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
  • 11.
    setContent class MainActivity :AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
  • 12.
    setContent class MainActivity :AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(stringResource(R.string.hello, name)) }
  • 13.
    Material Theme • Defineapplication’s theme with its respective colors, fonts, shapes, … • Often is the root element of the screen (but you can nested themes). setContent { AppTheme { Greeting("Android") } }
  • 14.
    Material Theme @Composable fun AppTheme( darkTheme:Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) { DarkColorPalette } else { LightColorPalette } MaterialTheme( colors = colors, typography = typography, shapes = shapes, content = content ) } private val DarkColorPalette = darkColors( primary = purple200, primaryVariant = purple700, secondary = teal200 ) private val LightColorPalette = lightColors( primary = purple500, primaryVariant = purple700, secondary = teal200 )
  • 15.
    Modifiers • Decorate anelement • Provide layout parameters • Assign behavior • They’re chained and the order is significant!
  • 16.
    val shape =CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp) Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 17.
    val shape =CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp) Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 18.
    val shape =CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp) Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 19.
    val shape =CutCornerShape(topLeft = 16.dp, bottomRight = 16.dp) Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 20.
    val shape =RoundedCornerShape(8.dp) Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 21.
    val shape =CircleShape Text( text = "Text 1", style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center), modifier = Modifier.fillMaxWidth() .padding(16.dp) .border(2.dp, MaterialTheme.colors.secondary, shape) .padding(1.dp) .background(MaterialTheme.colors.primary, shape) .clickable(onClick = { // Click event }) .padding(16.dp) )
  • 22.
    Layouts Column Row BoxConstraint Layout
  • 23.
    Box(modifier = Modifier.fillMaxWidth()){ Column( modifier = Modifier .padding(16.dp) .fillMaxWidth() ) { Text("Column Text 1") Text("Column Text 2") Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { Text(text = "Row Text 1") Text(text = "Row Text 2") } } Text( "Stack Text", modifier = Modifier .align(Alignment.TopEnd) .padding(end = 16.dp, top = 16.dp) ) }
  • 24.
    Box(modifier = Modifier.fillMaxWidth()){ Column( modifier = Modifier .padding(16.dp) .fillMaxWidth() ) { Text("Column Text 1") Text("Column Text 2") Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { Text(text = "Row Text 1") Text(text = "Row Text 2") } } Text( "Stack Text", modifier = Modifier .align(Alignment.TopEnd) .padding(end = 16.dp, top = 16.dp) ) }
  • 25.
    Box(modifier = Modifier.fillMaxWidth()){ Column( modifier = Modifier .padding(16.dp) .fillMaxWidth() ) { Text("Column Text 1") Text("Column Text 2") Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { Text(text = "Row Text 1") Text(text = "Row Text 2") } } Text( "Stack Text", modifier = Modifier .align(Alignment.TopEnd) .padding(end = 16.dp, top = 16.dp) ) }
  • 26.
    Box(modifier = Modifier.fillMaxWidth()){ Column( modifier = Modifier .padding(16.dp) .fillMaxWidth() ) { Text("Column Text 1") Text("Column Text 2") Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { Text(text = "Row Text 1") Text(text = "Row Text 2") } } Text( "Stack Text", modifier = Modifier .align(Alignment.TopEnd) .padding(end = 16.dp, top = 16.dp) ) }
  • 27.
  • 28.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) }
  • 29.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) }
  • 30.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) } 👇
  • 31.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) } 👇
  • 32.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) } 👇
  • 33.
    ConstraintLayout(modifier = Modifier.fillMaxSize().padding(16.dp)){ val (text1Ref, edit1Ref, btn1Ref, btn2Ref) = createRefs() Text("Nome", modifier = Modifier.constrainAs(text1Ref) { top.linkTo(parent.top) centerHorizontallyTo(parent) }) TextField(modifier = Modifier.padding(top = 8.dp) .constrainAs(edit1Ref) { start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(text1Ref.bottom) }) Button(onClick = {}, modifier = Modifier.padding(top = 8.dp) .constrainAs(btn1Ref) { end.linkTo(edit1Ref.end) top.linkTo(edit1Ref.bottom) } ) TextButton(onClick = {}, modifier = Modifier.padding(end = 8.dp) .constrainAs(btn2Ref) { end.linkTo(btn1Ref.start) baseline.linkTo(btn1Ref.baseline) } ) } 👇
  • 34.
  • 35.
    Button Button( content = {Text("Button") }, onClick = {} ) OutlinedButton( content = { Text("OutlinedButton") }, onClick = {} ) TextButton( content = { Text("TextButton") }, onClick = {} )
  • 36.
    Button Button( content = { if(isLoading) { CircularProgressIndicator(color = Color.White) } else { Text("Button") } }, onClick = {} )
  • 37.
    Image Image( asset = imageResource(R.drawable.recife), contentScale= ContentScale.FillHeight ) Image( asset = vectorResource(id = R.drawable.ic_android), contentScale = ContentScale.Fit, colorFilter = ColorFilter.tint(Color.Cyan) ) CoilImage( modifier = Modifier .size(96.dp) .clip(CircleShape), data = photoUrl )
  • 38.
    Image Image( asset = imageResource(R.drawable.recife), contentScale= ContentScale.FillHeight ) Image( asset = vectorResource(id = R.drawable.ic_android), contentScale = ContentScale.Fit, colorFilter = ColorFilter.tint(Color.Cyan) ) PicassoImage( modifier = Modifier .size(96.dp) .clip(CircleShape), data = photoUrl )
  • 39.
    ScrollableColumn ScrollableColumn { for (iin 0..200) { Text( "Item: $i", modifier = Modifier.padding(8.dp).fillMaxWidth() ) } }
  • 40.
    ScrollableRow ScrollableRow { for (iin 0..200) { Text( "Item: $i", modifier = Modifier.padding(8.dp) ) } }
  • 41.
    Lists @Composable fun UserList(users: List<User>){ LazyColumnFor( items = users, modifier = Modifier.fillMaxSize()) { UserItem(user = it) } }
  • 42.
    Lists @Composable fun UserList(users: List<User>){ LazyColumnForIndexed( items = users, itemContent = { index, item -> UserItem(user = item, index = index) } ) }
  • 43.
    Scaffold Scaffold( topBar = {...}, drawerContent= {...}, bodyContent = {...}, floatingActionButton = {...}, bottomBar = {...} )
  • 44.
    TopAppBar( backgroundColor = MaterialTheme.colors.primary, contentColor= Color.Yellow, title = { Text(text = "Compose") }, actions = { IconButton( onClick = {}, icon = { Icon(Icons.Default.Search) } ) DropdownMenu(…) } )
  • 45.
    FloatingActionButton( onClick = {}, icon= { Icon(Icons.Filled.Add) }, backgroundColor = Color.Red, contentColor = Color.White )
  • 46.
    BottomAppBar( backgroundColor = MaterialTheme.colors.primary, content= { BottomNavigationItem( icon = { Icon(Icons.Filled.Home) }, selected = selectedTab == 0, onClick = { selectedTab = 0 }, selectedContentColor = Color.White, unselectedContentColor = Color.DarkGray, label = { Text(text = "Home") } ) BottomNavigationItem(…) } )
  • 47.
    BottomAppBar( backgroundColor = MaterialTheme.colors.primary, content= { BottomNavigationItem( icon = { Icon(Icons.Filled.Home) }, selected = selectedTab == 0, onClick = { selectedTab = 0 }, selectedContentColor = Color.White, unselectedContentColor = Color.DarkGray, label = { Text(text = "Home") } ) BottomNavigationItem(…) } )
  • 48.
    State • State inan app is any value that can change over time.  • Component is updated when state has changed var nameState by remember { mutableStateOf("") } TextField( value = nameState, label = { Text("Digite seu nome") }, onValueChange = { s: String -> nameState = s } )
  • 49.
    State data class Score( varteam: String, var score: Int )
  • 50.
    State class Score( team: String, score:Int ) { var team by mutableStateOf(team) var score by mutableStateOf(score) }
  • 51.
    class Score( team: String, score:Int ) { var team by mutableStateOf(team) var score by mutableStateOf(score) }
  • 52.
    @Composable fun TeamScore(score: Score){ Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(text = score.team, style = MaterialTheme.typography.h6) Button( content = { Text("+") }, onClick = { score.score += 1 } ) Text(text = score.score.toString(), style = MaterialTheme.typography.h5) Button( content = { Text("-") }, onClick = { score.score = max(score.score - 1, 0) } ) } }
  • 53.
    @Composable fun ScoreScreen(homeScore: Score,visitorScore: Score) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row { TeamScore(score = homeScore) Text(text = "x", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.h6) TeamScore(score = visitorScore) } OutlinedButton( modifier = Modifier.padding(top = 16.dp), content = { Text("Reset") }, onClick = { homeScore.score = 0 visitorScore.score = 0 } ) } }
  • 54.
  • 55.
    Observing state • LiveData.observeAsState •Flow.collectAsState • Observable.subscribeAsState View Model UI state event
  • 56.
    LiveData @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser:(UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { val users by usersLiveData.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> onSaveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = onDeleteUser ) } }
  • 57.
    LiveData @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser:(UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { val users by usersLiveData.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> onSaveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = onDeleteUser ) } }
  • 58.
    LiveData @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser:(UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { val users by usersLiveData.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> onSaveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = onDeleteUser ) } }
  • 59.
    LiveData @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser:(UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { val users by usersLiveData.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> onSaveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = onDeleteUser ) } }
  • 60.
    LiveData @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser:(UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { val users by usersLiveData.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> onSaveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = onDeleteUser ) } }
  • 61.
    LiveData UserScreen( usersLiveData = viewModel.allUsers, onSaveUser= { user -> viewModel.saveUser(user) }, onDeleteUser = { user -> viewModel.deleteUser(user) } ) @Composable fun UserScreen( usersLiveData: LiveData<List<UserBinding>>, onSaveUser: (UserBinding) -> Unit, onDeleteUser: (UserBinding) -> Unit ) { … }
  • 62.
    ViewModel + LiveData+ Compose @Composable fun UserScreen( viewModel: UsersViewModel ) { val users by viewModel.allUsers.observeAsState() Column(modifier = Modifier.fillMaxSize()) { InputPanel(currentUser, onInsertUser = { user -> viewModel.saveUser(user) }) UserList( users = users ?: emptyList(), onDeleteUser = { user -> viewModel.deleteUser(user) } ) } }
  • 63.
  • 64.
    In fragments… class MyFragment:Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ComposeView(requireContext()).apply { setContent { AppTheme { YourComposable() } } } } }
  • 65.
    In fragments… class MyFragment:Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ComposeView(requireContext()).apply { setContent { AppTheme { YourComposable() } } } } }
  • 66.
    In layout files <androidx.compose.ui.platform.ComposeView android:id="@+id/my_composable" android:layout_width="wrap_content" android:layout_height="wrap_content"/> findViewById<ComposeView>(R.id.my_composable).setContent { MaterialTheme { Surface { Text(text = "Hello!") } } }
  • 67.
    @Composable fun MyCalendar(onDateUpdate: (Date)-> Unit) { AndroidView( viewBlock = { context: Context -> val view = LayoutInflater.from(context).inflate(R.layout.my_layout, null, false) val textView = view.findViewById<TextView>(R.id.txtDate) val calendarView = view.findViewById<CalendarView>(R.id.calendarView) calendarView?.setOnDateChangeListener { cv, year, month, day -> val date = Calendar.getInstance().apply { set(year, month, day) }.time textView?.text = date.toString() onDateUpdate(date) } view }, update = { view -> // Update view } ) }
  • 68.
    @Composable fun MyCalendar(onDateUpdate: (Date)-> Unit) { AndroidView( viewBlock = { context: Context -> val view = LayoutInflater.from(context).inflate(R.layout.my_layout, null, false) val textView = view.findViewById<TextView>(R.id.txtDate) val calendarView = view.findViewById<CalendarView>(R.id.calendarView) calendarView?.setOnDateChangeListener { cv, year, month, day -> val date = Calendar.getInstance().apply { set(year, month, day) }.time textView?.text = date.toString() onDateUpdate(date) } view }, update = { view -> // Update view } ) }
  • 69.
    @Composable fun MyCalendar(onDateUpdate: (Date)-> Unit) { AndroidView( viewBlock = { context: Context -> val view = LayoutInflater.from(context).inflate(R.layout.my_layout, null, false) val textView = view.findViewById<TextView>(R.id.txtDate) val calendarView = view.findViewById<CalendarView>(R.id.calendarView) calendarView?.setOnDateChangeListener { cv, year, month, day -> val date = Calendar.getInstance().apply { set(year, month, day) }.time textView?.text = date.toString() onDateUpdate(date) } view }, update = { view -> // Update view } ) }
  • 70.
    AndroidViewBinding android { ... viewBinding { enabled= true } } implementation "androidx.compose.ui:ui-viewbinding:$compose_version" AndroidViewBinding(bindingBlock = MyBindingLayoutBinding::inflate) { textView.text = "My Text" seekBar.progress = 50 }
  • 71.
    Resource files • stringResources(R.string.your_string) •dimensionResource(R.dimen.padding_small) • colorResource(R.color.blue) • …
  • 72.
    On config changes… Unlikethe current UI toolkit, the state is not saved automatically across configuration changes. val nameState = savedInstanceState { "" }
  • 73.
  • 74.
    Wrap up • Theway of Compose works seems very interesting, once it is following the same modern paradigm of other UI toolkits. • Can we cheer up to see library in other platforms? • It’s in alpha stage, so DON’T USE IN PRODUCTION! • Be prepared, because unlearn is harder than learn 😉
  • 75.
    References • Página oficialdo Jetpack Compose https://developer.android.com/jetpack/compose • Codelab Jetpack Compose https://codelabs.developers.google.com/codelabs/jetpack-compose- basics/#0 • Jetpack Compose Samples https://github.com/android/compose-samples • Romain Guy Sample https://github.com/romainguy/sample-materials-shop
  • 76.
    References • Lista declasses do Compose https://developer.android.com/reference/kotlin/androidx/ui/classes • Compose Academy https://compose.academy/ • Classic Android to Jetpack (by Vinay Gaba) https://jetpackcompose.app/ • Canal #Compose no Slack do Kotlin slack.kotlinlang.org (#compose)
  • 77.
    References • Understanding Compose(Android Dev Summit 2019) https://www.youtube.com/watch?v=Q9MtlmmN4Q0 • What’s new in Jetpack Compose (Android Dev Summit 2019) https://www.youtube.com/watch?v=dtm2h-_sNDQ • Jetpack Compose (#Android11 - 2020) https://www.youtube.com/watch?v=U5BwfqBpiWU • Jetpack Compose - Next Gen Kotlin UI Toolkit for Android (Right?) https://www.youtube.com/watch?v=I5zRmCheVVg
  • 78.
    References • Thinking inCompose https://www.youtube.com/watch?v=SMOhl9RK0BA • Repositório do Jetpack Compose https://android.googlesource.com/platform/frameworks/support/+/refs/ heads/androidx-master-dev/compose/ • Request Features & Bug Tracker https://issuetracker.google.com/issues/new?component=612128
  • 79.
    References • goo.gle/compose-samples • goo.gle/compose-codelabs •goo.gle/compose-docs • goo.gle/compose-feedback • goo.gle/compose-slack
  • 80.