kotlin 记住具有多个节的窗体状态的最佳方法

kh212irz  于 2023-04-12  发布在  Kotlin
关注(0)|答案(1)|浏览(135)

我正在构建的应用程序将有一个表单屏幕,可以有多个“TextArea”字段,复选框,下拉列表等。

现在我想以某种方式记住每个字段的状态,以便能够在导航操作中持久化它(类似于rememberSaveable),然后将所有数据转换为一个JSON,然后发送到后端。值得注意的是,底部的按钮只有在每个部分都填充了数据时才能启用,即文本区域不是空的,至少有一个复选框被选中,等等。
什么是最好的方法来记住所有的信息,同时避免每个组件有一个rememberSaveable字段?我应该使用一个Form数据类,它将保存一个Map<String,String>用于存储下拉组件名称和答案,例如,对于其余部分也是如此?
还有,有没有什么方法可以避免在每个“onValueChanged”回调中检查是否所有表单部分都有数据以启用按钮?
这是现在的屏幕:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RegistrationStep2Screen(
    navController: NavController,
    registrationViewModel: RegistrationViewModel = getViewModel()
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(vertical = 40.dp, horizontal = 16.dp)
            .verticalScroll(rememberScrollState())
    ) {
        var section1Text by rememberSaveable { mutableStateOf("") }
        var selectedDropdownOption by rememberSaveable { mutableStateOf("Answer") }
        val checkboxStates by registrationViewModel.checkboxStates.observeAsState()
        val canProceed by registrationViewModel.canProceedToStep3.observeAsState(false)

        LaunchedEffect(
            key1 = section1Text,
            key2 = checkboxStates!!.values.count { it },
            key3 = selectedDropdownOption
        ) {
            registrationViewModel.canProceedToStep3(
                section1Text,
                checkboxStates!!.values.count { it },
                selectedDropdownOption
            )
        }

        RegistrationProgressBar(currentStep = 2)

        Spacer(modifier = Modifier.height(30.dp))

        Column(
            Modifier
                .fillMaxWidth()
        ) {
            Text(
                text = "STEP 2",
                style = MaterialTheme.typography.displayMedium,
                fontSize = 16.sp,
                color = colorResource(id = R.color.form_field_error_color),
                fontFamily = FontFamily(Font(R.font.montserrat_bold)),
                fontWeight = FontWeight.Bold
            )
            Text(
                text = "Please answer the following questions",
                style = MaterialTheme.typography.bodySmall
            )
        }

        Spacer(modifier = Modifier.height(20.dp))

        Column(
            modifier = Modifier
                .fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(20.dp),
            horizontalAlignment = Alignment.Start
        ) {
            FormSection(
                orderNum = 1,
                title = "Question full text"
            ) {
                OutlinedTextField(
                    modifier = Modifier
                        .padding(top = 10.dp)
                        .fillMaxWidth()
                        .heightIn(min = 111.dp, max = 111.dp),
                    value = section1Text,
                    onValueChange = { value ->
                        section1Text = value.trim()
                    },
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        unfocusedBorderColor = colorResource(id = R.color.form_field_border_color),
                        focusedBorderColor = colorResource(id = R.color.form_field_border_color),
                        errorBorderColor = colorResource(id = R.color.form_field_error_color),
                        errorSupportingTextColor = colorResource(id = R.color.form_field_error_color)
                    ),
                )
            }

            FormSection(
                orderNum = 2,
                title = "Question multiple choice"
            ) {
                Row(
                    modifier = Modifier.padding(top = 13.dp),
                    horizontalArrangement = Arrangement.spacedBy(40.dp)
                ) {
                    Column(verticalArrangement = Arrangement.spacedBy(11.dp)) {
                        FormCheckBox(
                            onCheckedStatusChange = {
                                registrationViewModel.updateCheckboxState(0 to it)
                            },
                            isCheckedInitVal = checkboxStates!![0]!!,
                            label = "Answer 1"
                        )
                        FormCheckBox(
                            onCheckedStatusChange = { registrationViewModel.updateCheckboxState(1 to it) },
                            isCheckedInitVal = checkboxStates!![1]!!,
                            label = "Answer 2"
                        )
                    }

                    Column(verticalArrangement = Arrangement.spacedBy(11.dp)) {
                        FormCheckBox(
                            onCheckedStatusChange = { registrationViewModel.updateCheckboxState(2 to it) },
                            isCheckedInitVal = checkboxStates!![2]!!,
                            label = "Answer 3"
                        )
                        FormCheckBox(
                            onCheckedStatusChange = { registrationViewModel.updateCheckboxState(3 to it) },
                            isCheckedInitVal = checkboxStates!![3]!!,
                            label = "Answer 4"
                        )
                    }
                }
            }

            FormSection(
                orderNum = 3,
                title = "Question drop down"
            ) {
                FormDropdownField(
                    modifier = Modifier
                        .fillMaxWidth(),
                    options = listOf("Answer 1", "Answer 2", "Answer 3"),
                    defaultOption = selectedDropdownOption
                ) {
                    selectedDropdownOption = it
                }
            }
        }
        Spacer(Modifier.weight(1f))

        if (!canProceed) {
            Text(
                text = "Please complete all fields to proceed to the next step",
                style = MaterialTheme.typography.bodyMedium,
                fontSize = 14.sp,
                color = colorResource(id = R.color.form_field_error_color)
            )

            Spacer(Modifier.height(10.dp))
        }

        FoodakaiButton(
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp),
            text = stringResource(id = R.string.next_step),
            enabled = canProceed,
            onClick = {
                navController.navigate(Screens.REGISTRATION_STEP3.navRoute)
            }
        )
    }
}

表单复选框可组合:

@Composable
fun FormCheckBox(
    modifier: Modifier = Modifier,
    label: String = "",
    isCheckedInitVal: Boolean = false,
    onCheckedStatusChange: (Boolean) -> Unit
) {
    var isChecked by rememberSaveable { mutableStateOf(isCheckedInitVal) }

    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            contentAlignment = Alignment.Center,
            modifier = modifier
                .width(22.dp)
                .height(22.dp)
                .border(
                    2.dp,
                    colorResource(id = R.color.form_field_border_color),
                    MaterialTheme.shapes.medium
                )
                .padding(1.dp)
                .background(
                    colorResource(id = if (isChecked) R.color.form_checkbox_filled_color else R.color.white),
                    MaterialTheme.shapes.medium
                )
                .clip(MaterialTheme.shapes.medium)
                .clickable {
                    isChecked = !isChecked
                    onCheckedStatusChange(isChecked)
                }
        ) {
        }
        Spacer(modifier = Modifier.width(16.dp))
        Text(
            label,
            fontSize = 16.sp,
            fontFamily = FontFamily(listOf(Font(R.font.montserrat_regular)))
        )
    }
}

视图模型:

class RegistrationViewModel(
    private val registrationRepository: RegistrationRepository
) : ViewModel() {
    private var _checkboxStates = MutableLiveData(
        mapOf(
            0 to false,
            1 to false,
            2 to false,
            3 to false
        )
    )
    val checkboxStates: LiveData<Map<Int, Boolean>> = _checkboxStates

    private var _userData = MutableLiveData<RegistrationCheckResponse>(null)
    val userData: LiveData<RegistrationCheckResponse>
        get() = _userData

    private var _canProceedToStep3 = MutableLiveData(false)
    val canProceedToStep3: LiveData<Boolean>
        get() = _canProceedToStep3

    fun updateCheckboxState(state: Pair<Int, Boolean>) {
        val map = _checkboxStates.value?.toMutableMap()
        map?.apply {
            map[state.first] = state.second
            _checkboxStates.value = map.toMap()
        }
    }

    fun canProceedToStep2(email: String, confirmEmail: String) =
        areEmailsMatching(email, confirmEmail) && isEmailValid(email)

    fun areEmailsMatching(email: String, confirmEmail: String) =
        email.trim() == confirmEmail.trim()

    fun isEmailValid(email: String) =
        InputValidator.validateEmail(email)

    fun canProceedToStep3(
        checkboxText: String,
        checkboxCount: Int,
        selectedDropdownValue: String
    ) {
        _canProceedToStep3.value =
            checkboxText.isNotBlank() && checkboxCount > 0 && selectedDropdownValue.isNotEmpty()
    }

    fun verifyByEmail(email: String, onResponse: (Boolean) -> Unit) {
        viewModelScope.launch(Dispatchers.IO) {
            when (val response = registrationRepository.isRegisteredByEmail(email)) {
                is ResponseResult.Success -> {
                    response.data?.let {
                        withContext(Dispatchers.Main) {
                            onResponse(it.isVerified)
                        }
                    }
                }

                is ResponseResult.Error -> {
                    withContext(Dispatchers.Main) {
                        onResponse(false)
                    }
                }
            }
        }
    }

    fun verifyByRegistrationId(regId: String) {
        viewModelScope.launch(Dispatchers.IO) {
            when (val response = registrationRepository.isRegisteredById(regId)) {
                is ResponseResult.Success -> {
                    response.data?.let {
                        _userData.postValue(it)
                    }
                }

                is ResponseResult.Error -> {
                    println(response.exception.message)
                }
            }
        }
    }

    fun resendVerificationEmail() {
        viewModelScope.launch(Dispatchers.IO) {
            val response = _userData.value?.id?.let {
                registrationRepository.resendVerificationRequest(it)
            }

            when (response) {
                is ResponseResult.Success -> {
                    println("SUCCESS")
                }

                is ResponseResult.Error -> {
                    println("Exception: ${response.exception.message}")
                }

                else -> {
                    println("UNKNOWN ERROR")
                }
            }
        }
    }

    fun verifyUser(onSuccess: () -> Unit, onFailure: () -> Unit) {
        viewModelScope.launch(Dispatchers.IO) {
            when (val response = _userData.value?.id?.let {
                registrationRepository.verifyUser(it)
            }) {
                is ResponseResult.Success -> {
                    onSuccess()
                }

                else -> {
                    onFailure()
                }
            }
        }

    }
}

在这里发布之前,我实际上尝试使用一个单一的Form数据类,就像我提到的那样,并用每个组件的回调来更新它,但它似乎并不真正记住状态,即使它被存储在viewModel中,并通过LiveData使用observeAsState进行观察。

njthzxwz

njthzxwz1#

除了使用Jetpack Room库之外,没有合适的方法。我也遇到了类似的问题(记住状态以便将来重用于后端),并意识到即使在重新启动后我也应该能够恢复程序状态.是的,使用Room听起来开销很大,但这是唯一的方法.您也可以使用共享首选项或数据存储但是如果你想实现高度结构化的表单,你应该以高度结构化的方式保存数据。

相关问题