0

I am trying to learn android programming with kotlin jetpack compose and am making a chatting app. However, I cant seem to pass my TextField value after pressing the send button.

I am using a custom clickable function which I define like this:

@Composable
fun Modifier.timedClick(
    timeInMillis: Long = 2500,
    defaultPercentage: Float = 0f,
    content: String = "",
    onClick: (Float, String) -> Unit,
    interactionSource: MutableInteractionSource = remember{MutableInteractionSource()}
) = composed{
    var timeOfTouch = -1L
    Log.d("BUTTON", "#1 The content before LaunchedEffect is: $content")
    LaunchedEffect(interactionSource){
        Log.d("BUTTON", "#2 The content before LaunchedEffect is: $content")
        interactionSource.interactions.onEach {
            when(it){
                is PressInteraction.Press ->{
                    timeOfTouch = System.currentTimeMillis()
                    Log.d("BUTTON", "#3 The content after button pressed is: ${content}")
                    Log.d("BUTTON", "the button has been pressed")
                }
                is PressInteraction.Release -> {
                    val currentTime = System.currentTimeMillis()
                    val calculatedPercentage: Float = defaultPercentage+((currentTime-timeOfTouch).toFloat()/timeInMillis)*(1-defaultPercentage)
                    onClick(min(calculatedPercentage, 1f),
                        content
                    )
                    Log.d("BUTTON", "#4 The content after button is released is: ${content}")
                    Log.d("BUTTON", "the button has been released, the calculated percentage is ${calculatedPercentage * 100}% and the time is ${(currentTime-timeOfTouch).toFloat()/timeInMillis}ms")
                }
                is PressInteraction.Cancel -> {
                    timeOfTouch = -1L
                }
            }
        }
            .launchIn(this)
    }
    Modifier.clickable (
        interactionSource = interactionSource,
        indication = rememberRipple(),
        onClick = {}
    )

}

then I use an image as the button and pass the textfield value like this

        var textFieldValue by remember { mutableStateOf("") }
        TextField(
            value = textFieldValue,
            onValueChange = { textFieldValue = it },
            modifier = Modifier.fillMaxWidth()
        )
        Box(modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()){
            Image(
                modifier = Modifier
                    .clip(CircleShape)
                    .size(70.dp)
                    .align(Alignment.Center)
                    .timedClick(
                        content = textFieldValue,
                        onClick = onClick
                    ),
                contentDescription = null,
                painter = painterResource(id = R.drawable.poop3),
                contentScale = ContentScale.Crop
            )
        }

However, every time I send, the content variable will always be empty inside the LaunchedEffect (i.e. in log.d #1, the content is updated regularly and properly, but log.d #2 is only called once at the beginning of the run and log.d #3 and #4 will always show an empty string. How do I fix this?

1 Answers1

0

The reason why the content is empty inside the Launched effect is because you are initialising the timedClick function with the value of the state when it is empty. You can fix this by passing the state as parameter instead of the value.

To do this you have to replace by with = when creating the textFieldValue variable.

val textFieldValue = remember { mutableStateOf("") }
TextField(
   value = textFieldValue.value,
   onValueChange = { textFieldValue.value = it },
   modifier = Modifier.fillMaxWidth()
 )

The timedClick function has to be changed into the following:

@Composable
fun Modifier.timedClick(
    timeInMillis: Long = 2500,
    defaultPercentage: Float = 0f,
    content: MutableState<String>,
    onClick: (Float, String) -> Unit,
    interactionSource: MutableInteractionSource = remember{MutableInteractionSource()}
) = composed{
    var timeOfTouch = -1L
    Log.d("BUTTON", "#1 The content before LaunchedEffect is: ${content.value}")
    LaunchedEffect(interactionSource) {
        Log.d("BUTTON", "#2 The content before LaunchedEffect is: ${content.value}")
        interactionSource.interactions.onEach {
            when(it){
                is PressInteraction.Press ->{
                    timeOfTouch = System.currentTimeMillis()
                    Log.d("BUTTON", "#3 The content after button pressed is: ${content.value}")
                    Log.d("BUTTON", "the button has been pressed")
                }
                is PressInteraction.Release -> {
                    val currentTime = System.currentTimeMillis()
                    val calculatedPercentage: Float = defaultPercentage+((currentTime-timeOfTouch).toFloat()/timeInMillis)*(1-defaultPercentage)
                    onClick(min(calculatedPercentage, 1f),
                        content.value
                    )
                    Log.d("BUTTON", "#4 The content after button is released is: ${content.value}")
                    Log.d("BUTTON", "the button has been released, the calculated percentage is ${calculatedPercentage * 100}% and the time is ${(currentTime-timeOfTouch).toFloat()/timeInMillis}ms")
                }
                is PressInteraction.Cancel -> {
                    timeOfTouch = -1L
                }
            }
        }
            .launchIn(this)
    }
    Modifier.clickable (
        interactionSource = interactionSource,
        indication = rememberRipple(),
        onClick = {}
    )
}

EDIT: When you pass the state as parameter it will pass a reference to the memory location where the state is stored. The value inside of this location will be updated when you type inside of the textfield. This is why the updated content will be available inside the timedClick function.

Macksly
  • 141
  • 5
  • Thanks! It works now, but I'm still quite confused, are there any cases where I should use by remember instead of = remember? – Justice Gabriel Antaputra Feb 07 '23 at 13:02
  • No problem! If you want to pass the state as a parameter to other composables you should use = remember. This is could be useful when you want to alter or use the state in another composable. When you only need the state in the composable you declare it in, you can use by remember. – Macksly Feb 07 '23 at 13:08