Realtime DB persistence distorts data display in order - android

When I open an app I display a list of groups which have name, creationDate and etc. One of the fields is recentMessage, which value is ID which points to messages collection's message. I get that recent message by:
fun loadRecentMessage(param: (Message) -> Unit) {
reference.child("/messages/$groupd/messages/$messageid")
.addValueEventListener(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.value != null) {
// If I had a list of ["1", "2", "3", "4", "5"]
// 'messageid' will show to message "5"
// and param will be invoked with it.
(snapshot.getValue(Message::class.java)?.let { param.invoke(it) })
}
}
override fun onCancelled(error: DatabaseError) {
// Log error
}
})
}
Now when I press on group, I navigate to messages fragment and attach childEventListener to messages and display them:
fun loadMsgs(data: MutableLiveData<MutableList<Message>>) {
reference
.child("/messages/$groupid/messages/${messageid}")
.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// If I have a list of messages: ["1", "2", "3", "4", "5"]
// and I called loadRecentMessage() before, my snapshot values comes in this order:
// "5", "1", "2", "3", "4", "5"
// If I don't call that method or that method uses get(), my messages
// order comes in good order
snapshot.getValue(Message::class.java)?.let { data.addAsynchronous(it) }
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Empty
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Empty
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Empty
}
override fun onCancelled(error: DatabaseError) {
// Log error
}
}
)
}
The problem is, that my recent message, that I have loaded before, shows up first in the list, even though, it's last. When I use get() instead of addValueEventListener() to load recent message everything is fine, but I need to keep that up to date. I think it's because the persistence had my recent message saved, thus it loads it first. How to display my messages in order, even though I have one message loaded before?
UPDATE 1:
// This is where param.invoke(it) comes in work:
database.getRecentMessage(groupid) {
groupsMutableValue.addAsynchronous(it)
}
// addAsynchronous() method
fun <T> MutableLiveData<MutableList<T>>.addAsynchronous(item: T) {
val newList = mutableListOf<T>()
this.value?.let { newList.addAll(it) }
newList.add(item)
this.postValue(newList)
}
// message list from DB
"messages" : {
"-MXSkzXdm2OxV6CC3Xl3" : {
"messages" : {
"-MXVeBEq5ksZfPn4BoDB" : {
"content" : "Hi",
"from" : "2YnfQSeBladXsD9sPEcKrhxJ6CB2"
},
"-MXVeJ5MBOOzl53kEEC1" : {
"content" : "Hey",
"from" : "2g87tAMuQSOgu7b1W0uOvKDpg3K2"
},
"-MXVeR6vPi7KZ8tC52EP" : {
"content" : "Hola",
"from" : "iLOmzFZqzVRtMb5eOC1gRRWY5Ar2"
},
"-MXXlQVAhbT6JBJbpIRt" : {
"content" : "Last message",
"from" : "2YnfQSeBladXsD9sPEcKrhxJ6CB2"
}
}
}

Related

App crash after sending message to Firebase Database?

So I use Firebase Realtime Database for messaging feature and when I send a message, the following error comes out, and the app crash. Later on, the chatting activity keeps crashing, and need to delete the message on Firebase manually to be able to reopen the chatting activity.
2022-07-19 22:56:27.078 21525-21525/com.example.gesit E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.gesit, PID: 21525
com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.String to type com.example.gesit.ChatMessage
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertBean(CustomClassMapper.java:436)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToClass(CustomClassMapper.java:232)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:80)
at com.google.firebase.database.DataSnapshot.getValue(DataSnapshot.java:203)
at com.example.gesit.Chatting$listenforMessages$1.onChildAdded(Chatting.kt:86)
at com.google.firebase.database.core.ChildEventRegistration.fireEvent(ChildEventRegistration.java:79)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Reading message code:
private fun listenforMessages() {
val fromId = FirebaseAuth.getInstance().uid
val toId = toUser?.uid
val ref = FirebaseDatabase.getInstance().getReference("/pesan-pengguna/$fromId/$toId")
ref.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val chatMessage = snapshot.getValue(ChatMessage::class.java)
if (chatMessage != null) {
Log.d(TAG, chatMessage?.text!!)
if (chatMessage.fromId == FirebaseAuth.getInstance().uid) {
val currentUser = MainScreen.currentUser
adapter.add(ChatItemRight(chatMessage.text, currentUser ?: return))
} else {
adapter.add(ChatItemLeft(chatMessage.text, toUser!!))
}
}
}
override fun onCancelled(error: DatabaseError) {
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
}
})
}
JSON DB for messages:
{
"pesan-pengguna": {
"P6scAzLXLcOsDiChjMAXOgnBnar1": {
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7Lf8PyUg9jWKmAv6zr",
"text": "Test 1 2 3",
"timestamp": 1658237785,
"toId": "P6scAzLXLcOsDiChjMAXOgnBnar1"
}
},
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"P6scAzLXLcOsDiChjMAXOgnBnar1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7Lf8PyUg9jWKmAv6zr",
"text": "Test 1 2 3",
"timestamp": 1658237785,
"toId": "P6scAzLXLcOsDiChjMAXOgnBnar1"
},
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7LfGGFTKerqT3Lma8Y",
"text": "Test 1 2 3",
"timestamp": 1658237818,
"toId": "nrX4w334X4R4kOyXHfp6rSFPWYv1"
}
}
}
}
Code for ChatMessage:
class ChatMessage(val id: String, val text: String, val fromId: String, val toId: String, val timestamp: Long) {
constructor() : this("","","","", -1)
}
When you're using the following reference:
val ref = FirebaseDatabase.getInstance().getReference("/pesan-pengguna/$fromId/$toId")
And when you attach a ChildEventListener on it, it means that you're trying to read all children that exist within that reference. Now, inside the onChildAdded method you're trying to convert each element into an object of type ChatMessage, which is actually not possible since the children under that node are strings, hence that error. If you want to read a ChatMessage object, please use addListenerForSingleValueEvent, as you can see in the following lines of code:
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val chatMessage = snapshot.getValue(ChatMessage::class.java)
Log.d("TAG", chatMessage.text)
}
override fun onCancelled(error: DatabaseError) {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
ref.addListenerForSingleValueEvent(valueEventListener)
The result in the logcat will be:
Test 1 2 3
Always remember, that Firebase Realtime Database queries work on a flat list. The objects that you want to read must be in a fixed location under each direct child node. If you keep your actual database schema, you won't be able to read the messages that belong to a particular formId. However, this topic was covered several times before, here on StackOverflow. So you should consider creating a schema where all messages exist under a direct node.

Check how many messages have been sent

I am making a messenger app, I am using firebase as my database, and I am using MVVM pattern.
I want to check if the user have new unread messages and display the number of messages near the person who sent it.
So in the MainActivity there is a RecyclerView filled with users.
This is the user model( https://ibb.co/w72K17m ).
I have a "seen" key,which means the user haven't read the message yet,in addition, I also have "reciever" which contains the current user UID. With those values I tried to do the followings:
chatRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val messageArray = ArrayList<String?>()
val usersArray = ArrayList<String?>()
for (snapshot2 in snapshot.children) {
val chat = snapshot2.getValue(Message::class.java) // Getting all the messages with values, like the photo.
if (chat!!.receiver.equals(currentUser.value!!.uid)) { // Check if the current user is the reciever
if (chat.seen == false){ // Check if the message is seen
messageArray.add(chat.message)
usersArray.add(chat.sender)
}
}
}
Log.e("Chat","messageArray: $messageArray")
Log.e("Chat","usersArray: $usersArray")
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
This is the output:
messageArray: [message1, message2 , message3, message4, message5 , message6]
usersArray: [4RDojKDSJZUOfpNky9MxuLHQJN63, 4RDojKDSJZUOfpNky9MxuLHQJN63, iromTJzrZCQVJnjcLhhdUjXi5bP2, iromTJzrZCQVJnjcLhhdUjXi5bP2, iromTJzrZCQVJnjcLhhdUjXi5bP2, iromTJzrZCQVJnjcLhhdUjXi5bP2]
I am getting all the messages and all the users who sent it to me,but I don't know how to check how many messages each ID sent.
I tried using hashMap but with no success..
I used HashMap after all, this is what I did:
fun searchNumberOfMessages(user: User){
val hasMap = HashMap<String, Any?>()
hasMap.clear()
hashMapArray.clear()
if (newMessages.value != null){
(newMessages.value as ArrayList<HashMap<String, Any?>>).clear()
}
chatRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val messageArray = ArrayList<String?>()
for (snapshot2 in snapshot.children) {
val chat = snapshot2.getValue(Message::class.java)
if (chat!!.receiver.equals(currentUser!!.uid) && chat.sender.equals(
user.uid)) {
if (chat.seen == false){
messageArray.add(chat.message)
}
}
}
hasMap["Sender"] = user
hasMap["Messages"] = messageArray.size
hashMapArray.add(hasMap)
newMessages.value = hashMapArray
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}

Doesn't stay on Dashboard and goes back to log in activity

val uid = FirebaseAuth.getInstance().currentUser!!.uid
val ref = FirebaseDatabase.getInstance().getReference("/users/$uid")
val uidRef = ref.child("users").child(uid)
val valueEventListener: ValueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.child("type").getValue(String::class.java) == "a") {
val intent = Intent(this#LoginActivity, DashBoardActivity::class.java)
intent.flags=Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else if (dataSnapshot.child("type").getValue(String::class.java) == "b") {
val intent = Intent(this#LoginActivity, DashBoardActivity::class.java)
intent.flags=Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else if (dataSnapshot.child("type").getValue(String::class.java) == "c") {
val intent = Intent(this#LoginActivity, AdminDashBoard::class.java)
intent.flags=Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("error", databaseError.message);
}
}
uidRef.addListenerForSingleValueEvent(valueEventListener)
}
Okay, I have gotten it to work. No nullpoint, No Crash. The code works, but for someone reason it doesn't stay on the dashboard and goes back to the log in page. Anyway of sorting this out because I am clueless on how to solve this. the login action occurs but doesn't change to another Activity. It Pops up but then goes back to LoginActivity.
D/AutofillManager: onActivityFinishing(): calling cancelLocked()
This is the call that is last made then stop before full opening the n going back to login screen
My database structure is like this
{
"users" : {
"H8pviFh1GVTLYZIrlCt7Xvx6nRE3" : {
"dateofbirth" : "",
"email" : "",
"firstname" : "bilal",
"lastname" : "dar",
"mobile" : "",
"type" : "a",
"uid" : "H8pviFh1GVTLYZIrlCt7Xvx6nRE3"
}
}
}
This is my firebase database structure

Customize onError() Message in Rx java from server API error output

I wanted to print the error which API message send, Like there are many kinds of them example: Email already present, Username already exits etc.
But this print only HTTP 400 Bad Request
fun doSignUp(
firstName: String,
lastName: String,
email: String,
password: String,
deviceId: String
) {
ApiHelper().userSignup(
firstName,
lastName,
email,
password,
"Android",
strAdd)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<UserResponse>{
override fun onComplete() {
Log.d("kb", "onComplete Call")
}
override fun onSubscribe(d: Disposable) {
Log.d("kb", "onSubscribe")
}
override fun onNext(t: UserResponse) {
Toast.makeText(applicationContext,"Please Upload Profile Pic...", Toast.LENGTH_SHORT).show()
val intent = Intent(applicationContext, SignupStep3Activity::class.java)
mPref.setAccessToken(t.data.accessToken)
startActivity(intent)
}
override fun onError(e: Throwable) {
Toast.makeText(applicationContext, e.localizedMessage, Toast.LENGTH_LONG).show()
}
})
}
Here is what my api show on error response
"message": "Email Already Exists",
"code": 400,
"errors": {
"error_id": "15",
"error_text": "Email Already Exists"
},
"data": []
}
I'm new to kotlin and here, pardon me for any mistake
In your onNext() you need something like this :
when (UserResponse.code()) {
200 -> {//Send appropriate message with intent here}
400 -> {//Send appropriate message with intent here}
401 -> {//Send appropriate message with intent here}
712 -> {//Send appropriate message with intent here}
}

How to retrieve a child from Firebase when there is a unique key Kotlin

I want to retrieve specific child values like (phonenumber, firstname, familyname) from Firebase real time database
but there is a unique key for each user
and this is the tree:
I've tried this:
var loginRef = rootRef.child("users").orderByChild("phoneNumber").equalTo(phone).addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get data object and use the values to update the UI
val phoneNumber = dataSnapshot.getValue<User>()!!.phoneNumber
// ...
Toast.makeText(applicationContext, "phone number is: $phoneNumber", Toast.LENGTH_LONG).show()
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Data failed, log a message
Log.w(TAG, "LoginData:onCancelled", databaseError.toException())
// ...
Toast.makeText(applicationContext, "error", Toast.LENGTH_LONG).show()
}
})
and I have a simple model called User to handle the data (I know the passwords should be hashed here)
#IgnoreExtraProperties
data class User(
var firstName: String? = "",
var fatherName: String? = "",
var familyName: String? = "",
var phoneNumber: String? = "",
var password: String? = ""
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"firstName" to firstName,
"fatherName" to fatherName,
"familyName" to familyName,
"phoneNumber" to phoneNumber,
"password" to password
)
}
}
but dataSnapshot.getValue<User>()!!.phoneNumber will never work, since the first node retrieved in this query is the unique key
what I need is something like dataSnapshot.child("unique-key/phoneNumber").value for each child i want to use, but a way easier and more efficient than making .addChildEventListener for each node
Let's firstly give some notes one the code:
first thing you need to be aware of is here:
dataSnapshot.getValue<User>()!!.phoneNumber
as it might be null if phoneNumber doesn't exist and will throw an error.
secondly, assuming you made some null handling it will still retrieve you empty string, because what you sent to model is just the unique key, and of course you can't handle it with this model.
The easiest way to solve this and get the children of retrieved node is by using for loop according to this solution: https://stackoverflow.com/a/38652274/10324295
you need to make for loop puts each item into an array list, try this code:
val userList: MutableList<User?> = ArrayList()
var loginRef = rootRef.child("users").orderByChild("phoneNumber").equalTo(phone).addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
userList.clear()
for (userSnapshot in dataSnapshot.children) {
val user: User? = userSnapshot.getValue(User::class.java)
userList.add(user)
// Get Data object and use the values to update the UI
// ...
Toast.makeText(applicationContext, "hi: ${user!!.phoneNumber}", Toast.LENGTH_LONG).show()
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Data failed, log a message
Log.w(TAG, "LoginData:onCancelled", databaseError.toException())
// ...
Toast.makeText(applicationContext, "error", Toast.LENGTH_LONG).show()
}
})
var loginRef = rootRef.child("users").orderByChild("phoneNumber").equalTo(phone).addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// retreive all children firstly by foreach
dataSnapshot.children.forEach { data ->
val userModel = data.getValue(User::class.java)
val phoneNumber = userModel!!.phoneNumber
Toast.makeText(applicationContext, "phone number is: $phoneNumber",
Toast.LENGTH_LONG).show()
}
// ...
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Data failed, log a message
Log.w(TAG, "LoginData:onCancelled", databaseError.toException())
// ...
Toast.makeText(applicationContext, "error",
Toast.LENGTH_LONG).show()
}
})

Categories

Resources