এই পৃষ্ঠায় ভ্যালু-ভিত্তিক 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()দিয়ে প্রতিস্থাপন করুন। -
onValueChangeএInputTransformationব্যবহার করে ফিল্টারিং লজিকটি পুনরায় প্রয়োগ করুন। -
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পদ্ধতির অনুরূপভাবে নির্বাচনটি সমন্বয় করুন।
- বর্তমান নির্বাচনের উপর ভিত্তি করে বিদ্যমান টেক্সটকে বিভক্ত করে এবং এর মাঝে মার্কডাউন অলঙ্করণগুলো সন্নিবেশ করে একটি নতুন
ভিউমডেল 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 স্টেট হোইস্টিং প্যারাডাইম ব্যবহার করে। তবে, টেক্সট ইনপুটের ক্ষেত্রে এই পদ্ধতির কিছু অপ্রত্যাশিত অসুবিধা রয়েছে। এই পদ্ধতির গভীর সিনক্রোনাইজেশন সমস্যাগুলো "কম্পোজে টেক্সটফিল্ডের জন্য কার্যকর স্টেট ম্যানেজমেন্ট" ব্লগ পোস্টে বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।
সমস্যাটি হলো, নতুন TextFieldState ডিজাইনটি StateFlow সমর্থিত ViewModel UI স্টেটের সাথে সরাসরি সামঞ্জস্যপূর্ণ নয়। username: String এবং password: String কে username: TextFieldState এবং password: TextFieldState দিয়ে প্রতিস্থাপন করাটা অদ্ভুত লাগতে পারে, কারণ TextFieldState হলো স্বভাবতই একটি পরিবর্তনযোগ্য ডেটা স্ট্রাকচার।
একটি প্রচলিত পরামর্শ হলো ViewModel এর মধ্যে UI ডিপেন্ডেন্সি রাখা এড়িয়ে চলা। যদিও এটি সাধারণত একটি ভালো অভ্যাস, তবুও কখনও কখনও এর ভুল ব্যাখ্যা হতে পারে। এটি বিশেষ করে সেইসব Compose ডিপেন্ডেন্সির ক্ষেত্রে প্রযোজ্য, যেগুলো সম্পূর্ণরূপে ডেটা স্ট্রাকচার এবং নিজেদের সাথে কোনো UI এলিমেন্ট বহন করে না, যেমন TextFieldState ।
MutableState বা TextFieldState মতো ক্লাসগুলো হলো সাধারণ স্টেট হোল্ডার, যা Compose-এর Snapshot স্টেট সিস্টেম দ্বারা সমর্থিত। এগুলো 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(usernameState) { snapshotFlow { usernameState.text.toString() }.collectLatest { loginViewModel.updatePassword(it) } } SecureTextField(passwordState) } }
- আপনার
ViewModelএবংUiStateক্লাসগুলো একই রাখুন। - স্টেটকে সরাসরি
ViewModelএ তুলে নিয়েTextFieldsজন্য সেটিকে তথ্যের উৎস বানানোর পরিবর্তে,ViewModelএকটি সাধারণ ডেটা হোল্ডারে পরিণত করুন।- এটি করার জন্য, একটি
LaunchedEffectএsnapshotFlowসংগ্রহ করে প্রতিটিTextFieldState.textএর পরিবর্তনগুলো পর্যবেক্ষণ করুন।
- এটি করার জন্য, একটি
- আপনার
ViewModelUI থেকে সর্বশেষ মানগুলো ঠিকই থাকবে, কিন্তু এরuiState: StateFlow<UiState>TextFieldচালনা করবে না। - আপনার
ViewModelএ প্রয়োগ করা অন্য যেকোনো ডেটা সংরক্ষণের যুক্তি অপরিবর্তিত থাকতে পারে।