So I have this chat functionality in my app. I am retrieving messages like this:
private void loadmessage(String class_id, String email_red) {
DatabaseReference messageRef = mRootRef.child("Announcement").child(email_red).child(class_id);
messageRef.keepSynced(true);
messageRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, String s) {
String messageKey = dataSnapshot.getKey();
Message message = dataSnapshot.getValue(Message.class).withID(messageKey);
messageList.add(message);
keyList.add(dataSnapshot.getKey());
mAdapter.notifyDataSetChanged();
mMessagesList.scrollToPosition(messageList.size() - 1);
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, String s) {
String messageKey = dataSnapshot.getKey();
Message message = dataSnapshot.getValue(Message.class).withID(messageKey);
int index = keyList.indexOf(messageKey);
messageList.set(index, message);
mAdapter.notifyDataSetChanged();
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
int index = keyList.indexOf(dataSnapshot.getKey());
messageList.remove(index);
keyList.remove(index);
mAdapter.notifyDataSetChanged();
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
As you can see I have used messageRef.keepSynced(true); for the offline feature.
Now lets say there can be 100-200 messages(only text) at max and I am putting each of them in the RecyclerView.
Now my question is: Every time the user opens this activity, firebase will get those 200 messages from database again or is it once? How will I be getting charged on this?
And lastly, putting 200 messages(only text) in the recyclerView is fine? Or should I use pagination?
As you can see I have used messageRef.keepSynced(true); for the offline feature.
If you want be able to query your the database even if you are offline, you should use the following line of code:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
And not messageRef.keepSynced(true); as you say you do now. When using the above line of code, you tell Firebase to create a local copy of your database.
Every time the user opens this activity, firebase will get those 200 messages from database again or is it once?
If you are calling loadmessage() method everytime you start an activity, it means that you are adding a ChildEventListener on your messageRef object. If you want to get the data only once, you should use a ListenerForSingleValueEvent, which means that the listener will read the data precisely once. That means that your onDataChange() method gets triggered with the current value (from the cache if available, otherwise from Firebase servers), and stop listening immediately after that.
How will I be getting charged on this?
According to Firebase Pricing Plans.
And lastly, putting 200 messages(only text) in the recyclerView is fine? Or should I use pagination?
It's good that you decided to use a RecyclerView rather than a simple ListView to display the messages because the views are constantly recycled. In case of 200 messages, I recommend you to implement pagination, to load data in smaller chuncks, let let's say 10 or 15 messages at the time. Maybe you'll be interested in reading ony 30 messages, why to download all of them?
Related
I have an activity that registers active time data to my Firebase Realtime Database, this is input manually using an EditText which then displays it to a TextView in the UI. This is all working fine, however, if I was to put more data in, it would simply replace the value in the database and TextView.
I have previously done something similar to this in another activity (Adding (Sum) multiple Firebase child values together to give a total value?), however, this activity had additional nodes with dates, etc so the same approach would not work in my current activity. Ideally, I would have this node organized with dateStrings etc, however, for demonstration purposes, it's not necessary. Therefore I would just like to take whatever value is already existing in the database and add to it the input value, then restore that to the database.
It's probably quite simple but I've been staring at it so long I've confused myself. This is nowhere near right but just a bit confused about my next steps.. I've seen perhaps something along these lines how to sum money value in firebase realtime android studio but wondering if this is the easiest way?
Register Activity method:
registerActivity.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String activityTime = activeMinutes.getText().toString().trim();
databaseReference.child("active time").setValue(activityTime);
addActivity.setVisibility(View.VISIBLE);
registerActivity.setVisibility(View.GONE);
activeTimeTI.setVisibility(View.GONE);
}
});
Method to display data (shortened):
databaseReference.addValueEventListener(new ValueEventListener() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int total = 0;
String activityTime = dataSnapshot.child("active time").getValue(String.class);
if (activityTime == null) {
activeTimeTV.setText("0");
} else {
total += Integer.parseInt(activityTime);
activeTimeTV.setText(String.valueOf(total) + " minutes");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w("TAG", "onCancelled", databaseError.toException());
}
});
Firebase Hierarchy:
According to your last comment:
Active Time is at 500 (mins) just now, if I did another 90 mins of activity, I would like to add 90 to that value and then update that to show 590.
To increment the value of "active time" by 90, first of all, you need the change the type of the field to be number. That being said, your field should look like this:
ndkdn
|
--- active time: 500
See, there are no quotation marks. If your "ndkdn" node is a direct child of your Firebase Realtime Database root, to increase the value by 90, simply use the following lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference activeTimeRef = rootRef.child("ndkdn").child("active time");
activeTimeRef.setValue(ServerValue.increment(90));
This kind of operation is atomic. If you are not allowed to change the type of your field, to also have consistent data, then you should read the value first and increment it right after that using a transaction:
activeTimeRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Integer activeTime = Integer.parseInt(mutableData.getValue(String.class));
if (score == null) {
return Transaction.success(mutableData);
}
mutableData.setValue((activeTime + 90) + "");
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError error, boolean b, DataSnapshot snapshot) {
Log.d("TAG", error.getMessage()); //Don't ignore potential errors!
}
});
I have two models photos and videos. To retrieve photos I call an addChildEventListener and to get videos I call another addChildEventListener added.
Code example
databaseReference = FirebaseDatabase.getInstance().getReference("videos");
Query queryContent= databaseReference;
queryContent.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
videos v= dataSnapshot.getValue(videos.class);
objectItems.add(v);
loading = true;
contentViewPager.setAdapter(new discover_fullscreen_adapter(getApplicationContext(), objectItems));
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
For photos is the same, I just change the reference, and the model
databaseReference = FirebaseDatabase.getInstance().getReference("photos");
photos p = dataSnapshot.getValue(photos.class);
objectItems.add(p)
First I add the videos and then the photos and the order is messy because I want to get videos and photos together in the order they were taken. Like a phone gallery. We have photos and videos ordered in the way they were taken (timestamp in his case). How can I achieve the same knowing that photos and videos are in different nodes and they are called by different models?
How to do it with Firebase Realtime Database
My models are based on getter and setter, I don't want to make the question bigger.
Thank you
I want to get videos and photos together in the order they were taken. Like a phone gallery.
You can perform a Firebase Realtime Database query only on a single node. You cannot get data across multiple nodes using a Query. If you want to get the "photos", as well as the "videos" in a single go, then both should exist within the same node. So you should create another node called "photosAndVideos" where you should add all the data. This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database.
Once you have all data under a single node, you can then perform the desired query according to a timestamp. Please see my answer from the following post:
How to save the current date/time when I add new value to Firebase Realtime Database
To see how to add a timestamp property to your object. By default Firebase orders the results ascending. However, if you need a descending order, please see my answer from the following post:
How to arrange firebase database data in ascending or descending order?
Edit:
You have to check each object from the results an instance of which class is. So when you read the data, you cannot only cast the value. You'll have to read each object and request the correct class in the call to getValue().
I am working on an app for a hotel, which enables hotel management to report and view concerns and issues. I am using Android and Firebase for this app.
Here is the database structure of a reported concern:
To minimize data download and optimize speed, I am adding "Active" and "Resolved" nodes in the database, like below:
Now, the hotel wants me to add the function to create an Excel report of concerns closed/resolved within the past month. For this, I will be attaching a Single Value Event Listener on the "resolved" node, get keys of resolved concerns, then for each key, fetch data from "allConcerns" node, store each node's data into an ArrayList of String. After which I will use this JSON to Excel API for Android to create Excel file.
I am able to access keys of resolved concerns with this code:
DatabaseReference resolvedReference = FirebaseDatabase.getInstance().getReference()
.child(getApplicationContext().getResources().getString(R.string.concerns))
.child(getApplicationContext().getResources().getString(R.string.resolved));
final ArrayList<String> keys = new ArrayList<>();
resolvedReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
//Getting keys of all resolved concerns in keys arraylist here
for (DataSnapshot ds : snapshot.getChildren()){
keys.add(ds.getValue(String.class));
}
//Storing JSON data in this arraylist
final ArrayList<String> data = new ArrayList<>();
for(int i = 0; i<keys.size() ; ++i){
String key = keys.get(i);
//Getting data of each concern here
FirebaseDatabase.getInstance().getReference().child(getApplicationContext().getResources().getString(R.string.allConcerns))
.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
String type = snapshot.child("type").getValue().toString();
Log.i("Type", type);
if(type.equals("0")) {
SafetyConcernClass s = snapshot.getValue(SafetyConcernClass.class);
Log.i("Snapshot of key", s.toString());
data.add(s.toString());
}
else{
GembaWalkClass g = snapshot.getValue(GembaWalkClass.class);
Log.i("Snapshot of key", g.toString());
data.add(g.toString());
}
Proof proof = snapshot.child("proof").getValue(Proof.class);
Log.i("Proof", proof.toString());
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
//Issue I am facing is here
Log.i("Data size", String.valueOf(data.size()));
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
The real issue here is while logging data.size(). Since Firebase is asynchronous, FOR loop ends before data is fetched and entered into the data ArrayList, hence it gives me a size of 0. And since no data is fetched, I can't create an Excel file.
My question is, how can I make sure I am proceeding to log data.size() ONLY after data of respective resolved concerns is stored in the ArrayList?
The typical approach is to keep a counter or a countdown latch to track how many of the concern snapshots you've already downloaded. Once the counter reaches keys.size() you know that you're done.
Also see Setting Singleton property value in Firebase Listener
You should write your method
addListenerForSingleValueEvent
using AsyncTask or Kotlin coroutines
and in onPostExecute() of AsyncTask, you can proceed to further action.
I want to extract database from Firebase. In for each loop, it shows me the description from database but after the for each loop when I check it again for that description, it shows me null and not only for description but for all other values also. Here is the code for extracting data from Firebase. It doesn't shows any kind of error. Help me.
public void getDatafromFirebase(){
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DataSnapshot Videoreference = dataSnapshot.child("Videos");
for (DataSnapshot videoschild : Videoreference.getChildren()){
DataSnapshot description = videoschild.child("Description");
DataSnapshot duration = videoschild.child("Duration");
DataSnapshot title = videoschild.child("Title");
DataSnapshot thumbnail = videoschild.child("Thumbnail");
details.setDescription(String.valueOf(description.getValue()));
details.setDuration(String.valueOf(duration.getValue()));
details.setTitle(String.valueOf(title.getValue()));
details.setThumbnail(String.valueOf(thumbnail.getValue()));
Log.e("details",details.description);
list.add(details);
}
Log.e("Size",list.get(0).description);
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(getActivity(), databaseError.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
If I understand correctly, you are trying to log the value of your description property (or any other property), outside the callback and you are getting null, right?
This ia happening because Firebase APIs are asynchronous, meaning that onDataChange() method returns immediately after it's invoked and the callback from the Task it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, the value of your description property you're trying to use it outside the onDataChange() method, will not have been populated from the callback yet.
Basically, you're trying to return a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to use the description value only inside the onDataChange() method, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
I have some data under same reference in the database and I'm trying to store it in a ArrayList<String>.
Here's how:
mDatabase.child("child").child(uid).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot.getValue() != null){
ids.add(dataSnapshot.getValue().toString());
Log.d("ids", ids.toString());
} else {
Toast.makeText(getBaseContext(), "No data!", Toast.LENGTH_SHORT).show();
}
}
...
...
});
The reference mDatabase.child("child").child(uid)... has 2 keys stored in it and they are getting stored in ids one-by-one like this:
D/ids: [-Kayr_ENTy4QZQKn3Bgx]
D/ids: [-Kayr_ENTy4QZQKn3Bgx, -KaywN_5pTOrB0ooBIC3]
What I want to know is how to make them get added at once in ids and not one-by-one?
UPDATE 1:-
the data structure is kind of like this:
-app
-child
-uid
-1484967071: -Kayr_ENTy4QZQKn3Bgx
-1484967222: -KaywN_5pTOrB0ooBIC3
and using valueEventListener(), as suggested by cartant, is resulting in this getting retrieved:
D/ids: [{1484967222=-KaywN_5pTOrB0ooBIC3, 1484967071=-Kayr_ENTy4QZQKn3Bgx}]
though I only want the keys, which are -KaywN_5pTOrB0ooBIC3 and -Kayr_ENTy4QZQKn3Bgx.
If you want the entire snapshot to be delivered to the listener, use either addValueEventListener or addListenerForSingleValueEvent.
If you use addValueEventListener, the listener will be called with the initial snapshot and again if the database changes.
And if you use addListenerForSingleValueEvent, the listener will be called only once with the initial snapshot.
The snapshot received by the listener will include all of the children. To iterate them you would do something like this:
#Override
public void onDataChange(DataSnapshot snapshot) {
ArrayList<String> ids = new ArrayList<String>();
for (DataSnapshot childSnapshot: snapshot.getChildren()) {
ids.add(childSnapshot.getValue().toString());
}
}