CodeNewbie Community 🌱

Cover image for My process for creating Custom Jetpack compose Components
Tristan
Tristan

Posted on

My process for creating Custom Jetpack compose Components

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

My app for cattle farmers on the Google play store

GitHub code

Table of contents

  1. Design
  2. Implementation
  3. Refactor

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:

feature design

  • 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<String>()}

    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))
                    }

                }
            }



        }

    }

}


Enter fullscreen mode Exit fullscreen mode
  • 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

  • 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
2)Always provide a Modifier parameter

  • 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<String>()}

    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()
        )


    }

}

Enter fullscreen mode Exit fullscreen mode
  • 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.

Top comments (1)

Collapse
 
grachevalexey profile image
GrachevAlexey

Moving to a new area is always stressful, but when I contacted company sqmoving.com/long-beach-movers/ , I didn’t feel it. The only thing that gave me difficulty was packing all my things into boxes. This company took care of everything else. They were very attentive to my fragile items and they secured large items like a refrigerator securely in their trucks. So I recommend this company for their quality.