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

এই পৃষ্ঠাটি উদাহরণ প্রদান করে যে আপনি কীভাবে মান-ভিত্তিক 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, "-")
        },
    )
}

  • ইনপুটের সর্বোচ্চ দৈর্ঘ্য সেট করতে একটি 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 নির্ভরতা স্থাপন করা এড়ানো। যদিও এটি সাধারণত একটি ভাল অভ্যাস, তবে এটি কখনও কখনও ভুল ব্যাখ্যা হতে পারে। এটি রচনা নির্ভরতার জন্য বিশেষভাবে সত্য যা সম্পূর্ণরূপে ডেটা স্ট্রাকচার এবং তাদের সাথে কোন UI উপাদান বহন করে না, যেমন TextFieldState

MutableState বা TextFieldState মতো ক্লাসগুলি হল সাধারণ স্টেট হোল্ডার যেগুলি কম্পোজের স্ন্যাপশট স্টেট সিস্টেম দ্বারা সমর্থিত৷ তারা StateFlow বা RxJava মত নির্ভরতা থেকে আলাদা নয়। অতএব, আমরা আপনাকে আপনার কোডে "ভিউমডেলে কোন 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 কম্পোজেবলের মধ্যে 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 s চালাবে না।
  • আপনার ViewModel প্রয়োগ করা অন্য যেকোন অধ্যবসায় যুক্তি একই থাকতে পারে।