You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Kotlin基础训练营Cupcake应用RadioButton绑定错误求助

RadioButton的checked属性数据绑定类型不兼容错误

参与Google Kotlin基础训练营,使用Shared ViewModel构建Cupcake应用,代码与官方仓库完全一致,但所有RadioButton的android:checked属性绑定出现错误,提示表达式与checked的boolean属性不兼容。

错误信息

@{viewModel.date.equals(viewModel.dateOptions[2])' is incompatible with attribute checked (attr) boolean.

相关代码

FlavorFragment布局代码

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2020 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<!-- Layout for selecting a flavor -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".FlavorFragment">

    <data>
        <variable
            name="viewModel"
            type="com.example.cupcake.model.OrderViewModel" />

        <variable
            name="flavorFragment"
            type="com.example.cupcake.FlavorFragment" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/side_margin">

            <RadioGroup
                android:id="@+id/flavor_options"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <RadioButton
                    android:id="@+id/vanilla"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.flavor.equals(@string/vanilla)}"
                    android:onClick="@{() -> viewModel.setFlavor(@string/vanilla)}"
                    android:text="@string/vanilla" />

                <RadioButton
                    android:id="@+id/chocolate"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.flavor.equals(@string/chocolate)}"
                    android:onClick="@{() -> viewModel.setFlavor(@string/chocolate)}"
                    android:text="@string/chocolate" />

                <RadioButton
                    android:id="@+id/red_velvet"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.flavor.equals(@string/red_velvet)}"
                    android:onClick="@{() -> viewModel.setFlavor(@string/red_velvet)}"
                    android:text="@string/red_velvet" />

                <RadioButton
                    android:id="@+id/salted_caramel"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.flavor.equals(@string/salted_caramel)}"
                    android:onClick="@{() -> viewModel.setFlavor(@string/salted_caramel)}"
                    android:text="@string/salted_caramel" />

                <RadioButton
                    android:id="@+id/coffee"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.flavor.equals(@string/coffee)}"
                    android:onClick="@{() -> viewModel.setFlavor(@string/coffee)}"
                    android:text="@string/coffee" />
            </RadioGroup>

            <View
                android:id="@+id/divider"
                style="@style/Widget.Cupcake.Divider"
                android:layout_width="0dp"
                android:layout_height="1dp"
                android:layout_marginTop="@dimen/side_margin"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/flavor_options" />

            <TextView
                android:id="@+id/subtotal"
                style="@style/Widget.Cupcake.TextView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/side_margin"
                android:text="@{@string/subtotal_price(viewModel.price)}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@id/divider"
                tools:text="Subtotal $5.00" />

            <Button
                android:id="@+id/cancel_button"
                style="?attr/materialButtonOutlinedStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/side_margin"
                android:onClick="@{() -> flavorFragment.cancelOrder()}"
                android:text="@string/cancel"
                app:layout_constraintEnd_toStartOf="@id/next_button"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@id/next_button" />

            <Button
                android:id="@+id/next_button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/side_margin"
                android:onClick="@{() -> flavorFragment.goToNextScreen()}"
                android:text="@string/next"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/cancel_button"
                app:layout_constraintTop_toBottomOf="@id/subtotal" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </ScrollView>
</layout>

OrderViewModel代码

package com.example.cupcake.model

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.*

private const val PRICE_PER_CUPCAKE = 2.00
private const val PRICE_FOR_SAME_DAY_PICKUP = 3.00

class OrderViewModel : ViewModel() {
    init {
        resetOrder()
    }
    private val _quantity = MutableLiveData<Int>()
    val quantity: LiveData<Int> = _quantity

    private val _flavor = MutableLiveData<String>()
    val flavor: LiveData<String> = _flavor

    private val _date = MutableLiveData<String>()
    val date: LiveData<String> = _date

    private val _price = MutableLiveData<Double>()
    val price: LiveData<String> = Transformations.map(_price) {
        NumberFormat.getCurrencyInstance().format(it)
    }

    val dateOptions = getPickupOptions()

    fun setQuantity(numberCupcakes: Int) {
        _quantity.value = numberCupcakes
        updatePrice()
    }

    fun setDate(pickupDate: String) {
        _date.value = pickupDate
        updatePrice()
    }

    fun setFlavor(desiredFlavor: String) {
        _flavor.value = desiredFlavor
    }


    fun hasNoFlavorSet(): Boolean {
        return _flavor.value.isNullOrEmpty()
    }

    private fun getPickupOptions(): List<String> {
        val options = mutableListOf<String>()
        val formatter = SimpleDateFormat("EE MMM d", Locale.getDefault())
        val calendar = Calendar.getInstance()

        // Create a list of dates starting with the current date and the following 3 dates
        repeat(4) {
            options.add(formatter.format(calendar.time))
            calendar.add(Calendar.DATE, 1)
        }
        return options
    }

    private fun updatePrice() {
        var calculatedPrice = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
        // If the user selected the first option (today) for pickup, add the surcharge
        if (dateOptions[0] == _date.value) {
            calculatedPrice += PRICE_FOR_SAME_DAY_PICKUP
        }
        _price.value = calculatedPrice
    }

    fun resetOrder() {
        _quantity.value = 0
        _flavor.value = ""
        _date.value = dateOptions[0]
        _price.value = 0.0
    }

}

PickupFragment布局代码

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2020 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<!-- Layout for selecting a pickup date -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".PickupFragment">

    <data>
        <variable
            name="viewModel"
            type="com.example.cupcake.model.OrderViewModel" />
        <variable
            name="pickupFragment"
            type="com.example.cupcake.PickupFragment" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/side_margin">

            <RadioGroup
                android:id="@+id/date_options"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <RadioButton
                    android:id="@+id/option0"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])"
                    android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[0])}"
                    android:text="@{viewModel.dateOptions[0]}"
                    tools:text="Thursday" />

                <RadioButton
                    android:id="@+id/option1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked= "@{viewModel.date.equals(viewModel.dateOptions[1])}"
                    android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[1])}"
                    android:text="@{viewModel.dateOptions[1]}"
                    tools:text="Friday" />

                <RadioButton
                    android:id="@+id/option2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.date.equals(viewModel.dateOptions[2])"
                    android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[2])}"
                    android:text="@{viewModel.dateOptions[2]}"
                    tools:text="Saturday" />

                <RadioButton
                    android:id="@+id/option3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="@{viewModel.date.equals(viewModel.dateOptions[3])"
                    android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[3])}"
                    android:text="@{viewModel.dateOptions[3]}"
                    tools:text="Sunday" />
            </RadioGroup>

            <View
                android:id="@+id/divider"
                style="@style/Widget.Cupcake.Divider"
                android:layout_width="0dp"
                android:layout_height="1dp"
                android:layout_marginTop="@dimen/side_margin"
                android:layout_marginBottom="@dimen/side_margin"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/date_options" />

            <TextView
                android:id="@+id/subtotal"
                style="@style/Widget.Cupcake.TextView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/side_margin"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@id/divider"
                android:text="@{@string/subtotal_price(viewModel.price)"
                tools:text="Subtotal $5.00" />

            <Button
                android:id="@+id/next_button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/side_margin"
                android:text="@string/next"
                android:onClick="@{() -> pickupFragment.goToNextScreen()}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/subtotal" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </ScrollView>
</layout>

解决方案

1. 修复XML语法错误

PickupFragment布局中多个表达式存在语法缺失,导致编译器无法正确解析类型:

  • 补全option0、option2、option3的android:checked属性表达式末尾的},例如将android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])"改为android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])}"
  • 补全subtotal的android:text属性表达式末尾的},改为android:text="@{@string/subtotal_price(viewModel.price)}"

2. 优化Data Binding表达式(可选)

由于viewModel.date和viewModel.flavor都是LiveData类型,Data Binding会自动解包获取当前值,可使用Kotlin的==运算符替代equals()方法,表达式更简洁且语义一致:

  • 将@{viewModel.date.equals(viewModel.dateOptions[0])}修改为@{viewModel.date == viewModel.dateOptions[0]}
  • 将@{viewModel.flavor.equals(@string/vanilla)}修改为@{viewModel.flavor == @string/vanilla}

完成上述修改后,编译器即可正确识别表达式的boolean返回值,与checked属性的类型匹配,错误即可解决。

内容的提问来源于stack exchange,提问作者Meet Soni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 06:00:17