0

I'm implementing LiveData to fetch user data to my ProfileFragment. I store the LiveData variable in ViewModel so that ViewModel doesn't return new LiveData everytime it's called.

However the LiveData doesn't update for the second time, it can display user data in the beginning, but when user edit its data/information, the profile text doesn't update but showing blank text (not showing anything).

I assume this is due to Observer never received the right value, but how can this happened?

ProfileFragment

The viewModel.getUserData is called on onViewCreated and everytime user finished editing his data/profile information

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProvider.AndroidViewModelFactory(activity!!.application).create(AboutViewModel::class.java)

        viewModel.getUserData()
        viewModel.userDataLiveData.observe(viewLifecycleOwner, Observer {
            when (it) {
                is Resource.Success -> {
                    text_account_name.text = it.data.name
                    text_account_email.text = it.data.email
                    text_account_phone_number.text = it.data.phoneNumber
                }
                is Resource.Failure -> {
                    when (it.throwable) {
                        is UserDataEmptyException -> startFillDataActivity()
                    }
                }
            }
        })
    }

ViewModel

var userDataLiveData: LiveData<Resource<User>> = MutableLiveData()
fun getUserData() {
        userDataLiveData = DatabaseRepository.getUserData(uid)
    }

DatabaseRepository

fun getUserData(uid: String): LiveData<Resource<User>>{
        val result = MutableLiveData<Resource<User>>().apply {
            value = Resource.Loading()
        }

        userRef.child(uid).addListenerForSingleValueEvent(object : ValueEventListener{
            override fun onDataChange(p0: DataSnapshot) {
                val name = p0.child(FIELD_NAME).value.toString()
                val email = p0.child(FIELD_EMAIL).value.toString()
                val phoneNumber = p0.child(FIELD_PHONE_NUMBER).value.toString()
                val user = User(name, email, phoneNumber)

                if(checkIfUserDataComplete(user)) result.value = Resource.Success(user)
                else result.value = Resource.Failure(UserDataEmptyException("User have no data"))
            }

            override fun onCancelled(p0: DatabaseError) {
                result.value = Resource.Failure(p0.toException())
            }
        })

        return result
    }
  • I think it's because you used `addListenerForSingleValueEvent`. Try `addValueEventListener` instead. – Nicolas May 09 '20 at 14:50
  • @Nicolas please note that: The viewModel.getUserData is called on onViewCreated and everytime user finished editing his data/profile information. That's why I'm using single value event. – Risal Fajar Amiyardi May 09 '20 at 15:48

3 Answers3

0

MustafaKhaled I don't think this is a problem. viewmodelfactory should return the same viewmodel instance, it doesn't matter when executed. Actually this is why we are using this provider.

I think you can find solution for real problem here:

Difference between addValueEventListener() and addListenerForSingleValueEvent() of firebase

  • Please note that: "The viewModel.getUserData is called on onViewCreated **AND everytime** user finished editing his data/profile information". That's why I'm using SingleValueEvent. – Risal Fajar Amiyardi May 09 '20 at 15:50
  • Look, there is a problem in your code, that calling viewModel.getUserData() changes userDataLiveData reference. So at first you start observing live data created at first invocation. But after next invocation, you create another live data. And you will be updating values of that new live data, but you keep observing the new one. – user2418366 May 09 '20 at 16:27
0

Look, there is a problem in your code, that calling viewModel.getUserData() changes userDataLiveData reference. So at first you start observing live data created at first invocation. But after next invocation, you create another live data. And you will be updating values of that new live data, but you keep observing the old one.

You should keep using the same live data, e.g. Repository

private val result: LiveData<Resource<User>> =  MutableLiveData<Resource<User>>()
fun getUserData(uid: String): LiveData<Resource<User>>{
        result.value = Resource.Loading()

        userRef.child(uid).addListenerForSingleValueEvent(object : ValueEventListener{
            override fun onDataChange(p0: DataSnapshot) {
                val name = p0.child(FIELD_NAME).value.toString()
                val email = p0.child(FIELD_EMAIL).value.toString()
                val phoneNumber = p0.child(FIELD_PHONE_NUMBER).value.toString()
                val user = User(name, email, phoneNumber)

                if(checkIfUserDataComplete(user)) result.value = Resource.Success(user)
                else result.value = Resource.Failure(UserDataEmptyException("User have no data"))
            }

            override fun onCancelled(p0: DatabaseError) {
                result.value = Resource.Failure(p0.toException())
            }

Viewmodel

getUserData() = DatabaseRepository.getUserData(uid)

Fragment

getUserData().observe...

This is quick solution, with possible refactors to do, but should resolve the problem.

  • Wouldn't calling ```getUserData().observe``` in the fragment will create a new Observer each time? My idea is to store the LiveData variable in ViewModel so that I just observe it once in ```onViewCreated``` – Risal Fajar Amiyardi May 10 '20 at 06:31
  • It starts observing each time on view created is called. But is fragment recreated before calling getUserData when user finished editing his data/profile information? Could you please paste the code making this call? I suspect there is the same fragment existing, so observing old data, but new livedata after getuserdata is updated with changed value. You can easily give breakpoints to debugger and verify this sequence. – user2418366 May 10 '20 at 09:05
  • I've been trying something, I think the problem rest in the ViewModel, maybe it's illegal to reassign the ViewModel LiveData? What I mean is this: `userDataLiveData = DatabaseRepository.getUserData(uid)` I came into this thinking because after I tried to use *MediatorLiveData*, it does the job – Risal Fajar Amiyardi May 10 '20 at 10:09
  • What I did is I changed the ViewModel into this: `private val _userDataLiveData = MediatorLiveData>()` `var userDataLiveData: LiveData> = _userDataLiveData` `fun getUserData() { val data = DatabaseRepository.getUserData(uid) _userDataLiveData.addSource(data){ if(it !is Resource.Loading) _userDataLiveData.removeSource(data) _userDataLiveData.value = it } }` – Risal Fajar Amiyardi May 10 '20 at 10:10
  • That was my point, don't use again and again another livedata. Yes, it will be much better if livedata in fragment would be val. In current solution you are using one livedata, and now it should be ok. – user2418366 May 10 '20 at 10:56
-1

I guess the problem you are facing, you initialize a new ViewModel instance.

Instead of adding :

        viewModel = ViewModelProvider.AndroidViewModelFactory(activity!!.application).create(AboutViewModel::class.java)

in your onViewCreated() , add it in onCreate(), so your ViewModel is initialized once.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
MustafaKhaled
  • 1,343
  • 9
  • 25