I am using firebase database and I have a weird error with transactions. Basically I have a key called "users" with a parameter count and an userlist like this:
"users" : {
"count" : 1,
"userList" : {
"LBBgLkOp3bWbZeSfnKVsqkHeW8s1" : true
}
}
I use a transaction to increment the count and add the current user's id to the userList. The problem is that if there isn't a users key (the first time) the transaction completes succesfully but if there is already an users key the transaction fails for no reason...
My code is this
void AddUserAndIncrementCount()
{
FirebaseDatabase.DefaultInstance.GetReference("users")
.RunTransaction(usersData =>
{
Dictionary<string, object> users = usersData.Value as Dictionary<string, object>;
if (users == null)
{ // firstTime
users = new Dictionary<string, object>();
users.Add("count", 1);
users.Add("userList", new Dictionary<string, object>() { { auth.CurrentUser.UserId, true }});
}
else
{
/* printing users returns this
{"count":1,"userList":{"LBBgLkOp3bWbZeSfnKVsqkHeW8s1":true}}
*/
// INCREMENT COUNT
users["count"] = int.Parse(users["count"].ToString()) + 1;
// ADD USER TO LIST
Dictionary<string, object> userList = users["userList"] as Dictionary<string, object>;
userList.Add(auth.CurrentUser.UserId, true);
users["userList"] = userList;
}
// END TRANSACTION
/* printing users returns this now
{"count":2,"userList":{"LBBgLkOp3bWbZeSfnKVsqkHeW8s1":true,"AM2vI8K106XghEgEgRSkCIpJn0w2":true}}
*/
usersData.Value = users;
return TransactionResult.Success(usersData);
}).ContinueWith(OnAddUserIncrementCountTask);
}
void OnAddUserIncrementCountTask(Task<DataSnapshot> task)
{
if (task.IsCompleted && !task.IsCanceled && !task.IsFaulted)
{
//Success
}
else
{
Debug.Log(task.IsFaulted+" - "+task.Exception.Message);
// True - Exception of type 'System.AggregateException' was thrown.
}
}
As you can see I printed what I was receiving and sending in the transaction and everything seems fine. I tried to set rules to public (read true and write true) and it also failed, so I don't know if I am doing something bad or if there is something wrong with transactions. The things that bothers me is that if the users key doesn't exist in the database then it doesn't fail so I think it is failing for some strange reason I can't see...
I am testing on an Android device.
Thank you.
I finally found what was causing the transaction to fail.
It seems there is a bug that makes transactions fail if one of the existing children of the node, or the children of the children, or the value of the node itself is a boolean (true, false).
To solve it I just replaced the true boolean for a "true" string and now all transactions work.
So in my above code I replaced (this appears twice in the code)
auth.CurrentUser.UserId, true
for:
auth.CurrentUser.UserId, "true"
Hope this post saves some time to the next one running to this bug.
Related
I'm trying to get notifications from a bluetooth device sending records, and periodically update the UI to display the records. The while loop below sits in its own thread to handle UI updates while the rest of the module takes care of other tasks. gattCallback is an instance of a BluetoothGattCallback class that adds to a list of received records and returns that list when getHistory() is called.
My problem is that when I hit the foreach line, after so many iterations I get an error:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
As far as I can tell, history isn't being updated here or anywhere else in my code so I'm confused by the error. I specifically retrieve a copy of the record history through getHistory() to avoid modifying it during the foreach. Can anyone suggest what might be causing it or any tips to find out?
It might be relevant that this has only caused issues since switching to a Moto E4 on Android 7.1.1 from a Moto G Play on Android 6.0.1.
// Periodically check to see what needs updating
while (!finishedDisplayThread)
{
// See if there are any new records to display
int count;
List<Record> history = gattCallback.getHistory();
if (history == null)
{
count = 0;
}
else
{
count = history.Count;
}
// Only update the display if it has changed
if(count != prevCount)
{
prevCount = count;
List<string> recordList = new List<string>();
if (history == null)
{
recordList = new List<string>();
recordList.Add("No history.");
}
else
{
foreach (Record record in history)
{
recordList.Add(record.ToRow());
}
}
//Update the display
RunOnUiThread(() =>
{
ListAdapter = new ArrayAdapter<string>(this,
Resource.Layout.ListItemLayout,
recordList);
recordCountText.Text = "" + count;
});
}
Thread.Sleep(100);
}
I specifically retrieve a copy of the record history through getHistory() to avoid modifying it during the foreach.
Are you certain that you're getting a copy? Imagine this implementation:
public List<Record> getHistory() {
return history;
}
This will not return a copy of history, but a reference directly to history itself. If two different places call this method, any changes to one of the returned values will affect the other returned value.
If you want a copy, you have to do something like this:
public List<Record> getHistory() {
return new ArrayList<>(history);
}
I have a service scheduling screen.
But there is a problem:
I need to check if the date and time the user is trying to schedule is available or reserved.
Structure DB:
Companies
-Company ID (Document)
--name
--phone
---Schedules (Collection)
------Event1
--------Hour: 08:30
--------Date: 01/01/2018
------Event2
--------Hour: 09:00
--------Date: 05/01/2018
------Event3
--------Hour: 10:30
--------Date: 01/002/2018
I access Scheduling data with this code:
String dateExample = "01/01/2018"
String hourExample = "08:30"
FirebaseFirestore mDB = FirebaseFirestore.getInstance();
CollectionReference mDBCompaniesSchedules = (CollectionReference) mDB.collection("Companies").document(mId_Company).collection("Schedules")
.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
for (Schedules document : documentSnapshots.toObjects(Schedules.class)) {
String dtSchedules = document.getSchedules_date();
String hourSchedules = document.getSchedules_hour();
if ( dtSchedules.equals(dateExample) && hourSchedules.equals(hourExample) ){
//Execute a "Toast" and closes the operation
} else {
//Call up the scheduling function.
startScheduling();
}
}
}
})
Process:
I need to run this code and go through all the documents in that collection. I need to check and analyze whether the date and time of the schedule already exists.
If there is: Show a Toast and block.
If there is no: Executing a specific function for the schedule record ("startScheduling()").
Problem:
When the data exists (it will only be once) then it will work.
When there is no data, it falls into the ELSE loop. And it is executing several times the same function "startScheduling();".
I need some way to go through this collection and when I do not find any results, the function "startScheduling (), be executed only once.
This how a for loop works. It will continue iterate till the last element to see if the condition is true or not. With other words, your if-else statement is triggered for every iteration in the loop. It means that, if the condition is true it will go with the if part, if the condition is false it will go with else part, for each and every element.
There are two ways in which you can solve this. One would be to break the loop once the condition was fulfilled. But this means that will iterate till it gets that element. Second, would be to change the logic of your code. Use first the if statement and second iterate.
Edit: The best option in this case would be to query your database using whereEqualTo() method.
Query query = db
.collection("Companies")
.document(mId_Company)
.collection("Schedules")
.whereEqualTo("dtSchedules", dateExample)
.whereEqualTo("hourSchedules", hourExample);
In which dateExample and hourExample are the actual values with which you want to compare.
To count the number of documents in a Collection, please use the following code:
public void onSuccess(QuerySnapshot documentSnapshots) {
if(documentSnapshots.size() == 0) {
startScheduling();
}
for (Schedules document : documentSnapshots.toObjects(Schedules.class)) {
String dtSchedules = document.getSchedules_date();
String hourSchedules = document.getSchedules_hour();
}
}
I create an app with Firebase. There is an issue that i can't solve, and didn't find it talked here.
In this method I want to check if some data is already in the server. If not - I want to add it (the code of adding works well. The Firebase database is being changed as I want). so I'm using onDataChange method as following:
public boolean insertNewList(String title)
{
System.out.println("start: ");
final boolean[] result = new boolean[1];
result[0]=false;
final String group = title;
mRootRef = some reference...
mRootRef.addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
System.out.println(0);
if (dataSnapshot.child(group).getValue() != null)
{
System.out.println(group + " is already exist");
System.out.println(1);
}
//write the data.
else
{
mRootRef=mRootRef.child(group);
mRootRef.push().setValue("some data");
System.out.println(2);
result[0]=true;
}
}
#Override
public void onCancelled(DatabaseError databaseError)
{
}
});
System.out.println(3);
return result[0];
}
But what realy happens is this output:
begin:
3 (just skip on onDataChange method and return false).
some print after calling the function
0 (goes back to function and enter to onDataChange method)
2 (finally goes where I want it to go)
0 (for some reason enters twice :( )
1
And because of that i receive wrong results in this function.
Can you help please?
Replace
mRootRef.addValueEventListener(new ValueEventListener()
with
mRootRef.addListenerForSingleValueEvent(new ValueEventListener()
When you add the the value to firebase, "addValueEventListener" called again, not like addListenerForSingleValueEvent that shots only once anywhy.
The output that you showed us looks normal to me. Let me try to explain:
begin: // a) this comes from the System.out.println("begin")
3 // b) you DECLARE your value event listener and then you run
// the System.out.print("3") statement
0 // c) you just added a listener so firebase calls you, and
// your listener fires
2 // d) this is the first time your listener fires, so you go
// into the block called "write the data"
0 // e) since you just wrote the data, firebase calls you again
1 // f) this time, since the data has been written, you go into
// the block called "is alraedy exist"
This is normal behaviour for firebase.
In c), firebase always calls you back one time when you declare a listener.
In e), firebase calls you because the data changed.
But in b), you are only declaring your listener, not yet running it, so the statements after this declaration are executed and you see "3" before anything else happens.
I am working with FireBase Notifications and I can send a notification which will send the user to the webview page I input on the console.
The problem is that when it matches the IF statement is fires the else statement too, what could be the cause of this?
if(getIntent().getExtras()!=null) {
for (String key : getIntent().getExtras().keySet()) {
if (key.equals("url")){
mwebView.loadUrl("http://example.com/" + getIntent().getExtras().getString(key));
}else {
mwebView.loadUrl("http://example.com");
}
}
}
Because it executes both at the same time the app crashes.
Also when I load the app the usual way it matches the with:
if(getIntent().getExtras()!=null)
and then loads the else statement. Shouldnt getExtras be null?
When I first install a new instance of the app it uses the following statement:
if(getIntent().getExtras()==null) {
if (haveNetworkConnection()) {
mwebView.loadUrl("http://example.com");
} else {
mwebView.loadUrl("file:///android_asset/myerrorpage.html");
}
}
Update:
As I cannot find out why this it happening I am trying another approach, How would I get the variable outside of the loop to use like the following:
if(getIntent().getExtras()!=null) {
for (String key : getIntent().getExtras().keySet()) {
String valuex = getIntent().getExtras().getString(key);
}
}
if (haveNetworkConnection()) {
mwebView.loadUrl("http://example.com/" + valuex);
} else {
mwebView.loadUrl("file:///android_asset/myerrorpage.html");
}
If and Else can not both be executed in one go.
You should check your other code and ensure, that this code section is not executed twice for some reason (once with TRUE and once with FALSE).
i'm having an issue that soon enough going to blow me.
i have Database table lets call it A. table A has field that determines if this row is processed or no. i update the field myself from within the Parse Browser to either True | False, and trying to call query.findInBackground() to check with the Boolean value however the returned List always returns False if its True and vice versa. enough talking let me show you what i'm doing.
public static void getMyRequests(ParseUser user, final FindCallback<ServicesModel> callback) {
ParseQuery<ServicesModel> query = new ParseQuery<>(ServicesModel.class);
if (!user.getBoolean(ParseHelper.CAN_UPLOAD)) {
query.whereEqualTo("user", user);
}
query.findInBackground(new FindCallback<ServicesModel>() {
#Override public void done(final List<ServicesModel> objects, ParseException e) {
if (e == null) {
if (objects != null && !objects.isEmpty()) {
for (ServicesModel object : objects) {
object.setHandlerUser(object.getParseUser("handlerUser"));
object.setProcessedTime(object.getLong("processedTime"));
object.setCategoryType(object.getString("categoryType"));
object.setUser(object.getParseUser("user"));
object.setUserRequest(object.getString("userRequest"));
object.setImageUrl(object.getString("imageUrl"));
object.setProcessed(object.getBoolean("isProcessed"));
Logger.e(object.getBoolean("isProcessed") + "");
}
callback.done(objects, null);
} else {
callback.done(null, new ParseException(1001, "No Services"));
}
} else {
callback.done(null, e);
}
}
});
}
the code above suppose to refresh my data but however my log always shows that isProcessed is False even tho it's set to True inside the Parse Browser
what i have tried besides this? fetchAllInBackground & fetch() you name it. the object will always return false until i re-run the application from Android Studio what i'm doing here wrong? btw here is how i initialize Parse
Parse.setLogLevel(BuildConfig.DEBUG ? DEBUG_LEVEL : Parse.LOG_LEVEL_NONE);
ParseObject.registerSubclass(ProductsModel.class);
ParseObject.registerSubclass(ProductRentalModel.class);
ParseObject.registerSubclass(ServicesModel.class);
Parse.enableLocalDatastore(context);
Parse.initialize(context, context.getString(R.string.app_id), context.getString(R.string.client_id));
the answer was to remove
Parse.enableLocalDatastore(context);
which is bad anyway, without the datastore enabled the data are refreshed probably, however with enabling the local database, the data will not refresh unless if i killed the app and/or re-install it. that's bad. but did the trick.