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)

in theory scroll state should be stored

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?