Firebase RecyclerView OnDataChanged only last element is Shown - android

I am developing android chat app following this i am retriving the whole list of friends Then on the basis of their user id i retrive the user's data from the user data that is user name, user image and user status the databases are shown below.
and
So now what is the main problem, the main problem is by the following code:
#Override
protected void onBindViewHolder(FriendsFragment.friendsViewHolder holder, int position, Friends model) {
final String list_User_id=getRef(position).getKey();
mUserdatabase.child(list_User_id).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
UserName = dataSnapshot.child("name").getValue().toString();
friendsViewHolder.setName(UserName);
UserStatus = dataSnapshot.child("status").getValue().toString();
friendsViewHolder.setStatus(UserStatus);
thumb_img = dataSnapshot.child("thumb_img").getValue().toString();
friendsViewHolder.setThumbnail(thumb_img,getActivity());
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent prfile_Intent=new Intent(getActivity(),ProfileActivity.class);
prfile_Intent.putExtra("User_id",list_User_id);
startActivity(prfile_Intent);
}
});
it give me a recyclerView in result
in which only last element is show as shown in figure
i google it and also searching on Stackover flow but cannot find the solution from last 3 days.
A simillar is Question is asked but i cannot solve my problem from here
please help and donot down vote my question
thanks for help

addValueEventListener in the onBindViewHolder method is the very bad idea. You can make a list of user in your Fragment and pass that list into Adapter.

This is happening because the onBindViewHolder() method is called as many times as the number of items that are coming from your database. So in this case, everything is inside this method is also triggered. This means that you are attaching a listener on every onBindViewHolder() method call. There is no need for doing that. You can simply get the user name and all the other properties directly from the model object which is passed as an argument to this method like this:
String UserName = model.getUserName();
String UserStatus = model.getUserStatus();
//And so on for the other properties.

Related

How to prevent my chats from duplicating themselves whenever someone sends a message in Android Studio firebase

I am designing a simple basic chat app using firebase real time database and i've designed everything well, however, i'm facing one sllight issue. My chats keep duplicating themselves on the inbox page (the page whrere the chats are laid out for a user to select which chat he wants to open and start talking).
I've attached an image of what i mean below.
Screenshot of the phone screen
The code i am using to get the chats and display them in the recycler view is given below. I have a directory called Conversations in my DB that saves a user's Id and under it, theres a child of each and every person he chats wit, under which is the last message and a seen boolean.
Database Structure
The code is given below
convoref = FirebaseDatabase.getInstance().getReference().child("Conversations").child(currentUid);
and then..
public void getConvoIds() {
convoref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for(DataSnapshot convo : dataSnapshot.getChildren()){
boolean isMessageSeen = false;
String lastMessage = "";
if(convo.child("seen").getValue() != null) {
isMessageSeen = (boolean) convo.child("seen").getValue();
}else{
Log.i("nolastseen", "location is null");
}
if(convo.child("lastMessage").getValue() != null) {
lastMessage = convo.child("lastMessage").getValue().toString();
}else{
Log.i("nolastMessage", "location is null");
}
Log.i ("the_convo_partner_key", convo.getKey());
Log.i ("lastseenmessage", lastMessage);
Log.i ("seenstate", String.valueOf(isMessageSeen));
FetchConvoInfo(convo.getKey(), isMessageSeen, lastMessage );
}
}
}
the fetch convo information functuion is below
public void FetchConvoInfo(final String key, final boolean isMessageSeen, final String lastMessage){
FirebaseDatabase.getInstance().getReference().child("Users").child(key).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean chatExists = false;
String username = "";
String thumbnail = "";
String chatPartner;
chatPartner = key;
if(dataSnapshot.child("username").exists()){
username = dataSnapshot.child("username").getValue().toString();
}
if(dataSnapshot.child("thumbnail").exists()){
thumbnail = dataSnapshot.child("thumbnail").getValue().toString();
}
ConvoClass obj= new ConvoClass(chatPartner, username, thumbnail, isMessageSeen, lastMessage);
resultConvos.add(obj);
mConvoAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Any help would be greatly appreciated. i cant seem to figure out why the chat duplicates.
In your onDataChanged method, you are going through every child of the dataSnapshot. Each child of the data snapshot indicates a particular conversation of that particular currentUid guy. So when you are going through every child of the dataSnapshot you are adding all its children to the listview or recyclerview(I don't know what you are using. But you are adding it to the adapter). So you are adding the old data again and again whenever some new data must be added. Hence-duplicate data.
There are two common solutions.
The first is naive method. Do what you are doing right now. But while adding an item (chat, you will call it in your case, I think) to the adapter, check if it is already present in the container resultConvos. That will prevent you from adding duplicate chats. I am sure it is obvious to you also why this method is inefficient. You are unnecessarily having to go through every conversation of a person. It takes O(n) time for just adding one item.
The second method is the recommended method. Remove all the code of ValueEventListener. Instead use ChildEventListener. I don't know if you are aware of it. Check this.
ChildEventListener has mainly 4 methods instead of onDataChanged. Among that, what you require here is onChildAdded. Just like your onDataChanged, it has one argument- a data snapshot. But this data snapshot contains only the newly added child, whereas the data snapshot in onDataChanged contains the whole data of the conversations of that particular user (that means the whole list). So using the data snapshot provided by onChildAdded you can directly add only that chat to the adapter, which takes O(1) time.
For more about ChildEventListener, read that link I attached

How to set activity layout based on user type / role?

I am working on an Android app with 2 types of users (doctors and patients), and I want each type to have their own UI. For eg, doctors must see ' Add days off' and patients must see ' Book appointment' . Somehow I don't get anywhere with whatever I try.
I also use Firebase Auth and Realtime Database which makes the user type retrieval kinda tricky. So far I've tried a lot of Async classes, methods, variables, shared preferences, retrieving data while on launcher splash screen.
The best I got is getting the user to login, it shows the good layout, then I start the app again and it shows the wrong layout. Somehow I noticed it just works on the second run, but not always so the behaviour is unpredictable to me at least. But at least the user type from the database is retrieved.
I have a class that extends Application, which checks if there's an user authenticated and then redirects the user to either LoginActivity, or MainMenuActivity.
I have created a method that retrieves the Firebase Auth user data from Realtime Database, looping through both Doctors and Patients 'children' until it finds the current user email and gets its type. Since Realtime Database is asynchronous, the methos gets an interface as an argument, and after the loop, the I call the interface's method, which sets a static boolean variable (isUserDoctor).
Before setting the content view (with 2 possible layouts), I call the function described before and it works the way I first mentioned, which is not good.
The method that retrives data
public void getUserType(final DataStatus dataStatus) {
currentUser = FirebaseAuth.getInstance().getCurrentUser();
currentUserEmail = currentUser.getEmail();
databaseReference = FirebaseDatabase.getInstance().getReference("Users");
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
currentUserType.clear();
FirebaseManager.isUserDoctor = false;
DataSnapshot allDoctors = dataSnapshot.child("Doctors");
DataSnapshot allPatients = dataSnapshot.child("Patients");
for (DataSnapshot ds : allDoctors.getChildren()) {
if (currentUserEmail.equals(Utils.decodeUserEmail(ds.getKey()))) {
currentUserType.add(ds.child("userType").getValue().toString());
} else {
for (DataSnapshot dsPacient : allPatients.getChildren()) {
if (currentUserEmail.equals(Utils.decodeUserEmail(dsPacient.getKey()))) {
currentUserType.add(dsPacient.child("userType").getValue().toString());
}
}
}
}
dataStatus.DataIsLoaded(currentUserType.get(0).toString());
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
The interface
public interface DataStatus {
void DataIsLoaded(String userType);
}
The method's call in Main Menu
FirebaseManager.getInstance().getUserType(new DataStatus() {
#Override
public void DataIsLoaded(String userType) {
if ("doctor".equals(userType))
FirebaseManager.isUserDoctor = true;
else
FirebaseManager.isUserDoctor = false;
}
});
if (FirebaseManager.isUserDoctor)
setContentView(R.layout.activity_main_menu_doctor);
else
setContentView(R.layout.activity_main_menu);
So if anyone has any ideas about how to show the proper layout and allow functions based on user role/type please share. What I basically need is to retrieve the userType from the current email just in time to set a variable needed throughout the whole app in order to hide/show certain views.

list cannot change inside ValueEventListener

I want to put data from firebase database in a list, however I get the error : "java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0", I even tried to add to the list manually inside the valueEventListener as shown in the comment, but still it's empty
this is my code :
public class Playing extends AppCompatActivity implements View.OnClickListener {
public static List<Question> list_question=new ArrayList<>();
Question currentQuestion;
int index=0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playing);
final DatabaseReference myRef = FirebaseDatabase.getInstance().getReference().child("quiz");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
/*
Question question=new Question();
question.setQuestion("eeee");
question.setAnswerA("zzzzz");
question.setAnswerB("aazss");
question.setAnswerC("ytyty");
question.setAnswerD("jkjkjkjk");
question.setCorrectAnswer("A");
list_question.add(question);
*/
for (DataSnapshot ds:dataSnapshot.child("questions").getChildren())
{
Question question=new Question();
question.setQuestion(ds.child("question").getValue().toString());
question.setAnswerA(ds.child("A").getValue().toString());
question.setAnswerB(ds.child("B").getValue().toString());
question.setAnswerC(ds.child("C").getValue().toString());
question.setAnswerD(ds.child("D").getValue().toString());
question.setCorrectAnswer(ds.child("sol").getValue().toString());
list_question.add(question);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
currentQuestion = list_question.get(index);
What is missing in my code ?
addValueEventListener is asynchronous and returns immediately. Your code goes on to execute list_question.get(index), but list_question is still empty. The listener you provided will not get invoked until some time later, after the database query is complete. There is no guarantee how long it will take.
If you want to use the results of a query, you must wait until the asynchronous database operation is complete. This means that you can only use the results inside the listener callback itself.
This methods to retrieve data is asynchronous. Which means that this isn't executed in the chronogical order.
Try to use the debug mode and you will see by yourself that it executes the inside of the listener after executing the rest of the code.
Put the
currentQuestion= list_question.get(index)
Inside the listener, just after the line where you add your question and you will see that it works.
It is just that you are trying to access the list before it is getting filled.
You can check for the size of the list by putting an if.
if(list_question.size() > 0)
currentQuestion = list_question.get(index);
Also make sure that you are doing this logic once a data change event happened.
Hope this helps.
Happy coding :)

FireBase Android Need 1 value saved under a single user

I have the following data structure on firebase for the user MF0qeRA4p7djfjgXxqwFOck3m6p02. I want to get the value of item3 to populate a single field into the User interface on an Android App. I have been looking through samples on Stackoverflow, but all I have found are outdated and do not work with the current version of firebase. I'm new to firebase completely and this is my first app on android. I've got the oncreate user method to populate the users email address and add the 4 item fields, but retrieving the data I'm completely lost and I am not sure where to even begin.
-Users
---MF0qeRA4p7djfjgXxqwFOck3m6p02
------item1:"1"
------item2:"2"
------item3:"3"
------item4:"4"
According to what I can identify is, you are facing problem retrieving data from this reference. Here is the code:
final DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Users");
databaseReference.child("MF0qeRA4p7djfjgXxqwFOck3m6p02").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map=(Map<String, Object>)dataSnapshot.getValue();
String item3=(String)map.get("item3");
display(item3);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope this helps.
You can create a custom model and inside you can insert elements. Something like this:
public class Item {
private List<Object> ojects;
}
There you can save instance of Item on database. In this case you have more controll. Other case is to use push() method, that will generate a new encoded key, something like this:
mDatabase.child("items").push().put(new Object());

How to change value and re-trigger eventListener in Firebase?

I am making a public chat app using Firebase Real-time Database, but stuck on this problem. Like normal chat apps, I am displaying only few texts to the user and when/if the user scrolls the List is updated and more messages are displayed.
I have a listener to my Firebase reference, which is only displaying (say 10 messages):
int j = 10;
myRef.child("Chat").limitToLast(j).addChildEventListener(new com.google.firebase.database.ChildEventListener() {
#Override
public void onChildAdded(com.google.firebase.database.DataSnapshot dataSnapshot, String s) {
// Map<String,Object> datemsg = (Map<String,Object>)dataSnapshot.getValue();
Map<String,Object> msg = (Map<String,Object>)dataSnapshot.getValue();
String text = msg.get("Text").toString();
values.add(text);
#Override
public void onChildChanged(com.google.firebase.database.DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(com.google.firebase.database.DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(com.google.firebase.database.DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Now I have a scroll Listener that changes the value for j to j+10 but then the ChildEvenListener is not triggered and the new messages are not added to the listview.
I also tried using queries, but again having the same problem, after updating the value, how should I update the adapter?
No new child is added, thus the childevent listener is not triggered, but I want to change the size of
limitToLast(j)
and re-populate the list.
Any help would be appreciated.
Firebase queries are immutable. If you change the value you pass into limitToLast(), it becomes a new query.
So you must attach a new listener to the new query, and then repopulate the list with the items from that listener.
If you keep track of the key (dataSnapshot.getKey()) for each existing item in your list, you can optimize the updating of the list by checking if each item from the new listener is already present in the list.

Categories

Resources