I have an application which uses Firebase as the data store. We are using Tier Pattern to separate our Business Logic(and data access logic) from the User Interface. This is what we were doing in other projects with other data stores.
As an example we want to read data from Firebase that is then set in a class called Stimmungsabfrage. Then we want to work with this data and to present it also in our controllers(textviews, listviews) in an activity (in our UI).
In our data access class we are using the following function to retrieve the data:
root.child(strKey).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
// Hier bekommst du dann letztlich die Stimmungsabfrage
StimmungAbfrage stimmungAbfrage = child.getValue(StimmungAbfrage.class);
}
}})
The problem with this function is that it does return void. So we are not able to return the retrieved object stimmungAbfrage to the user interface.
The only way is to assign the user interface controls right in this function, which we want to avoid, because we want to separate the UI from the data access logic.
How can this be done in Firebase?
Remove for loop:
for (DataSnapshot child : dataSnapshot.getChildren()){}
Simply write:
StimmungAbfrage stimmungAbfrage = child.getValue(StimmungAbfrage.class);
It will not return void.
Related
When my app loads, the datasnapshot returns all the fields in the child listener in the onChildAdded() method which is how I populate a local list variable called TaskGroups from the datasnapshot and below is the contents of that datasnapshot
taskGroupList={KYTD1647450187={taskGroupName=Task, taskGroupID=KYTD1647450187, taskGroupColorString=#FFFF0BFF}, AONS1647450187={taskGroupName=Fitness, taskGroupID=AONS1647450187, taskGroupColorString=#FF6E2E7B}, BNZH1648154782={taskGroupName=School, taskGroupID=BNZH1648154782, taskGroupColorString=#FF6E2E7C}
and here is what it looks like on Firebase before adding anything
But when I attempt to add a new "taskgroup" from my Android app (I just added new taskgroup "Apdjf" just for this example), all the correct fields are uploaded to Firebase shown in the image below
However in the onChildChanged() method of the same listener of my app, I don't receive all the fields for the new "taskgroup" that I just added. Instead I'm only getting the "taskGroupID" for the newly added taskgroup and missing the "taskGroupColorString" and the "taskGroupName" fields and I need all this info to rebuild a certain part of my app. Below is what my datasnapshot gives me
taskGroupList={HDCL1649906550={taskGroupID=HDCL1649906550}, KYTD1647450187={taskGroupName=Task, taskGroupID=KYTD1647450187, taskGroupColorString=#FFFF0BFF}, AONS1647450187={taskGroupName=Fitness, taskGroupID=AONS1647450187, taskGroupColorString=#FF6E2E7B}, BNZH1648154782={taskGroupName=School, taskGroupID=BNZH1648154782, taskGroupColorString=#FF6E2E7C}
What is going on?
This is the Java code that gets called when I add a new "TaskGroup" to the database
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) {
if (childDataSnapshot.child("taskGroupList").getValue() != null) {
String taskGroupId = generateTaskGroupID();
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupID").getRef().setValue(taskGroupId);
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupColorString").getRef().setValue(finalDatabaseTaskGroupColorString[0]);
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupName").getRef().setValue(taskGroupSelected);
}
}
}
You're performing three separate write operations in the code you shared here:
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupID").getRef().setValue(taskGroupId);
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupColorString").getRef().setValue(finalDatabaseTaskGroupColorString[0]);
childDataSnapshot.child("taskGroupList").child(taskGroupId).child("taskGroupName").getRef().setValue(taskGroupSelected);
If taskGroupId is a new unique ID, then the first call to setValue (setValue(taskGroupId)) generates an onChildAdded event, while the other two setValue calls generate onChildChanged events.
To prevent this from happening, you should add all child nodes in a single write operation, for example with:
Map<String,Object> values = new HashMap<>();
values.put("taskGroupID", taskGroupId);
values.put("taskGroupColorString", finalDatabaseTaskGroupColorString[0]);
values.put("taskGroupName", taskGroupSelected);
childDataSnapshot.child("taskGroupList").child(taskGroupId).setValue(values);
Now there's only a single write operation, so this will (if the taskGroupId is a new unique ID) only result in a single onChildAdded call with all three properties.
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.
HelloBelow is my database structure
I want to first get results from post object and using "userid" from that I want to get results from Users Object.
I want to update ViewModel with the above results that contain fields that are in both the objects
How to achieve this ?
I have written code to get the result from post object but how to again make call to get user object and update viewmodel and livedata object
private static final DatabaseReference POST_REF =
FirebaseDatabase.getInstance().getReference("/Post");
private final FirebaseQueryLiveData liveData = new
FirebaseQueryLiveData(POST_REF);
#NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
There are different ways to model you database to archive this, It is always good to follow best practices see: https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure
In this case, since you only want the Profile Pic and name I would save it directly into the object:
{
"Posts":{
"post1":
{
"likes":23,
"userId":"id",
"user":
{
"imageUrl":"url",
"name":"Name"
}
}
}
}
Of course, the tradeoff is that the image URL won’t be updated if the user node gets updated (unless you code something to update it in a Cloud Function for example)
On the other hand, you could also perform those two calls to the Firebase Realtime Database (one to get the post, and the other to get the user data):
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Post object and use the values to update the UI
Post post = dataSnapshot.getValue(Post.class);
ValueEventListener userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot userDataSnapshot) {
post.setUser(userDataSnapshot.getValue(User.class);)
}
};
mUserReference.addListenerForSingleValueEvent(userListener);//only fetch data once
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
// ...
}
};
mPostReference.addValueEventListener(postListener);
EDIT:
Having the user object inside the post would work using the architecture components (Just be sure to have the proper class to deserialize), on the other hand, since you are using the FirebaseQueryLiveData https://firebase.googleblog.com/2017/12/using-android-architecture-components.html and may want to avoid writing the user on that post node, I think you can have both ViewModels with different Database references, and once the data is fetched you could just update the post object e.g. post.setUser(user) with the user obtained from the other ViewModel Observer and then update the UI. You could also have a HashMap to keep track of what post needs what user, although this answer looks like a way to go: https://stackoverflow.com/a/46483213/1537389. Hope that helps
i want to make this type of collection in my firestore
where chatRooms will be my collection name, combination of myUid and opponentsUid will be my sub-collection in which different documents will be placed. My problem is i want to check if my collection contains sub-collection named myUid_opponentsUid or opponentsUid_myUid and i am not able to search a best query for doing this.
All i know is that we can fetch the whole list and then check if it contains the specific room or not, but its a lengthy process, so i want to better method for it.
Thanks in advance.
There are a few misconceptions in your question to clear up first:
In Firestore collections don't really exist as distinct entities. It's the documents inside a collection that cause it to become visible.
Also, collections can only contain documents, which in turn can contain collections, but the structure must alternate, so you can't have a collection called chatRooms that contains a collection myUid_opponentUid. Inside chatRooms there must be a document.
So if chat rooms contain messages, a straightforward way to do what you want is to create a document that represents that chatRoom. Then within that create a subcollection for the messages.
If you sort the UIDs before creating the composite chatRoom key you can then test whether or not the chat room exists by using a single get(). The structure would look like this:
chatRooms/(uid-pair)/messages/(message-id)
Note that you don't actually need to store anything at the chatRoom/(uid-pair) level to create children at the messages level: you can just create new messages and listen directly.
Try to Read Total Number of child .! Hope this thing may helps you.and if you want to implement your own api then try using Firebase Functions..and last thing I want to add is that if You want to add get Count without reading number of child you have to implement one method that getChildCount before storing data and then append them with key like JiGh_31GA20JabpZBfa,1` and only read keys and then use comma separator and you will get your result that this parent contains child or not.?
DatabaseReference myRef = database.getReference();
//You can use the single or the value.. depending if you want to keep track
String id= UUID.randomUUID().toString();//randomID for task
Object object=new Object ();
public int chidcount(String child){
string childcount="0";
//You can use the single or the value.. depending if you want to keep track
myRef.child(child).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snap: dataSnapshot.getChildren()) {
childcount=snap.getChildrenCount();
Log.e(snap.getKey(),snap.getChildrenCount() + "");
}
addvalue(childcount);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
private addvalue(String childcount){
object=setid(id);
object=setname("name");
getchildCount("object");
mdatabaseRef.child("rating").child(manager.getId()+childcount).child(currentEmployee.getId()).child(id).setValue(rating);}
I know I am late.
Posting for future users.
Try this:
DocumentReference datab = db.collection("collection_name").document("Doc_name");
datab.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if(documentSnapshot.contains("field_name"))
{
Toast.makeText(YourActivity.this, "Child exixts.", Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(YourActivity.this, "Doesnt exits.", Toast.LENGTH_SHORT).show();
}
});
For Firebase Firestore to check whether the document has entries (fields), Use this command
firebaseFirestore.collection("Users").document(userId)
.addSnapshotListener {
documentSnapshot, _ ->
if (documentSnapshot!!.contains("name")) {
Log.i("Name", "Name exists")
} else {
Log.i("Name", "Name doesn't exists")
}
}
I have several strings stored under specific reference: mReference.child(rID).child(userID2) which I want to retrieve using childEventListener as I am doing some task also when these string gets removed and that is only possible with onChildRemoved of ChildEventListener.
Here's what I have tried:
mReference.child(rID).child(userID2).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d("dataHEre", dataSnapshot.getValue().toString());
}
}
The problem is that I'm unable to retrieve data using keys here as dataHEre is logging out:
D/dataHEre: 28.8419556
D/dataHEre: 78.779063
D/dataHEre: 3
D/dataHEre: Railway Colony
D/dataHEre: Sleep
D/dataHEre: 12:36 AM
D/dataHEre: Superman
which are values?
So, I want to know how can I retrieve data here using keys and using ChildEventListener and then assign the data retrieved to various strings?
I think that you are doing your query in the wrong way. onChildAdded method is retrieving you each child of a specific value (userID2 I suppose). If that is what you want, then just use a onValueEventListener() to retrieve the whole dataSnapshot of your userId2 node each time that it changes.
If you want to retrieve the data just once, you should use onSingleValueEventListener(). And OnChildEvent() is used to retrieve lists of data where you want to track each of the child individually. For example if you attach an OnChildEventListener to mReference.child(rID) you will get a OnChildAdded for each userId, what is pretty usefull like for example fill a RecyclerView or a ListView being able to update each item individually together with your Firebase Data.
If i'm not wrong what you want is just get updates of your userId2 reference, in that case attach a OnValueEventListener to that reference and you will get a call each time a value is modified, deleted, or added.
firebaseDatabaseRef.addValueEventListener(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
customPOJO customPOJO = dataSnapshot.getValue(YourCustomPOJO.class);
customPOJO.getName();
customPOJO.getEmail();
customPOJO.getfavoriteFood();
//and so on....
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});