इस पेज पर, वैल्यू के आधार पर तय होने वाले 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को कॉल किया जाता है. TextFieldBufferके ज़रिएInputTransformationके सुझाए गए बदलाव तुरंत लागू हो जाते हैं. इससे सॉफ़्टवेयर कीबोर्ड और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कंपोज़ेबल में, उनTextFieldStateऑब्जेक्ट कोTextFieldsमें पास करें.
मानकों के मुताबिक तरीका
इस तरह के आर्किटेक्चरल बदलाव करना हमेशा आसान नहीं होता. हो सकता है कि आपके पास ये बदलाव करने की अनुमति न हो या फिर नए TextFields का इस्तेमाल करने के फ़ायदों के मुकाबले, समय का निवेश ज़्यादा हो. इस मामले में, आपके पास अब भी राज्य के हिसाब से टेक्स्ट फ़ील्ड इस्तेमाल करने का विकल्प है. इसके लिए, आपको थोड़ा बदलाव करना होगा.
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(passwordState) { snapshotFlow { passwordState.text.toString() }.collectLatest { loginViewModel.updatePassword(it) } } SecureTextField(passwordState) } }
- अपनी
ViewModelऔरUiStateक्लास को एक जैसा रखें. - स्टेट को सीधे
ViewModelमें होस्ट करने और उसेTextFieldsके लिए सोर्स ऑफ़ ट्रुथ बनाने के बजाय,ViewModelको एक सामान्य डेटा होल्डर में बदलें.- इसके लिए,
LaunchedEffectमेंsnapshotFlowइकट्ठा करके, हरTextFieldState.textमें होने वाले बदलावों को देखें.
- इसके लिए,
- आपके
ViewModelमें अब भी यूज़र इंटरफ़ेस (यूआई) से मिली नई वैल्यू होंगी. हालांकि, इसकाuiState: StateFlow<UiState>,TextFieldको नहीं चलाएगा. - आपके
ViewModelमें लागू किया गया कोई अन्य पर्सिस्टेंस लॉजिक पहले जैसा ही रह सकता है.