0

I have a BluetoothService Class which offers BLE Services like scanning and holding a MutableList of known devices. When scanning it adds new devices to the list and posts them with the postValue() function to notify about the change.

My goal is to bring this data to the frontend and listing the found devices. I tried following the android widgets-sample and almost have the same code as there except for replacing the ViewModelFactory as its deprecated. I checked my MutableLiveData List in the debugger and they are in fact up to date. I suspect the observer not being registered correctly, as it fires.

My Recycler View looks like this:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/listFoundDevices"
    android:layout_width="347dp"
    android:layout_height="352dp"
    android:layout_marginTop="28dp"
    android:background="#CD3131"
    android:visibility="visible"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:listitem="@layout/recycler_view_item" />

recycler_view_item:

<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="66dp"
    android:layout_marginTop="3dp"
    android:textColor="@android:color/black"
    android:textSize="20sp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:text="@tools:sample/full_names" />

MainActivity shortened:

private val mainActivityViewModel by viewModels<MainActivityViewModel>()
private val bluetoothService = BluetoothService()

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

    val peripheralAdapter = PeripheralAdapter {peripheral -> adapterOnClick(peripheral)}
    val recyclerView: RecyclerView = findViewById(R.id.listFoundDevices)
    recyclerView.adapter = peripheralAdapter
    recyclerView.layoutManager = LinearLayoutManager(this)

    setSupportActionBar(binding.toolbar)
    mainActivityViewModel.scanLiveData.observe(this) {
        it?.let {
            //Never fires the message despite updating the liveData with postValue()
            println("Got an Update!") 
            peripheralAdapter.submitList(it as MutableList<MyPeripheral>)
        }
    }
}
private fun adapterOnClick(peripheral: MyPeripheral){
    print("clicked on item")
}

ViewModel:

class MainActivityViewModel (private var bluetoothService: BluetoothService): ViewModel() {
    private val repository by lazy { bluetoothService.scanLiveData }
    val scanLiveData = MutableLiveData(repository.value)
}

PeripheralAdapter:

class PeripheralAdapter(private val onClick: (MyPeripheral) -> Unit) :
    ListAdapter<MyPeripheral, PeripheralAdapter.PeripheralViewHolder>(PeripheralDiffCallback) {
    class PeripheralViewHolder(itemView: View, val onClick: (MyPeripheral) -> Unit) :
        RecyclerView.ViewHolder(itemView) {
        private val peripheralTextView: TextView = itemView.findViewById(R.id.textView)
        private var currentPeripheral: MyPeripheral? = null

        init {
            itemView.setOnClickListener {
                currentPeripheral?.let {
                    onClick(it)
                }
            }
        }
        fun bind(peripheral: MyPeripheral) {
            currentPeripheral = peripheral

            peripheralTextView.text = peripheral.name
        }
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PeripheralViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.recycler_view_item, parent, false)
        return PeripheralViewHolder(view, onClick)
    }
    override fun onBindViewHolder(holder: PeripheralViewHolder, position: Int) {
        val peripheral = getItem(position)
        holder.bind(peripheral)
    }
}

BluetoothService:

var knownPeripherals = ArrayList<MyPeripheral>()
var scanLiveData = MutableLiveData(knownPeripherals)

fun handleDevice(result: ScanResult, data: ByteArray?) {
    val peripheral = knownPeripherals.find { it.serialNumberAdv == foundSN }
    if (peripheral == null){
        knownPeripherals.add(MyPeripheral(this, result))
        updateMutableData()
    }
}

private fun updateMutableData(){
    scanLiveData.postValue(knownPeripherals)
}

A hint why it's not working out would be appreciated.

  • 1
    Make `bluetoothService` public in `MainActivityViewModel` then observe `scanLiveData` from `BluetoothService` inside `MainActivity`. Try this and let me know if it works. – Ali Asjad Aug 10 '22 at 11:40
  • do you mean mainActivityViewModel.bluetoothService.scanLiveData.observe or bluetoothService.scanLiveData.observe? Either way, sadly it didn't work, the view stays empty. – Schattennarr Aug 10 '22 at 11:56
  • 1
    Are you sure `handleDevice` function is called? Because that's where the value of LiveData is changed. – Ali Asjad Aug 11 '22 at 04:12
  • yes I am! I tested it in the debugger. Its also kind of the main function in the program at the moment with a debug message and it gets fired constantly. The MutableLiveDataList in BluetoothService() is also of the same address as of the ViewModel. – Schattennarr Aug 11 '22 at 10:59

1 Answers1

1

I finally figured it out and I had several flaws in my code I'd like to walk you through.

private val mainActivityViewModel by viewModels<MainActivityViewModel>() only works if the ViewModel has a zero arguments-constructor, which I didn't have. I'm not sure how to handle this as the ViewModelFactory is deprecated. In my case it didn't matter. The ViewModel looks now like this:

class MainActivityViewModel : ViewModel() {
    val bluetoothService = BluetoothService()
}

With that, my ViewModel can be instantiated the way I did it in the code above and it registers the observers correctly, which it didn't before. Oddly enough it didn't throw me any error at first, but after trying to call mainActivityViewModel.bluetoothService.liveData.hasActiveObservers()

Secondly, the adapter doesn't work with a mutable list, as it seems like doesn't check for the contents of the list. Instead it just checks if its the same list. Thats the case if you always give your list with changed content into it. My answer to it is the following in the onCreate of my MainActivity

mainActivityViewModel.scanLiveData.observe(this) {
    it?.let {
        //now it fires
        println("Got an Update!") 
        peripheralAdapter.submitList(it.toMutableList())
    }
}

It now updates correctly.