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