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!
}
});
Related
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'm developing a social app, and I'm at the Feed part, which will display recent interactions made by friends. These friends are sorted by the amount of interaction we both have in the app - I call it RankFactor. The higher this value is, the higher my friend's interaction will appear.
First I read the User ID from my list of friends, ordered by The Rank Factor then I get each recent interaction.
I want the list of interactions displayed is created dynamically. It's supposed to be an infinite scroll, where per each scroll there will appear around 10 interactions.
So, ideally, I'd like that I'd keep reading friends ID's, and their interactions until I have 10 on the list. However, I'll have these interactions saved in a member List, which will be inside the ValueEventListener scope.
Even if I use orderByChild("rankFactor"), and then I limitToLast(10), nothing guarantees that these highest ranked Friends would have interacted recently. Therefore I believe I would need to have some kind of loop, focusing on the size of the interactions List.
How could I achieve my goal, knowing that "logically", what I want to achieve, would be something like the following:
while(mListOfInteractions.size() <= 10){
Query friendQuery = mDatabase.getReference().child(DB_REFERENCE).child(uId).child(FRIENDS_DB)
.orderByChild("rankFactor").endAt(mLastRankFactor - 0.0000001).limitToLast(1);
friendQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()){
FriendRank friendRank = dataSnapshot.getValue(FriendRank.class);
mLastRankFactor = friendRank.getRankFactor();
final String userId = friendRank.getUserId();
Query interactionsQuery = mDatabase.getReference().child(DB_REFERENCE).child(userId).child(INTERACTIONS_GIVEN).orderByChild("date")
.startAt(System.currentTimeMillis() - THRESHOLD_DAYS_IN_MILLIS).limitToLast(3);
interactionsQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(dataSnapshot.hasChildren()){
for(DataSnapshot interactionData: dataSnapshot.getChildren()){
String interactionKey = interactionData.getKey();
Interaction interaction = interactionData.getValue(Interaction.class);
if(!DataUtils.doesListContainItem(mSavedInteractionsKeys, interactionsKey)){
mListOfInteractions.add(interactions);
mSavedInteractionsKeys.add(interactionKey);
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
So, as you see, I'd like that my list would keep on building up until I had gathered the amount of 10 Interactions (or a little more). The thing is, as many of you know, truly I can only access mListOfInteractions real value within the 2nd ValueEventListener.
How can I make the mListOfInteractions value accessible to my While loop?
Was I able to explain my challenge and goal?
Thank you in advance!
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?
So... thanks to some great assistance from TomiQsrd I have got my data being saved in Firebase as below:
private void saveUserInformation(){
String name = editTextName.getText().toString().trim();
String address = editTextAddress.getText().toString().trim();
FirebaseUser user = firebaseAuth.getCurrentUser();
databaseReference.child(user.getUid()).child("Name").setValue(name);
databaseReference.child(user.getUid()).child("Address").setValue(address);
Toast.makeText(this, "Information Saved...", Toast.LENGTH_SHORT).show();
}
The rest of my project is pretty much completed, but I have one last part I can't figure out no matter how many tutorials I watch/read.
How on earth do I get an event listener to pick up changes in the data and update a TextView field.
eg. As a test, I want to add a textViewName field to the page to update when the name is updated in editTextName
I have tried all sorts of approaches and must have been close, but I have not been able to get it to work.
See https://firebase.google.com/docs/database/android/read-and-write
Modified from there:
databaseReference.child(user.getUid()).child("Name").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
String name = snapshot.getValue(String.class);
myTextView.setText(name);
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
// ...
}
};
Good day, I am using Android studio to create a question and answer based game and I have a 2 things that I am struggling with. I have a firebase database with the following structure:
database name{
answers{
1{
answer_option_1: .....
answer_option_2: .....
answer_option_3: .....
answer_option_4: .....
}
2{
answer_option_1: .....
answer_option_2: .....
}
and so it carries on similar to the above shown structure.
My question is regarding how I could retrieve the answer options and store them in their respective variables? such as:
Option1 = answer_option_1
Is there a way to count the number of children under the parent, such as, it should tell me that under the parent 2, there are 2 children?
My code for retrieving a single value is as follows:
mDatabase.child("answers").child(num).child("answer_option_1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Option1 = dataSnapshot.getValue().toString();
option1.setText(Option1);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
where mDatabase is the reference to my Firebase Database and num is a random value to retrieve a random question . I have repeated the above code a 4 times for the 4 options, but when I have 2 options, as mentioned and shown in the database structure above, my app stops working.
All help will be highly appreciated.
I'm still not getting why do you want to count the children but you might be interested in using ChildEventListener to get a callback for each child inside /answers/num.
Set a list that will track the options:
List<String> options = new ArrayList<>();
And when loading the view or whatever make sure you start the listener:
mDatabase.child("answers").child(num).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot) {
String option = dataSnapshot.getValue().toString();
options.add(option);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});