Skip to content

Commit 5182577

Browse files
committed
Adding TextField migration snippets
This is for the new version of https://developer.android.com/develop/ui/compose/text/user-input
1 parent 903fcbc commit 5182577

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
package com.example.compose.snippets.text
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.text.input.InputTransformation
5+
import androidx.compose.foundation.text.input.OutputTransformation
6+
import androidx.compose.foundation.text.input.delete
7+
import androidx.compose.foundation.text.input.insert
8+
import androidx.compose.foundation.text.input.TextFieldLineLimits
9+
import androidx.compose.foundation.text.input.TextFieldState
10+
import androidx.compose.foundation.text.input.maxLength
11+
import androidx.compose.foundation.text.input.rememberTextFieldState
12+
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
13+
//noinspection UsingMaterialAndMaterial3Libraries
14+
import androidx.compose.material.SecureTextField
15+
//noinspection UsingMaterialAndMaterial3Libraries
16+
import androidx.compose.material.Text
17+
//noinspection UsingMaterialAndMaterial3Libraries
18+
import androidx.compose.material.TextField
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.saveable.rememberSaveable
25+
import androidx.compose.runtime.setValue
26+
import androidx.compose.runtime.snapshotFlow
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.text.AnnotatedString
29+
import androidx.compose.ui.text.TextRange
30+
import androidx.compose.ui.text.input.OffsetMapping
31+
import androidx.compose.ui.text.input.PasswordVisualTransformation
32+
import androidx.compose.ui.text.input.TextFieldValue
33+
import androidx.compose.ui.text.input.TransformedText
34+
import androidx.compose.ui.text.input.VisualTransformation
35+
import androidx.compose.ui.text.substring
36+
import androidx.compose.ui.tooling.preview.Preview
37+
import androidx.lifecycle.ViewModel
38+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
39+
import com.example.compose.snippets.touchinput.Button
40+
import kotlinx.coroutines.flow.MutableStateFlow
41+
import kotlinx.coroutines.flow.StateFlow
42+
import kotlinx.coroutines.flow.asStateFlow
43+
import kotlinx.coroutines.flow.collectLatest
44+
import kotlinx.coroutines.flow.update
45+
46+
// [START android_compose_text_textfield_migration_old_simple]
47+
@Composable
48+
fun OldSimpleTextField() {
49+
var state by rememberSaveable { mutableStateOf("") }
50+
TextField(
51+
value = state,
52+
onValueChange = { state = it },
53+
singleLine = true,
54+
)
55+
}
56+
// [END android_compose_text_textfield_migration_old_simple]
57+
58+
// [START android_compose_text_textfield_migration_new_simple]
59+
@Composable
60+
fun NewSimpleTextField() {
61+
TextField(
62+
state = rememberTextFieldState(),
63+
lineLimits = TextFieldLineLimits.SingleLine
64+
)
65+
}
66+
// [END android_compose_text_textfield_migration_new_simple]
67+
68+
// [START android_compose_text_textfield_migration_old_filtering]
69+
@Composable
70+
fun OldNoLeadingZeroes() {
71+
var input by rememberSaveable { mutableStateOf("") }
72+
TextField(
73+
value = input,
74+
onValueChange = { newText ->
75+
input = newText.trimStart { it == '0' }
76+
}
77+
)
78+
}
79+
// [END android_compose_text_textfield_migration_old_filtering]
80+
81+
// [START android_compose_text_textfield_migration_new_filtering]
82+
83+
@Preview
84+
@Composable
85+
fun NewNoLeadingZeros() {
86+
TextField(
87+
state = rememberTextFieldState(),
88+
inputTransformation = InputTransformation {
89+
while (length > 0 && charAt(0) == '0') delete(0, 1)
90+
}
91+
)
92+
}
93+
// [END android_compose_text_textfield_migration_new_filtering]
94+
95+
// [START android_compose_text_textfield_migration_old_credit_card_formatter]
96+
@Composable
97+
fun OldTextFieldCreditCardFormatter() {
98+
var state by remember { mutableStateOf("") }
99+
TextField(
100+
value = state,
101+
onValueChange = { if(it.length <= 16) state = it },
102+
visualTransformation = VisualTransformation { text ->
103+
// Making XXXX-XXXX-XXXX-XXXX string.
104+
var out = ""
105+
for (i in text.indices) {
106+
out += text[i]
107+
if (i % 4 == 3 && i != 15) out += "-"
108+
}
109+
110+
111+
TransformedText(
112+
text = AnnotatedString(out),
113+
offsetMapping = object : OffsetMapping {
114+
override fun originalToTransformed(offset: Int): Int {
115+
if (offset <= 3) return offset
116+
if (offset <= 7) return offset + 1
117+
if (offset <= 11) return offset + 2
118+
if (offset <= 16) return offset + 3
119+
return 19
120+
}
121+
122+
123+
override fun transformedToOriginal(offset: Int): Int {
124+
if (offset <= 4) return offset
125+
if (offset <= 9) return offset - 1
126+
if (offset <= 14) return offset - 2
127+
if (offset <= 19) return offset - 3
128+
return 16
129+
}
130+
})
131+
}
132+
)
133+
}
134+
// [END android_compose_text_textfield_migration_old_credit_card_formatter]
135+
136+
// [START android_compose_text_textfield_migration_new_credit_card_formatter]
137+
@Composable
138+
fun NewTextFieldCreditCardFormatter() {
139+
val state = rememberTextFieldState()
140+
TextField(
141+
state = state,
142+
inputTransformation = InputTransformation.maxLength(16),
143+
outputTransformation = OutputTransformation {
144+
if (length > 4) insert(4, "-")
145+
if (length > 9) insert(9, "-")
146+
if (length > 14) insert(14, "-")
147+
},
148+
)
149+
}
150+
// [END android_compose_text_textfield_migration_new_credit_card_formatter]
151+
152+
private object StateUpdateSimpleSnippet {
153+
object UserRepository {
154+
suspend fun fetchUsername(): String = TODO()
155+
}
156+
// [START android_compose_text_textfield_migration_old_update_state_simple]
157+
@Composable
158+
fun OldTextFieldStateUpdate(userRepository: UserRepository) {
159+
var username by remember { mutableStateOf("") }
160+
LaunchedEffect(Unit) {
161+
username = userRepository.fetchUsername()
162+
}
163+
TextField(
164+
value = username,
165+
onValueChange = { username = it }
166+
)
167+
}
168+
// [END android_compose_text_textfield_migration_old_update_state_simple]
169+
170+
// [START android_compose_text_textfield_migration_new_update_state_simple]
171+
@Composable
172+
fun NewTextFieldStateUpdate(userRepository: UserRepository) {
173+
val usernameState = rememberTextFieldState()
174+
LaunchedEffect(Unit) {
175+
usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername())
176+
}
177+
TextField(state = usernameState)
178+
}
179+
// [END android_compose_text_textfield_migration_new_update_state_simple]
180+
}
181+
182+
// [START android_compose_text_textfield_migration_old_state_update_complex]
183+
@Composable
184+
fun OldTextFieldAddMarkdownEmphasis() {
185+
var markdownState by remember { mutableStateOf(TextFieldValue()) }
186+
Button(onClick = {
187+
// add ** decorations around the current selection, also preserve the selection
188+
markdownState = with(markdownState) {
189+
copy(
190+
text = buildString {
191+
append(text.take(selection.min))
192+
append("**")
193+
append(text.substring(selection))
194+
append("**")
195+
append(text.drop(selection.max))
196+
},
197+
selection = TextRange(selection.min + 2, selection.max + 2)
198+
)
199+
}
200+
}) {
201+
Text("Bold")
202+
}
203+
TextField(
204+
value = markdownState,
205+
onValueChange = { markdownState = it },
206+
maxLines = 10
207+
)
208+
}
209+
// [END android_compose_text_textfield_migration_old_state_update_complex]
210+
211+
// [START android_compose_text_textfield_migration_new_state_update_complex]
212+
@Composable
213+
fun NewTextFieldAddMarkdownEmphasis() {
214+
val markdownState = rememberTextFieldState()
215+
LaunchedEffect(Unit) {
216+
// add ** decorations around the current selection
217+
markdownState.edit {
218+
insert(originalSelection.max, "**")
219+
insert(originalSelection.min, "**")
220+
selection = TextRange(originalSelection.min + 2, originalSelection.max + 2)
221+
}
222+
}
223+
TextField(
224+
state = markdownState,
225+
lineLimits = TextFieldLineLimits.MultiLine(1, 10)
226+
)
227+
}
228+
// [END android_compose_text_textfield_migration_new_state_update_complex]
229+
230+
private object ViewModelMigrationOldSnippet {
231+
// [START android_compose_text_textfield_migration_old_viewmodel]
232+
class LoginViewModel : ViewModel() {
233+
private val _uiState = MutableStateFlow(UiState())
234+
val uiState: StateFlow<UiState>
235+
get() = _uiState.asStateFlow()
236+
237+
fun updateUsername(username: String) = _uiState.update { it.copy(username = username) }
238+
239+
fun updatePassword(password: String) = _uiState.update { it.copy(password = password) }
240+
}
241+
242+
data class UiState(
243+
val username: String = "",
244+
val password: String = ""
245+
)
246+
247+
@Composable
248+
fun LoginForm(
249+
loginViewModel: LoginViewModel,
250+
modifier: Modifier = Modifier
251+
) {
252+
val uiState by loginViewModel.uiState.collectAsStateWithLifecycle()
253+
Column(modifier) {
254+
TextField(
255+
value = uiState.username,
256+
onValueChange = { loginViewModel.updateUsername(it) }
257+
)
258+
TextField(
259+
value = uiState.password,
260+
onValueChange = { loginViewModel.updatePassword(it) },
261+
visualTransformation = PasswordVisualTransformation()
262+
)
263+
}
264+
}
265+
// [END android_compose_text_textfield_migration_old_viewmodel]
266+
}
267+
268+
private object ViewModelMigrationNewSimpleSnippet {
269+
// [START android_compose_text_textfield_migration_new_viewmodel_simple]
270+
class LoginViewModel : ViewModel() {
271+
val usernameState = TextFieldState()
272+
val passwordState = TextFieldState()
273+
}
274+
275+
@Composable
276+
fun LoginForm(
277+
loginViewModel: LoginViewModel,
278+
modifier: Modifier = Modifier
279+
) {
280+
Column(modifier) {
281+
TextField(state = loginViewModel.usernameState,)
282+
SecureTextField(state = loginViewModel.passwordState)
283+
}
284+
}
285+
// [END android_compose_text_textfield_migration_new_viewmodel_simple]
286+
}
287+
288+
private object ViewModelMigrationNewConformingSnippet {
289+
// [START android_compose_text_textfield_migration_new_viewmodel_conforming]
290+
class LoginViewModel : ViewModel() {
291+
private val _uiState = MutableStateFlow(UiState())
292+
val uiState: StateFlow<UiState>
293+
get() = _uiState.asStateFlow()
294+
295+
fun updateUsername(username: String) = _uiState.update { it.copy(username = username) }
296+
297+
fun updatePassword(password: String) = _uiState.update { it.copy(password = password) }
298+
}
299+
300+
data class UiState(
301+
val username: String = "",
302+
val password: String = ""
303+
)
304+
305+
@Composable
306+
fun LoginForm(
307+
loginViewModel: LoginViewModel,
308+
modifier: Modifier = Modifier
309+
) {
310+
val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value }
311+
Column(modifier) {
312+
val usernameState = rememberTextFieldState(initialUiState.username)
313+
LaunchedEffect(usernameState) {
314+
snapshotFlow { usernameState.text.toString() }.collectLatest {
315+
loginViewModel.updateUsername(it)
316+
}
317+
}
318+
TextField(usernameState)
319+
320+
val passwordState = rememberTextFieldState(initialUiState.password)
321+
LaunchedEffect(usernameState) {
322+
snapshotFlow { usernameState.text.toString() }.collectLatest {
323+
loginViewModel.updatePassword(it)
324+
}
325+
}
326+
SecureTextField(passwordState)
327+
}
328+
}
329+
// [END android_compose_text_textfield_migration_new_viewmodel_conforming]
330+
}
331+

0 commit comments

Comments
 (0)