this is my logcat output
I am trying to fetch data from Firestore and work on them tried this but not working
getting null value for "emergencyNumber" outside "readData()"
I have tried this solution Text but still getting null
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_emergency_contact);
Log.d("TAG","Initial");
firebaseAuth = FirebaseAuth.getInstance();
fStore = FirebaseFirestore.getInstance();
userID = firebaseAuth.getCurrentUser().getUid();
readData(new FirebaseCallBack() {
#Override
public void onCallback(String str) {
emergencyNumber = str;
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
phone = (TextView) dialog.findViewById(R.id.EmergencyContactNumber) ;
//emergencyNumber = phone.getText().toString() + "Hello";
phone.setText(emergencyNumber);
Log.d("TAG",emergencyNumber+"inner read me");
}
});
Log.d("TAG",emergencyNumber+"middle");
Log.d("TAG",emergencyNumber+"end");
}
private void readData(FirebaseCallBack firebaseCallBack) {
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot document = task.getResult();
long l = document.getLong("EmergencyContact");
emergencyNumber = "0" + Long.toString(l);
firebaseCallBack.onCallback(emergencyNumber);
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
Log.d("TAG",emergencyNumber+"exit read me");
}
}
});
}
private interface FirebaseCallBack {
void onCallback(String str);
}
}
Firestore asynchronous API getting null values outside readData.
Any code that needs data from Cloud Firestore needs to be inside the "onComplete()" method, or be called from there. It doesn't really matter if you create another callback, the same rules apply. This means that you cannot use the value of "emergencyNumber" outside the "onCallback()" method. Please note that this method fires, only when "onComplete()" method fires, hence that behavior. When the following Log statement is triggered:
Log.d("TAG",emergencyNumber+"middle");
The data isn't finished loading yet, that's why you have that order of execution in your logcat.
If you are not comfortable with callbacks, then I recommend you using the modern way of dealing with asynchronous programming when getting data from Firestore, which is using LiveData and ViewModel. Here you can find an example from one of my repositories where I have used the MVVM architecture pattern with LiveData and ViewModel to authenticate users in Firebase.
If you consider at some point in time to try using Kotlin, please check below an example:
https://github.com/alexmamo/FirestoreJetpackCompose
Where I have used Kotlin Coroutine for getting data from Firestore.
Related
I'm trying to update parts of a WebView in my Android app with data I'm getting from a peer connected via Firebase. For that, it could be helpful to execute blocking operations that will return the needed data. For example, an implementation of the Chat example that will wait until another chat participant writes something before the push.setValue() to return.
Is such a behavior possible with Firebase?
import com.google.android.gms.tasks.Tasks;
Tasks.await(taskFromFirebase);
On a regular JVM, you'd do this with regular Java synchronization primitives.
For example:
// create a java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// tell the caller that we're done
semaphore.release();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
// wait until the onDataChange callback has released the semaphore
semaphore.acquire();
// send our response message
ref.push().setValue("Oh really? Here is what I think of that");
But this won't work on Android. And that's a Good Thing, because it is a bad idea to use this type of blocking approach in anything that affects the user interface. The only reason I had this code lying around is because I needed in a unit test.
In real user-facing code, you should go for an event driven approach. So instead of "wait for the data to come and and then send my message", I would "when the data comes in, send my message":
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// send our response message
ref.push().setValue("Oh really? Here is what I think of that!");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
throw firebaseError.toException();
}
});
The net result is exactly the same, but this code doesn't required synchronization and doesn't block on Android.
I came up with another way of fetching data synchronously.
Prerequisite is to be not on the UI Thread.
final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();
firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
tcs.setResult(mapper.map(dataSnapshot));
}
#Override
public void onCancelled(DatabaseError databaseError) {
tcs.setException(databaseError.toException());
}
});
Task<List<Object>> t = tcs.getTask();
try {
Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
t = Tasks.forException(e);
}
if(t.isSuccessful()) {
List<Object> result = t.getResult();
}
I tested my solution and it is working fine, but please prove me wrong!
Here's a longer example based on Alex's compact answer:
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance();
final CollectionReference chatMessageReference = firestore.collection("chatmessages");
final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments();
final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);
Note that this is not good practice as it blocks the UI thread, one should use a callback instead in general. But in this particular case this helps.
If anyone is also thinking about how to use Kotlin's coroutine you can use kotlinx-coroutines-play-services.
Add to your app build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"
Then simply:
suspend fun signIn(email: String, password: String) {
try {
val auth: FirebaseAuth = FirebaseAuth.getInstance()
auth.signInWithEmailAndPassword(email, password).await()
} catch (e: FirebaseAuthException) {
println("${e.errorCode}: ${e.message}")
}
}
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});
I want to add data to an ArrayList inside a For-Loop and it doesn't work.
I've got a Cloud Firestore in Firebase with a collection filled with documents by another app. This one is meant to get the data and put it in a Recycler View. Adapter and all are set. Far as I can see, the problem is with filling the (Array)list inside the For-Loop? (As also just filling in any data inside this loop doesn't work, but I'm not sure.)
I noted some of my tries and errors in the code using comments. I'm sure, it must be just some minor mistake, but I really can't find it.
Adding data with the exact same code outside the For-Loop works, inside doesn't work (see "Blah")
private ArrayList<String> patients;
private String testString;
//-> String einzig zu Testzwecken
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
patients = new ArrayList<>();
textView = findViewById(R.id.textView);
db.collection("Test")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
//patients = new ArrayList<>();
//-> doesn't work
patients.add((document.getData().toString()));
//doesn't work
listData.add(new Data(R.drawable.hohlbrot, "ahjotest"));
//doesn't work (trying to feed the recyclerView more directly)
//Toast.makeText(MainActivity.this, document.getData().toString(), Toast.LENGTH_SHORT).show();
//textView.setText(document.getData().toString());
// -> both work
testString = testString + document.getData().toString();
textView.setText(testString);
// -> works
patients.add("Blah1");
//doesn't work
//Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
//Log.d(TAG, "Error getting documents: ", task.getException());
patients.add("BlahError");
}
}
});
patients.add("Blah2");
//works
Well, I tried several things, but with the setting, that I would expect to work, there were no error messages. It just didn't do anything. In the RecyclerView the "Blah2" occurs as do any Strings I add later. Anything I tried inside the For-Loop (Toast, String-to-TextView,...) actually worked, as long as it didn't use the ArrayList.
PS: Me am Noob, beg your patience -.-
try to run your for loop inside OnSuccessListener if you are using firebase.
the syntax is similar to this:
firebaseFirestore.collection("some collection").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
//do your work here
})
I couldn't get it running with the solutions/approaches mentioned, but I rebuild it following this tutorial:
https://www.youtube.com/watch?time_continue=2&v=ub6mNHWGVHw&feature=emb_title
-> so using the Firestore RecyclerView (instead of the normal one) and the given setup (no For-Loop involved).
How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. 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.
Image of DB structure
I have currently created a method for updating a book using Firestore. I am attempting to call this method in onOptionsItemSelected.
I am relatively new to Android and was always under the impression when calling this method I would call it as updateBook(Book book).
Below is my updateBook method
#Override
public void updateBook(Book book) {
String chapterName = editTextChapterName.getText().toString().trim();
String chapterInfo = editTextChapterInfo.getText().toString().trim();
int chapterNumber = numberPickerChapterNumber.getValue();
if (chapterName.trim().isEmpty() || chapterInfo.trim().isEmpty()) { //ensure that user has not left boxes empty
Toast.makeText(this, "Please add a chapter name and the chapter information", Toast.LENGTH_SHORT).show();
}
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference bookRef = db.collection("Book")
.document();
bookRef.update("chapterName", book.getChapterName(),
"chapterInfo", book.getChapterInfo(),"chapterNumber", book.getChapterNumber())
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()){
Toast.makeText(AdminUpdateActivity.this, "Success", Toast.LENGTH_SHORT).show();
}
}
});
Below is where I am trying to call my method
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.save_icon:
updateBook();
return true;
default:
return super.onOptionsItemSelected(item);
}
Within the updateBook parenthesis I am getting an error "Cannot be applied to ()".
I have read through other questions on here to do with this topic but have not found a solution. Can someone explain how to fix this issue and why?
Thanks
To solve this, you should change the following line of code:
DocumentReference bookRef = db.collection("Book")
.document();
to
DocumentReference bookRef = db.collection("Book")
.document(bookId);
Without passing the id of the book to the document() method, the Firebase SDK doesn't really know which book object you want to update. Beside that, calling document() method on a DocumentReference it only generates a new random id each time.
There is also another change is also needed. Please also remove the argument from your updateBook() method. Should be:
public void updateBook() {}
Without any argument.
I'm trying to update parts of a WebView in my Android app with data I'm getting from a peer connected via Firebase. For that, it could be helpful to execute blocking operations that will return the needed data. For example, an implementation of the Chat example that will wait until another chat participant writes something before the push.setValue() to return.
Is such a behavior possible with Firebase?
import com.google.android.gms.tasks.Tasks;
Tasks.await(taskFromFirebase);
On a regular JVM, you'd do this with regular Java synchronization primitives.
For example:
// create a java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// tell the caller that we're done
semaphore.release();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
// wait until the onDataChange callback has released the semaphore
semaphore.acquire();
// send our response message
ref.push().setValue("Oh really? Here is what I think of that");
But this won't work on Android. And that's a Good Thing, because it is a bad idea to use this type of blocking approach in anything that affects the user interface. The only reason I had this code lying around is because I needed in a unit test.
In real user-facing code, you should go for an event driven approach. So instead of "wait for the data to come and and then send my message", I would "when the data comes in, send my message":
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// send our response message
ref.push().setValue("Oh really? Here is what I think of that!");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
throw firebaseError.toException();
}
});
The net result is exactly the same, but this code doesn't required synchronization and doesn't block on Android.
I came up with another way of fetching data synchronously.
Prerequisite is to be not on the UI Thread.
final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();
firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
tcs.setResult(mapper.map(dataSnapshot));
}
#Override
public void onCancelled(DatabaseError databaseError) {
tcs.setException(databaseError.toException());
}
});
Task<List<Object>> t = tcs.getTask();
try {
Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
t = Tasks.forException(e);
}
if(t.isSuccessful()) {
List<Object> result = t.getResult();
}
I tested my solution and it is working fine, but please prove me wrong!
Here's a longer example based on Alex's compact answer:
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance();
final CollectionReference chatMessageReference = firestore.collection("chatmessages");
final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments();
final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);
Note that this is not good practice as it blocks the UI thread, one should use a callback instead in general. But in this particular case this helps.
If anyone is also thinking about how to use Kotlin's coroutine you can use kotlinx-coroutines-play-services.
Add to your app build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"
Then simply:
suspend fun signIn(email: String, password: String) {
try {
val auth: FirebaseAuth = FirebaseAuth.getInstance()
auth.signInWithEmailAndPassword(email, password).await()
} catch (e: FirebaseAuthException) {
println("${e.errorCode}: ${e.message}")
}
}
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});