Expanding the App

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Refactor Into Separate Composables

In your project, open MainActivity.kt. Look through the code — where is the @Composable annotation?

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Column { ...
public fun ComponentActivity.setContent(
  parent: CompositionContext? = null,
  content: @Composable () -> Unit
) {...}

@Composable
fun ConversationContent() {
  // TODO: create conversation UI here
}
@Composable
fun ConversationContent() {
 Column(
  modifier = Modifier
   .fillMaxSize()
   .padding(16.dp)
  ) {
    val context = LocalContext.current
    val chatInputText by remember { mutableStateOf(context.getString(R.string.chat_entry_default)) }
    val chatOutputText by remember { mutableStateOf(context.getString(R.string.chat_display_default)) }
        Text(
          text = chatOutputText,
          fontStyle = FontStyle.Italic, // 1
          color = Color.Magenta, // 2
          fontSize = 30.sp, // 3
          fontWeight = FontWeight.Bold, // 4
          modifier = Modifier
            .background(Color.Blue)
            .padding(16.dp)
        )

        OutlinedTextField(
          value = chatInputText,
          modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .background(Color.Green)
          ,

          onValueChange = {
          },
          label = { Text(text = stringResource(id = R.string.chat_entry_label)) }
        )

        Button(onClick = {},
          Modifier
            .width(200.dp)
            .align(alignment = Alignment.CenterHorizontally)
          ) {
          Text(text = stringResource(id = R.string.send_button))
  }
 }
}
class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      ConversationContent()
    }
  }
}
Button(onClick = {
	chatOutputText = chatInputText
	chatInputText = ""
}) {
	Text(text = stringResource(id = R.string.send_button))
}

Layout Groups

Layout Groups in Compose allow you to arrange elements of your UI on the device screen in various ways. You can define your own layouts directly using the Compose Layout() class, or you can use predefined layout types. Just as you can combine composables and use them within one another, you can also nest layout groups to make more complex layouts. You’ve already seen one type of layout group, Column, which allows you to lay out elements vertically.

Surface {
  Box {
    Column {
      Messages()
      SimpleUserInput()
    }
  // Channel name bar floats above the messages
  ChannelNameBar(channelName = "Android Apprentice")
  }
}

@Composable
fun Messages() {
  TODO("Not yet implemented")
}

@Composable
fun SimpleUserInput() {
  TODO("Not yet implemented")
}

@Composable
fun ChannelNameBar(channelName: String) {

}
KodecochatAppBar(
  title = {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
      // Channel name
      Text(
        text = channelName,
        style = MaterialTheme.typography.titleMedium
      )
    }
  },
  actions = {
    // Info icon
    Icon(
      imageVector = Icons.Outlined.Info,
      tint = MaterialTheme.colorScheme.onSurfaceVariant,
      modifier = Modifier
        .clickable(onClick = { })
        .padding(horizontal = 12.dp, vertical = 16.dp)
        .height(24.dp),
      contentDescription = stringResource(id = R.string.info)
    )
  }
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun KodecochatAppBar(
  modifier: Modifier = Modifier,
  scrollBehavior: TopAppBarScrollBehavior? = null,
  onNavIconPressed: () -> Unit = { },
  title: @Composable () -> Unit,
  actions: @Composable RowScope.() -> Unit = {}
) {
  // 1  
  CenterAlignedTopAppBar(
    modifier = modifier,
    // 2
    actions = actions,
    title = title,
    scrollBehavior = scrollBehavior,
    // 3    
    navigationIcon = {
      KodecoChatIcon(
        contentDescription = stringResource(id = R.string.navigation_drawer_open),
        modifier = Modifier
          .size(64.dp)
          .clickable(onClick = onNavIconPressed)
          .padding(16.dp)
      )
    }
  )
}

Previews

Open KodecochatAppBar.kt. At the top of Android Studio, beneath the run and debug icons, click the icon for split view, which is a combination of the code and design views:

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun KodecochatAppBarPreview() {
  KodecochatTheme {
    KodecochatAppBar(title = { Text("Preview!") })
  }
}

More With Modifiers

You learned in the last lesson that modifiers tell a UI element how to lay out, display, or behave within its parent layout, and you started using modifiers to style the app.

@Composable
fun SimpleUserInput() {
  val context = LocalContext.current
  var chatInputText by remember { mutableStateOf("") }
  var chatOutputText by remember { mutableStateOf(context.getString(R.string.chat_display_default)) }
  Text(text = chatOutputText)
  Row {
    OutlinedTextField(
      value = chatInputText,
      placeholder = { Text(text = stringResource(id = R.string.chat_entry_default)) },
      onValueChange = {
        chatInputText = it
      },
    )
    Button(onClick = {
      chatOutputText = chatInputText
      chatInputText = ""
    }) {
      Text(text = stringResource(id = R.string.send_button))
    }
  }
}

Surface {
  Box(
    modifier = Modifier
      .fillMaxSize()
      .background(color = Color.DarkGray)
  ) {...

Surface {
  Box(modifier = Modifier.fillMaxSize()) {
    Column(
      Modifier
        .fillMaxSize()
     ) {
       Messages(
          modifier = Modifier.weight(1f),
        )
        SimpleUserInput()
      }
      // Channel name bar floats above the messages
      ChannelNameBar(channelName = "Android Apprentice")
    }
  }
@Composable
fun Messages(modifier: Modifier = Modifier){
  Box(modifier = modifier) {
    // TODO: implement this part in the next section!
  }
}

Lists

What happens when you have to display more elements than you can fit on the screen? In that case, while the elements are all composed, the limited screen size prevents you from seeing all of them. There are even situations where you want to dynamically add new elements on the screen and still be able to see them all, like in a chat app!

@Composable
fun Messages(
  messages: List<String>,
//  scrollState: LazyListState,
  modifier: Modifier = Modifier
) {
  Box(modifier = modifier) {
    LazyColumn(
      // Add content padding so that the content can be scrolled (y-axis)
      // below the status bar + app bar
      contentPadding =
      WindowInsets.statusBars.add(WindowInsets(top = 90.dp)).asPaddingValues(),
      modifier = Modifier
        .fillMaxSize()
    ) {
      item {
        Text(text = "First message")
      }
      item {
        Text(text = "Second message")
      }
      item {
        Text(text = "Third message")
      }
    }
  }
}

See forum comments
Download course materials from Github
Previous: Introduction Next: Demo