I am working on a student-teacher private messenger application using Firebase and I can insert data inside the root like this:
The usernames are retrived from a MySQL database, so I just only need to make a query. I would like to get only the data inside a ListView where chatID equals to my generated ID. I've searched in a lot of places like here but can't make the DatabaseReference working. Here is my code:
private void displayStudentChatMessages() {
listofMessages = (ListView) findViewById(R.id.list_msg);
adapter = new FirebaseListAdapter<BubbleMessageStudent>(this, BubbleMessageStudent.class, R.layout.item_chat_right, FirebaseDatabase.getInstance().getReference()) {
#Override
protected void populateView(View v, BubbleMessageStudent model, int position) {
TextView studentUsername = (TextView) v.findViewById(R.id.message_user_sender);
TextView studentMessage = (TextView) v.findViewById(R.id.message_user);
TextView studentSentDate = (TextView) v.findViewById(R.id.message_usertime);
studentUsername.setText(model.getStudentUsername());
studentMessage.setText(model.getStudentMessage());
studentSentDate.setText(android.text.format.DateFormat.format("yyyy-MM-dd%n(HH:mm:ss)", model.getStudentMessageTime()));
}
};
listofMessages.setAdapter(adapter);
listofMessages.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
}
As #cricket_007 commented, you'll need a query to get the chat messages with the correct ID:
rootRef = FirebaseDatabase.getInstance().getReference();
chatQuery = rootRef.orderByChild("chatID").equalTo("teststudent-testteacher...");
adapter = new FirebaseListAdapter<BubbleMessageStudent>(
this, BubbleMessageStudent.class, R.layout.item_chat_right, chatQuery)...
With this code your adapter will only show the messages with the correct chatID.
But in general your data model seems suboptimal. You've essentially mapped a table from your MySQL database into a node in Firebase. It probably works for now, but there are better ways to do this mapping.
In Firebase (and many other NoSQL databases) you should model your data for what you show on the screen. So if your app shows a chat room, which is a list of messages with the same chatID, then you should model precisely that in the database: a list of messages with the same chat ID:
chatId1
chat1Message1: ...
chat1Message2: ...
chat1Message3: ...
chatId2
chat2Message1: ...
chat2Message2: ...
chat2Message3: ...
With this structure you don't need a query to read the messages for a specific chat room. Instead you can directly access the node where they're under:
chatRoom = rootRef.child("teststudent-testteacher...");
adapter = new FirebaseListAdapter<BubbleMessageStudent>(
this, BubbleMessageStudent.class, R.layout.item_chat_right, chatRoom)...
This results in much better scalability.
I recommend reading NoSQL data modeling and watching Firebase for SQL developers.
Related
Basically what I am trying to do is I have a database with the name users having an attribute username. I have some usernames in one list and I want to show details of these users only whose username is present in the list. How can I write a query to fetch details of those users only whose username is found in this list? And note that there is no lexicographical ordering so i can't use startAt() and endAt() functions as well.
code snippet:
=> myList contains usernames. This code doesn't yield accurate results.
Any help would be really appreciated! Thank you!
FirebaseRecyclerOptions<MainModel> options =
new FirebaseRecyclerOptions.Builder<MainModel>()
.setQuery(FirebaseDatabase.getInstance().getReference().child("users").orderByChild("username")
.startAt(myList.get(0)).endAt(myList.get(myList.size()-1)),MainModel.class).build();
As already mentioned in the comment, the Firebase-UI library doesn't help in your case, because it doesn't allow you to pass multiple queries to the FirebaseRecyclerOptions object. So you need to perform a separate query and use the combined result.
When you are calling .get() on a Firebase Realtime Database query object, you are getting back a Task object. So the key to solving this problem is to use whenAllSuccess(Collection> tasks). In your case, it should look like this:
DatabaseReference db = FirebaseDatabase.getInstance().getReference();
DatabaseReference usersRef = db.child("users");
Query query = usersRef.orderByChild("username");
List<Task<DataSnapshot>> tasks = new ArrayList<>();
for (String username : myList) {
tasks.add(query.equalTo(username).get());
}
Tasks.whenAllSuccess(tasks).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list.
for (Object object : list) {
MainModel mm = ((DataSnapshot) object).getValue(MainModel.class);
if(mm != null) {
Log.d("TAG", mm.getUsername());
}
}
}
});
Assuming that you have in your MainModel class a getter called getUsername(), the result in your logcat will be all the usernames of all returned children.
Is it possible to specify the which data is read by the FirebaseListAdapter?
At the moment, I could only read the complete database. The database holds a lot of different objects. The object has a value which represents the User who has created the object.
Now, I only want to read the objects from this special user. When I try it like this, there are empty list field.
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReferenceFromUrl("URL");
//HOW CAN I DO THE QUERY FOR THAT DATABASE REFERENCE?
FirebaseListAdapter<Machine> firebaseListAdapter = new FirebaseListAdapter<Machine>(
this,
Machine.class,
R.layout.list_item,
databaseReference) {
#Override
protected void populateView(View v, final Machine model, int position) {
//CAN I DO THIS BY QUERY?
if(model.getS_UserID.equals(user.getID)){
//SOME STUFF WHICH I DO
}
};
}
list.setAdapter(firebaseListAdapter);
The question is, can I do the If-Statement by doing a query for the FirebaseListAdapter?
At the moment, I do something like this:
if(model.getS_UserID.equals(user.getID()) --> DO SOME STUFF
But it also creates some empty fields which are really ugly.
My Database looks like this:
OBJECT
-USERID = STRING
-NAME = ...
-.....
You can pass in a Query to the FirebaseListAdapter instead of the database reference you use right now. To filter on items with the user ID:
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReferenceFromUrl("URL");
Query query = databaseReference.orderByChild("s_UserId").equalTo(user.getID);
FirebaseListAdapter<Machine> firebaseListAdapter = new FirebaseListAdapter<Machine>(
this,
Machine.class,
R.layout.list_item,
query) {
Read more about this in the Firebase documentation on ordering and filtering data.
I'm currently using FirebaseRecyclerAdapter to retrieve data from a Firebase database, and I've pretty much worked out how to retrieve the data in the order I desire.
To avoid duplication and ease database maintenance, I'd like to add a key to a database entry that allows me to return queries based on that key. I was originally storing data twice. Once for all to see, and once if a user had joined a certain group (under groupName).
To return a query based on group, my original search was as follows:
databaseReference.child(groupName).child("exerciseId"+mExerciseId).orderByChild(sortOrder).limitToFirst(100);
but I believe duplication can be avoided by adding the key "group" to my post. (it also make maintenance much easier as users switch groups).
The "group" database query has now become:
databaseReference.child("exerciseId"+mExerciseId).orderByChild("group").equalTo(groupName);
All is good, except that the data is no longer sorted as per "sortOrder". As firebase does not allow multiple sort criteria, I believe my solution lies in offline sorting.
So, how does one sort the adapter offline?
My adapter is pretty standard:
mAdapter = new FirebaseRecyclerAdapter<Post, PostViewHolder>(Post.class, R.layout.item_post, PostViewHolder.class, dataQuery)
{
#Override
protected void populateViewHolder(final PostViewHolder viewHolder, final Post model, final int position)
{
final DatabaseReference postRef = getRef(position);
// Bind Post to ViewHolder, setting OnClickListener for the star button
viewHolder.bindToPost(model, position, postRef.getKey(), null);
}
mRecycler.setAdapter(mAdapter);
}
I've implemented Comparable in Post, the problem is where is the data stored so that I can pass it to Collections.sort() in this sort of way:
private List<Post> mPosts = new ArrayList<>();
mPosts.add(model);
Collections.sort(mPosts, Post.Comparators.ALLTIME);
The adapters in the FirebaseUI library currently always display data in the order that it is returned by the underlying FirebaseDatabase reference or Query.
You could file a feature request on the Github repo for it (since I'm not sure it is covered in this one yet). Alternatively you could fork the library and roll your own implementation of this functionality.
To feedback on the solution I came up with. I ended up populating my own List, which I can then sort or add to (if using multiple queries) depending on what I'm trying to achieve. This is then displayed using a standard RecyclerView adapter. It works great.
private final List<Post> postList = new ArrayList<>();
dataQuery.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
postList.clear(); // in this example it's a newQuery, so clear postList
for (DataSnapshot child : dataSnapshot.getChildren())
{
Post post = child.getValue(Post.class);
postList.add(post);
int myPosition = sortData(); // uses Collections.sort() to sort data
mAdapter.addPosts(postList);
mAdapter.notifyDataSetChanged();
mRecycler.scrollToPosition(myPosition);
}
}
});
I am new to Android and just started to understand some concepts like the RecyclerView. I am using Firebase as a database so I implemented the Firebase solution for that.
My Adapter:
FirebaseRecyclerAdapter<Offer,OfferViewHolder> adapter = new FirebaseRecyclerAdapter<Offer, OfferViewHolder>(
Offer.class,
R.layout.card_item,
OfferViewHolder.class,
mDatabaseReference.child("offer").getRef()
) {
#Override
protected void populateViewHolder(OfferViewHolder viewHolder, Offer model, int position) {
if(tvNoMovies.getVisibility()== View.VISIBLE){
tvNoMovies.setVisibility(View.GONE);
}
viewHolder.tvHeading.setText(model.getHeader());
viewHolder.tvStoreName.setText(model.getStoreName());
}
};
Now I have two questions:
Is it possible to filter results inside the adapter or do i have to use ChildEventListeners for that?
When referencing to a key that contains an Array or an object itself how to retrieve child values?
You can initialize the FirebaseRecyclerAdapter with either a DatabaseReference or a Query. As its name implies, the latter allows you to get a subset of the children at a specific location in the database.
For example say if you want to only show offers from a specific store:
DatabaseReference offers = mDatabaseReference.child("offer").getRef();
Query storeOffers = offers.orderByChild("storeName").equalTo("A. lazzi's store");
FirebaseRecyclerAdapter<Offer,OfferViewHolder> adapter = new FirebaseRecyclerAdapter<Offer, OfferViewHolder>(
Offer.class,
R.layout.card_item,
OfferViewHolder.class,
storeOffers
) {
Working on an Android app that is using the new Firebase Database framework. It has data objects that are modeled like this:
Where the Top parent (1234-4321) is the 'chat room', the data object are the 'chat messages', and the numbered items (0, 1, 2) are the 'individual message'.
I am able to get the entire Database without any trouble and read it via listeners:
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference();
myRef.addChildEventListener(this);
myRef.addValueEventListener(this);
And I am able to get a single child in this fashion:
FirebaseDatabase database = FirebaseDatabase.getInstance();
String id = "1234-4321";
DatabaseReference myRef = database.getReference().child(id);
myRef.addChildEventListener(this);
myRef.addValueEventListener(this);
But I cannot for the life of me figure out how to get multiple objects of the same type. What I mean is, a user will be able to get More than one chat room (IE, both '1234-4321' and '1234-4432'), but the only way I can see to do this is either to:
1) loop through the onChildAdded or onDataChange listeners, separate out the items by matching the String ids, and updating them. This is, however, extremely inefficient as I am parsing the entire Database, which could be quite large
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if(dataSnapshot != null){
try {
ChatObjectV2 objectV2 = (ChatObjectV2) dataSnapshot.getValue();
//Check here for ids and loop through everything
} catch (Exception e){
e.printStackTrace();
}
}
}
or
2) To add a specific child, but if I try to add more children it is going 'deeper' into the nested object when I want it to go 'wider'.
//This won't work because it is going 'deeper' instead of 'wider'
String id = "1234-4321";
String id2 = "1234-4432";
Query myQuery = myRef.child(id).child(id2);
And then loop through in the listener the same way, but, I would need to create a different DatabaseReference for every chat room, which is horribly inefficient.
It looks like the solution is probably to use filters, but I cannot for the life of me figure out how to utilize them in the existing FirebaseDatabase and DatabaseReference objects. Does anyone have any idea how to make a filter work with regards to the data schema / model I have listed here?
Thanks for the help!
I would try to explain you the basic use of filtering in these examples:
//getting first two chat rooms
Query chatRoomsQuery = databaseReference.limitToLast(2);
//getting last two chat rooms
Query chatRoomsQuery = databaseReference.limitToFirst(2)
//getting all active id
Query chatRoomsQuery = databaseReference.orderByChild("active").equalTo(true);
This is just a basic sample I would encourage you to go through this blog. Which explains advanced queries amazingly.
Alternatively, you can also go through these docs. They are different than what you shared.
Do let me know if this is what you were looking for.