1

I'm working on an app where a data source is defined in a Provider/Manager. This class (let's call it InfoProvider) is pretty much just a black box - it has some properties, and calls that when executed, result in change of these properties (similar to how a Repository work, but instead of calls returning values, they execute an async call that will result in the change of one or more properties of the provider).

This setup is specifically for Bluetooth Low Energy - we all know how badly managed it is on Android, and I wanted to make it as asynchronous as possible, and use databinding+livedata+viewmodels to achieve a fully responsive architecture.

With Xamarin this would be easy, just define the InfoProvider as a field in the ViewModel, and bind to its fields. However I don't necessarily want to expose all fields in all viewmodels (some might only need the battery status of the device, some might need full access, some might just execute functions without waiting for a response). For functions, it's easy to proxy, but for LiveData<T> I haven't found much information. How would I go forward and "pass around" the LiveData field?

Example:

class InfoProvider {
    var batteryPercent = MutableLiveData<Int>()

    public fun requestBatteryUpdate() {
        [...]
        batteryPercent.value = newValue
    }
}

// ViewModel for accessing device battery, inheriting from architecture ViewModel
class DeviceBatteryViewModel: ViewModel() {
    var batteryPercentage = MutableLiveData<Int>()
    val infoProvider: InfoProvider by inject()

    init {
        // TODO: Subscribe this.batteryPercentage to infoProvider.batteryPercent

    fun onButtonClick() {
        infoProvider.requestBatteryUpdate()
    }
}

class DeviceBatteryFragment: Fragment() {
    val ViewModel: DeviceBatteryViewModel by inject()
    private lateinit var binding: DeviceBatteryBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
            DeviceBatteryBinding.inflate(inflater, container, false).also { binding = it }.root

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.viewModel = this.ViewModel
    }

}


// res/layout/fragment_devicebattery.xml

<layout [namespaces]>
    <data class="bindings.DeviceBatteryBinding>
        <variable android:name="viewModel" type=".DeviceBatteryViewModel />
    </data>

    <WhatEverLayout [...]>
        <TextView [...] android:text="@{viewModel.batteryPercentage.toString()}" />
        <Button [...] android:onClick="@{() -> viewModel.onButtonClick()}" />
    </WhatEverLayout>
</layout>

What I'd like to avoid is the Rx-style .observe(() -> {}), .subscribe(() -> {}) etc. kind of exchanges. Can this be done (i.e. if I assign the value of infoProvider.batteryPercent to the VM's batteryPercentage field, will it also receive updates), or should I bind directly to the infoProvider?

fonix232
  • 2,132
  • 6
  • 39
  • 69
  • One option to replace the Rx-style `.observe(() -> {})`, `.subscribe(() -> {})` is to use the library "ObservableViewModel". It is easy to implement and give you the advantage of not manage the observables by you. [The library!](https://github.com/ludusdeveloper/ObservableViewModel) – Esteban Higuita Duarte Aug 14 '19 at 16:51
  • Did you just seriously link a .Net library under an Android question? – fonix232 Aug 28 '19 at 10:39

1 Answers1

0

There is no way to "pass around" the LiveData field without calling batteryPercent.observe(...). Additionally, you will need to use a Lifecycler Owner to Observe the field (unless you want to ObserveForever which is not a recommended solution).

My suggestion would be something like this:

InfoProvider {
    val repositoryBatteryUpdate = BehaviorSubject.create<Int>()

    fun observeRepositoryBatteryUpdate(): Observable<Int> {
        return repositoryBatteryUpdate
    }

    fun requestBatteryUpdate(){
        // Pseudo code for actually update

        // Result goes into repositoryBatteryUpdate.onNext(...)
    }
}

ViewModel{
    val status: MutableLiveData<Int>

    init{
        repository.observeRepositoryItems()
            .subscribe( update -> status.postValue(status))
    }

    fun update(){
        repository.requestBatteryUpdate()
    }
}

Fragment{
    viewModel.status.observe() // <-- Here you observe Updates

    viewModel.update()
}

Note that you will have to dispose the subscription in the ViewModel onCleared.
Note that all of this is pseudo code and it should be done a lot cleaner than this.

Bam
  • 478
  • 6
  • 19
  • In the meantime I realized that the VM's field can be just a forward-pointing property, by simply making `val batteryPercentage = infoProvider.batteryPercent`. Or if conversion is required, the VM's field can be a MediatorLiveData, observing the infoProvider (without a lifecycle owner at that!), making my original idea quite usable. – fonix232 Jan 18 '19 at 13:37