इस पेज पर, वैल्यू के आधार पर तय किए गए 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() ) } }
यह डिज़ाइन, TextFields के साथ पूरी तरह से काम करता है. ये TextFields, value,
onValueChange स्टेट होस्टिंग पैराडाइम का इस्तेमाल करते हैं. हालांकि, टेक्स्ट इनपुट के मामले में इस तरीके के कुछ ऐसे नुकसान भी हैं जिनके बारे में पहले से नहीं बताया जा सकता. इस तरीके से डीप सिंक्रनाइज़ेशन से जुड़ी समस्याओं के बारे में ज़्यादा जानकारी, 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वैल्यू से बदलें.- उन
TextFieldStateऑब्जेक्ट कोLoginFormकंपोज़ेबल में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को एक सामान्य डेटा होल्डर में बदलें.- इसके लिए, हर
TextFieldState.textमें हुए बदलावों को देखें. इसके लिए,LaunchedEffectमेंsnapshotFlowइकट्ठा करें.
- इसके लिए, हर
- आपके
ViewModelमें अब भी यूज़र इंटरफ़ेस (यूआई) से मिली नई वैल्यू होंगी. हालांकि, इसकाuiState: StateFlow<UiState>,TextFieldको नहीं बढ़ाएगा. - आपके
ViewModelमें लागू किया गया कोई भी अन्य परसिस्टेंस लॉजिक पहले जैसा ही बना रह सकता है.