如何避免在重构发生时运行if else代码块?

How to avoid the if else code block to run when recomposition happens?

提问人:Notchdev66 提问时间:11/10/2023 最后编辑:Mandar PanditNotchdev66 更新时间:11/10/2023 访问量:45

问:

有一个可组合项,用户可以在其中添加新客户或更新现有客户,我有一个可空的状态(如果是新客户,则可空),还有两个状态或,并且我使用 if else 来显示添加和更新客户的成功和失败的 toast 消息, 问题是,当客户更新时,或者即使添加了新客户,重组也会发生两次,如果 else 代码块触发两次,并在成功更新或添加时显示两次 Toast 消息和两次导航返回AddOrUpdateCustomerRouteexistingcustomerisCustomerUpdatedisCustomerAdded

这里是 Screen 的代码

@Composable
fun AddOrEditCustomerRoute(
    modifier: Modifier = Modifier,
    customerId: Long? = null,
    viewModel: CustomerViewModel = hiltViewModel(),
    navigateBack: () -> Unit
) {

    val context = LocalContext.current
    viewModel.getCustomer(customerId)
    val existingCustomer = viewModel.existingCustomer.collectAsStateWithLifecycle()

    val isCustomerAdded = viewModel.isCustomerAdded.collectAsStateWithLifecycle()
    val isCustomerUpdated = viewModel.isCustomerUpdated.collectAsStateWithLifecycle()
    
    if (isCustomerAdded.value == true) {
        Toast.makeText(context, "Customer Added", Toast.LENGTH_SHORT).show()
        navigateBack.invoke()
    } else if (isCustomerAdded.value == false) {
        Toast.makeText(context, "Failed to add customer! Try again", Toast.LENGTH_SHORT).show()
    }

    if (isCustomerUpdated.value == true) {
        Toast.makeText(context, "CustomerUpdated", Toast.LENGTH_SHORT).show()
        navigateBack.invoke()
    } else if (isCustomerUpdated.value == false) {
        Toast.makeText(context, "Failed to update customer, Try again", Toast.LENGTH_SHORT)
            .show()
    }

    AddOrEditCustomerScreens(
        modifier = modifier,
        customer = existingCustomer.value,
        addCustomer = { name, phone, email ->
            viewModel.addCustomer(name, phone, email)
        },
        updateCustomer = {
            viewModel.updateCustomer(it)
        },
        navigateBack = navigateBack
    )
}

@Composable
fun AddOrEditCustomerScreens(
    modifier: Modifier = Modifier,
    customer: Customer? = null,
    addCustomer: (String, String, String) -> Unit,
    updateCustomer: (customer: Customer) -> Unit,
    navigateBack: () -> Unit
) {
    val isEditing = customer != null
    val title = if (isEditing) stringResource(id = R.string.update_customer) else stringResource(
        id = R.string.add_customer
    )
    val customerName = remember(customer) {
        mutableStateOf(customer?.name ?: "")
    }
    val phoneNumber = remember(customer) {
        mutableStateOf(customer?.phone ?: "")
    }
    val email = remember(customer) {
        mutableStateOf(customer?.email ?: "")
    }

    Scaffold(
        modifier = modifier.fillMaxSize(),
        topBar = {
            DefaultAppBar(title = title, navigateBack = navigateBack, showSearch = false)
        }
    ) { paddingValue ->
        Column(
            modifier = Modifier
                .padding(paddingValue)
                .padding(vertical = 12.dp, horizontal = 16.dp)
        ) {
            OutlinedTextField(
                value = customerName.value,
                onValueChange = { customerName.value = it },
                label = {
                    TextH70(text = stringResource(id = R.string.customer_name))
                },
                modifier = Modifier
                    .fillMaxWidth()
            )

            OutlinedTextField(
                value = phoneNumber.value,
                onValueChange = {
                    if (it.length <= MAX_PHONE_LIMIT) {
                        phoneNumber.value = it
                    }
                },
                label = {
                    TextH70(text = stringResource(id = R.string.mobile_number_optional))
                },
                keyboardOptions = KeyboardOptions.Default.copy(
                    keyboardType = KeyboardType.Number
                ),
                modifier = Modifier
                    .fillMaxWidth()
            )

            OutlinedTextField(
                value = email.value,
                onValueChange = { email.value = it },
                label = {
                    TextH70(text = stringResource(id = R.string.email_id))
                },
                modifier = Modifier
                    .fillMaxWidth()
            )

            Button(
                enabled = (customerName.value.isNotEmpty() && phoneNumber.value.isNotEmpty()),
                onClick = {
                    if (isEditing)
                        updateCustomer(
                            customer!!.copy(
                                name = customerName.value,
                                phone = phoneNumber.value,
                                email = email.value
                            )
                        )
                    else
                        addCustomer(customerName.value, phoneNumber.value, email.value)
                },
                modifier = Modifier
                    .padding(top = 24.dp)
                    .align(Alignment.End),
                colors = ButtonDefaults.buttonColors(
                    containerColor = MaterialTheme.colorScheme.primaryContainer
                ),
                shape = RoundedCornerShape(8.dp)
            ) {
                Icon(imageVector = Icons.Filled.PersonAdd, null)
                Spacer(modifier = Modifier.width(4.dp))
                TextH40(
                    title,
                    color = Color.White
                )
            }
        }
    }
}

here' 是 viewModel 代码

@HiltViewModel
class CustomerViewModel @Inject constructor(
    private val getCustomersUseCase: GetCustomersUseCase,
    private val searchCustomersUseCase: SearchCustomersUseCase,
    private val getCustomerUseCase: GetCustomerUseCase,
    private val addCustomerUseCase: AddCustomerUseCase,
    private val updateCustomerUseCase: UpdateCustomerUseCase
) : ViewModel() {
    
private val _existingCustomer = MutableStateFlow<Customer?>(null)
val existingCustomer = _existingCustomer
    
private val _customers = MutableStateFlow<PagingData<Customer>>(PagingData.empty())
val customers = _customers
    
private val _isCustomerAdded = MutableStateFlow<Boolean?>(null)
val isCustomerAdded = _isCustomerAdded
    
private val _isCustomerUpdated = MutableStateFlow<Boolean?>(null)
val isCustomerUpdated = _isCustomerUpdated
    
init {
    fetchInitCustomers()
}
    
private fun fetchInitCustomers() {
    viewModelScope.launch {
        getCustomersUseCase.perform()
        .map { pagingData ->
            pagingData.map {
                it.toCustomer()
            }
        }.cachedIn(viewModelScope)
        .collect {
            _customers.value = it
        }
    }
}
    
fun searchCustomers(query: String) {
    viewModelScope.launch {
        searchCustomersUseCase.perform(query)
            .map { pagingData -> pagingData.map { it.toCustomer() } }.cachedIn(viewModelScope)
        .collect {
            customers.value = it
        }
    }
}
    
fun getCustomer(id: Long?) {
    viewModelScope.launch {
        if (id != null) {
            _existingCustomer.value = getCustomerUseCase.perform(id)
        } else {
            _existingCustomer.value = null
        }
    }
}
    
fun addCustomer(name: String, phone: String, email: String) {
    viewModelScope.launch {
        val request = AddCustomerRequest(
            name = name,
            phone = phone,
            email = email,
            amountDue = null
        )
        _isCustomerAdded.value = addCustomerUseCase.perform(request)
    }
}
    
fun updateCustomer(customer: Customer) {
    viewModelScope.launch {
        val request = UpdateCustomerRequest(
            id = customer.id,
            name = customer.name,
            phone = customer.phone,
            email = customer.email,
            amountDue = customer.amountDue
        )
        _isCustomerUpdated.value = updateCustomerUseCase.perform(customer.id, request)
        }
    }
} 

我认为状态的提取可以在这里做得更好,但我就是无法理解如何做到这一点,这是 Compose 的新手,这在 XML 中是小菜一碟,但在 Compose 中做事对我来说是新的,因为我正在努力学习它

我尝试使用像 LauncedEffect 这样的副作用,但它根本没有帮助,代码甚至没有运行,所以找不到太多关于我在这里能做什么的信息

LaunchedEffect(isCustomerAdded) {
    if (isCustomerAdded.value == true) {
        Toast.makeText(context, "Customer Added", Toast.LENGTH_SHORT).show()
            navigateBack.invoke()
        } else if (isCustomerAdded.value == false) {
            Toast.makeText(context, "Failed to add customer! Try again", Toast.LENGTH_SHORT).show()
        }
}

LaunchedEffect(isCustomerUpdated) {
    if (isCustomerUpdated.value == true) {
        Toast.makeText(context, "CustomerUpdated", Toast.LENGTH_SHORT).show()
            navigateBack.invoke()
    } else if (isCustomerUpdated.value == false) {
        Toast.makeText(context, "Failed to update customer, Try again", Toast.LENGTH_SHORT)
        .show()
    }
}
android xml kotlin android-jetpack-compose 状态管理

评论

0赞 Notchdev66 11/11/2023
感谢 Mandar 格式化问题

答:

1赞 JanItor 11/10/2023 #1

尝试添加到密钥。.valueLaunchedEffect

LaunchedEffect(isCustomerAdded.value)

或者保留,但使用 切换到属性委派。LaunchedEffectby

val isCustomerAdded by viewModel.isCustomerAdded.collectAsStateWithLifecycle()

评论

0赞 Notchdev66 11/11/2023
使用带有启动效果的属性委托,工作起来很有魅力。谢谢
0赞 Mohamed Shasho 11/10/2023 #2

这将调用每次重构发生, 若要确保代码调用仅执行一次,可以使用以下命令:viewModel.getCustomer(customerId)

 LaunchedEffect(Unit){ // this it will execute just one
 viewModel.getCustomer(customerId)
 }

或者你可以直接从 ViewModel 中获取导航参数

 class CustomerViewModel(private val savedStateHandle: SavedStateHandle) : 
  ViewModel(){
  private val customerId = savedStateHandle.getLong(....)
  init{
  getCustomer(customerId )
  }

您可以与 一起使用。参考我之前的回答byLaunchedEffect

一些提示: 为了避免不必要的重构,将这些函数转换为使用函数引用,可以直接从 .这是你如何做到的:viewModel

addCustomer = viewModel::addCustomer,
updateCustomer = viewModel::updateCustomer

使用 Kotlin 在 Android 中处理共享状态时,请利用 StateFlow 来观察值的变化。请参阅 Android 参考

   // Backing property to avoid state updates from other classes
private val _existingCustomer = MutableStateFlow<Customer?>(null)
// The UI collects from this StateFlow to get its state updates
val existingCustomer: StateFlow<Customer?> = _existingCustomer

评论

0赞 Notchdev66 11/11/2023
感谢 shasho 的所有建议,尤其是 savedStateHandle 方法