Introduction
- Recently I created a new feature for my Android app and this is the process I took to create it. The process consists of 3 main steps,
1) Design
,2) Implement
,3) Refactor
- The point of this blog post is to be short and give beginners a systemic way they can approach creating custom Jetpack Compose components. The focus will not be on the code but instead on the process as a whole
2024-04-30 update
- HERE is the latest design wiki for how I am create custom Jetpack Compose components
GitHub code
Table of contents
1) Design
- This step is where we design what the component will do and how it will work. When designing the feature I like to break down everything in terms of Jetpack Composes' standard layout elements, the Row(in blue) and Column(in red). This step will give us a design looking like this:
- While it might look a little simple and silly now, the blue Rows and red Columns will not only help us build the component but also show us where to begin when we start refactoring
2) Implementation
- This part in the process is all about taking the design and getting it to work. Throw all ideas about clean coding practices away and just get the dang thing working. This is where spaghetti code is allowed, matter of fact here is my spaghetti code for my most recent feature. You don't need to understand what this code does, just notice how hard it is to understand what it actually does: ```
fun VaccinationCheck(){
var vaccineText by remember { mutableStateOf("") }
var dateText by remember { mutableStateOf(Date().toString()) }
val vaccineList = remember { mutableStateListOf()}
val convertedDate = Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
val selectedDate = remember { mutableStateOf<LocalDate?>(convertedDate) }
val calendarState = rememberSheetState()
CalendarDialog(
state = calendarState,
config = CalendarConfig(
monthSelection = true,
yearSelection = true
),
selection = CalendarSelection.Date(selectedDate = selectedDate.value){ newDate ->
selectedDate.value = newDate
dateText = newDate.toString()
}
)
Column(){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
//I NEED A TEXT INPUT AND A DATE INPUT
OutlinedTextField(
modifier = Modifier.weight(1.5f),
singleLine = true,
value = vaccineText,
onValueChange = { vaccineText = it },
textStyle = TextStyle(fontSize = 20.sp),
placeholder = {
Text(text = "Vaccination", fontSize = 20.sp)
}
)
OutlinedTextField(
modifier = Modifier
.clickable { calendarState.show() }
.weight(1f),
enabled = false,
value = selectedDate.value.toString(),
onValueChange = { dateText = it },
textStyle = TextStyle(fontSize = 20.sp),
placeholder = {
Text(text = dateText, fontSize = 20.sp)
}
)
}
//INSIDE THE COLUMN
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
){
Spacer(Modifier.weight(1.5f))
Button(
enabled = vaccineText.length >1,
onClick = {
val text = "$vaccineText " + selectedDate.value
val check = vaccineList.indexOf(text)
if(check == -1){
vaccineList.add(text)
}
},
modifier = Modifier.weight(1f)
) {
Text(text = "Vaccinate")
}
}
LazyColumn(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.height(150.dp)
){
itemsIndexed(
items = vaccineList,
key ={index:Int,item:String ->item.hashCode() + index}
){ index:Int, item:String ->
/***********SETTING UP SWIPE TO DISMISS****************/
val currentVaccine by rememberUpdatedState(item)//references the current vaccine
val dismissState = rememberDismissState(
confirmStateChange = {
vaccineList.remove(item)
true
}
)
SwipeToDismiss(state = dismissState, background ={} ) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.border(
BorderStroke(2.dp, Color.LightGray),
shape = RoundedCornerShape(8)
)
){
Text(text = item, fontSize = 20.sp,modifier = Modifier.padding(8.dp))
}
}
}
}
}
}
- As you can see from the code above, its a maze and almost impossible to understand what is going on. But guess what? IT WORKS!! which means we can move on to the next step
###3) Refactor <a name="refactor"></a>
- This is the step where we have to start implementing some good coding practices. For writing clean compose code I recommend these 2 resources:
**1)**[Twitter Compose Ruleset](https://twitter.github.io/compose-rules/)
**2)**[Always provide a Modifier parameter](https://chris.banes.me/posts/always-provide-a-modifier/)
- Using the resource above my compose function now looks like this:
@OptIn(ExperimentalMaterialApi::class)
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun VaccinationCheck(){
var vaccineText by remember { mutableStateOf("") }
var dateText1 by remember { mutableStateOf(Date().toString()) }
val vaccineList = remember { mutableStateListOf()}
val convertedDate = Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
val selectedDate = remember { mutableStateOf<LocalDate?>(convertedDate) }
val calendarState = rememberSheetState()
CalendarView(
calendarState,
selectedDate = selectedDate,
updateDateText = {
newDate -> dateText1 = newDate
}
)
Column(){
VaccineRow(
vaccineText =vaccineText,
updateText = {text -> vaccineText = text},
calendarState = calendarState,
selectedDate = selectedDate,
dateText = dateText1,
updateDateText = {dateText ->dateText1 = dateText },
modifier = Modifier.fillMaxWidth()
)
VaccineButtonRow(
vaccineText = vaccineText,
addToVaccineList ={
val text = "$vaccineText " + selectedDate.value
val check = vaccineList.indexOf(text)
if(check == -1){
vaccineList.add(text)
}
},
modifier = Modifier.fillMaxWidth()
)
VaccineLazyColumn(
vaccineList = vaccineList,
removeItemFromList ={item -> vaccineList.remove(item)},
modifier = Modifier.fillMaxWidth()
)
}
}
- Way better right? I used the Rows and Columns from our design stage to refactor our code. Taking each Row and Column and putting it into it's own component
- I hope this short blog post gave you a starting point for creating cleaner compose code.
###Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).
Top comments (1)
@artesanoandroid This article is a little out of date. You can visit the design wiki I created, HERE, for a more update demonstration of creating custom components