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