Compose - GUI
aNew Application with defined application composable
@Composable
fun SomeGuiApplication(){
ComposeWorkshopsTheme {
UserList()
}
}
}
@Composable
fun UserList(){
//at this point user repository is hardcoded
val users=UsersRepository.select()
users.forEach{
Text("User ${it.name} ")
}
}
change background with surface
ComposeWorkshopsTheme {
Surface(color = Color.Yellow) {
UserList()
}
}
format user list with column
Column(
verticalArrangement = Arrangement.Center // just check possibilities in the code
) {
users.forEach {
Text(
text = "User : ${it.name} ",
modifier = Modifier.padding(1.dp),
)
}
}
make text clickable
Modifier
.padding(1.dp)
.clickable { Toast.makeText(context, "user clicked ${it.name}",Toast.LENGTH_LONG).show() }
modifiers order
show difference between
Modifier
.padding(30.dp)
.clickable { Toast.makeText(context, "user clicked ${it.name}",Toast.LENGTH_LONG).show() }
and:
Modifier
.clickable { Toast.makeText(context, "user clicked ${it.name}",Toast.LENGTH_LONG).show() }
.padding(30.dp)
Scaffold
First attempt
@Composable
fun SomeGuiApplication() {
ComposeWorkshopsTheme {
Surface(color = Color.Yellow) {
AppBody()
}
}
}
@Composable
fun AppBody(){
Scaffold {
UserList()
}
}
Better composition structure
@Composable
fun SomeGuiApplication() {
ComposeWorkshopsTheme {
Scaffold {
AppBody()
}
}
}
@Composable
fun AppBody(){
Surface(color = Color.Yellow) {
UserList()
}
}
Top Bar
@Composable
fun SomeGuiApplication() {
ComposeWorkshopsTheme {
Scaffold(
topBar = { topBar() } // waith for kotlin 1.6
) {
AppBody()
}
}
}
Top Bar Compsable - first variant
@Composable
fun topBar(){
TopAppBar() {
Text(
text = "Some AppTitle",
style = MaterialTheme.typography.h3
)
}
}
Top Bar Composable - Second variant
There is a second variant of TopAppBar function and you can not use function syntax with it!
to make code more readable you may want to extract some function definitions
remember about @Composable annotation before function definitions
(to me: during workshops explain topBarActions syntax)
@Composable
fun topBar(){
val appTitle= @Composable {
Text(
text = "Some AppTitle",
style = MaterialTheme.typography.h3
)
}
val topBarActions: @Composable RowScope.() -> Unit ={
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Email, contentDescription = null)
}
}
TopAppBar(
actions = topBarActions,
title = appTitle
)
}
Floating Button
val fap =@Composable {
FloatingActionButton(onClick = { /* ... */ }) {
Icon(Icons.Filled.AccountBox, contentDescription = null)
}
}
...
Scaffold(
topBar = { topBar() }, // waith for kotlin 1.6
floatingActionButton = fap
) {
AppBody()
}
Users List
Extract layout for single user
@Composable
private fun DisplayUser(context: Context,user: User) {
Text(
text = "User : ${user.name} ",
modifier =
Modifier
.clickable {
Toast
.makeText(context, "user clicked ${user.name}", Toast.LENGTH_LONG)
.show()
}
.padding(30.dp)
)
}
Bonus : FP and currying
private fun displayUser(context: Context): @Composable (User) -> Unit = { user ->
...
}
val context = LocalContext.current
val userComposable= displayUser(context)
users.forEach{userComposable(it)}
Change data source in "our very sophisticated DI framework"
object DI{
// val usersRepository:UsersRepository = InMemoryUsersRepository
val usersRepository:UsersRepository = InMemorySourceOfALotOfUsersy
}
Lazy Column - First attempt
LazyColumn(
verticalArrangement = Arrangement.Center // just chek in the code
) {
val context = LocalContext.current //we are not in Composable anymore!!!asdasdLa Column - second attemptzy
val userComposable= displayUser(context)
users.forEach{userComposable(it)}
}
Lazy Column - Second attempt
LazyColumn(
verticalArrangement = Arrangement.Center // just chek in the code
) {
//srogie rozwiązanie - import androidx.compose.foundation.lazy.items
items(users.toList()) {
userComposable(it)
}
}
Add state to View
//remember about : import androidx.compose.runtime.*
var userClickedState:String? by remember {
mutableStateOf(null)
}
if(userClickedState!=null) {
Text(text = "clicked user $userClickedState")
}
val userOnClick:(User)->Unit={ //currying once again!!!
userClickedState=it.name
Toast
.makeText(context, "user clicked ${it.name}", Toast.LENGTH_LONG)
.show()
}
....
val userComposable = displayUser(userOnClick)
...
private fun displayUser(onClick:(User)->Unit): @Composable (User) -> Unit = { user ->
Text(
text = "User : ${user.name} ",
modifier =
Modifier
.clickable {
onClick(user)
}
.padding(30.dp)
)
}
(but it doesnt match theory)
Refactor state
class UsersPageState {
private var userClickedState: String? by mutableStateOf(null)
val userInfo:String
get() = userClickedState ?: throw RuntimeException("users state was reset")
val updateClickedState: (User) -> Unit = {
if (userClicked())
userClickedState = "$userClickedState:${it.name}"
else
userClickedState = it.name
}
fun userClicked() = userClickedState != null
}
@Composable
fun UserList(usersPage: UsersPageState = UsersPageState(), usersRepository: UsersRepository = DI.usersRepository) {
val users = usersRepository.select().toList()
val userComposable = displayUser(onClick = usersPage.updateClickedState)
....
}
fun displayUser(onClick: (User) -> Unit): @Composable (User) -> Unit = { user ->
Puzzler - when state is created?
@Composable
fun UserList(usersPage: UsersPageState= UsersPageState(), usersRepository: UsersRepository = DI.usersRepository) {
println("****************************RECOMPOSE*******************************")
val users = usersRepository.select().toList()
val userComposable = displayUser(onClick = usersPage.updateClickedState)
class UsersPageState {
init {
println("**************USER PAGE STATE INITIALIZED***************")
}
still working
@Composable
fun AppBody() {
Surface(color = Color.Yellow) {
val state=UsersPageState()
UserList(state)
}
}
so compose actually saves stores default parameters outside "particular method compose"!
Following will not work:
@Composable
fun UserList(usersRepository: UsersRepository = DI.usersRepository) {
println("****************************RECOMPOSE*******************************")
val usersPage=UsersPageState()
Survive Rotation
First add saver
saver doesnt play well with null values!!!!!
companion object {
private val stateKey: String = "StateKey"
val saver = mapSaver(
save = {
val value = it.userClickedState ?: ""
mapOf(stateKey to value)
},
restore = { storedMap ->
val stored=storedMap[stateKey] as String
val value=if(stored=="") null else stored
UsersPageState().apply { userClickedState = value }
}
)
}
then use it in Composable function
remember that param name is stateSaver!!!!
@Composable
fun AppBody() {
Surface(color = Color.Yellow) {
val usersPageState by rememberSaveable(stateSaver = UsersPageState.saver) {
mutableStateOf(UsersPageState())
}
UserList(usersPageState)
}
}
Fixing Width
modifier = Modifier.width(IntrinsicSize.Max),
gives
Process: com.wlodar.jug.compose, PID: 7170
java.lang.IllegalStateException: Asking for intrinsic measurements of SubcomposeLayout layouts is not supported. This includes components that are built on top of SubcomposeLayout, such as lazy lists, BoxWithConstraints, TabRow, etc. To mitigate this:
- if intrinsic measurements are used to achieve 'match parent' sizing,, consider replacing the parent of the component with a custom layout which controls the order in which children are measured, making intrinsic measurement not needed
but following is ok:
@Composable
fun UserList(usersPage: UsersPageState, usersRepository: UsersRepository = DI.usersRepository) {
@Composable
fun displayHeaders(usersPage: UsersPageState) {
...
}
@Composable
fun displayUsersColumn() {
...
}
Column(
modifier = Modifier.fillMaxWidth()
) {
displayHeaders(usersPage)
displayUsersColumn()
}
}
User "Animation"
This is an example of "a state" which can be stored in composable - view state!
fun displayUser(onClick: (User) -> Unit): @Composable (User) -> Unit = { user ->
var isExpanded by remember { mutableStateOf(false) } // our animation state
val paddingValue by animateDpAsState( //animation "logic"
if (isExpanded) 48.dp else 20.dp
)
Surface(
color = Color.Blue,
modifier = Modifier.padding(vertical = 1.dp)
) {
Text(
text = "User : ${user.name} ",
color=Color.White,
modifier = Modifier
.clickable {
isExpanded = !isExpanded
onClick(user)
}
.padding(paddingValue) //animation styling
)
}
}
Column manual navigation
@Composable
fun displayColumnNavigation(scrollState: LazyListState, numberOfUsers: Int) {
//coroutine scope
val coroutineScope = rememberCoroutineScope()
//for better readability declare "clicks" function outside composable definitions
val scrolUp: () -> Unit = {
coroutineScope.launch {
scrollState.animateScrollToItem(0)
}
}
val scrolDown: () -> Unit = {
coroutineScope.launch {
//scroll state is described below
scrollState.animateScrollToItem(numberOfUsers)
}
}
Row(
modifier = Modifier.padding(vertical = 10.dp),
)
{
Button(onClick = scrolUp, modifier = Modifier.padding(end=5.dp)){
Text("Scroll Up")
}
Button(onClick = scrolDown){
Text("Scroll Down")
}
}
}
To make it work we need to have control over lazy list scroll state
Column(
modifier = Modifier.fillMaxWidth()
) {
val users = usersRepository.select().toList()
val scrollState = rememberLazyListState()
displayColumnNavigation(scrollState,users.size)
displayHeaders(usersPage)
displayUsersColumn(scrollState,users)
}
....
@Composable
fun displayUsersColumn(scrollState: LazyListState, users: List<User>) {
......
LazyColumn(
state = scrollState, //<---HERE
verticalArrangement = Arrangement.Center // just chek in the code
) {
...
Application theme
Lets return to this one:
@Composable
fun SomeGuiApplication() {
....
ComposeWorkshopsTheme { /// <- This
(...)
//it was auto generated
@Composable
fun ComposeWorkshopsTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
at the top of this file you have color pallete definitionns:
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200
)
and aprticular colors are defined in Color.kt
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
now using site such like this one : https://www.w3schools.com/colors/colors_palettes.asp
we can define here some general colors
//remember top add FF at the begining which is (most likely) transparency value FF- visible
val backgroundColor = Color(0xFFFEB236)
val controlsColor = Color(0xFF6b5b95)
now you can define second "preview annotation" and define colors inside pallette
val yellow = Color(0xFFFEB236)
val purple = Color(0xFF6b5b95)
val pink = Color(0xFFd64161)
val orange = Color(0xFFff7b25)
private val LightColorPalette = lightColors(
primary = yellow,
primaryVariant = orange,
secondary = purple
)
and use those colors directly in the composables
Surface(
color=MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 1.dp)
)
Last updated
Was this helpful?