Todo App Series: Building UI with Android Compose
Introduction
Welcome back! This time, we will embark on an exciting journey: building a Todo App's user interface (UI) entirely from scratch! I will guide you through enhancing this app step by step, culminating in a sophisticated, modern application. Please note that this particular article is exclusively dedicated to UI implementation. The Todo items-related logic implementation is covered here.
How to Follow this Tutorial
Dive into this tutorial with the assurance that you can complete it in under 10 minutes and return to study the details whenever you wish. Each composable, designed for standalone preview, allows you to focus on one piece at a time without any intertwined logic, so feel free to copy-paste the code as you go along, and don’t worry about grasping every detail right away.
This tutorial contains many code and explanations. There is no rush to understand it all in one sitting. Remember, it’s perfectly fine to absorb the concepts slowly and revisit sections whenever you need clarity. My aim is for you to seamlessly integrate these concepts and code snippets into your projects, at your own pace. This tutorial is not just about learning; it’s about feeling the joy of accomplishment and the excitement of continuous learning. Let’s get started!
Environment
- Android Studio Hedgehog | 2023.1.1
- Compose version:
androidx.compose:compose-bom:2023.08.00
- Mac OS Sonoma 14.1
- Pixel 5 Emulator
On Today’s Menu
- Implementing a Todo App UI: An in-depth guide to crafting the user interface of a Todo application using Android Compose.
- Designing Future-Proof Composables: Explore the art of designing future-proof composables in Android Compose. We will explore how to effectively employ flow and lambda expressions, alongside various parameters, to construct composables that are both flexible and enduring in the ever-changing landscape of the app development cycle.
Step 1: Shared Resources Across the Project
The initial phase in creating our Todo App UI is crucial — we will set up a foundation of design elements that will bring consistency and elegance to our user interface. By defining a set of constants and icons right at the outset, we not only simplify the development process but also ensure a harmonious and appealing design throughout the app. This strategy is key to saving time and elevating the development experience.
Let’s begin by integrating the following key elements into your newly created empty composable project. Create, update and add the following elements:
- Dimension Constants (
ui/constants/DpValues.kt
): This file plays an important role in ensuring uniform dimensions, such as padding, icon sizes, and item heights, fostering a cohesive visual appearance across the app.
// Your package...
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val SmallDp: Dp = 4.dp
val MediumDp: Dp = 8.dp
val LargeDp: Dp = 16.dp
val TodoItemHeight: Dp = 48.dp
val TodoItemIconSize: Dp = 24.dp
val TodoItemActionButtonRippleRadius: Dp = 32.dp
val TodoInputBarHeight: Dp = 64.dp
val TodoInputBarFabSize: Dp = 40.dp
val OverlappingHeight = TodoInputBarHeight
- Color Scheme (
ui/constants/Colors.kt
): Centralizing the app’s color palette allows for easy UI theme adjustments and maintains consistency in visual design.
// Your package...
import androidx.compose.ui.graphics.Color
val PrimaryColor = Color(0xFF0F0A2C)
val PrimaryLightColor = Color(0xFF82BD85)
val TodoItemBackgroundColor = PrimaryLightColor
val TodoItemIconColor = PrimaryColor
val TodoItemTextColor = PrimaryColor
val TodoInputBarBackgroundColor = PrimaryColor
val TodoInputBarFabColor = PrimaryLightColor
- Text Styles (
ui/constants/TextStyles.kt
): Define uniform text styles for a consistent typographical look, enhancing readability and reducing redundant code inText()
composables.
// Your package...
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
val TodoItemTitleTextStyle = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 18.sp,
letterSpacing = 0.5.sp,
color = TodoItemTextColor
)
val TodoInputBarTextStyle = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 18.sp,
letterSpacing = 0.5.sp,
color = Color.White
)
- String Resources (
res/values/strings.xml
): A central location for all textual content, facilitating easy updates and localization.
<resources>
<string name="app_name">TodoDemoAppTest</string>
<string name="todo_input_bar_hint">Write your todo</string>
</resources>
- Material Design Icons (
res/drawable
): Incorporate a selection of Material Design icons in.xml
format from here, you can directly add them to your drawable directory.
Step 2: Todo Item UI Implementation
Our next step is to craft the visual representation of individual todo items. This involves defining a composable function that displays each todo item’s title and status.
Todo Item Model
Begin by creating the data/TodoItem.kt
file. This file will contain the TodoItem
data class, which is fundamental to our Todo App. The TodoItem
class encapsulates the essential attributes of each todo item:
id
: A unique identifier for the todo item, crucial for differentiating between individual tasks. We useUUID.randomUUID()
to automatically generate a unique identifier. This function creates a universally unique identifier (UUID
) that is highly unlikely to be duplicated.title
: The descriptive text of the todo item, representing the task to be accomplished.isDone
: A boolean value indicating whether the task has been completed.
// Your package...
import java.util.UUID
data class TodoItem(
val id: UUID = UUID.randomUUID(),
val title: String,
var isDone: Boolean = false
)
Todo Item UI
With our model in place, let’s proceed to create thecomposables/TodoItemUi.kt
file. This composable is designed to showcase the visual elements of a todo item. It efficiently processes a TodoItem
and two lambda functions — onItemClick
and onItemDelete
— as parameters, enhancing its functionality and adaptability.
// Your package...
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import YOUR_PACKAGE_NAME.R
import YOUR_PACKAGE_NAME.data.TodoItem
import YOUR_PACKAGE_NAME.ui.constants.*
@Composable
fun TodoItemUi(
todoItem: TodoItem = TodoItem(title = "Todo Item"),
// 1. Lambda Function Parameters for Flexibility
onItemClick: (TodoItem) -> Unit = {},
onItemDelete: (TodoItem) -> Unit = {}
) {
// 2. Adaptive Color Scheme
val backgroundColor = if (todoItem.isDone) TodoItemBackgroundColor.copy(alpha = 0.5f) else TodoItemBackgroundColor
val textColor = if (todoItem.isDone) TodoItemTextColor.copy(alpha = 0.5f) else TodoItemTextColor
// 3. Text Decoration
val textDecoration = if (todoItem.isDone) TextDecoration.LineThrough else null
// 4. Dynamic Icons
val iconId = if (todoItem.isDone) R.drawable.ic_selected_check_box else R.drawable.ic_empty_check_box
val iconColorFilter = if (todoItem.isDone) ColorFilter.tint(TodoItemIconColor.copy(alpha = 0.5f)) else ColorFilter.tint(TodoItemIconColor)
val iconTintColor = if (todoItem.isDone) TodoItemIconColor.copy(alpha = 0.5f) else TodoItemIconColor
Card(
modifier = Modifier
.fillMaxWidth()
.height(TodoItemHeight),
elevation = CardDefaults.cardElevation(defaultElevation = LargeDp),
shape = RoundedCornerShape(size = MediumDp)
) {
Row(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
// 5. Clickable Modifier with Ripple Effect:
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = true)
) { onItemClick(todoItem) },
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = iconId),
contentDescription = null,
modifier = Modifier
.padding(MediumDp)
.size(TodoItemIconSize),
colorFilter = iconColorFilter
)
Text(
text = todoItem.title,
modifier = Modifier.weight(1f),
style = TodoItemTitleTextStyle.copy(color = textColor),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textDecoration = textDecoration
)
// 6. IconButton for Deletion
IconButton(
onClick = { onItemDelete(todoItem) },
modifier = Modifier.size(TodoItemActionButtonRippleRadius)
) {
Icon(
modifier = Modifier.size(TodoItemIconSize),
painter = painterResource(id = R.drawable.ic_delete),
contentDescription = null,
tint = iconTintColor
)
}
}
}
}
@Preview
@Composable
fun TodoItemUiPreview() {
Column(
modifier = Modifier.padding(MediumDp),
verticalArrangement = Arrangement.spacedBy(MediumDp)
) {
TodoItemUi(todoItem = TodoItem(title = "Todo Item 1"))
TodoItemUi(todoItem = TodoItem(title = "Todo Item 2", isDone = true))
TodoItemUi(todoItem = TodoItem(title = "Todo Item 3"))
TodoItemUi(todoItem = TodoItem(title = "Todo Item 4", isDone = true))
}
}
- Lambda Function Parameters for Flexibility: The
onItemClick
andonItemDelete
lambda functions in theTodoItemUi()
composable add flexibility, allowing the component to adapt to various use cases. TheonItemClick
function can, for example, toggle a todo item's state, whileonItemDelete
manages item deletion. This flexible approach lets you design the composable without pre-defining specific actions for these events, enabling us to focus on creating a user-friendly interface while maintaining adaptability. - Adaptive Color Scheme: The UI dynamically changes colors based on the
isDone
state ofTodoItem
, providing clear visual feedback to the user. - Text Decoration: A line-through text decoration is applied when a todo item is marked as done, offering an immediate visual cue of its completion status.
- Dynamic Icons: Icons change depending on the checked/unchecked state of the todo item, aiding quick identification of task status.
- Clickable Modifier with Ripple Effect: The Clickable Modifier is used to make each todo item row interactive. It uses
interactionSource = remember { MutableInteractionSource() }
to monitor user interactions like pressing and releasing, ensuring the component's interactive state is maintained across recompositions. Additionally,indication = rememberRipple(bounded = true)
introduces a bounded ripple effect, which visually spreads from the point of user touch, offering an intuitive and satisfying feedback. - IconButton for Deletion: This
IconButton
is designed as a straightforward and intuitive way for users to delete items. Importantly, the use of a lambda function for the delete action serves as an abstraction, allowing for flexible design and functionality.
When you run the Preview, the display distinctly highlights each completed task. These tasks stand out with a 0.5 alpha transparency affecting the background, icons, and text colors. Additionally, completed tasks are marked by a strikethrough effect on the text and an updated check icon, signaling their completed status. Each task item also boasts a dedicated clickable area for the delete icon, allowing for seamless item removal without disrupting the overall interaction with the task. Furthermore, a ripple effect is triggered at the point of user interaction on each task, visually indicating the exact spot of contact and thereby enriching the interactive experience for the user.
Step 3: Todo Input Bar Implementation
Next, we will create the TodoInputBar()
for our Todo App, a key feature combining a TextField()
and a FloatingActionButton()
(FAB) for task addition. This composable is designed for efficiency and aesthetic harmony, focusing on input text state management, and FAB click event handling.
Let's start by setting up the TodoInputBar.kt
file for this integral component:
// Your package...
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import YOUR_PACKAGE_NAME.R
import YOUR_PACKAGE_NAME.ui.constants.*
@Composable
fun TodoInputBar(
modifier: Modifier = Modifier,
onAddButtonClick: (String) -> Unit = {}
) {
// 1. State Management
val input = remember { mutableStateOf("") }
Card(
// 2. Shape Customization
shape = RoundedCornerShape(size = MediumDp),
modifier = modifier
.padding(MediumDp)
.height(TodoInputBarHeight)
.fillMaxWidth(),
// 3. Elevation for Depth
elevation = CardDefaults.cardElevation(defaultElevation = LargeDp),
) {
Row(
modifier = Modifier
.fillMaxSize()
.background(color = TodoInputBarBackgroundColor),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
modifier = Modifier.weight(1f),
textStyle = TodoInputBarTextStyle,
// 4. Data Binding
value = input.value,
// 5. Event Handling
onValueChange = { newText -> input.value = newText },
placeholder = {
Text(
text = stringResource(id = R.string.todo_input_bar_hint),
// 6. Text Styling Depending on TodoItem Status
style = TodoInputBarTextStyle.copy(color = Color.White.copy(alpha = 0.5f))
)
},
singleLine = true,
colors = TextFieldDefaults.colors(
// 7. Custom TextField Appearance
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
cursorColor = Color.White,
disabledTextColor = Color.White,
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
)
)
FloatingActionButton(
containerColor = TodoInputBarFabColor,
onClick = {
// 8. Task Submission Logic
if (input.value.isEmpty()) return@FloatingActionButton
onAddButtonClick(input.value)
input.value = ""
},
// 9. FAB Customization
shape = CircleShape,
modifier = Modifier.size(TodoInputBarFabSize),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp
)
) {
Icon(
painter = painterResource(id = R.drawable.ic_add),
contentDescription = null,
tint = TodoInputBarBackgroundColor
)
}
Spacer(modifier = Modifier.width(LargeDp))
}
}
}
@Preview
@Composable
fun TodoInputBarPreview() {
TodoInputBar()
}
- State Management:
remember { mutableStateOf("") }
is pivotal in maintaining the state of the input field. This construct ensures that the input field's content is preserved during recompositions of the UI. When a user types into theTextField()
, the UI may redraw itself for various reasons.remember
ensures that the state (text in this case) isn't lost during these redraws, andmutableStateOf
provides a mutable memory space that can hold the changing text. - Shape Customization:
RoundedCornerShape(size = MediumDp)
gives theCard()
a more aesthetic, rounded appearance. Rounded corners are a key aspect of Material Design. The degree of roundness is customizable, providing flexibility in UI design. - Elevation for Depth:
CardDefaults.cardElevation(defaultElevation = LargeDp)
adds a shadow beneath theCard()
, creating a 3D effect that makes the Card appear raised above the rest of the UI. This elevation is not just a stylistic choice but also helps in distinguishing different UI layers, enhancing the overall user experience. - Data Binding: The
value
property of theTextField()
is bound to theinput
state. This binding is a two-way connection: theTextField()
displays the current value ofinput
, and any changes in theTextFieldd()
(due to user input) are immediately reflected ininput
. This ensures that the data (text) and the UI (TextField
) are always in sync. - Event Handling:
onValueChange
is used to update the state (input.value
) whenever there is a change in the text field. This function is crucial for responding to user interactions, ensuring that every keystroke is captured and the state is updated accordingly, which in turn updates the UI. - Text Styling: The placeholder styling in the
TextField()
is customized to enhance user experience. The style, including color and opacity, is adjusted to make it visually distinctive from the entered text, guiding the user intuitively about the expected input. This subtle use of style plays a significant role in UI usability. - Custom TextField Appearance: Customizing the appearance of the
TextField()
throughTextFieldDefaults.colors
involves setting various aspects like container color, cursor color, and text color for different states (focused, unfocused, disabled). This customization is key in maintaining a consistent theme across your app, aligning theTextField()
’s look and feel with the app's overall design language. - Task Submission Logic: First checks if the input field (managed by
input.value
) is non-empty to ensure that no blank tasks are added. If the input is valid, it proceeds to trigger theonAddButtonClick
function, passing the current input value to add the task. After this addition, the input field is immediately cleared by resettinginput.value
to an empty string, readying it for the next entry. This approach streamlines the task-adding process, ensuring both data integrity and an efficient user experience. - FAB Customization: Both the shape and elevation aspects are tailored to create a distinct visual style. The
shape = CircleShape
parameter gives the FAB a circular form, aligning with the familiar and user-friendly design commonly used for FABs. Alongside this, the elevation is set usingFloatingActionButtonDefaults.elevation(defaultElevation = 0.dp, pressedElevation = 0.dp)
, which eliminates the usual shadow effect under the button both in its normal and pressed states.
Upon successful implementation, the TodoInputBar()
function, as previewed below, will feature a text field that automatically resets to empty each time the FAB is pressed. This design ensures a seamless user experience by clearing the input field after each task is submitted, making it immediately ready for the next entry without requiring manual intervention.
Step 4: Assembling the UI Components
Now, we have reached the exciting culmination of our efforts: it’s time to put together the created composables into a seamless, unified interface for our Todo app.
Items Container Implementation
Begin by adding the last composable, composables/TodoItemsContainer.kt
. This file is the cornerstone of our UI assembly, and here's what it will encompass:
// Your package...
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import YOUR_PACKAGE_NAME.data.TodoItem
import YOUR_PACKAGE_NAME.ui.constants.MediumDp
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@Composable
fun TodoItemsContainer(
modifier: Modifier = Modifier,
todoItemsFlow: Flow<List<TodoItem>> = flowOf(listOf()),
onItemClick: (TodoItem) -> Unit = {},
onItemDelete: (TodoItem) -> Unit = {},
overlappingElementsHeight: Dp = 0.dp
) {
// 1. Flow Data Collection
val todos = todoItemsFlow.collectAsState(initial = listOf()).value
// 2. LazyColumn Setup
LazyColumn(
modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(MediumDp),
verticalArrangement = Arrangement.spacedBy(MediumDp)
) {
// 3. Items Rendering
items(todos, key = { it.id }) { item ->
TodoItemUi(
todoItem = item,
onItemClick = onItemClick,
onItemDelete = onItemDelete
)
}
// 4. Layout Adjustment
item { Spacer(modifier = Modifier.height(overlappingElementsHeight)) }
}
}
@Preview
@Composable
fun TodoItemsContainerPreview() {
TodoItemsContainer(
todoItemsFlow = flowOf(
listOf(
TodoItem(title = "Todo Item 1", isDone = true),
TodoItem(title = "Todo Item 2"),
TodoItem(title = "Todo Item 3"),
TodoItem(title = "Todo Item 4", isDone = true),
)
)
)
}
- Flow Data Collection: This part of the code is crucial for collecting data from the
Flow<List<TodoItem>>
. Here,collectAsState()
is used to convert the flow into a state object for use in the composable function. Importantly,todoItemsFlow
is given a default value offlowOf(listOf())
, which is an empty list wrapped in a flow. This default handling is essential to prevent potential bugs related to uninitialized or null data sources. The state objecttodos
is initialized with an empty list and updates whenever there's a new emission from the flow.collectAsState
ensures that the composable function responds to changes in the data flow, allowing the UI to automatically update and display the latest state of the data. By considering the default or empty case, the code becomes more robust and less prone to errors related to missing data. - LazyColumn Setup: The
LazyColumn()
is configured to display a list of items. It stretches to fill the maximum available size (fillMaxSize
) and applies padding and spacing for visual appeal (contentPadding
andverticalArrangement
). TheLazyColumn
is efficient for rendering large lists, as it only recomposes items that are currently visible on the screen. - Items Rendering: This block is responsible for displaying individual
TodoItem
s. Each item is rendered using theTodoItemUi()
composable. Theitems
function with a key parameter (key = { it.id }
) is used for optimizing the recomposition process. The key should uniquely identify each item, aiding the system in efficiently updating the UI when the data changes. - Layout Adjustment: A spacer is added at the end of the
LazyColumn()
to provide additional space below the last item. This way, we can add an empty space at the bottom of the container, creating the space for theTodoInputBar()
.
Tweaking the AndroidManifest for Keyboard Adjustments
Navigate to the AndroidManifest.xml
file and insert the line android:windowSoftInputMode="adjustResize"
within the <activity>
tag. This modification ensures that when the soft keyboard appears, the screen adjusts smoothly, preserving the UI layout’s design without encountering unexpected shifts.
...
<application
android:allowBackup="true"
...
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustResize"
...
</activity>
</application>
...
MainActivity: Culminating the UI Build
As our last step, let’s assemble everything together in MainActivity.kt
. This is where we integrate all our composables, bringing the Todo app's UI to life.
// your package...
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import YOUR_PACKAGE_NAME.composables.TodoInputBar
import YOUR_PACKAGE_NAME.composables.TodoItemsContainer
import YOUR_PACKAGE_NAME.data.TodoItem
import YOUR_PACKAGE_NAME.ui.constants.OverlappingHeight
import kotlinx.coroutines.flow.flowOf
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Box(
modifier = Modifier.fillMaxSize()
) {
TodoItemsContainer(
// 1. Mock Data for Todo Items
todoItemsFlow = flowOf(
listOf(
TodoItem(title = "Todo Item 1"),
TodoItem(title = "Todo Item 2", isDone = true),
TodoItem(title = "Todo Item 3"),
TodoItem(title = "Todo Item 4", isDone = true),
TodoItem(title = "Todo Item 5"),
TodoItem(title = "Todo Item 6"),
TodoItem(title = "Todo Item 7"),
TodoItem(title = "Todo Item 8"),
TodoItem(title = "Todo Item 9"),
TodoItem(title = "Todo Item 10"),
TodoItem(title = "Todo Item 11", isDone = true),
TodoItem(title = "Todo Item 12"),
TodoItem(title = "Todo Item 13"),
TodoItem(title = "Todo Item 14"),
TodoItem(title = "Todo Item 15")
)
),
onItemClick = {},
onItemDelete = {},
// 2. Space Adjustment for Overlapping UI Elements
overlappingElementsHeight = OverlappingHeight
)
TodoInputBar(
modifier = Modifier.align(Alignment.BottomStart),
onAddButtonClick = {}
)
}
}
}
}
- Mock Data for Todo Items: Creates a mock list of
TodoItem
objects to simulate real data. TheflowOf
function is used to wrap this list into a flow, which is a type of asynchronous data stream used in Kotlin coroutines. ThetodoItemsFlow
is then fed into theTodoItemsContainer
composable. This list includes various items, some marked as done (isDone = true
) and others not, to demonstrate how different states are handled in the UI. - Space Adjustment for Overlapping UI Elements: The
overlappingElementsHeight
parameter inTodoItemsContainer
is set to the value ofOverlappingHeight
defined in theDpValues.kt
, which is the same as the height of theTodoInputBarHeight
. This adjustment ensures there is enough space at the bottom of theTodoItemsContainer()
so that it does not overlap with theTodoInputBar()
.
Once you run the application, you will witness a fully assembled Todo app UI. Well done on successfully completing this tutorial journey!
Next Step: Room Database Integration
Now that we have created composables that are flexible and receptive to lambda functions and flows, our next step is to introduce the Room Database for efficient local data storage. We will also weave in a ViewModel
to manage data transactions expertly. This upgrade will transition our conceptual app to a dynamic, real-world app.
Wrapping Up
You can get the code here. Your support, shown through a star on the repository, would be greatly appreciated 🙏.
A big thank you for your time and engagement! Don’t forget to clap if you found this helpful, and consider following for more updates.
If anything in this tutorial catches your eye for an odd reason, or if you have ideas for enhancements, please drop a comment. I’m eager to discuss and refine this content.
See you soon!
Discover More
Are you eager for more knowledge? Dive into my Android Compose Tutorials Library. These tutorials cover a wide spectrum of UI design techniques in Jetpack Compose. Each guide is meticulously crafted to enhance your skills and creativity in Android development!
Deuk Services: Your Gateway to Leading Android Innovation
Are you looking to boost your business with top-tier Android solutions?Partner with Deuk services and take your projects to unparalleled heights.