I found a few examples of "task not yet complete," but have not found any examples for transactions. I am using a transaction because in my application I need the operation to be able to fail if there is no internet connection. I can detect this with a transaction.
I have a Collection with Documents. I am trying to obtain the names of the documents. Sometimes the code works fine, but majority of the time I get the "task not yet complete" error. The frustrating thing is that I have a callback for "onComplete" so it's weird that the transaction isn't complete when the callback is... called.
I get the "task not yet complete exception in the onCompleteListener(). What's frustrating is that I even check to ensure if (task.isSuccessful() && task.isComplete()). Do I need to use a continuation? If so, please provide an example - I just don't quite understand it yet.
// Note: states is an ArrayList<String>
// snapshot is a QuerySnapshot
public void getStatesList(){
states.clear();
states.add("Select A State");
db.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) {
// Collect Snapshot data
snapshot = db.collection("DATA").get();
return null;
}
}).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful() && task.isComplete()){
try{
for(QueryDocumentSnapshot document : snapshot.getResult()){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
} catch (Exception e){
e.printStackTrace(); // Transaction is not yet complete
sendResponseToActivity("Fail", RESULT_OK);
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
if(e.getMessage().contains("UNAVAILABLE"))
sendResponseToActivity("NoInternet", RESULT_OK);
else
sendResponseToActivity("Fail", RESULT_OK);
}
});
} // End getStatesList()
In your code, you call db.collection("DATA").get() but this doesn't operate inside of the transaction (that is done using transaction.get(docRef), transaction.update(docRef, newData) and so on). Just because the Task of the "transaction" has completed, it doesn't mean that this rogue database call has.
If the purpose is to get the server's copy of /DATA and only the server's copy, use Query#get(Source source) with Source.SERVER. (Note: pending server writes may be merged into the data to reflect the most up-to-date copy of the server's data)
public void getStatesList(){
states.clear();
states.add("Select A State");
db.collection("DATA").get(Source.SERVER)
.addOnSuccessListener(querySnapshot -> {
for(QueryDocumentSnapshot document : querySnapshot){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
})
.addOnFailureListener(ex -> {
if (ex.getMessage().contains("UNAVAILABLE")) {
sendResponseToActivity("NoInternet", RESULT_OK); // RESULT_OK?
} else {
sendResponseToActivity("Fail", RESULT_OK); // RESULT_OK?
}
});
}
However, because the above version uses and modifies global variables, I would implement it using:
/** Returns server's list of states */
public Task<ArrayList<String>> getStatesList() {
return db.collection("DATA").get(Source.SERVER)
.onSuccessTask(querySnapshot -> {
ArrayList<String> states = new ArrayList<>();
// NOTE: states.add("Select A State"); was removed here
for (QueryDocumentSnapshot document : querySnapshot) {
states.add(document.getId());
}
return Tasks.forResult(states);
});
}
Then use it like so:
getStatesList().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
ArrayList<String> states = task.getResult();
// do something with states, like update UI
// don't forget that "Select A State" isn't in the array
} else {
Exception ex = task.getException();
if (ex.getMessage().contains("UNAVAILABLE")) {
// offline, do something
} else {
// unexpected error, do something
}
}
})
Related
i have created a function to list all files with in a reference.mStorageRef is defined public.
public void ListFiles()
{
Log.d("FETCHING_DATA_STATUS","STARTED");
mStorageRef = FirebaseStorage.getInstance().getReference("ImageFolder");
mStorageRef.listAll()
.addOnSuccessListener(new OnSuccessListener<ListResult>() {
#Override
public void onSuccess(ListResult listResult) {
for (StorageReference prefix : listResult.getPrefixes()) {
Log.d("Storage_Prefix :",prefix.getName());
}
/* for (StorageReference item : listResult.getItems()) {
}*/
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("FETCHING_DATA_STATUS","Failed due to"+e.getMessage());
}
}).addOnCompleteListener(new OnCompleteListener<ListResult>() {
#Override
public void onComplete(#NonNull Task<ListResult> task) {
if(task.isSuccessful()) {
Log.d("FETCHING_DATA_STATUS", "FINISHED");
}
}
});
}
but when i execute this i get following ouput:
D/FETCHING_DATA_STATUS: STARTED
W/StorageUtil: no auth token for request
W/NetworkRequest: no auth token for request
D/FETCHING_DATA_STATUS: FINISHED
W/example.larn: Checksum mismatch for dex base.apk!classes2.dex
Could not merge the profile. Clearing the profile data.
how can i use list all correctly.Please help
You're looping over listResult.getPrefixes(), which are the subfolders in your ImageFolder.
Since you indicate you want to list files, you should loop over listResult.getItems() instead.
I find keeping the reference documentation open key to quickly fixing this sort of common mistake.
My problem is this instead of adding new data, the existing data in the Firestore Database gets updated. This leads to the display of the last message that is either sent or received in the ChatActivity. Also the message I send appears twice on the screen after sending but once I leave the activity and open it again as i just stated, only the last message that is either sent or received in the ChatActivity is displayed. After a lot of scouring on the internet, and testing out various alternatives on my own and failing to resolve this issue, I now come to this community for help.
I have posted below the methods I am using to send my message from the app to the database and the method to then display those messages.
send message method
private void sendMessage(String sender,String receiver,String message){
DocumentReference documentReference = rootRef.collection("chats").document(roomId).collection("messages").document(roomId);
Map<String,Object> user = new HashMap<>();
user.put("sender",sender);
user.put("receiver",receiver);
user.put("message",message);
user.put("time", FieldValue.serverTimestamp());
user.put("rid",roomId);
documentReference.set(user,SetOptions.merge()).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "onSuccess: MessageSent "+ userId);
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "onSuccess: Error"+ userId);
}
});
}
get message method
private void readMessages(final String userId, final String recipientId){
mchat = new ArrayList<>();
CollectionReference collectionReference = rootRef.collection("chats").document(roomId).collection("messages");
collectionReference.orderBy("time", Query.Direction.DESCENDING);
collectionReference.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot documentSnapshots, #Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.e(TAG, "onEvent: Listen failed.", e);
return;
}
if(documentSnapshots!=null){
for(QueryDocumentSnapshot queryDocumentSnapshots : documentSnapshots){
Chat chat = queryDocumentSnapshots.toObject(Chat.class);
if(chat.getReceiver().equals(recipientId)&&chat.getSender().equals(userId)||
chat.getReceiver().equals(userId)&&chat.getSender().equals(recipientId)){
mchat.add(chat);
}
messageAdapter = new MessageAdapter(MessageActivity.this,mchat);
recyclerView.setAdapter(messageAdapter);
}
}
}
});
}
I think the new messages are overriding the old messages because of this line:
DocumentReference documentReference = rootRef.collection("chats").document(roomId).collection("messages").document(roomId);
I believe you need to make the last .document() call have something other than roomId for the value because if the roomId stays the same then you will never be able to have more than one message in the same messages collection. Maybe change the roomId to messageId.
For reading problem see #Frank van Puffelen answer
Since you're attaching your listener with addSnapshotListener, your onEvent method will get called once when you attach the listener and then each time the data it listens to gets modified. So when a message is added, your onEvent gets called again.
Each time the onEvent runs, you read all messages from the database and add them to your view. So the first time that is correct, as you add all initial messages. But on the subsequent calls, you're re-adding the same messages that you already processed over and over.
You have two main options to solve this:
Wipe the existing messages before processing
Handle only the updates
Wiping the existing messages is the simplest, as all you have to do is clear `` at the start of onEvent:
private void readMessages(final String userId, final String recipientId){
mchat = new ArrayList<>();
CollectionReference collectionReference = rootRef.collection("chats").document(roomId).collection("messages");
collectionReference.orderBy("time", Query.Direction.DESCENDING).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot documentSnapshots, #Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.e(TAG, "onEvent: Listen failed.", e);
return;
}
mChat.clear();
if(documentSnapshots!=null){
for(QueryDocumentSnapshot queryDocumentSnapshots : documentSnapshots){
Chat chat = queryDocumentSnapshots.toObject(Chat.class);
if(chat.getReceiver().equals(recipientId)&&chat.getSender().equals(userId)||
chat.getReceiver().equals(userId)&&chat.getSender().equals(recipientId)){
mchat.add(chat);
}
messageAdapter = new MessageAdapter(MessageActivity.this,mchat);
recyclerView.setAdapter(messageAdapter);
}
}
}
});
}
Note that I also changed where the .orderBy("time", Query.Direction.DESCENDING) call is. Each call to orderBy (and most other query building methods) returns a new object, so you have to chain the calls.
Handling only updates is a bit more involved, but will be more efficient. This means that when you have a lot of documents, it has less chances of flicker in your UI.
I highly recommend reading handling changes between snapshots in the documentation. For the most simple case where you only add new documents to the collection, you could get by with:
private void readMessages(final String userId, final String recipientId){
mchat = new ArrayList<>();
CollectionReference collectionReference = rootRef.collection("chats").document(roomId).collection("messages");
collectionReference.orderBy("time", Query.Direction.DESCENDING).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot documentSnapshots, #Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.e(TAG, "onEvent: Listen failed.", e);
return;
}
mChat.clear();
if(documentSnapshots!=null){
for(QueryDocumentSnapshot queryDocumentSnapshots : documentSnapshots.getDocumentChanges()){ // first change is here
switch (dc.getType()) {
case ADDED:
Chat chat = queryDocumentSnapshots.toObject(Chat.class);
if(chat.getReceiver().equals(recipientId)&&chat.getSender().equals(userId)||
chat.getReceiver().equals(userId)&&chat.getSender().equals(recipientId)){
mchat.add(chat);
}
break;
case MODIFIED:
Log.d(TAG, "Modified city: " + dc.getDocument().getData());
break;
case REMOVED:
Log.d(TAG, "Removed city: " + dc.getDocument().getData());
break;
}
messageAdapter = new MessageAdapter(MessageActivity.this,mchat);
recyclerView.setAdapter(messageAdapter);
}
}
}
});
}
For a more complete solution, you'll want to:
Handle the MODIFIED and REMOVED events too, by updating and removing the existing messages in/from mchat.
Only call new MessageAdapter(MessageActivity.this,mchat) once, and on subsequent updates call adapter.notifyDataSetChanged to tell it of the changes.
I'm trying to use Google's Saved Games feature with Google Play Games Services in my Android app. Google provides sample code how to do so:
private static final int RC_SAVED_GAMES = 9009;
private String mCurrentSaveName = "snapshotTemp";
private void showSavedGamesUI() {
SnapshotsClient snapshotsClient =
Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this));
int maxNumberOfSavedGamesToShow = 5;
Task<Intent> intentTask = snapshotsClient.getSelectSnapshotIntent(
"See My Saves", true, true, maxNumberOfSavedGamesToShow);
intentTask.addOnSuccessListener(new OnSuccessListener<Intent>() {
#Override
public void onSuccess(Intent intent) {
startActivityForResult(intent, RC_SAVED_GAMES);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
if (intent != null) {
if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA)) {
// Load a snapshot.
SnapshotMetadata snapshotMetadata =
intent.getParcelableExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA);
mCurrentSaveName = snapshotMetadata.getUniqueName();
// Load the game data from the Snapshot
// ...
} else if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_NEW)) {
// Create a new snapshot named with a unique string
String unique = new BigInteger(281, new Random()).toString(13);
mCurrentSaveName = "snapshotTemp-" + unique;
// Create the new snapshot
// ...
}
}
}
Obviously, Google wants you to use their provided intent to let the user decide which saved game to load or if a new save game should be created.
I, on the other hand, want to do this decision for the user. However, I'm unable to find a way to return a list of snapshots and to load snapshot data.
Since my game won't require to maintain more than one saved game per user I'm less interested in getting a list of snapshots without an intent (which would be an interesting solution, though) and more in loading a snapshot based on the name of the saved game, silently.
How can I load a snapshot without showing an intent?
The comment of jess leaded me to a solution that is now deprecated. However the person who posted the answer pointed out that there is also a working solution in the CollectAllTheStars sample app that is provided by Google. I was tempted to check this sample app to find out if the Google team has changed the code to fit the new way. For my amuse the comments in that sample app were describing the old deprecated way, still, but the code was changed for my luck.
Inspecting the code gave me ideas, so I came up with this solution:
String serializedSavedGameData;
public void downloadSavedGameData(final String name) {
if(snapshotsClient != null) {
snapshotsClient.open(name, true, SnapshotsClient.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "Error while opening snapshot: ", e);
}
}).continueWith(new Continuation<SnapshotsClient.DataOrConflict<Snapshot>, byte[]>() {
#Override
public byte[] then(#NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) throws Exception {
Snapshot snapshot = task.getResult().getData();
// Opening the snapshot was a success and any conflicts have been resolved.
try {
// Extract the raw data from the snapshot.
return snapshot.getSnapshotContents().readFully();
} catch (IOException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
} catch (NullPointerException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
}
return null;
}
}).addOnCompleteListener(new OnCompleteListener<byte[]>() {
#Override
public void onComplete(#NonNull Task<byte[]> task) {
if(task.isSuccessful()) {
byte[] data = task.getResult();
try {
serializedSavedGameData = new String(data, "UTF-16BE");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "Failed to deserialize save game data: " + e.getMessage());
}
} else {
Exception ex = task.getException();
Log.d(TAG, "Failed to load saved game data: " + (ex != null ? ex.getMessage() : "UNKNOWN"));
}
}
});
}
}
I implemented a simple resolving policy (take the newest saved game on conflicts), but I had no time to hard test all the different cases like conflicts, but it passed my simple tests so far.
Hopefully anybody can profit from this.
I can't read data from the firestore database. I can write to it although sometimes the data appears on firestore console (very) late.
And i can Delete a record although it is only delete (from firestore console) when i do a write.
It is some stupid simple error that i make, but i can't find it. Buffers? Setting? i don't know :-(
It looks like firestore is delayed responding, it never reach the line with:
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful())
....
Below the logcat lines...
I am using java/android app
Thanks in advance for any help
,, E/PlayMissionActivity: sleep 2 <==
,, E/PlayMissionActivity: sleep 2 <==
,, W/Firestore: (0.6.6-dev) [OnlineStateTracker]: Could not reach Firestore backend.
,, E/PlayMissionActivity: sleep 2 <==
My Code (just a copy of the samples)
public class MyPlay extends AppCompatActivity {
FirebaseFirestore dbRef = null;
protected void onCreate(Bundle savedInstanceState) {
{
initDB();
getDocument();
int q=0;
while (q<10) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
q += 1;
Log.e(LOG_TAG, "sleep 2 " + "<==");
}
}
public void initDB() {
dbRef = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setTimestampsInSnapshotsEnabled(true)
.build();
dbRef.setFirestoreSettings(settings);
}
public void getDocument() {
DocumentReference docRef = dbRef.collection("users").document("123");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Log.d(TAG, "----------------------------> DocumentSnapshot data: " + document.getData());
} else {
Log.d(TAG, "----------------------------> No such document");
}
} else {
Log.d(TAG, "----------------------------> get failed with ", task.getException());
}
}
});
}
}
app/build.gradle:
compile 'com.google.firebase:firebase-core:16.0.1'
compile 'com.google.firebase:firebase-firestore:17.0.2'
compile 'com.google.code.gson:gson:2.8.4'
Firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Firestore data:
users/123/born=1815
users/123/first="Ada"
users/123/last="Lovelace"
Solved it, it was indeed fundamental.
Basically i didn't understand the concept of threads, events etc.
When a thread (event) is running, the thread that is reading firestore db is waiting for finishing the main thread. (Pseudo code, when the onCreate is finished)
Probably someone can explain it better i am just an old amature
So what isn't working (pseudo code android activity):
onCreat{
object = readFirestore
print object.id <== this line will give you an error
}
readFirestore() {
read id
onSucces
object = document.toObject()
}
So what's working fine:
onCreat{
object = readFirestore
}
readFirestore() {
read id
onSucces
object = document.toObject()
print object.id <== this line will print your result
}
I want to implement a very simple Java Telegram Client, which is capable of sending and receiving messages and store the sessions across multiple starts. I already managed to authenticate and receive messages
api = new TelegramApi(apiState, new AppInfo(API_ID, "console", "1", "1", "en"), new ApiCallback() {
#Override
public void onAuthCancelled(TelegramApi api) {
Log.d(TAG, "-----------------CANCELLED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdatesInvalidated(TelegramApi api) {
Log.d(TAG, "-----------------INVALIDATED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdate(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------UPDATE----------------");
Log.d(TAG, tlAbsUpdates.toString());
if (tlAbsUpdates instanceof TLUpdateShortMessage) {
Log.d(TAG, "-----------------UPDATE CHAT MESSAGE----------------");
int senderId = ((TLUpdateShortMessage) tlAbsUpdates).getUserId();
Log.d(TAG, "Message from " + senderId);
String message = ((TLUpdateShortMessage) tlAbsUpdates).getMessage();
Log.d(TAG, message);
activity.appendMessage(TAG, message);
}
}
});
api.switchToDc(2);
TLConfig config = null;
try {
config = api.doRpcCallNonAuth(new TLRequestHelpGetConfig());
} catch (TimeoutException | IOException e) {
e.printStackTrace();
}
apiState.updateSettings(config);
However, I struggle to send messages to another user. For the beginning, it would be enough if I could send a message back to the user, who sent me a message before (by retrieving the senderId, as you can see in the onUpdate method before). However, if someone could also help me with retrieving the ids of my saved contacts, it would be perfect.
Furthermore, I want to store the sessions accross multiple startups, since I get a FLOOD_WAIT error (420), if I test my code to often.
For this I used https://github.com/rubenlagus/TelegramApi/blob/51713e9b6eb9e0ae0d4bbbe3d4deffff9b7f01e4/src/main/java/org/telegram/bot/kernel/engine/MemoryApiState.java and its used classes (e.g. TLPersistence), which stores and loads the ApiState. However, apparently it does not store the signin status, since I always have to authenticate my number every time I update the code.
By the way, I am using Api layer 66 (https://github.com/rubenlagus/TelegramApi/releases).
UPDATE 1:
Problems with sending messages solved myself:
private void sendMessageToUser(int userId, String message) {
TLInputPeerUser peer = new TLInputPeerUser();
peer.setUserId(userId);
TLRequestMessagesSendMessage messageRequest = new TLRequestMessagesSendMessage();
messageRequest.setFlags(0);
messageRequest.setPeer(peer);
messageRequest.setRandomId(new SecureRandom().nextLong());
messageRequest.setMessage(message);
api.doRpcCallNonAuth(messageRequest, 1500, new RpcCallback<TLAbsUpdates>() {
#Override
public void onResult(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------------MESSAGE SENT-----------------------");
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "-----------------------MESSAGE SENT ERROR-----------------------");
Log.d(TAG, String.valueOf(i));
if(s != null) {
Log.d(TAG, s);
}
}
});
}
However, now I am stuck at finding the userIds of my contacts.
After first update this is left:
Saving the session state (and signin state)
Find userIds of contacts
Update 2:
I managed to fetch the users, with which there are already dialogs. This is enough for my use case, however, loading all contacts would be perfect. This is how to load users from existing dialogs:
private int getUserId(String phone) throws InterruptedException {
TLRequestMessagesGetDialogs dialogs = new TLRequestMessagesGetDialogs();
dialogs.setOffsetId(0);
dialogs.setLimit(20);
dialogs.setOffsetPeer(new TLInputPeerUser());
CountDownLatch latch = new CountDownLatch(1);
api.doRpcCallNonAuth(dialogs, 1500, new RpcCallback<TLAbsDialogs>() {
#Override
public void onResult(TLAbsDialogs tlAbsDialogs) {
Log.d(TAG, "----------------------getUsers--------------------");
for(TLAbsUser absUser : ((TLDialogs) tlAbsDialogs).getUsers()) {
users.add((TLUser) absUser);
}
latch.countDown();
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "----------------------getUsers ERROR--------------------");
latch.countDown();
}
});
latch.await();
for(TLUser user : users) {
if(user.getPhone().equals(phone)) {
return user.getId();
}
}
return 0;
}
After second update this is left:
Saving the session state (and signin state)
Get user ids from contacts instead of dialogs