Android中如何通过setOnClickListener修改ViewModel的LiveData值
问题原因
你代码里存在4个核心错误导致功能不生效:
- 点击事件设置逻辑完全错误:你将
queryBtn的点击事件设置放在了userName的observe回调中,只有当userName变化时才会给按钮绑定点击事件,初始状态下userName为null不会触发回调,按钮根本没有绑定有效点击事件;后续你又在response的observe回调中再次给queryBtn设置了空的点击事件,直接把之前的有效点击覆盖了。 - 取输入内容时写错控件类型,取值方法错误:
nameText是EditText类型,你写成了findViewById<Button>,而且直接对控件调用toString拿到的是控件对象的内存地址,不是输入的文本内容,正确写法是findViewById<EditText>(R.id.nameText).text.toString()。 - ViewModel中请求接口传参错误:你调用
api.listRepos时传的是userName.toString(),userName是MutableLiveData对象,直接toString拿到的是LiveData的类信息,正确应该取userName.value的值。 - ViewModel初始化时就调用了
refreshData,此时userName还没有赋值,会直接触发异常。
修复后的代码
RestActivity.kt
package com.example.internet_ex import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.TextView import androidx.lifecycle.* import kotlinx.coroutines.launch import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.GET import retrofit2.http.Path import java.lang.StringBuilder data class Owner(val login: String) data class Repo(val name: String, val owner: Owner, val url: String) data class Contributor(val login: String, val contributions: Int) interface RestApi { @GET("users/{user}/repos") suspend fun listRepos(@Path("user") user: String): List<Repo> @GET("/repos/{owner}/{repo}/contributors") suspend fun contributors( @Path("owner") owner: String, @Path("repo") repo: String ): List<Contributor> } class MyViewModel : ViewModel() { private val baseURL = "https://api.github.com/" private lateinit var api: RestApi val userName = MutableLiveData<String>() val response = MutableLiveData<String>() init { retrofitInit() // 初始化不要直接调用refreshData,此时没有userName } fun refreshData() { viewModelScope.launch { // 先判断userName有没有值 val user = userName.value ?: return@launch try { val repos = api.listRepos(user) response.value = StringBuilder().apply { repos.forEach { append(it.name) append(" - ") append(it.owner.login) append("\n") } }.toString() } catch (e: Exception) { response.value = "Failed to connect to the server" } } } private fun retrofitInit() { val retrofit = Retrofit.Builder() .baseUrl(baseURL) .addConverterFactory(MoshiConverterFactory.create()) .build() api = retrofit.create(RestApi::class.java) } } class RestActivity : AppCompatActivity() { private lateinit var myViewModel: MyViewModel // 提前初始化控件,不要每次都findViewById private lateinit var nameText: EditText private lateinit var queryBtn: Button private lateinit var textResponse: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_rest) // 初始化控件 nameText = findViewById(R.id.nameText) queryBtn = findViewById(R.id.queryBtn) textResponse = findViewById(R.id.textResponse) myViewModel = ViewModelProvider(this).get(MyViewModel::class.java) // 只设置一次点击事件,不要放在observe里 queryBtn.setOnClickListener { // 取输入框内容赋值给userName myViewModel.userName.value = nameText.text.toString() // 触发请求 myViewModel.refreshData() } // 观察响应结果更新UI myViewModel.response.observe(this) { textResponse.text = it } } }
activity_rest.xml (原有代码正确,不需要修改)
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".RestActivity"> <TextView android:id="@+id/textResponse" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:text="TextView" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/queryBtn" /> <Button android:id="@+id/queryBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Query" app:layout_constraintStart_toEndOf="@+id/nameText" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/nameText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
需要补充学习的知识点
- Android基础控件使用:掌握EditText、Button等常见控件的属性和方法,正确获取输入框文本内容
- LiveData工作机制:理解observe回调的触发时机,不要在observe回调中做控件事件绑定这类只需要执行一次的操作
- ViewModel使用规范:掌握LiveData的取值、赋值方法,不要直接对LiveData对象调用toString
- 点击事件设置逻辑:同一个控件不要重复设置OnClickListener,后设置的会覆盖之前的逻辑
- 协程+Retrofit网络请求规范:请求前做好参数合法性校验,合理捕获异常
内容的提问来源于stack exchange,提问作者yeong
相关产品推荐
相关产品推荐

