রাজ্য-ভিত্তিক পাঠ্য ক্ষেত্রে স্থানান্তর করুন

এই পৃষ্ঠাটি কীভাবে মান-ভিত্তিক 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 দিয়ে প্রতিস্থাপন করুন।
  • ড্যাশ যোগ করতে 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) দিয়ে প্রতিস্থাপন করুন।
  • একটি নতুন TextFieldValue গণনার যুক্তি TextFieldState.edit কলের মাধ্যমে পরিবর্তন করুন।
    • বর্তমান নির্বাচনের উপর ভিত্তি করে বিদ্যমান টেক্সটকে আলাদা করে এবং এর মধ্যে মার্কডাউন ডেকোরেশন সন্নিবেশ করে একটি নতুন 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 স্টেট হোস্টিং প্যারাডাইম ব্যবহার করে। তবে, টেক্সট ইনপুটের ক্ষেত্রে এই পদ্ধতির কিছু অপ্রত্যাশিত অসুবিধা রয়েছে। এই পদ্ধতির সাথে গভীর সিঙ্ক্রোনাইজেশন সমস্যাগুলি Effective state management for TextField in Compose ব্লগ পোস্টে বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

সমস্যা হলো নতুন TextFieldState ডিজাইনটি StateFlow সমর্থিত ViewModel UI অবস্থার সাথে সরাসরি সামঞ্জস্যপূর্ণ নয়। username: String এবং password: String কে username: TextFieldState এবং password: TextFieldState দিয়ে প্রতিস্থাপন করা অদ্ভুত লাগতে পারে, কারণ TextFieldState একটি সহজাতভাবে পরিবর্তনযোগ্য ডেটা কাঠামো।

একটি সাধারণ সুপারিশ হল ViewModel এ UI নির্ভরতা রাখা এড়িয়ে চলা। যদিও এটি সাধারণত একটি ভালো অনুশীলন, তবে কখনও কখনও এটির ভুল ব্যাখ্যা করা যেতে পারে। এটি বিশেষ করে Compose নির্ভরতার ক্ষেত্রে সত্য যা সম্পূর্ণরূপে ডেটা স্ট্রাকচার এবং TextFieldState মতো কোনও UI উপাদান বহন করে না।

MutableState অথবা TextFieldState মতো ক্লাসগুলি হল সরল স্টেট হোল্ডার যা Compose এর Snapshot state সিস্টেম দ্বারা সমর্থিত। এগুলি StateFlow অথবা RxJava এর মতো নির্ভরশীলতা থেকে আলাদা নয়। অতএব, আমরা আপনাকে আপনার কোডে "no UI dependences in 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 মান ব্যবহার করুন।
  • LoginForm কম্পোজেবলের TextFieldState অবজেক্টগুলিকে 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 একটি সাধারণ ডেটা হোল্ডারে পরিণত করুন।
    • এটি করার জন্য, LaunchedEffect এ একটি snapshotFlow সংগ্রহ করে প্রতিটি TextFieldState.text এর পরিবর্তনগুলি পর্যবেক্ষণ করুন।
  • আপনার ViewModel এখনও UI থেকে সর্বশেষ মান থাকবে, কিন্তু এর uiState: StateFlow<UiState> TextField গুলি চালাবে না।
  • আপনার ViewModel এ প্রয়োগ করা অন্য যেকোনো স্থায়িত্ব যুক্তি একই থাকতে পারে।