I've got a Firebase Database with User data. And I have a User class with this method:
private void getFromFirebase(){
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference reference = database.getReference( FirebaseReferences.USERS ).child( userId );
reference.addValueEventListener( new ValueEventListener(){
#Override
public void onDataChange( #NonNull DataSnapshot dataSnapshot ){
display_name = dataSnapshot.child( "username" ).getValue( String.class );
photo = dataSnapshot.child( "profile_image" ).getValue( String.class );
}
#Override
public void onCancelled( #NonNull DatabaseError databaseError ){
}
} );
}
In the MainActivity.java, I want to get the profile photo from the database and show it in an ImageView. Problem is that Firebase is asynchronous and it returns immediately, so I can't just call myUser.getFromFirebase() because myUser will still have all null values. I searched a lot but can't find the solution, since I don't want my User class to interact at all with the UI (I want to use a 3-tier methodology).
I tried creating an AsyncTask extension class, but it has the same problem, because the issue is at the User class. I also tried the CountdownLatch approach, but since the value is already on the database, the onDataChange method never gets called at all!!
Does anyone has any idea how to solve this? I'm sure it's extremely easy, because it's not a weird scenario, but I'm so stucked...
Create callback listener, like below
public interface OnDataReceiveCallback {
void onDataReceived(String display_name, String photo);
}
Modify method to pass callback
private void getFromFirebase(OnDataReceiveCallback callback){
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference reference = database.getReference( FirebaseReferences.USERS ).child( userId );
reference.addValueEventListener( new ValueEventListener(){
#Override
public void onDataChange( #NonNull DataSnapshot dataSnapshot ){
display_name = dataSnapshot.child( "username" ).getValue( String.class );
photo = dataSnapshot.child( "profile_image" ).getValue( String.class );
callback.onDataReceived(display_name,photo);
}
#Override
public void onCancelled( #NonNull DatabaseError databaseError ){
}
});
}
Final call of getFromFirebase
getFromFirebase(new OnDataReceiveCallback(){
public void onDataReceived(String display_name, String photo){
// do something
}
});
You will have to arrange for your views to be updated with each callback you receive to onDataChange. If you don't want your callback to directly modify views, you will need to adopt some form of app architecture to abstract your repository (Realtime Database) from your views.
This is not "extremely easy". Also, you have a lot of choices for app architecture (MVP, MVC, MVVM), and various frameworks to help with this (such as Android's own LiveData). What you are venturing into is highly opinionated, and involves writing a lot more code than you have here.
I can point you to a repository that uses Jetpack's Android Architecture Components as app architecture for a demo app that uses both RTDB and Firestore, but you'll see that it's a lot of lines of code, and it's also just my opinion about how to get things done. You will find lots of other opinions out there.
Related
So I have firebase initialised in my acitivity.
mFirebase = FirebaseDatabase.getInstance();
mDatabaseReference = mFirebase.getReference("buses");
mBusReference = mDatabaseReference.child(mSelectedBusModel.getRegistrationNo());
mValueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
...
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.e("Data", "Cancelled");
databaseError.toException().printStackTrace();
}
}
mBusReference.addValueEventListener(mValueEventListener);
I have a singleton that extends Application and inside the onCreate method I have enabled firebase persistence:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Now my problem is when I go offline and reopen my activity the data has not been cached, nothing loads. I have read the docs and done everything mentioned but it still does not work.
What I want to do is enable my app to cache data already loaded from firebase so it's always available even after the app has been closed and re-opened.
Please note no errors are displayed in my console.
Just using setPersistenceEnabled(true) doesn't cut it. You must change your listener because you save the data in your disk but you dont handle them. You must use something like this (it was taken from firebase documentation i have used it for my app and worked for me).
--- edit ---
If you want to keep your data synced you must use ref.keepSynced(true);. The below piece of code is what i used to retrieve cached data from reference. When i am offline the child returns the values normally.
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("user");
ref.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot snapshot, String previousChild) {
System.out.println(snapshot.getValue());
}
});
I'm using both Google Firebase and Room Database to store Event objects I have created, containing all the details of a given event. I use Firebase for online storage and user interaction, and I use Room Database for an easier, persistent RecyclerView implementation.
My problem is that my function for populating the Room Database with events in the user's radius doesn't seem to execute at all in NEITHER an asynchronous task NOR a Room Database callback that overrides the onCreate method. The function was fully debugged while it was used on the main thread, so I think the reason why it isn't working right now has to do with my lack of understanding of how asynchronous tasks work.
Here is my function, currently within the onCreate method of the Room Database class:
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference().child("events").child("eventid");
if (ref != null) ref.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Event event = dataSnapshot.getValue(Event.class);
if (event != null) {
LatLng user_location = new LatLng(current_user.get(0).getLatitude(),
current_user.get(0).getLongitude());
LatLng event_location = new LatLng(event.getLatitude(), event.getLongitude());
int distance_preference = current_user.get(0).getDistance();
double distance_between_user_and_event = SphericalUtil
.computeDistanceBetween(user_location, event_location) / 1609.344;
if (distance_between_user_and_event <= distance_preference) {
eventDao.insert(event);
}
}
}
#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) {
}
});
In an asynchronous task, I can't really debug it since it isn't on the main thread (unless I don't know how lol), and this code doesn't seem to execute at all when in the onCreate method for Room Database. There's obviously something I'm missing here.
Also, it's worth saying that my EventDao, EventDatabase, and EventRepository have all been fully debugged. I can use all of them perfectly fine at runtime -- it's just this early population task that isn't working!
Thanks so much for any help!!
Similar project created by me which uses
Firebase authentication to login user
Save and cache user notes to
sql lite database with Room
Save user notes to firebase base
database
✍️ Simple Note Making App use Sqllite Room 🧰 for caching the notes and 📥 Firebase Database for online storage
https://github.com/LanguageXX/Simple-Note-App-with-Online-Storage
Hope that helps you
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 can't get to work this code to read (getUser method) from my Firebase DB. I have searched for answer for 2 hours, tried different tutorials and nothing helped. Problem is that it looks like the onDataChange method is never actually called (tested with Log) and I don't know what am I doing wrong. Writing to DB (saveUser method) is working as it should.
Code:
public class UserDatabase {
private User user;
private DatabaseReference databaseReference;
public UserDatabase() {
databaseReference = FirebaseDatabase.getInstance().getReference();
}
public void saveUser(User user) {
databaseReference.child("users").child(user.getUid()).setValue(user);
}
public User getUser(String uid) {
databaseReference = FirebaseDatabase.getInstance().getReference().child("users").child(uid);
databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
user = dataSnapshot.getValue(User.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return user;
} }
Thanks for answer
As I see in your comment, you are saying that "eventually gets the data" but I tell you that the way in which you are trying to use the getUser() method which has as a return type the User class, is not the correct way to solve this problem, especially when it comes to asynchronous methods. You cannot return something now that hasn't been loaded yet. onDataChange() method has an asynchronous behaviour. A quick fix to your problem would be to use those user objects that you are getting from the database only inside onDataChange() method or, if you want to use those objects outside, please see the last part of my answer from this post, in which I explain the way in which you can use a callback in order to achieve this.
I have an activity and a model called CourseDetails.
String getData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
getData = c.getCourseName();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
textview1.setText(getData);
Using above code throws NullPointerException at last line above. But if I put textview1.setText(getData) into the ValueEventListener, under getData = c.getCourseName(), the data can be displayed correctly.
Methods I found working are using SharedPreferences or setting data from a method such as public void display(String data) { textview1.setText(data); }. But what are the other ways to keep the retrieved data even if the data is outside ValueEventListener?
For instance I want to persist the data added into an ArrayList.
ArrayList<String> listData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
String code = c.getCourseCode();
String name = c.getCourseName();
String CodeName = code + " " + name;
listData.add(CodeName);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// data in ArrayList should be able to display here
StringBuilder builder = new StringBuilder();
for (String s : listData) {
builder.append(s + "\n");
}
textview1.setText(builder.toString());
How to achieve this kind of persistence?
As per my understanding, Firebase will notify all it's data listener attached to specific references (database references wherever the addValueEventListener is added) when those specific data gets modified. That is when
onDataChange will be called, when there is modification of the data at those database references,
(besides modification the method will always be called first time).
And this happens
asynchronously, so in the first case where null occurs because we don't know whether data is retreived from Firebase and
as far as I know, Android's main thread cannot be put on hold or pause until we retreive the data that's why we use Asynchronous tasks in Android.
So, I think the best way to do specific updates or task on data change is within onDataChange method. So, like you stated it could be
done by making those changes within onDataChange itself or by calling some other method from onDataChange.
Or, if you are using
adapter then, notifying adapter about the change within onDataChange. Also, you can take a look at other choice i.e. FirebaseRecyclerAdapter then,
it will handle the update automatically without any extra effort.