Last active
November 23, 2023 21:55
-
-
Save nemscep/793cbcb90371c32f9f75886fd3c002e0 to your computer and use it in GitHub Desktop.
TextField which automatically triggers provided `onValueChange` lambda with the full text input (not for every character).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.foundation.interaction.MutableInteractionSource | |
import androidx.compose.foundation.shape.ZeroCornerSize | |
import androidx.compose.foundation.text.KeyboardActions | |
import androidx.compose.foundation.text.KeyboardOptions | |
import androidx.compose.material.LocalTextStyle | |
import androidx.compose.material.MaterialTheme | |
import androidx.compose.material.TextField | |
import androidx.compose.material.TextFieldColors | |
import androidx.compose.material.TextFieldDefaults | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Shape | |
import androidx.compose.ui.text.TextStyle | |
import androidx.compose.ui.text.input.VisualTransformation | |
import kotlinx.coroutines.delay | |
import kotlinx.coroutines.flow.MutableStateFlow | |
import kotlinx.coroutines.flow.collect | |
import kotlinx.coroutines.flow.transformLatest | |
@Composable | |
fun TextFieldWithBackoff( | |
value: String, | |
onValueChange: (String) -> Unit, | |
modifier: Modifier = Modifier, | |
enabled: Boolean = true, | |
readOnly: Boolean = false, | |
textStyle: TextStyle = LocalTextStyle.current, | |
label: @Composable (() -> Unit)? = null, | |
placeholder: @Composable (() -> Unit)? = null, | |
leadingIcon: @Composable (() -> Unit)? = null, | |
trailingIcon: @Composable (() -> Unit)? = null, | |
isError: Boolean = false, | |
visualTransformation: VisualTransformation = VisualTransformation.None, | |
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | |
keyboardActions: KeyboardActions = KeyboardActions(), | |
singleLine: Boolean = false, | |
maxLines: Int = Int.MAX_VALUE, | |
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | |
shape: Shape = | |
MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), | |
colors: TextFieldColors = TextFieldDefaults.textFieldColors(), | |
changeTriggerTime: Long = 1500L | |
) { | |
var inputText by remember { mutableStateOf(value) } | |
val inputStateFlow = remember { | |
MutableStateFlow<InputFieldState<String>>(InputChanged(inputText)) | |
} | |
// Set key1 as true indicating that the behaviour is not reoccurring after recomposition. | |
LaunchedEffect(key1 = true) { | |
// Observe input field state. | |
// For every input change start a timer and wait for it to expire. | |
// Once expired, move to idle state which triggers onValueChange call. | |
inputStateFlow | |
.transformLatest { state -> | |
when (state) { | |
Idle -> emit(inputText) | |
is InputChanged -> { | |
delay(changeTriggerTime) | |
inputStateFlow.value = Idle | |
} | |
} | |
} | |
.collect { value -> onValueChange(value) } | |
} | |
TextField( | |
enabled = enabled, | |
readOnly = readOnly, | |
value = value, | |
onValueChange = { | |
if (value != inputText) { | |
inputText = value | |
inputStateFlow.value = InputChanged(inputText) | |
} | |
}, | |
modifier = modifier, | |
singleLine = singleLine, | |
textStyle = textStyle, | |
label = label, | |
placeholder = placeholder, | |
leadingIcon = leadingIcon, | |
trailingIcon = trailingIcon, | |
isError = isError, | |
visualTransformation = visualTransformation, | |
keyboardOptions = keyboardOptions, | |
keyboardActions = keyboardActions, | |
maxLines = maxLines, | |
interactionSource = interactionSource, | |
shape = shape, | |
colors = colors | |
) | |
} | |
/** | |
* Sealed class representing all text input states. | |
*/ | |
sealed class InputFieldState<out T> { | |
object Idle : InputFieldState<Nothing>() | |
data class InputChanged<T>(val value: T) : InputFieldState<T>() | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment