How to handle Asynchronous call in firebase - java

I have to return the response from the firebase to another function which will do further processing with that data.
public ArrayList<String> getDatas(String number) {
final ArrayList<String> requestList = new ArrayList<>();
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference reference = database.getReference("Users").child(number).child("request_list");
final DatabaseReference requestReference = database.getReference("Request");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.i(TAG, "onDataChange: ");
for (final DataSnapshot data : dataSnapshot.getChildren()) {
Log.i(TAG, "onDataChange: data: " + data.getValue());
requestList.add(data.getValue().toString());
}
Log.i(TAG, "onDataChange: for ended");
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return requestList;
}
Since the firebase operation is asynchronous I am not able to wait for the data to return. Can someone help me with this problem?

you can use a callback method
http://developer.android.com/reference/java/util/concurrent/Callable.html
By using Callable interfaces you can pass an argument as function I added a simple code snippet for understanding.
public class MainActivity<V> extends Activity {
Callable<String> doLogin=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doLogin=new Callable<String>() { //created but not called now.
#Override
public String call() throws Exception {
//make some piece of code
return "something"; //or false
}
};
CheckSession checkSession=new CheckSession("sessionName");
String sessionKey="";
try { //we are sending callable to the DAO or any class we want
sessionKey=checkSession.getSessionKey(doLogin);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class CheckSession{
String sessionName="";
Callable<String> func=null;
public CheckSession(String sessionName) {
super();
this.sessionName = sessionName;
}
public String getSessionKey(Callable<String> doLogin) throws Exception{
func=doLogin;
return (String) func.call();
}
}

Related

How to ensure Completion handler for one Firebase lookup completes before another one does

I have the following Firebase DB node structure:
UserInGroup
--- GroupID
--- UserId : true/false
Users
--- UserId
--- Username : String
--- ...
GroupStatus
--- GroupId
--- UserId: true/false
I need to pull for the first node to get all the users in the Group
Then use that info to get the users account info details
Finally check to see the users status in the Group
I cannot figure a way to implement the completionhandler in Java/Android ? I have done so for iOS with completionhandlers.
Can anyone assist with helping me implement the solution in Java?
---- UPDATE ----
I have done the following:
// Create an interface to init all the callback functions
private interface AllUsersCallback {
void onSuccess(DataSnapshot dataSnapshot);
void onStart();
void onFailure();
}
private void readData(Query query, AllUsersActivity.AllUsersCallback listener) {
listener.onStart();
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
listener.onSuccess(dataSnapshot);
} else { // dataSnapshot doesn't exist
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
//
listener.onFailure();
}
});
}
And lastly the Activity view:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Init ArrayList
userList = new ArrayList<>();
userInGroupReference = mFirebaseDatabase.getReference("GroupUsers");
userInGroupQuery = userInGroupReference.child(groupID).orderByValue().equalTo(true);
// Completion Handler for Lookups
readData(userInGroupQuery, new AllUsersActivity.AllUsersCallback() {
#Override
public void onSuccess(DataSnapshot dataSnapshot) {
// Clear the List (remove dupes)
userList.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
String userId = snapshot.getKey();
// Call function to set usernames to the users
setUsername(userId);
}
/*
THIS ALWAYS COMES OUT BLANK!? <--------
*/
for (int i = 0; i < userList.size(); i++) {
Log.e(TAG,"List element: " + userList.get(i).getUsername());
}
}
#Override
public void onStart() {
// When starting
Log.d("ONSTART", "Started");
}
#Override
public void onFailure() {
// If failed
Log.d("onFailure", "Failed");
}
});
}
and the function used to set the users username to the userList:
public void setUsername(String userId) {
userReference = mFirebaseDatabase.getReference("Users");
userQuery = userReference.child(userId).child("username");
// Add handle for listener
userQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
String username = dataSnapshot.getValue().toString();
AllUsers result = new AllUsers(username);
userList.add(result);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
These database calls are asynchronous - the callback code does not run immediately, it runs some time in the future when you actually get the data.
The easiest way to chain multiple dependent async queries is to put each query into its own function, and call it from the dependent query's callback. In your case, you could have multiple callbacks running at once, so as each one completes you can check for it to be done and check for them all to be done by comparing the size of the list with the number of queries launched.
For example:
private ArrayList<String> userList = new ArrayList<>();
private int numUsers = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// other setup stuff
startInitialQuery();
}
private void startInitialQuery() {
// make your initial query
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
userList.clear();
numUsers = 0; // dataSnapshot.getChildren().size();
// If the size() call above works, use that, otherwise
// you can count the number of children this way.
for(DataSnapshot snap : dataSnapshot.getChildren()) {
++numUsers;
}
for(DataSnapshot snap : dataSnapshot.getChildren()) {
String userId = snap.getKey();
readUser(userId);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
}
});
}
private void readUser(String userId) {
// make userQuery using "userId" input
userQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
String username = dataSnapshot.getValue().toString();
userList.add(username);
checkLoaded();
}
else {
--numUsers;
checkLoaded();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
--numUsers;
checkLoaded();
}
});
}
private void checkLoaded() {
if( userList.size() == numUsers ) {
// All done getting users! Show a toast, update a view, etc...
}
}
Alternately, if you switch to using Kotlin and coroutines you can write this as a pretty simple linear suspend function where you can actually make the different tasks wait.
A cleaner, but more invasive change, would be to move this all to a ViewModel that contains LiveData of each of these steps. As data is received, you post it to the LiveData and the UI can observe that and react accordingly (e.g update views, trigger the next call, etc).
Update
Here is an example showing how to do this with a ViewModel and LiveData
public class MainViewModel extends ViewModel {
private final MutableLiveData<List<String>> users = new MutableLiveData<>();
LiveData<List<String>> getUsers() {
return users;
}
private final ArrayList<String> userList = new ArrayList<>();
void startFetchingData() {
// build query
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
userList.clear();
for(DataSnapshot snap : dataSnapshot.getChildren()) {
String userId = snap.getKey();
readUser(userId);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
}
});
}
private void readUser(String userId) {
// build userQuery
userQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
String username = dataSnapshot.getValue().toString();
userList.add(username);
users.postValue(userList);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
}
});
}
}
and in the activity you set an observer for the LiveData that is notified any time the data changes.
model = new ViewModelProvider(this).get(MainViewModel.class);
final Observer<List<String>> userObserver = userList -> {
// Update the UI, or call something else
// this will get called every time the list of users is
// updated in the ViewModel
System.out.println("TEST: got data " + userList);
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getUsers().observe(this, userObserver);
model.startFetchingData();

When I send a message I can see it in firebase but i cannot read it on emulator or my phone it show nothing

I am making chat app ... but when i send a message it sent and i can see it on firebase but in emuloter and phone i can't ... why it happen ? i think problem may be in readMessages()
This is my chat activity class
userQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String name = "" + ds.child("name").getValue();
hisImage = "" + ds.child("image").getValue();
nameTv.setText(name);
try {
Picasso.get().load(hisImage).placeholder(R.drawable.ic_defult_img_face).into(profileTv);
} catch (Exception e) {
Picasso.get().load(R.drawable.ic_defult_img_face).into(profileTv);
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
sendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String message = messageEt.getText().toString().trim();
if (TextUtils.isEmpty(message)) {
Toast.makeText(ChatActivity.this, "Cannot send empty message...", Toast.LENGTH_SHORT).show();
} else {
sendMessage(message);
}
}
});
readMessages();
seenMessages();
}
read and send function
private void readMessages() {
chatList = new ArrayList<>();
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference("Chats");
dbRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
chatList.clear();
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Modelchat chat = ds.getValue(Modelchat.class);
if (myUid.equals(chat.getReceiver()) && hisUid.equals(chat.getSender()) ||
hisUid.equals(chat.getReceiver()) && myUid.equals(chat.getSender())) {
chatList.add(chat);
}
adapterChat = new AdapterChat(ChatActivity.this, chatList, hisImage);
adapterChat.notifyDataSetChanged();
recyclerView.setAdapter(adapterChat);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private void sendMessage(String message) {
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
String timeStamp = String.valueOf(System.currentTimeMillis());
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("Sender", myUid);
hashMap.put("receiver", hisUid);
hashMap.put("message", message);
hashMap.put("timeStamp", timeStamp);
hashMap.put("isSeen", false);
databaseReference.child("Chats").push().setValue(hashMap);
messageEt.setText("");
}
anyone can help me ?
if you want more code or please comment and i will submit it.
I will give some hints:
Move this in your readMessages():
chatList.clear();
And place it under this:
chatList = new ArrayList<>();
//here...
Make sure that your Modelchat looks like this:
class Modelchat{
//the main thing is that these must be written exactly like the keys in your database
private String Sender;
private String receiver;
private String message;
private String timeStamp;
private boolean isSeen;
public Modelchat(String Sender,String receiver,String message,String timeStamp,boolean isSeen){
this.Sender=Sender;
this.receiver=receiver;
this.message=message;
this.timeStamp=timeStamp;
this.isSeen=isSeen;
}
//generate also getters and setters......
}

Making Java thread wait for value

I need to retrieve a users name from my Firebase database from their UID. This is my code for doing this, but I cannot work out how to get this working. The thread goes from:
final String[] returnName = new String[1];
Immediately to:
return returnName[0].toString();
Without waiting for it to populate data from the listener first, so the return value is null and the app crashes. Here is my full code for this module:
private synchronized String getFriendName(String key) {
final String[] returnName = new String[1];
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot datas : dataSnapshot.getChildren()) {
String userResult = datas.getKey();
if (userResult != null) {
returnName[0] = dataSnapshot.getValue().toString();
String temp = dataSnapshot.getValue().toString();
Toast.makeText(getBaseContext(), "Step 1: " + temp, Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Step 2: " + returnName[0], Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return returnName[0].toString();
}
I have spent hours reading and trying stuff to get this to work but I cannot make the code actually execute properly first before firing off the return value.
Can anyone help me?
#TaherKorshidi
This is the code that calls this function:
GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(currentLocation.latitude, currentLocation.longitude), radius);
geoQuery.removeAllListeners();
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
#Override
public void onKeyEntered(String key, GeoLocation location) {
friendID = key;
String friend = getFriendName(friendID);
}
}
Answer, #TaherKorshidi the answer was to get the other values in the same listener. There is no other way around this and I wasn't sure how to do it until you pointed me in this direction. Working solution:
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
#Override
public void onKeyEntered(String key, GeoLocation location) {
getFriendKey = key;
if (getFriendKey != ping_userID) {
for(ContactsList d : UserList){
if(d.getUID() != null && d.getUID().contains(getFriendKey)) {
friendName = d.getName();
}
}
Toast.makeText(getBaseContext(), "Name: " + String.valueOf(friendName), Toast.LENGTH_SHORT).show();
getFriendLocation(getFriendKey, friendName);
}
}
You can use asyncTask for this purpose as below
public static class GetFriendName extends AsyncTask<String, Void, Void>{
String returnName;
#Override
protected void onPreExecute() {
super.onPreExecute();
//Show progress bar
}
#Override
protected Void doInBackground(String... strings) {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot datas : dataSnapshot.getChildren()) {
String userResult = datas.getKey();
if (userResult != null) {
returnName = dataSnapshot.getValue().toString();
String temp = dataSnapshot.getValue().toString();
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
//Dismiss progress bar
showFriendName(returnName);
}
}
you can call this class from onCreate() as below
GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(currentLocation.latitude, currentLocation.longitude), radius);
geoQuery.removeAllListeners();
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
#Override
public void onKeyEntered(String key, GeoLocation location) {
friendID = key;
new GetFriendName().execute(friendID);
}
}
then you can assign your friendName to a string value by using this method and do your work there
private static void showFriendName(String friendName){
Toast.makeText(stackOverflowActivity, ""+friendName, Toast.LENGTH_SHORT).show();
String friend = friendName;
//Do your work here
}
this method is calling from onPostExecute().
one way is to use CountDownLatch class:
private synchronized String getFriendName(String key) {
final String[] returnName = new String[1];
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
final CountDownLatch latch = new CountDownLatch(1);
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot datas : dataSnapshot.getChildren()) {
String userResult = datas.getKey();
if (userResult != null) {
returnName[0] = dataSnapshot.getValue().toString();
String temp = dataSnapshot.getValue().toString();
Toast.makeText(getBaseContext(), "Step 1: " + temp, Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Step 2: " + returnName[0], Toast.LENGTH_SHORT).show();
latch.countDown();
}
}
latch.countDown();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnName[0].toString();
}
although it solve your problem, but it is not a good solution.

Android: What is the right approach to returning values from an anonymous class to thread?

What modification is needed for getVehicleName () function to return vehicle name correctly to the parent thread? What will be the best approach? I tried handler and call back functions, but ended up with compile time errors ("attempting to assign weaker access privileges").
Parent function (complete code not pasted)
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
boolean val = checkifVehicleAvailable("1111A");
} catch (Exception e){
}
});
Functions
public boolean checkifVehicleAvailable(String drive_this) {
if( getVehicleName(drive_this).equals("1111A"){
return true;
}
return false;
}
// Need to be modified to return String
public String getVehicleName(String ID){
/*DB references*/
//final String vehicleName;
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef;
DatabaseReference childRef;
myRef = database.getReference();
childRef = myRef.child("vehicles");
childRef.child(ID).addListenerForSingleValueEvent(new ValueEventListener() {
String vehicleSecretName;
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
vehicleName = dataSnapshot.child("vehicleName").getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
You could save the value inside the SharedPreferences and register a SharedPreferenceListener to notify this listener if a certain value has changed. The listener could look like that:
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals("myKey") {
sharedPreferences.getString(key);
//do whatever after that > e.g. start your async task or load that value into an AsyncTask via execute(..) method.
}
}
you coult for example say:
final boolean[] result = new boolean[1];
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
boolean val = foo();
result [0] = val;
} catch (Exception e){
}
});
}
private boolean foo(){
return false;
}

map.containsKey(key) returns true, but map.get(key) doesn't return anything

I'm working on getting a simple messaging system working using Firebase. But this one thing is causing me to want to pull out my own hair. I have a map that I'm using to store <String,Message> pairs, where Message is a class I wrote. I can't get my messages out of the map though. I've isolated the problem to be with map.get(key) not returning the Message, even though map.containsKey(key) returns true. What could be causing this and how do I fix it?
Here's the Message class:
public class Message {
private long timeStamp;
private String message;
private String to;
private String from;
public Message(String message, String to, String from)
{
timeStamp = new Date().getTime();
this.message = message;
this.to = to;
this.from = from;
}
public Message(){
}
public String getMessageText() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTo() {
return this.to;
}
public void setTo(String to) {
this.to = to;
}
public String getFrom() {
return this.from;
}
public void setFrom(String from) {
this.from = from;
}
public long getTimeStamp() {
return this.timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
}
This is the code I used that showed me map.containsKey(key) returns true:
public class LoadMessages extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_load_messages);
// Load previous messages
final LinearLayout messagesLayout = (LinearLayout) findViewById(R.id.messagesLayout);
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference databaseReference = database.getReference(getIntent().getStringExtra("user") + "/Messages");
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
Map<String, Message> map = new TreeMap<String, Message>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Message m = snapshot.getValue(Message.class);
map.put(String.valueOf(m.getTimeStamp()), m);
TextView TV = new TextView(LoadMessages.this);
Boolean ck = map.containsKey(String.valueOf(m.getTimeStamp()));
if (ck) {
TV.setText("Contains key");
} else {
TV.setText("Doesn't contain key.");
}
messagesLayout.addView(TV);
}
// code to iterate through map entries and display messages
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// code Add new message onClick of fab
}
}
And this version of the code shows nothing in the TextView:
public class LoadMessages extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_load_messages);
// Load previous messages
final LinearLayout messagesLayout = (LinearLayout) findViewById(R.id.messagesLayout);
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference databaseReference = database.getReference(getIntent().getStringExtra("user") + "/Messages");
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
Map<String, Message> map = new TreeMap<String, Message>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Message m = snapshot.getValue(Message.class);
map.put(String.valueOf(m.getTimeStamp()), m);
TextView TV = new TextView(LoadMessages.this);
Message test = map.get(String.valueOf(m.getTimeStamp()));
TV.setText(test.getMessageText());
messagesLayout.addView(TV);
}
// code to iterate through map entries and display messages
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// code Add new message onClick of fab
}
}
Edit: Here's my database structure:
I think the problem is that snapshot.getValue(Message.class) returns null.
TreeMap can store null as a value, so while it contains your key the value is actually null.
Okay, I figured it out! It turns out that the message String in Message was being changed to messageText in the database, so when I tried to extract it into a Message object it wouldn't recognize it. The fix was to change the message String in the Message class to messageText.

Categories

Resources