无法将 LiveData 列表绑定到 Spinner 条目
Unable to bind LiveData List to Spinner entries
我目前有一个 Fragment MyFragment
,它有一个 Spinner my_spinner
。为了测试我的应用程序,我最初通过观察 AndroidViewModel MyViewModel
中的 属性 myLiveDataList
手动填充 my_spinner
的内容,如下所示:
my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragments.MyFragment">
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp" />
</FrameLayout>
MyFragment.kt
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.lifecycle.Observer
import com.example.app.R
import com.example.app.data.room.entities.MyEntity
import com.example.app.ui.viewmodels.MyViewModel
import kotlinx.android.synthetic.main.my_fragment.*
class MyFragment : Fragment() {
companion object {
fun newInstance() = MyFragment()
}
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
val myAdapter = ArrayAdapter<MyEntity>(this.context!!, android.R.layout.simple_spinner_item)
// This is where I populate my_spinner
viewModel.myLiveDataList.observe(this, Observer<List<MyEntity>> { data ->
data?.forEach {
myAdapter.add(it)
}
})
my_spinner.adapter = myAdapter
}
}
MyViewModel.kt
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.toLiveData
import com.example.app.data.repositories.MyRepository
import com.example.app.data.room.entities.MyEntity
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val myRepository = MyRepository(application)
val myLiveDataList: LiveData<List<MyEntity>>
get() = myRepository.getAllData().toLiveData()
}
当我导航到 MyFragment
:
时,这会成功填充 my_spinner
由于它按预期填充,我继续对 my_fragment.xml
进行以下更改:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewmodel"
type="com.example.app.ui.viewmodels.MyViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragments.MyFragment">
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp"
app:entries="@{viewmodel.myLiveDataList}"/>
</FrameLayout>
</layout>
我在绑定适配器文件中添加了 BindingAdapterUtil
(以下代码是从 this article 复制的):
import android.R
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import com.example.app.ui.adapter.SpinnerExtensions.getSpinnerValue
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerEntries
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerInverseBindingListener
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerItemSelectedListener
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerValue
@BindingAdapter("entries")
fun Spinner.setEntries(entries: List<Any>?) {
setSpinnerEntries(entries)
}
@BindingAdapter("onItemSelected")
fun Spinner.setItemSelectedListener(itemSelectedListener: SpinnerExtensions.ItemSelectedListener?) {
setSpinnerItemSelectedListener(itemSelectedListener)
}
@BindingAdapter("newValue")
fun Spinner.setNewValue(newValue: Any?) {
setSpinnerValue(newValue)
}
@BindingAdapter("selectedValue")
fun Spinner.setSelectedValue(selectedValue: Any?) {
setSpinnerValue(selectedValue)
}
@BindingAdapter("selectedValueAttrChanged")
fun Spinner.setInverseBindingListener(inverseBindingListener: InverseBindingListener?) {
setSpinnerInverseBindingListener(inverseBindingListener)
}
@InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged")
fun Spinner.getSelectedValue(): Any? {
return getSpinnerValue()
}
object SpinnerExtensions {
fun Spinner.setSpinnerEntries(entries: List<Any>?) {
if (entries != null) {
val arrayAdapter = ArrayAdapter(context, R.layout.simple_spinner_item, entries)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter = arrayAdapter
}
}
fun Spinner.setSpinnerItemSelectedListener(listener: ItemSelectedListener?) {
if (listener == null) {
onItemSelectedListener = null
} else {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
if (tag != position) {
listener.onItemSelected(parent.getItemAtPosition(position))
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
}
fun Spinner.setSpinnerInverseBindingListener(listener: InverseBindingListener?) {
if (listener == null) {
onItemSelectedListener = null
} else {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
if (tag != position) {
listener.onChange()
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
}
fun Spinner.setSpinnerValue(value: Any?) {
if (adapter != null ) {
val position = (adapter as ArrayAdapter<Any>).getPosition(value)
setSelection(position, false)
tag = position
}
}
fun Spinner.getSpinnerValue(): Any? {
return selectedItem
}
interface ItemSelectedListener {
fun onItemSelected(item: Any)
}
}
而且我已经修改了 MyFragment
中的 onActivityCreated
,如下所示:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
DataBindingUtil.setContentView<MyFragmentBinding>(
this.activity!!, R.layout.my_fragment
).apply {
this.setLifecycleOwner(this@MyFragment)
this.viewmodel = viewModel
}
}
这样做的结果是 my_spinner
不再填充 MyViewModel.myLiveDataList
的内容。为了确定 属性 是否有问题,我在 MyViewModel
中创建了一个新的 属性 ,如下所示:
val myList: List<String>?
get() = listOf("First", "Second", "Third")
并且我已经将 属性 绑定到 my_spinner
就像上面的 MyViewModel.myLiveDataList
一样,这次成功了。
MyRepository.getAllData()
中的函数(其中myLiveDataList
returns)returns一个Flowable<List<MyEntity>>
(RxJava),它调用一个Room DAO来获取数据.我这里的假设是 myLiveDataList
在第一次尝试绑定值时没有任何服务,并且再也不会尝试。
我在尝试将 LiveData 数据源绑定到 Spinner 时是否遗漏了什么?
阅读 后,我将 my_fragment.xml
修改为以下内容:
...
<data>
<import type="java.util.List" />
<import type="com.example.app.data.room.entities.MyEntity" />
<import type="androidx.lifecycle.LiveData" />
<variable name="viewmodel"
type="com.example.app.ui.viewmodels.MyViewModel" />
<variable name="myTestList"
type="LiveData<List<MyEntity>>" />
</data>
...
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp"
app:entries="@{myTestList}"/>
...
我也删除了 MyFragment.onActivityCreated
的内容并修改了 MyFragment.onCreateView
如下:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
val binding = MyFragmentBinding.inflate(inflater, container, false)
binding.setLifecycleOwner(this)
binding.viewmodel = viewModel
binding.myTestList = viewModel.myLiveDataList
return binding.root
}
这不是一个完美的解决方案,我仍然不知道为什么我在这个问题上的最初回合没有产生预期的结果,但它会做到的。如果有更好的方法以这种方式将 Spinner 绑定到 LiveData,请告诉我。
我目前有一个 Fragment MyFragment
,它有一个 Spinner my_spinner
。为了测试我的应用程序,我最初通过观察 AndroidViewModel MyViewModel
中的 属性 myLiveDataList
手动填充 my_spinner
的内容,如下所示:
my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragments.MyFragment">
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp" />
</FrameLayout>
MyFragment.kt
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.lifecycle.Observer
import com.example.app.R
import com.example.app.data.room.entities.MyEntity
import com.example.app.ui.viewmodels.MyViewModel
import kotlinx.android.synthetic.main.my_fragment.*
class MyFragment : Fragment() {
companion object {
fun newInstance() = MyFragment()
}
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
val myAdapter = ArrayAdapter<MyEntity>(this.context!!, android.R.layout.simple_spinner_item)
// This is where I populate my_spinner
viewModel.myLiveDataList.observe(this, Observer<List<MyEntity>> { data ->
data?.forEach {
myAdapter.add(it)
}
})
my_spinner.adapter = myAdapter
}
}
MyViewModel.kt
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.toLiveData
import com.example.app.data.repositories.MyRepository
import com.example.app.data.room.entities.MyEntity
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val myRepository = MyRepository(application)
val myLiveDataList: LiveData<List<MyEntity>>
get() = myRepository.getAllData().toLiveData()
}
当我导航到 MyFragment
:
my_spinner
由于它按预期填充,我继续对 my_fragment.xml
进行以下更改:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewmodel"
type="com.example.app.ui.viewmodels.MyViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragments.MyFragment">
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp"
app:entries="@{viewmodel.myLiveDataList}"/>
</FrameLayout>
</layout>
我在绑定适配器文件中添加了 BindingAdapterUtil
(以下代码是从 this article 复制的):
import android.R
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import com.example.app.ui.adapter.SpinnerExtensions.getSpinnerValue
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerEntries
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerInverseBindingListener
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerItemSelectedListener
import com.example.app.ui.adapter.SpinnerExtensions.setSpinnerValue
@BindingAdapter("entries")
fun Spinner.setEntries(entries: List<Any>?) {
setSpinnerEntries(entries)
}
@BindingAdapter("onItemSelected")
fun Spinner.setItemSelectedListener(itemSelectedListener: SpinnerExtensions.ItemSelectedListener?) {
setSpinnerItemSelectedListener(itemSelectedListener)
}
@BindingAdapter("newValue")
fun Spinner.setNewValue(newValue: Any?) {
setSpinnerValue(newValue)
}
@BindingAdapter("selectedValue")
fun Spinner.setSelectedValue(selectedValue: Any?) {
setSpinnerValue(selectedValue)
}
@BindingAdapter("selectedValueAttrChanged")
fun Spinner.setInverseBindingListener(inverseBindingListener: InverseBindingListener?) {
setSpinnerInverseBindingListener(inverseBindingListener)
}
@InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged")
fun Spinner.getSelectedValue(): Any? {
return getSpinnerValue()
}
object SpinnerExtensions {
fun Spinner.setSpinnerEntries(entries: List<Any>?) {
if (entries != null) {
val arrayAdapter = ArrayAdapter(context, R.layout.simple_spinner_item, entries)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter = arrayAdapter
}
}
fun Spinner.setSpinnerItemSelectedListener(listener: ItemSelectedListener?) {
if (listener == null) {
onItemSelectedListener = null
} else {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
if (tag != position) {
listener.onItemSelected(parent.getItemAtPosition(position))
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
}
fun Spinner.setSpinnerInverseBindingListener(listener: InverseBindingListener?) {
if (listener == null) {
onItemSelectedListener = null
} else {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
if (tag != position) {
listener.onChange()
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
}
fun Spinner.setSpinnerValue(value: Any?) {
if (adapter != null ) {
val position = (adapter as ArrayAdapter<Any>).getPosition(value)
setSelection(position, false)
tag = position
}
}
fun Spinner.getSpinnerValue(): Any? {
return selectedItem
}
interface ItemSelectedListener {
fun onItemSelected(item: Any)
}
}
而且我已经修改了 MyFragment
中的 onActivityCreated
,如下所示:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
DataBindingUtil.setContentView<MyFragmentBinding>(
this.activity!!, R.layout.my_fragment
).apply {
this.setLifecycleOwner(this@MyFragment)
this.viewmodel = viewModel
}
}
这样做的结果是 my_spinner
不再填充 MyViewModel.myLiveDataList
的内容。为了确定 属性 是否有问题,我在 MyViewModel
中创建了一个新的 属性 ,如下所示:
val myList: List<String>?
get() = listOf("First", "Second", "Third")
并且我已经将 属性 绑定到 my_spinner
就像上面的 MyViewModel.myLiveDataList
一样,这次成功了。
MyRepository.getAllData()
中的函数(其中myLiveDataList
returns)returns一个Flowable<List<MyEntity>>
(RxJava),它调用一个Room DAO来获取数据.我这里的假设是 myLiveDataList
在第一次尝试绑定值时没有任何服务,并且再也不会尝试。
我在尝试将 LiveData 数据源绑定到 Spinner 时是否遗漏了什么?
阅读 my_fragment.xml
修改为以下内容:
...
<data>
<import type="java.util.List" />
<import type="com.example.app.data.room.entities.MyEntity" />
<import type="androidx.lifecycle.LiveData" />
<variable name="viewmodel"
type="com.example.app.ui.viewmodels.MyViewModel" />
<variable name="myTestList"
type="LiveData<List<MyEntity>>" />
</data>
...
<Spinner
android:id="@+id/my_spinner"
android:layout_width="match_parent"
android:layout_height="100dp"
app:entries="@{myTestList}"/>
...
我也删除了 MyFragment.onActivityCreated
的内容并修改了 MyFragment.onCreateView
如下:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
val binding = MyFragmentBinding.inflate(inflater, container, false)
binding.setLifecycleOwner(this)
binding.viewmodel = viewModel
binding.myTestList = viewModel.myLiveDataList
return binding.root
}
这不是一个完美的解决方案,我仍然不知道为什么我在这个问题上的最初回合没有产生预期的结果,但它会做到的。如果有更好的方法以这种方式将 Spinner 绑定到 LiveData,请告诉我。