From 5182577019052eb4f1fcd23795293d1b13c3c9c0 Mon Sep 17 00:00:00 2001 From: Halil Ozercan Date: Thu, 15 May 2025 19:26:08 +0100 Subject: [PATCH 1/3] Adding TextField migration snippets This is for the new version of https://developer.android.com/develop/ui/compose/text/user-input --- .../text/TextFieldMigrationSnippets.kt | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt new file mode 100644 index 000000000..769c5069d --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt @@ -0,0 +1,331 @@ +package com.example.compose.snippets.text + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.delete +import androidx.compose.foundation.text.input.insert +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.maxLength +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.SecureTextField +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.Text +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.TextField +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.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.substring +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.example.compose.snippets.touchinput.Button +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.update + +// [START android_compose_text_textfield_migration_old_simple] +@Composable +fun OldSimpleTextField() { + var state by rememberSaveable { mutableStateOf("") } + TextField( + value = state, + onValueChange = { state = it }, + singleLine = true, + ) +} +// [END android_compose_text_textfield_migration_old_simple] + +// [START android_compose_text_textfield_migration_new_simple] +@Composable +fun NewSimpleTextField() { + TextField( + state = rememberTextFieldState(), + lineLimits = TextFieldLineLimits.SingleLine + ) +} +// [END android_compose_text_textfield_migration_new_simple] + +// [START android_compose_text_textfield_migration_old_filtering] +@Composable +fun OldNoLeadingZeroes() { + var input by rememberSaveable { mutableStateOf("") } + TextField( + value = input, + onValueChange = { newText -> + input = newText.trimStart { it == '0' } + } + ) +} +// [END android_compose_text_textfield_migration_old_filtering] + +// [START android_compose_text_textfield_migration_new_filtering] + +@Preview +@Composable +fun NewNoLeadingZeros() { + TextField( + state = rememberTextFieldState(), + inputTransformation = InputTransformation { + while (length > 0 && charAt(0) == '0') delete(0, 1) + } + ) +} +// [END android_compose_text_textfield_migration_new_filtering] + +// [START android_compose_text_textfield_migration_old_credit_card_formatter] +@Composable +fun OldTextFieldCreditCardFormatter() { + var state by remember { mutableStateOf("") } + TextField( + value = state, + onValueChange = { if(it.length <= 16) state = it }, + visualTransformation = VisualTransformation { text -> + // Making XXXX-XXXX-XXXX-XXXX string. + var out = "" + for (i in text.indices) { + out += text[i] + if (i % 4 == 3 && i != 15) out += "-" + } + + + TransformedText( + text = AnnotatedString(out), + offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 3) return offset + if (offset <= 7) return offset + 1 + if (offset <= 11) return offset + 2 + if (offset <= 16) return offset + 3 + return 19 + } + + + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 4) return offset + if (offset <= 9) return offset - 1 + if (offset <= 14) return offset - 2 + if (offset <= 19) return offset - 3 + return 16 + } + }) + } + ) +} +// [END android_compose_text_textfield_migration_old_credit_card_formatter] + +// [START android_compose_text_textfield_migration_new_credit_card_formatter] +@Composable +fun NewTextFieldCreditCardFormatter() { + val state = rememberTextFieldState() + TextField( + state = state, + inputTransformation = InputTransformation.maxLength(16), + outputTransformation = OutputTransformation { + if (length > 4) insert(4, "-") + if (length > 9) insert(9, "-") + if (length > 14) insert(14, "-") + }, + ) +} +// [END android_compose_text_textfield_migration_new_credit_card_formatter] + +private object StateUpdateSimpleSnippet { + object UserRepository { + suspend fun fetchUsername(): String = TODO() + } + // [START android_compose_text_textfield_migration_old_update_state_simple] + @Composable + fun OldTextFieldStateUpdate(userRepository: UserRepository) { + var username by remember { mutableStateOf("") } + LaunchedEffect(Unit) { + username = userRepository.fetchUsername() + } + TextField( + value = username, + onValueChange = { username = it } + ) + } + // [END android_compose_text_textfield_migration_old_update_state_simple] + + // [START android_compose_text_textfield_migration_new_update_state_simple] + @Composable + fun NewTextFieldStateUpdate(userRepository: UserRepository) { + val usernameState = rememberTextFieldState() + LaunchedEffect(Unit) { + usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) + } + TextField(state = usernameState) + } + // [END android_compose_text_textfield_migration_new_update_state_simple] +} + +// [START android_compose_text_textfield_migration_old_state_update_complex] +@Composable +fun OldTextFieldAddMarkdownEmphasis() { + var markdownState by remember { mutableStateOf(TextFieldValue()) } + Button(onClick = { + // add ** decorations around the current selection, also preserve the selection + markdownState = with(markdownState) { + copy( + text = buildString { + append(text.take(selection.min)) + append("**") + append(text.substring(selection)) + append("**") + append(text.drop(selection.max)) + }, + selection = TextRange(selection.min + 2, selection.max + 2) + ) + } + }) { + Text("Bold") + } + TextField( + value = markdownState, + onValueChange = { markdownState = it }, + maxLines = 10 + ) +} +// [END android_compose_text_textfield_migration_old_state_update_complex] + +// [START android_compose_text_textfield_migration_new_state_update_complex] +@Composable +fun NewTextFieldAddMarkdownEmphasis() { + val markdownState = rememberTextFieldState() + LaunchedEffect(Unit) { + // add ** decorations around the current selection + markdownState.edit { + insert(originalSelection.max, "**") + insert(originalSelection.min, "**") + selection = TextRange(originalSelection.min + 2, originalSelection.max + 2) + } + } + TextField( + state = markdownState, + lineLimits = TextFieldLineLimits.MultiLine(1, 10) + ) +} +// [END android_compose_text_textfield_migration_new_state_update_complex] + +private object ViewModelMigrationOldSnippet { + // [START android_compose_text_textfield_migration_old_viewmodel] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() + + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } + + data class UiState( + val username: String = "", + val password: String = "" + ) + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val uiState by loginViewModel.uiState.collectAsStateWithLifecycle() + Column(modifier) { + TextField( + value = uiState.username, + onValueChange = { loginViewModel.updateUsername(it) } + ) + TextField( + value = uiState.password, + onValueChange = { loginViewModel.updatePassword(it) }, + visualTransformation = PasswordVisualTransformation() + ) + } + } + // [END android_compose_text_textfield_migration_old_viewmodel] +} + +private object ViewModelMigrationNewSimpleSnippet { + // [START android_compose_text_textfield_migration_new_viewmodel_simple] + class LoginViewModel : ViewModel() { + val usernameState = TextFieldState() + val passwordState = TextFieldState() + } + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + Column(modifier) { + TextField(state = loginViewModel.usernameState,) + SecureTextField(state = loginViewModel.passwordState) + } + } + // [END android_compose_text_textfield_migration_new_viewmodel_simple] +} + +private object ViewModelMigrationNewConformingSnippet { + // [START android_compose_text_textfield_migration_new_viewmodel_conforming] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() + + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } + + data class UiState( + val username: String = "", + val password: String = "" + ) + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value } + Column(modifier) { + val usernameState = rememberTextFieldState(initialUiState.username) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updateUsername(it) + } + } + TextField(usernameState) + + val passwordState = rememberTextFieldState(initialUiState.password) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updatePassword(it) + } + } + SecureTextField(passwordState) + } + } + // [END android_compose_text_textfield_migration_new_viewmodel_conforming] +} + From 4a3e1e54a99115c28cab6d4b85e94b49643cc678 Mon Sep 17 00:00:00 2001 From: Meghan Date: Fri, 16 May 2025 17:10:46 -0400 Subject: [PATCH 2/3] Update TextFieldMigrationSnippets.kt optimize import --- .../compose/snippets/text/TextFieldMigrationSnippets.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt index 769c5069d..e06682816 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt @@ -3,10 +3,10 @@ package com.example.compose.snippets.text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.input.InputTransformation import androidx.compose.foundation.text.input.OutputTransformation -import androidx.compose.foundation.text.input.delete -import androidx.compose.foundation.text.input.insert import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.delete +import androidx.compose.foundation.text.input.insert import androidx.compose.foundation.text.input.maxLength import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd From fa74c7fca1c0b21261fba5fe8bb4c5790116c8b2 Mon Sep 17 00:00:00 2001 From: MagicalMeghan <46006059+MagicalMeghan@users.noreply.github.com> Date: Fri, 16 May 2025 21:13:31 +0000 Subject: [PATCH 3/3] Apply Spotless --- .../text/TextFieldMigrationSnippets.kt | 422 +++++++++--------- 1 file changed, 218 insertions(+), 204 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt index e06682816..a9e970266 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.compose.snippets.text import androidx.compose.foundation.layout.Column @@ -46,35 +62,35 @@ import kotlinx.coroutines.flow.update // [START android_compose_text_textfield_migration_old_simple] @Composable fun OldSimpleTextField() { - var state by rememberSaveable { mutableStateOf("") } - TextField( - value = state, - onValueChange = { state = it }, - singleLine = true, - ) + var state by rememberSaveable { mutableStateOf("") } + TextField( + value = state, + onValueChange = { state = it }, + singleLine = true, + ) } // [END android_compose_text_textfield_migration_old_simple] // [START android_compose_text_textfield_migration_new_simple] @Composable fun NewSimpleTextField() { - TextField( - state = rememberTextFieldState(), - lineLimits = TextFieldLineLimits.SingleLine - ) + TextField( + state = rememberTextFieldState(), + lineLimits = TextFieldLineLimits.SingleLine + ) } // [END android_compose_text_textfield_migration_new_simple] // [START android_compose_text_textfield_migration_old_filtering] @Composable fun OldNoLeadingZeroes() { - var input by rememberSaveable { mutableStateOf("") } - TextField( - value = input, - onValueChange = { newText -> - input = newText.trimStart { it == '0' } - } - ) + var input by rememberSaveable { mutableStateOf("") } + TextField( + value = input, + onValueChange = { newText -> + input = newText.trimStart { it == '0' } + } + ) } // [END android_compose_text_textfield_migration_old_filtering] @@ -83,249 +99,247 @@ fun OldNoLeadingZeroes() { @Preview @Composable fun NewNoLeadingZeros() { - TextField( - state = rememberTextFieldState(), - inputTransformation = InputTransformation { - while (length > 0 && charAt(0) == '0') delete(0, 1) - } - ) + TextField( + state = rememberTextFieldState(), + inputTransformation = InputTransformation { + while (length > 0 && charAt(0) == '0') delete(0, 1) + } + ) } // [END android_compose_text_textfield_migration_new_filtering] // [START android_compose_text_textfield_migration_old_credit_card_formatter] @Composable fun OldTextFieldCreditCardFormatter() { - var state by remember { mutableStateOf("") } - TextField( - value = state, - onValueChange = { if(it.length <= 16) state = it }, - visualTransformation = VisualTransformation { text -> - // Making XXXX-XXXX-XXXX-XXXX string. - var out = "" - for (i in text.indices) { - out += text[i] - if (i % 4 == 3 && i != 15) out += "-" - } - - - TransformedText( - text = AnnotatedString(out), - offsetMapping = object : OffsetMapping { - override fun originalToTransformed(offset: Int): Int { - if (offset <= 3) return offset - if (offset <= 7) return offset + 1 - if (offset <= 11) return offset + 2 - if (offset <= 16) return offset + 3 - return 19 - } + var state by remember { mutableStateOf("") } + TextField( + value = state, + onValueChange = { if (it.length <= 16) state = it }, + visualTransformation = VisualTransformation { text -> + // Making XXXX-XXXX-XXXX-XXXX string. + var out = "" + for (i in text.indices) { + out += text[i] + if (i % 4 == 3 && i != 15) out += "-" + } + TransformedText( + text = AnnotatedString(out), + offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 3) return offset + if (offset <= 7) return offset + 1 + if (offset <= 11) return offset + 2 + if (offset <= 16) return offset + 3 + return 19 + } - override fun transformedToOriginal(offset: Int): Int { - if (offset <= 4) return offset - if (offset <= 9) return offset - 1 - if (offset <= 14) return offset - 2 - if (offset <= 19) return offset - 3 - return 16 - } - }) - } - ) + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 4) return offset + if (offset <= 9) return offset - 1 + if (offset <= 14) return offset - 2 + if (offset <= 19) return offset - 3 + return 16 + } + } + ) + } + ) } // [END android_compose_text_textfield_migration_old_credit_card_formatter] // [START android_compose_text_textfield_migration_new_credit_card_formatter] @Composable fun NewTextFieldCreditCardFormatter() { - val state = rememberTextFieldState() - TextField( - state = state, - inputTransformation = InputTransformation.maxLength(16), - outputTransformation = OutputTransformation { - if (length > 4) insert(4, "-") - if (length > 9) insert(9, "-") - if (length > 14) insert(14, "-") - }, - ) + val state = rememberTextFieldState() + TextField( + state = state, + inputTransformation = InputTransformation.maxLength(16), + outputTransformation = OutputTransformation { + if (length > 4) insert(4, "-") + if (length > 9) insert(9, "-") + if (length > 14) insert(14, "-") + }, + ) } // [END android_compose_text_textfield_migration_new_credit_card_formatter] private object StateUpdateSimpleSnippet { - object UserRepository { - suspend fun fetchUsername(): String = TODO() - } - // [START android_compose_text_textfield_migration_old_update_state_simple] - @Composable - fun OldTextFieldStateUpdate(userRepository: UserRepository) { - var username by remember { mutableStateOf("") } - LaunchedEffect(Unit) { - username = userRepository.fetchUsername() + object UserRepository { + suspend fun fetchUsername(): String = TODO() } - TextField( - value = username, - onValueChange = { username = it } - ) - } - // [END android_compose_text_textfield_migration_old_update_state_simple] + // [START android_compose_text_textfield_migration_old_update_state_simple] + @Composable + fun OldTextFieldStateUpdate(userRepository: UserRepository) { + var username by remember { mutableStateOf("") } + LaunchedEffect(Unit) { + username = userRepository.fetchUsername() + } + TextField( + value = username, + onValueChange = { username = it } + ) + } + // [END android_compose_text_textfield_migration_old_update_state_simple] - // [START android_compose_text_textfield_migration_new_update_state_simple] - @Composable - fun NewTextFieldStateUpdate(userRepository: UserRepository) { - val usernameState = rememberTextFieldState() - LaunchedEffect(Unit) { - usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) + // [START android_compose_text_textfield_migration_new_update_state_simple] + @Composable + fun NewTextFieldStateUpdate(userRepository: UserRepository) { + val usernameState = rememberTextFieldState() + LaunchedEffect(Unit) { + usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) + } + TextField(state = usernameState) } - TextField(state = usernameState) - } - // [END android_compose_text_textfield_migration_new_update_state_simple] + // [END android_compose_text_textfield_migration_new_update_state_simple] } // [START android_compose_text_textfield_migration_old_state_update_complex] @Composable fun OldTextFieldAddMarkdownEmphasis() { - var markdownState by remember { mutableStateOf(TextFieldValue()) } - Button(onClick = { - // add ** decorations around the current selection, also preserve the selection - markdownState = with(markdownState) { - copy( - text = buildString { - append(text.take(selection.min)) - append("**") - append(text.substring(selection)) - append("**") - append(text.drop(selection.max)) - }, - selection = TextRange(selection.min + 2, selection.max + 2) - ) + var markdownState by remember { mutableStateOf(TextFieldValue()) } + Button(onClick = { + // add ** decorations around the current selection, also preserve the selection + markdownState = with(markdownState) { + copy( + text = buildString { + append(text.take(selection.min)) + append("**") + append(text.substring(selection)) + append("**") + append(text.drop(selection.max)) + }, + selection = TextRange(selection.min + 2, selection.max + 2) + ) + } + }) { + Text("Bold") } - }) { - Text("Bold") - } - TextField( - value = markdownState, - onValueChange = { markdownState = it }, - maxLines = 10 - ) + TextField( + value = markdownState, + onValueChange = { markdownState = it }, + maxLines = 10 + ) } // [END android_compose_text_textfield_migration_old_state_update_complex] // [START android_compose_text_textfield_migration_new_state_update_complex] @Composable fun NewTextFieldAddMarkdownEmphasis() { - val markdownState = rememberTextFieldState() - LaunchedEffect(Unit) { - // add ** decorations around the current selection - markdownState.edit { - insert(originalSelection.max, "**") - insert(originalSelection.min, "**") - selection = TextRange(originalSelection.min + 2, originalSelection.max + 2) + val markdownState = rememberTextFieldState() + LaunchedEffect(Unit) { + // add ** decorations around the current selection + markdownState.edit { + insert(originalSelection.max, "**") + insert(originalSelection.min, "**") + selection = TextRange(originalSelection.min + 2, originalSelection.max + 2) + } } - } - TextField( - state = markdownState, - lineLimits = TextFieldLineLimits.MultiLine(1, 10) - ) + TextField( + state = markdownState, + lineLimits = TextFieldLineLimits.MultiLine(1, 10) + ) } // [END android_compose_text_textfield_migration_new_state_update_complex] private object ViewModelMigrationOldSnippet { - // [START android_compose_text_textfield_migration_old_viewmodel] - class LoginViewModel : ViewModel() { - private val _uiState = MutableStateFlow(UiState()) - val uiState: StateFlow - get() = _uiState.asStateFlow() + // [START android_compose_text_textfield_migration_old_viewmodel] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() - fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } - fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } - } + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } - data class UiState( - val username: String = "", - val password: String = "" - ) + data class UiState( + val username: String = "", + val password: String = "" + ) - @Composable - fun LoginForm( - loginViewModel: LoginViewModel, - modifier: Modifier = Modifier - ) { - val uiState by loginViewModel.uiState.collectAsStateWithLifecycle() - Column(modifier) { - TextField( - value = uiState.username, - onValueChange = { loginViewModel.updateUsername(it) } - ) - TextField( - value = uiState.password, - onValueChange = { loginViewModel.updatePassword(it) }, - visualTransformation = PasswordVisualTransformation() - ) + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val uiState by loginViewModel.uiState.collectAsStateWithLifecycle() + Column(modifier) { + TextField( + value = uiState.username, + onValueChange = { loginViewModel.updateUsername(it) } + ) + TextField( + value = uiState.password, + onValueChange = { loginViewModel.updatePassword(it) }, + visualTransformation = PasswordVisualTransformation() + ) + } } - } - // [END android_compose_text_textfield_migration_old_viewmodel] + // [END android_compose_text_textfield_migration_old_viewmodel] } private object ViewModelMigrationNewSimpleSnippet { - // [START android_compose_text_textfield_migration_new_viewmodel_simple] - class LoginViewModel : ViewModel() { - val usernameState = TextFieldState() - val passwordState = TextFieldState() - } + // [START android_compose_text_textfield_migration_new_viewmodel_simple] + class LoginViewModel : ViewModel() { + val usernameState = TextFieldState() + val passwordState = TextFieldState() + } - @Composable - fun LoginForm( - loginViewModel: LoginViewModel, - modifier: Modifier = Modifier - ) { - Column(modifier) { - TextField(state = loginViewModel.usernameState,) - SecureTextField(state = loginViewModel.passwordState) + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + Column(modifier) { + TextField(state = loginViewModel.usernameState,) + SecureTextField(state = loginViewModel.passwordState) + } } - } - // [END android_compose_text_textfield_migration_new_viewmodel_simple] + // [END android_compose_text_textfield_migration_new_viewmodel_simple] } private object ViewModelMigrationNewConformingSnippet { - // [START android_compose_text_textfield_migration_new_viewmodel_conforming] - class LoginViewModel : ViewModel() { - private val _uiState = MutableStateFlow(UiState()) - val uiState: StateFlow - get() = _uiState.asStateFlow() + // [START android_compose_text_textfield_migration_new_viewmodel_conforming] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() - fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } - fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } - } + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } - data class UiState( - val username: String = "", - val password: String = "" - ) + data class UiState( + val username: String = "", + val password: String = "" + ) - @Composable - fun LoginForm( - loginViewModel: LoginViewModel, - modifier: Modifier = Modifier - ) { - val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value } - Column(modifier) { - val usernameState = rememberTextFieldState(initialUiState.username) - LaunchedEffect(usernameState) { - snapshotFlow { usernameState.text.toString() }.collectLatest { - loginViewModel.updateUsername(it) - } - } - TextField(usernameState) + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value } + Column(modifier) { + val usernameState = rememberTextFieldState(initialUiState.username) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updateUsername(it) + } + } + TextField(usernameState) - val passwordState = rememberTextFieldState(initialUiState.password) - LaunchedEffect(usernameState) { - snapshotFlow { usernameState.text.toString() }.collectLatest { - loginViewModel.updatePassword(it) + val passwordState = rememberTextFieldState(initialUiState.password) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updatePassword(it) + } + } + SecureTextField(passwordState) } - } - SecureTextField(passwordState) } - } - // [END android_compose_text_textfield_migration_new_viewmodel_conforming] + // [END android_compose_text_textfield_migration_new_viewmodel_conforming] } -