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

基于Kotlin的BMI计算器ViewModel改造求助:数据绑定与ViewModel数据访问问题

Hey there! Let's fix your ViewModel implementation step by step—right now, your ViewModel is doing things it shouldn't be handling (like touching UI elements directly), which is why you're running into issues. Let's break this down properly following Android's MVVM principles.

First: Fix the ViewModel (Separate Logic from UI)

Your ViewModel should only hold data and handle business logic—never reference UI elements like EditText or Button. Instead, we'll use LiveData to expose data to the UI, so the UI can observe changes and update itself automatically.

Here's the corrected ViewModel:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class BmiViewModel : ViewModel() {
    // MutableLiveData for internal state management (hidden from UI)
    private val _bmiResult = MutableLiveData<String>()
    private val _errorMessage = MutableLiveData<String>()
    
    // Immutable LiveData for UI to observe (prevents UI from modifying state directly)
    val bmiResult: LiveData<String> = _bmiResult
    val errorMessage: LiveData<String> = _errorMessage

    fun calculateBMI(heightInput: String, weightInput: String) {
        // Parse input values safely
        val heightValue = heightInput.toDoubleOrNull()
        val weightValue = weightInput.toDoubleOrNull()

        // Validate inputs and calculate
        when {
            heightValue == null || heightValue <= 0.0 -> {
                _errorMessage.value = "Please enter a valid height greater than 0"
            }
            weightValue == null || weightValue <= 0.0 -> {
                _errorMessage.value = "Please enter a valid weight greater than 0"
            }
            else -> {
                val bmi = (weightValue * 703) / (heightValue * heightValue)
                val bmiStatus = getBmiStatus(bmi)
                val formattedBmi = String.format("%.2f", bmi)
                
                _bmiResult.value = "BMI is $formattedBmi, you are $bmiStatus"
                _errorMessage.value = null // Clear error on successful calculation
            }
        }
    }

    private fun getBmiStatus(bmi: Double): String {
        return when {
            bmi < 18.5 -> "Underweight"
            bmi in 18.5..24.9 -> "Normal"
            bmi in 25.0..29.9 -> "Overweight"
            else -> "Obese"
        }
    }
}

Key improvements here:

  • Removed all UI-related code (findViewById, setOnClickListener, Toast)
  • Used LiveData to communicate results/errors between ViewModel and UI
  • Moved calculation logic into a public method that accepts input values from the UI
  • Cleaned up input validation and status checking

Second: Connect ViewModel to Your Activity/Fragment

Now in your Activity (or Fragment), you'll handle UI interactions, pass input data to the ViewModel, and observe the ViewModel's LiveData to update the UI. We'll use View Binding (safer than findViewById) for clean UI access.

Here's how to implement this in an Activity:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import com.yourpackage.databinding.ActivityMainBinding // Replace with your actual binding class

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: BmiViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Initialize ViewModel (this ensures it survives configuration changes)
        viewModel = ViewModelProvider(this)[BmiViewModel::class.java]

        // Handle calculate button click
        binding.calcButton.setOnClickListener {
            val height = binding.heightEditText.text.toString().trim()
            val weight = binding.weightEditText.text.toString().trim()
            viewModel.calculateBMI(height, weight)
        }

        // Observe BMI result updates from ViewModel
        viewModel.bmiResult.observe(this) { result ->
            binding.bmiInfo.text = result
            binding.bmiInfo.visibility = android.view.View.VISIBLE
        }

        // Observe error messages from ViewModel
        viewModel.errorMessage.observe(this) { message ->
            message?.let {
                Toast.makeText(this, it, Toast.LENGTH_LONG).show()
                binding.bmiInfo.visibility = android.view.View.GONE
            }
        }
    }
}

Important setup note:
Enable View Binding in your module-level build.gradle file to generate the binding class:

android {
    buildFeatures {
        viewBinding true
    }
}

Third: Why This Works

  • Configuration Change Survival: The ViewModel is retained by the system across screen rotations or other configuration changes—your input values and calculation results won't be lost.
  • Separation of Concerns: The UI handles only user interaction and display, while the ViewModel manages all data and logic. This makes your code easier to test and maintain.
  • Automatic UI Updates: LiveData ensures the UI updates instantly whenever the ViewModel's state changes, no manual refresh needed.

内容的提问来源于stack exchange,提问作者Dante Zelaya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 13:28:12