इस पेज पर, वैल्यू के आधार पर TextField
को राज्य के आधार पर TextField
में माइग्रेट करने के उदाहरण दिए गए हैं. वैल्यू और स्टेटस पर आधारित TextField
के बीच के अंतर के बारे में जानकारी के लिए, टेक्स्ट फ़ील्ड कॉन्फ़िगर करें पेज देखें.
बुनियादी इस्तेमाल
वैल्यू के आधार पर
@Composable fun OldSimpleTextField() { var state by rememberSaveable { mutableStateOf("") } TextField( value = state, onValueChange = { state = it }, singleLine = true, ) }
राज्य के हिसाब से
@Composable fun NewSimpleTextField() { TextField( state = rememberTextFieldState(), lineLimits = TextFieldLineLimits.SingleLine ) }
value, onValueChange
औरremember { mutableStateOf("")
} कोrememberTextFieldState()
से बदलें.singleLine = true
कोlineLimits = TextFieldLineLimits.SingleLine
से बदलें.
onValueChange
के हिसाब से फ़िल्टर करना
वैल्यू के आधार पर
@Composable fun OldNoLeadingZeroes() { var input by rememberSaveable { mutableStateOf("") } TextField( value = input, onValueChange = { newText -> input = newText.trimStart { it == '0' } } ) }
राज्य के हिसाब से
@Preview @Composable fun NewNoLeadingZeros() { TextField( state = rememberTextFieldState(), inputTransformation = InputTransformation { while (length > 0 && charAt(0) == '0') delete(0, 1) } ) }
- वैल्यू कॉलबैक लूप को
rememberTextFieldState()
से बदलें. InputTransformation
का इस्तेमाल करके,onValueChange
में फ़िल्टर करने का लॉजिक फिर से लागू करें.state
को अपडेट करने के लिए,InputTransformation
के रिसीवर स्कोप में मौजूदTextFieldBuffer
का इस्तेमाल करें.InputTransformation
को उपयोगकर्ता इनपुट का पता चलने के ठीक बाद कॉल किया जाता है.InputTransformation
सेTextFieldBuffer
के ज़रिए सुझाए गए बदलाव तुरंत लागू हो जाते हैं. इससे, सॉफ़्टवेयर कीबोर्ड औरTextField
के बीच सिंक करने से जुड़ी समस्या से बचा जा सकता है.
क्रेडिट कार्ड फ़ॉर्मैटर TextField
वैल्यू के आधार पर
@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 } } ) } ) }
राज्य के हिसाब से
@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, "-") }, ) }
- इनपुट की ज़्यादा से ज़्यादा लंबाई सेट करने के लिए,
onValueChange
में फ़िल्टर करने की सुविधा कोInputTransformation
से बदलें.onValueChange
के हिसाब से फ़िल्टर करना सेक्शन देखें.
- डैश जोड़ने के लिए,
VisualTransformation
कोOutputTransformation
से बदलें.VisualTransformation
का इस्तेमाल करने पर, आपको डैश के साथ नया टेक्स्ट बनाना होगा. साथ ही, यह भी हिसाब लगाना होगा कि विज़ुअल टेक्स्ट और बैकिंग स्टेटस के बीच इंडेक्स कैसे मैप किए जाते हैं.OutputTransformation
, ऑफ़सेट मैपिंग को अपने-आप मैनेज करता है. आपकोOutputTransformation.transformOutput
के रिसीवर स्कोप सेTextFieldBuffer
का इस्तेमाल करके, सही जगहों पर सिर्फ़ डैश जोड़ने होंगे.
स्थिति अपडेट करना (सरल)
वैल्यू के आधार पर
@Composable fun OldTextFieldStateUpdate(userRepository: UserRepository) { var username by remember { mutableStateOf("") } LaunchedEffect(Unit) { username = userRepository.fetchUsername() } TextField( value = username, onValueChange = { username = it } ) }
राज्य के हिसाब से
@Composable fun NewTextFieldStateUpdate(userRepository: UserRepository) { val usernameState = rememberTextFieldState() LaunchedEffect(Unit) { usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) } TextField(state = usernameState) }
- वैल्यू कॉलबैक लूप को
rememberTextFieldState()
से बदलें. - वैल्यू असाइनमेंट को
TextFieldState.setTextAndPlaceCursorAtEnd
से बदलें.
स्टेटस अपडेट करना (जटिल)
वैल्यू के आधार पर
@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 ) }
राज्य के हिसाब से
@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) ) }
इस उदाहरण में, कर्सर या चुने गए टेक्स्ट के आस-पास टेक्स्ट को बोल्ड करने के लिए, बटन से मार्कडाउन डेकोरेशन जोड़े जाते हैं. बदलाव करने के बाद भी, चुने गए आइटम की पोज़िशन में कोई बदलाव नहीं होता.
- वैल्यू कॉलबैक लूप को
rememberTextFieldState()
से बदलें. maxLines = 10
कोlineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)
से बदलें.TextFieldState.edit
कॉल की मदद से, नएTextFieldValue
का हिसाब लगाने का लॉजिक बदलें.- मौजूदा टेक्स्ट को चुनकर, उसके बीच में मार्कडाउन डेकोरेशन डालकर, नया
TextFieldValue
जनरेट किया जाता है. - साथ ही, चुने गए हिस्से को टेक्स्ट के नए इंडेक्स के हिसाब से अडजस्ट किया जाता है.
TextFieldState.edit
मेंTextFieldBuffer
का इस्तेमाल करके, मौजूदा स्थिति में बदलाव करने का ज़्यादा आसान तरीका है.- चुने गए विकल्प से साफ़ तौर पर पता चलता है कि सजावट कहां डालनी है.
- इसके बाद,
onValueChange
के तरीके की तरह ही, चुने गए आइटम में बदलाव करें.
- मौजूदा टेक्स्ट को चुनकर, उसके बीच में मार्कडाउन डेकोरेशन डालकर, नया
ViewModel StateFlow
का आर्किटेक्चर
कई ऐप्लिकेशन, ऐप्लिकेशन डेवलपमेंट के आधुनिक दिशा-निर्देशों का पालन करते हैं. इन दिशा-निर्देशों के मुताबिक, किसी स्क्रीन या कॉम्पोनेंट के यूज़र इंटरफ़ेस (यूआई) की स्थिति तय करने के लिए, StateFlow
का इस्तेमाल किया जाता है. इसके लिए, पूरी जानकारी देने वाली एक ऐसी क्लास का इस्तेमाल किया जाता है जिसे बदला नहीं जा सकता.
इस तरह के ऐप्लिकेशन में, टेक्स्ट इनपुट वाली लॉगिन स्क्रीन जैसे फ़ॉर्म को आम तौर पर इस तरह डिज़ाइन किया जाता है:
class LoginViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> 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() ) } }
यह डिज़ाइन, value,
onValueChange
स्टेटस होस्टिंग पैराडाइम का इस्तेमाल करने वाले TextFields
के साथ पूरी तरह से फ़िट बैठता है. हालांकि, टेक्स्ट इनपुट के मामले में, इस तरीके के कुछ नुकसान हो सकते हैं. इस तरीके से डीप सिंक करने से जुड़ी समस्याओं के बारे में ज़्यादा जानकारी, Compose में TextField के लिए बेहतर स्टेटस मैनेजमेंट ब्लॉग पोस्ट में दी गई है.
समस्या यह है कि TextFieldState
का नया डिज़ाइन, StateFlow
के साथ काम करने वाले ViewModel यूज़र इंटरफ़ेस (यूआई) स्टेटस के साथ सीधे तौर पर काम नहीं करता. username: String
और password: String
को username: TextFieldState
और password: TextFieldState
से बदलना अजीब लग सकता है. ऐसा इसलिए, क्योंकि TextFieldState
एक ऐसा डेटा स्ट्रक्चर है जिसे आसानी से बदला जा सकता है.
आम तौर पर, यह सुझाव दिया जाता है कि ViewModel
में यूज़र इंटरफ़ेस (यूआई) की डिपेंडेंसी न डालें.
आम तौर पर, यह एक अच्छा तरीका है. हालांकि, कभी-कभी इसका गलत मतलब निकाला जा सकता है.
यह बात खास तौर पर उन Compose डिपेंडेंसी के लिए सही है जो पूरी तरह से डेटा स्ट्रक्चर हैं और जिनमें TextFieldState
जैसे कोई यूज़र इंटरफ़ेस (यूआई) एलिमेंट नहीं होता.
MutableState
या TextFieldState
जैसी क्लास, स्टेटस के सामान्य होल्डर होती हैं. इन्हें Compose के स्नैपशॉट स्टेटस सिस्टम की मदद से मैनेज किया जाता है. ये StateFlow
या RxJava
जैसी डिपेंडेंसी से अलग नहीं हैं. इसलिए, हमारा सुझाव है कि आप अपने कोड में, "ViewModel में कोई यूज़र इंटरफ़ेस (यूआई) डिपेंडेंसी नहीं" सिद्धांत को लागू करने के तरीके का फिर से आकलन करें. अपने ViewModel
में TextFieldState
का रेफ़रंस रखना, ज़रूरी नहीं है कि गलत हो.
सुझाया गया आसान तरीका
हमारा सुझाव है कि आप UiState
से username
या password
जैसी वैल्यू निकालें और ViewModel
में उनके लिए अलग से रेफ़रंस रखें.
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) } }
MutableStateFlow<UiState>
को कुछTextFieldState
वैल्यू से बदलें.LoginForm
composable में, उनTextFieldState
ऑब्जेक्ट कोTextFields
पर पास करें.
नियमों का पालन करने वाला तरीका
इस तरह के आर्किटेक्चर में बदलाव करना हमेशा आसान नहीं होता. ऐसा हो सकता है कि आपके पास इन बदलावों को करने की अनुमति न हो या नए TextField
का इस्तेमाल करने के फ़ायदों के मुकाबले, समय का फ़ायदा ज़्यादा हो. इस मामले में, थोड़ी सी बदलाव करके, अब भी राज्य के हिसाब से टेक्स्ट फ़ील्ड का इस्तेमाल किया जा सकता है.
class LoginViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> 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) } }
ViewModel
औरUiState
क्लास को एक जैसा रखें.ViewModel
में सीधे स्टेटस को होस्ट करने और उसेTextFields
के लिए सटीक जानकारी का सोर्स बनाने के बजाय,ViewModel
को डेटा होल्डर में बदलें.- ऐसा करने के लिए,
LaunchedEffect
मेंsnapshotFlow
इकट्ठा करके, हरTextFieldState.text
में हुए बदलावों को देखें.
- ऐसा करने के लिए,
- आपके
ViewModel
में अब भी यूज़र इंटरफ़ेस (यूआई) से मिली नई वैल्यू होंगी. हालांकि, इसकाuiState: StateFlow<UiState>
,TextField
को ड्राइव नहीं करेगा. - आपके
ViewModel
में लागू किया गया कोई भी अन्य पर्सिस्टेंस लॉजिक पहले जैसा ही रह सकता है.