Unable to bind LiveData List to Spinner entries

Multi tool use
Multi tool use












0















I have a Fragment MyFragment currently which has a Spinner my_spinner. For testing my app, I originally populated the contents of my_spinner manually by observing the property myLiveDataList in the AndroidViewModel MyViewModel as below:



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()
}


This fills my_spinner successfully when I navigate to MyFragment:



App before making data-binding changes



Since it populates as expected, I went ahead to make the following changes to 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>


I've added in a Binding Adapter file BindingAdapterUtil (following code was copied from 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)
}
}


And I've modified the onActivityCreated in MyFragment like so:



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
}
}


The result of this is that my_spinner is no longer populating with the contents of MyViewModel.myLiveDataList. To try to ascertain if the property was at fault, I created a new property in MyViewModel like so:



val myList: List<String>?
get() = listOf("First", "Second", "Third")


And I have bound this property to my_spinner just like MyViewModel.myLiveDataList above with success this time.



The function in MyRepository.getAllData() (which myLiveDataList returns) returns a Flowable<List<MyEntity>> (RxJava), which calls a Room DAO to get the data. My assumption here is that myLiveDataList doesn't have anything to serve when it tries to bind the values for the first time, and never tries again.



Am I missing something when trying to bind a LiveData datasource to a Spinner?










share|improve this question





























    0















    I have a Fragment MyFragment currently which has a Spinner my_spinner. For testing my app, I originally populated the contents of my_spinner manually by observing the property myLiveDataList in the AndroidViewModel MyViewModel as below:



    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()
    }


    This fills my_spinner successfully when I navigate to MyFragment:



    App before making data-binding changes



    Since it populates as expected, I went ahead to make the following changes to 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>


    I've added in a Binding Adapter file BindingAdapterUtil (following code was copied from 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)
    }
    }


    And I've modified the onActivityCreated in MyFragment like so:



    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
    }
    }


    The result of this is that my_spinner is no longer populating with the contents of MyViewModel.myLiveDataList. To try to ascertain if the property was at fault, I created a new property in MyViewModel like so:



    val myList: List<String>?
    get() = listOf("First", "Second", "Third")


    And I have bound this property to my_spinner just like MyViewModel.myLiveDataList above with success this time.



    The function in MyRepository.getAllData() (which myLiveDataList returns) returns a Flowable<List<MyEntity>> (RxJava), which calls a Room DAO to get the data. My assumption here is that myLiveDataList doesn't have anything to serve when it tries to bind the values for the first time, and never tries again.



    Am I missing something when trying to bind a LiveData datasource to a Spinner?










    share|improve this question



























      0












      0








      0








      I have a Fragment MyFragment currently which has a Spinner my_spinner. For testing my app, I originally populated the contents of my_spinner manually by observing the property myLiveDataList in the AndroidViewModel MyViewModel as below:



      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()
      }


      This fills my_spinner successfully when I navigate to MyFragment:



      App before making data-binding changes



      Since it populates as expected, I went ahead to make the following changes to 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>


      I've added in a Binding Adapter file BindingAdapterUtil (following code was copied from 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)
      }
      }


      And I've modified the onActivityCreated in MyFragment like so:



      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
      }
      }


      The result of this is that my_spinner is no longer populating with the contents of MyViewModel.myLiveDataList. To try to ascertain if the property was at fault, I created a new property in MyViewModel like so:



      val myList: List<String>?
      get() = listOf("First", "Second", "Third")


      And I have bound this property to my_spinner just like MyViewModel.myLiveDataList above with success this time.



      The function in MyRepository.getAllData() (which myLiveDataList returns) returns a Flowable<List<MyEntity>> (RxJava), which calls a Room DAO to get the data. My assumption here is that myLiveDataList doesn't have anything to serve when it tries to bind the values for the first time, and never tries again.



      Am I missing something when trying to bind a LiveData datasource to a Spinner?










      share|improve this question
















      I have a Fragment MyFragment currently which has a Spinner my_spinner. For testing my app, I originally populated the contents of my_spinner manually by observing the property myLiveDataList in the AndroidViewModel MyViewModel as below:



      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()
      }


      This fills my_spinner successfully when I navigate to MyFragment:



      App before making data-binding changes



      Since it populates as expected, I went ahead to make the following changes to 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>


      I've added in a Binding Adapter file BindingAdapterUtil (following code was copied from 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)
      }
      }


      And I've modified the onActivityCreated in MyFragment like so:



      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
      }
      }


      The result of this is that my_spinner is no longer populating with the contents of MyViewModel.myLiveDataList. To try to ascertain if the property was at fault, I created a new property in MyViewModel like so:



      val myList: List<String>?
      get() = listOf("First", "Second", "Third")


      And I have bound this property to my_spinner just like MyViewModel.myLiveDataList above with success this time.



      The function in MyRepository.getAllData() (which myLiveDataList returns) returns a Flowable<List<MyEntity>> (RxJava), which calls a Room DAO to get the data. My assumption here is that myLiveDataList doesn't have anything to serve when it tries to bind the values for the first time, and never tries again.



      Am I missing something when trying to bind a LiveData datasource to a Spinner?







      android kotlin android-databinding android-livedata






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Dec 31 '18 at 12:37







      Hayden

















      asked Dec 31 '18 at 11:45









      HaydenHayden

      603515




      603515
























          1 Answer
          1






          active

          oldest

          votes


















          0














          After reading this answer, I've modified my_fragment.xml to the following:



          ...
          <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&lt;List&lt;MyEntity&gt;&gt;" />
          </data>
          ...
          <Spinner
          android:id="@+id/my_spinner"
          android:layout_width="match_parent"
          android:layout_height="100dp"
          app:entries="@{myTestList}"/>
          ...


          I've also removed the contents of MyFragment.onActivityCreated and modified MyFragment.onCreateView as followed:



          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
          }


          Not a perfect solution, and I still don't know why my original bout at this problem didn't yield the desired results, but it will do. If there is a better way of binding a Spinner to LiveData in this fashion, please let me know.






          share|improve this answer

























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53987100%2funable-to-bind-livedata-list-to-spinner-entries%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            0














            After reading this answer, I've modified my_fragment.xml to the following:



            ...
            <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&lt;List&lt;MyEntity&gt;&gt;" />
            </data>
            ...
            <Spinner
            android:id="@+id/my_spinner"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            app:entries="@{myTestList}"/>
            ...


            I've also removed the contents of MyFragment.onActivityCreated and modified MyFragment.onCreateView as followed:



            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
            }


            Not a perfect solution, and I still don't know why my original bout at this problem didn't yield the desired results, but it will do. If there is a better way of binding a Spinner to LiveData in this fashion, please let me know.






            share|improve this answer






























              0














              After reading this answer, I've modified my_fragment.xml to the following:



              ...
              <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&lt;List&lt;MyEntity&gt;&gt;" />
              </data>
              ...
              <Spinner
              android:id="@+id/my_spinner"
              android:layout_width="match_parent"
              android:layout_height="100dp"
              app:entries="@{myTestList}"/>
              ...


              I've also removed the contents of MyFragment.onActivityCreated and modified MyFragment.onCreateView as followed:



              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
              }


              Not a perfect solution, and I still don't know why my original bout at this problem didn't yield the desired results, but it will do. If there is a better way of binding a Spinner to LiveData in this fashion, please let me know.






              share|improve this answer




























                0












                0








                0







                After reading this answer, I've modified my_fragment.xml to the following:



                ...
                <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&lt;List&lt;MyEntity&gt;&gt;" />
                </data>
                ...
                <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                app:entries="@{myTestList}"/>
                ...


                I've also removed the contents of MyFragment.onActivityCreated and modified MyFragment.onCreateView as followed:



                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
                }


                Not a perfect solution, and I still don't know why my original bout at this problem didn't yield the desired results, but it will do. If there is a better way of binding a Spinner to LiveData in this fashion, please let me know.






                share|improve this answer















                After reading this answer, I've modified my_fragment.xml to the following:



                ...
                <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&lt;List&lt;MyEntity&gt;&gt;" />
                </data>
                ...
                <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                app:entries="@{myTestList}"/>
                ...


                I've also removed the contents of MyFragment.onActivityCreated and modified MyFragment.onCreateView as followed:



                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
                }


                Not a perfect solution, and I still don't know why my original bout at this problem didn't yield the desired results, but it will do. If there is a better way of binding a Spinner to LiveData in this fashion, please let me know.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Jan 1 at 9:14

























                answered Jan 1 at 0:01









                HaydenHayden

                603515




                603515






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53987100%2funable-to-bind-livedata-list-to-spinner-entries%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    ZAe YEnJv x,sxvRUKl,N,IHAq,d MYj KoR qzuXA9JEl,870B8awc
                    l,EOZip,ZVN58IT0LBSoHFj1i kHw8hYjEQviGD76yr,X 7fSni H xMDRnawWbfmOnuWr8io,0

                    Popular posts from this blog

                    Monofisismo

                    Angular Downloading a file using contenturl with Basic Authentication

                    Olmecas