Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is observeForever lifecycle aware?

I'm working with MVVM, and I have made different implementations of it, but one thing that is still making me doubt is how do I get data from a Repository (Firebase) from my ViewModel without attaching any lifecycle to the ViewModel.

I have implemented observeForever() from the ViewModel, but I don't think that is a good idea because I think I should communicate from my repository to my ViewModel either with a callback or a Transformation.

I leave here an example where I fetch a device from Firebase and update my UI, if we can see here, I'm observing the data coming from the repo from the UI, but from the ViewModel I'm also observing data from the repo, and here is where I really doubt if I'm using the right approach, since I don't know if observeForever() will be cleared on onCleared() if my view is destroyed, so it won't keep the observer alive if the view dies.

UI

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val deviceId = editText.text.toString().trim()
            observeData(deviceId)
        }
    }

    fun observeData(deviceId:String){
        viewModel.fetchDeviceData(deviceId).observe(this, Observer {
            textView.text = "Tipo: ${it.devType}"
        })

ViewModel

class MainViewmodel: ViewModel() {

    private val repo = Repo()
    fun fetchDeviceData(deviceId:String):LiveData<Device>{
        val mutableData = MutableLiveData<Device>()
        repo.getDeviceData(deviceId).observeForever {
            mutableData.value = it
        }

        return mutableData
    }
}

Repository

class Repo {

    private val db = FirebaseDatabase.getInstance().reference
    fun getDeviceData(deviceId:String):LiveData<Device>{
        val mutableData = MutableLiveData<Device>()
        db.child(deviceId).child("config/device").addListenerForSingleValueEvent(object: ValueEventListener{

            override fun onDataChange(dataSnapshot: DataSnapshot) {
                    val device = dataSnapshot.getValue(Device::class.java)
                    mutableData.value = device
            }

            override fun onCancelled(dataError: DatabaseError) {
                Log.e("Error","handle error callback")
            }
        })

        return mutableData
    }
}

This example just shows how to fetch the device from Firebase, it works, but from my ViewModel, it keeps making me think that observeForever() is not what I'm looking for to communicate data between the repository to the ViewModel.

I have seen Transformations, but I, in this case, I just need to deliver the entire Device object to my UI, so I don't need to transform the Object I'm retrieving to another Object

What should be here the right approach to communicate the repository and the ViewModel properly?

like image 832
Gastón Saillén Avatar asked Nov 29 '19 15:11

Gastón Saillén


People also ask

What is ObserveForever?

added in version 1.0.0. void observeForever (Observer<T> observer) Adds the given observer to the observers list. This call is similar to observe(LifecycleOwner, Observer) with a LifecycleOwner, which is always active. This means that the given observer will receive all events and will never be automatically removed.

Is LiveData lifecycle aware?

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.

Is ViewModel Life Cycle Aware?

Lifecycle Awareness :Viewmodel is lifecycle aware. It is automatically cleared when the lifecycle they are observing gets permanently destroyed.

What does lifecycle aware mean?

Lifecycle-aware components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments. These components help you produce better-organized, and often lighter-weight code, that is easier to maintain.


2 Answers

is observeForever lifecycle aware?

No, that's why it's called observeForever.

I have implemented observeForever() from the ViewModel, but I don't think that is a good idea

No, it's not, you should be using Transformations.switchMap {.

since I don't know if observeForever() will be cleared on onCleared() if my view is destroyed, so it won't keep the observer alive if the view dies.

Well if you're not clearing it in onCleared() using removeObserver(observer), then it won't clear itself, because it observes forever.

here is where I really doubt if I'm using the right approach,

No, you can do much better than this following a reactive approach.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    button.setOnClickListener {
        val deviceId = editText.text.toString().trim()
        viewModel.onSelectedDeviceChanged(deviceId)
    }

    viewModel.selectedDevice.observe(this, Observer { device ->
        textView.text = "Tipo: ${device.devType}"
    })
}

And

class MainViewModel(
    private val savedStateHandle: SavedStateHandle,
): ViewModel() {
    private val repo = Repo() // TODO: move to Constructor Argument with ViewModelProvider.Factory

    private val selectedDeviceId: MutableLiveData<String> = savedStateHandle.getLiveData<String>("selectedDeviceId")

    fun onSelectedDeviceChanged(deviceId: String) {
        selectedDeviceId.value = deviceId
    }

    val selectedDevice = Transformations.switchMap(selectedDeviceId) { deviceId ->
        repo.getDeviceData(deviceId)
    }
}

And

class Repo {
    private val db = FirebaseDatabase.getInstance().reference // TODO: move to constructor arg? Probably

    fun getDeviceData(deviceId:String) : LiveData<Device> {
        return object: MutableLiveData<Device>() {
            private val mutableLiveData = this

            private var query: Query? = null
            private val listener: ValueEventListener = object: ValueEventListener {
                override fun onDataChange(dataSnapshot: DataSnapshot) {
                    val device = dataSnapshot.getValue(Device::class.java)
                    mutableLiveData.value = device
                }

                override fun onCancelled(dataError: DatabaseError) {
                    Log.e("Error","handle error callback")
                }
            }

            override fun onActive() {
                query?.removeEventListener(listener)
                val query = db.child(deviceId).child("config/device")
                this.query = query
                query.addValueEventListener(listener)
            }
    
            override fun onInactive() {
                query?.removeEventListener(listener)
                query = null
            }
        }
    }
}

This way, you can observe for changes made in Firebase (and therefore be notified of future changes made to your values) using LiveData, rather than only execute a single fetch and then not be aware of changes made elsewhere to the same data.

like image 113
EpicPandaForce Avatar answered Oct 21 '22 16:10

EpicPandaForce


observeForever() is not Lifecycle aware and will continue to run until removeObserver() is called. In your ViewModel do this instead,

class MainViewmodel: ViewModel() {

    private val repo = Repo()
    private var deviceData : LiveData<Device>? = null
    fun fetchDeviceData(deviceId:String):LiveData<Device>{
        deviceData = repo.getDeviceData(deviceId)
        return deviceData!!
    }
}
like image 33
Networks Avatar answered Oct 21 '22 17:10

Networks