এই পৃষ্ঠায় আপনি ভ্যালু-ভিত্তিক 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গণনা করার লজিক পরিবর্তন করুন।- বর্তমান বেছে নেওয়া বিকল্পের উপর ভিত্তি করে আগে থেকে থাকা টেক্সট স্প্লাইস করে
এবং মাঝে Markdown ডেকোরেশন যোগ করে নতুন
TextFieldValueতৈরি করা হয়। - এছাড়াও, টেক্সটের নতুন ইন্ডেক্স অনুযায়ী বেছে নেওয়া অংশ অ্যাডজাস্ট করা হয়।
TextFieldBufferব্যবহার করে বর্তমান অবস্থা এডিট করার আরও স্বাভাবিক উপায়TextFieldState.edit-এর কাছে আছে।- কোথায় সাজসজ্জা যোগ করতে হবে তা বেছে নেওয়া বিকল্পটি স্পষ্টভাবে উল্লেখ করে।
- তারপর,
onValueChangeপদ্ধতির মতো, বেছে নেওয়া অংশ অ্যাডজাস্ট করুন।
- বর্তমান বেছে নেওয়া বিকল্পের উপর ভিত্তি করে আগে থেকে থাকা টেক্সট স্প্লাইস করে
এবং মাঝে Markdown ডেকোরেশন যোগ করে নতুন
ViewModel StateFlow আর্কিটেকচার
অনেক অ্যাপ্লিকেশন আধুনিক অ্যাপ ডেভেলপমেন্ট নির্দেশিকা অনুসরণ করে, যা
একটি স্ক্রিন বা উপাদানের UI স্টেটকে সংজ্ঞায়িত করার জন্য একটি 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 UI স্টেটের সাথে
মানানসই নয়। username: String এবং password: String-এর পরিবর্তে username: TextFieldState এবং
password: TextFieldState ব্যবহার করা অদ্ভুত লাগতে পারে, কারণ TextFieldState হল সহজাতভাবে পরিবর্তনশীল ডেটা
স্ট্রাকচার।
সাধারণত, ViewModel-এ UI ডিপেন্ডেন্সি না রাখার সাজেশন দেওয়া হয়।
যদিও এটি সাধারণত একটি ভাল অভ্যাস, তবে কখনও কখনও এটি ভুলভাবে ব্যাখ্যা করা হতে পারে।
এটি বিশেষ করে Compose ডিপেন্ডেন্সির ক্ষেত্রে প্রযোজ্য যা সম্পূর্ণভাবে ডেটা
স্ট্রাকচার এবং এর সাথে কোনও UI এলিমেন্ট থাকে না, যেমন TextFieldState।
MutableState বা TextFieldState-এর মতো ক্লাস হল সাধারণ স্টেট হোল্ডার যা
Compose-এর স্ন্যাপশট স্টেট সিস্টেম দ্বারা ব্যাক-আপ করা হয়। এগুলি StateFlow বা RxJava-এর মতো
ডিপেন্ডেন্সির থেকে আলাদা নয়। তাই,আপনার কোডে "ViewModel-এ কোনও UI ডিপেন্ডেন্সি নেই" নীতিটি কীভাবে প্রয়োগ করবেন তা
পুনরায় মূল্যায়ন করার জন্য আমরা আপনাকে
উৎসাহিত করি। আপনার 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কম্পোজেবলেTextFields-এ সেইTextFieldStateঅবজেক্টগুলি পাস করুন।
সম্মতিসূচক পদ্ধতি
এই ধরনের আর্কিটেকচারাল পরিবর্তন করা সবসময় সহজ হয় না। এইসব পরিবর্তন করার স্বাধীনতা আপনার নাও থাকতে পারে অথবা নতুন 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-কে একটি সাধারণ ডেটা হোল্ডারে পরিণত করুন।- এটি করতে,
LaunchedEffect-এsnapshotFlowসংগ্রহ করে প্রতিটিTextFieldState.text-এ হওয়া পরিবর্তন লক্ষ্য করুন।
- এটি করতে,
- আপনার
ViewModel-এ এখনও UI থেকে লেটেস্ট ভ্যালু থাকবে, তবে এরuiState: StateFlow<UiState>,TextField-কে ড্রাইভ করবে না। - আপনার
ViewModel-এ প্রয়োগ করা অন্য যেকোনও পারসিসটেন্স লজিক একই থাকতে পারে।