2

This is a chatting app and I am using LiveData to observe new updated messages on Firebase Realtime Database. but when I open its activity, LiveData was called and the last message in Firebase Realtime Databaseadded to the message list in the activity even though any message in Firebase Realtime Database was not added.

My logic is to first get all messages from Realtime Database with getAllMessages function and then get newly updated messages also from Realtime Database with getUpdatedMessage function when others send messages to me but the problem above happened.

I do not know where problem is from livedata itself or addChildEventListener ,,,

How can I avoid the first callback of LiveData after its registration in onCreate?

Activity

class RoomActivity : AppCompatActivity() {

private lateinit var binding : ActivityRoomBinding
private val viewModel : RoomViewModel by viewModel<RoomViewModel>()
private var room : ChatRoomEntity? = ChatRoomEntity()

private val messageList = mutableListOf<MessageEntity>()
private lateinit var adapter: MessageAdapter

override fun onCreate(savedInstanceState: Bundle?) {


    super.onCreate(savedInstanceState)

    binding = DataBindingUtil.setContentView(this, R.layout.activity_room)
    binding.lifecycleOwner = this

    val intent = getIntent()

    room = intent.getSerializableExtra("room") as? ChatRoomEntity

    binding.recyclerMessages.layoutManager = LinearLayoutManager(this)


    adapter = MessageAdapter(messageList)
    binding.recyclerMessages.adapter = adapter

    viewModel.getAllMessage(room!!.chatRoomUid).observe(this, Observer {

        adapter.changeMessages(it)
        adapter.notifyDataSetChanged()

    })
    
    viewModel.getUpdatedMessage(room!!.chatRoomUid).observe(this, Observer { 
        
        adapter.updateMessage(it)
        adapter.notifyDataSetChanged()
    })


    binding.buttonSend.setOnClickListener {

        if (binding.editMessage.text!!.length>0) {

            viewModel.sendMessage(binding.editMessage.text!!.toString(), room!!)

        }
    }

  
} }

ViewModel

class RoomViewModel (

private val repository: ChatRepository

    ) : ViewModel() {




        fun getAllMessage (roomUid : String) : LiveData<MutableList<MessageEntity>> {


            val _messageList = MutableLiveData<MutableList<MessageEntity>>()

            viewModelScope.launch {

                _messageList.postValue(repository.getAllMessages(roomUid))
            }


            return _messageList
        }


        fun sendMessage(contents : String, room : ChatRoomEntity ) {

            repository.sendMessage(contents, room)

        }

        fun getUpdatedMessage(roomUid: String) = repository.getUpdatedMessage(roomUid).asLiveData()           }

DataSourceImpl

override fun getUpdatedMessage(chatRoomUid: String): Flow<MessageEntity> = callbackFlow {


            val reference = databaseReference.child("chat").child("messageList").child(chatRoomUid)

            val subscription = reference.addChildEventListener(object : ChildEventListener{

                    override fun onChildAdded(
                            snapshot: DataSnapshot,
                            previousChildName: String?
                    ) {

                            Log.d("message-get", "getUpdatedMessage-added")
                            trySend(snapshot.getValue(MessageEntity::class.java)!!)

                    }

                    override fun onChildChanged(
                            snapshot: DataSnapshot,
                            previousChildName: String?
                    ) {

                    }

                    override fun onChildRemoved(snapshot: DataSnapshot) {

                    }

                    override fun onChildMoved(
                            snapshot: DataSnapshot,
                            previousChildName: String?
                    ) {

                    }

                    override fun onCancelled(error: DatabaseError) {

                    }

            })

            awaitClose { reference.removeEventListener(subscription) }


    }
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
tring yuo
  • 67
  • 6

1 Answers1

1

When you attach a ChildEventListener to a location in the Realtime Database, the onChildAdded() method fires and provides a DataSnapshot object that contains all the data from the location to which this listener was added. Unfortunately, you cannot skip this from happening. And it makes sense since the data should be downloaded on the device, so you can always have a copy of that data in your cache. This means that if you'll lose the internet connection, you'll be able to continue to use your app.

As a workaround, you can add under each child a Date property (here is how you can add it) and query the location, according to this new property, for all children that have changed since a previous time. In this way, you'll only get what's new, and not all the data.

If you attach a listener to a location that doesn't exist, the onChildAdded() method doesn't fire, since there is no data that should be added to the DataSnapshot object. However, as soon as you add some data under the location the reference is pointing to, the method will immediately fire.

So it's not about the LiveData, it's about how the listener works.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • ok I see,, that was because of listener haha. I should consider another function ,,, or your alternative thanks a lot ! – tring yuo Sep 22 '22 at 09:29
  • 1
    You're very welcome, tring yuo. – Alex Mamo Sep 22 '22 at 09:56
  • 1
    Can I help you with other information regarding this question? – Alex Mamo Sep 22 '22 at 09:57
  • btw, these livedata - flow structure is fine? – tring yuo Sep 22 '22 at 10:28
  • 1
    No. it's not. I already have provided you an [answer](https://stackoverflow.com/questions/73645308/firestore-tasks-whenallcomplete-with-coroutines) to that. – Alex Mamo Sep 22 '22 at 10:32
  • hmm so is there another way to get return from callback method like listener? btw, I have used a coroutine to implement firestore get method synchronously, for example, signin - await - check uid - await - get user info. is it fine...? – tring yuo Sep 22 '22 at 11:04
  • https://stackoverflow.com/q/73813778/19819114 here is my question! – tring yuo Sep 22 '22 at 11:27
  • 1
    Regarding the last question, I already saw you got a really good answer ;) – Alex Mamo Sep 22 '22 at 14:32
  • okay. Actually, I am using a [singleLiveEvent](https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java) to avoid the callback of livedata when configuration change happens. however, I have no idea to connect it to flow on repository and could not find related code in google,,, – tring yuo Sep 23 '22 at 04:24
  • [question](https://stackoverflow.com/questions/73823080/android-kotlin-singleliveevent-with-flow) here is my question! – tring yuo Sep 23 '22 at 04:45
  • 1
    Can I help you with other information regarding this question? – Alex Mamo Sep 23 '22 at 07:15
  • maybe not? haha – tring yuo Sep 23 '22 at 08:25