Как написать адаптер привязки для спиннера, который получает LiveData в качестве ввода? - PullRequest
0 голосов
/ 21 апреля 2019

У меня есть спиннер, определенный в XML как это

<Spinner
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/expense_category"
        app:sourceData="@{()->createExpenseViewModel.getAllSourceItems(1)}"
        app:layout_constraintStart_toStartOf="@+id/textView"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintWidth_percent="0.7"
/>

createExpenseViewModel.getAllSourceItems (1) этот метод возвращает LiveData >, поэтому я написал адаптер привязки для этого случая

@BindingAdapter("app:sourceData")
fun setSourceData(spinner: Spinner, sourceList: List<Source>) {

    val categoryItems = ArrayList<String>()
    categoryItems.addAll(sourceList.map { it.sourceName })
    val spinnerAdapter =
        ArrayAdapter<String>(spinner.context, R.layout.simple_spinner_dropdown_item, categoryItems)
    spinner.adapter = spinnerAdapter


}

при создании приложения я получаю следующую ошибку, ****/ data binding error ****msg:Cannot find the proper callback class for app:sourceData. Tried java.util.List but it has 25 abstract methods, should have 1 abstract methods. file:/home/naveen/Desktop/project-expense/app/src/main/res/layout/activity_create_expense.xml loc:94:34 - 94:80 ****\ data binding error ****

что на самом деле означает эта ошибка, как ее устранить?

Изменить:

что я собираюсь сделать, это получить список, возвращенный живыми данными, и преобразовать его в тип ArrayList, мне нужно, чтобы мой адаптер привязки был запущен после того, как liveata вернет список, но если я использую это приложение: sourceData = "@ {createExpenseViewModel .getAllSourceItems (1)} "и установите адаптер привязки, адаптер получит только нулевой список

Ответы [ 2 ]

1 голос
/ 21 апреля 2019

Вы связываете метод с app:sourceData, но вы ожидаете переменную для него в адаптере привязки.Это не может работать.Я думаю, вы хотите заполнить список в Spinner.Для этого я хотел бы создать свойство в вашей viewModel и связать это свойство в XML.Я сделал это в приложении, где у меня был список проектов для отображения в Spinner.Вот код, включающий InverseBindingAdapter для автоматического сохранения выбранного проекта в другой переменной ViewModel.

ViewModel :

// getProjects() returns the LiveData
val projects = metaDataRepository.getProjects() 

// use _selectedProject only within ViewModel. Do not expose MediatorLiveData to UI.
// in UI observe selectedProject
private val _selectedProject = MediatorLiveData<Project>()
val selectedProject: LiveData<Project>
    get() = _selectedProject 

Layout XML :

<Spinner
    android:id="@+id/spProjects"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:projects="@{viewModel.projects}"
    app:selectedProject="@={viewModel.selectedProject}" />

BindingAdapter (для заполнения данных из viewModel в пользовательский интерфейс):

/**
 * fill the Spinner with all available projects.
 * Set the Spinner selection to selectedProject.
 * If the selection changes, call the InverseBindingAdapter
 */
@BindingAdapter(value = ["projects", "selectedProject", "selectedProjectAttrChanged"], requireAll = false)
fun setProjects(spinner: Spinner, projects: List<Project>?, selectedProject: Project, listener: InverseBindingListener) {
    if (projects == null) return
    spinner.adapter = ProjectAdapter(spinner.context, android.R.layout.simple_spinner_dropdown_item, projects)
    setCurrentSelection(spinner, selectedProject)
    setSpinnerListener(spinner, listener)
}

Вспомогательные методы для BindingAdapter :

private fun setSpinnerListener(spinner: Spinner, listener: InverseBindingListener) {
    spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
        override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = listener.onChange()
        override fun onNothingSelected(adapterView: AdapterView<*>) = listener.onChange()
    }
}

private fun setCurrentSelection(spinner: Spinner, selectedItem: Project?): Boolean {
    if (selectedItem == null) {
        return false
    }

    for (index in 0 until spinner.adapter.count) {
        val currentItem = spinner.getItemAtPosition(index) as Project
        if (currentItem.name == selectedItem.name) {
            spinner.setSelection(index)
            return true
        }
    }

    return false
}

Простой адаптер для вашего счетчика .Измените это для своих нужд:

/**
 * Adapter for displaying the name-field of an Project in a Spinner
 */
class ProjectAdapter(context: Context, textViewResourceId: Int, private val values: List<Project>) : ArrayAdapter<Project>(context, textViewResourceId, values) {

    override fun getCount() = values.size
    override fun getItem(position: Int) = values[position]
    override fun getItemId(position: Int) = position.toLong()

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val label = super.getView(position, convertView, parent) as TextView
        label.text = values[position].name
        return label
    }

    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
        val label = super.getDropDownView(position, convertView, parent) as TextView
        label.text = values[position].name
        return label
    }
}

InverseBindingAdapter (для сохранения выбранного элемента Spinner в viewModel)

/**
 * get the selected projectName and use it to return a
 * Project which is then used to set appEntry.value.project
 */
@InverseBindingAdapter(attribute = "selectedProject")
fun getSelectedProject(spinner: Spinner): Project {
    return spinner.selectedItem as Project
}
0 голосов
/ 22 апреля 2019

Я следовал основной идее того, что предложил @muetzenflo, я создал свойство для модели представления, подобную этой

class MainViewModel @Inject constructor(

    val expenseSourceItems:LiveData<List<Source>> = getAllSourceItems(1)

        fun getAllSourceItems(sourceType:Int?): LiveData<List<Source>> {
        val res = sourceRepository.getAllSourceItems(sourceType)
        return res
    }

    // the methods below are omitted for brevity


}

, затем я привязался к счетчику, используя синтаксис доступа к свойству

<Spinner
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/expense_category"
        app:sourceData="@{createExpenseViewModel.expenseSourceItems}"
        app:layout_constraintStart_toStartOf="@+id/textView"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintWidth_percent="0.7"
/>

, а затем использовал тот же адаптер привязки

@BindingAdapter("app:sourceData")
fun setSourceData(spinner: Spinner, sourceList: List<Source>) {

    val categoryItems = ArrayList<String>()
    categoryItems.addAll(sourceList.map { it.sourceName })
    val spinnerAdapter =
        ArrayAdapter<String>(spinner.context, R.layout.simple_spinner_dropdown_item, categoryItems)
    spinner.adapter = spinnerAdapter


}

для оперативных данных. Вызов метода внутри привязки данных работает только для обратных вызовов, таких как onclick, а доступ к свойству необходим для использования для нормальной привязки данных, такой как заполнение.прядильщик.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...