How to use runTransaction with firebase_database 9.0.3 - firebase

Can anyone give where to look, or what I am missing, for how to use runTransaction in 9.0.3? (Flutter, Firebase realtime DB)
I updated firebase_database ^7.0.0 to ^9.0.3. Had to change runTransaction(MutableData mutable) to runTransaction(Object object), and the Object is null for Map data. Does return a String when point to single node of a String.
For something to work I placed + ‘/sap’ at end of .ref(…), and it gave ‘DROID’
Code Updated for Comment below:
bool bCanWeStartGame = false;
bool bIfiOS = false;
bIfiOS = Theme.of(referencedData.mngGamesPageMaterialContext)
.platform == TargetPlatform.iOS;
DatabaseReference playersRef = referencedData.database.ref(
TABLENAME_STARTS + '/' + referencedData.strFBGameUniqueIDKey);
if(bIfiOS){
//apparently iPhone needs to 'open line to DB record' to work
playersRef.once()
.then((DatabaseEvent event) {//dont need to do anything??
});
}
TransactionResult txResult;
//while loop to repeat runTransaction until solved
int iWhileLoopCheck = 0; //so whileLoop does not go forever
bool bTransactionCommittedOrGetOutAnyWay = false;
while(!bTransactionCommittedOrGetOutAnyWay
&& (iWhileLoopCheck<30)){
iWhileLoopCheck++;
txResult = await playersRef
.runTransaction(
(Object post) {
if (post == null) {
if(bIfiOS){sleep(Duration(milliseconds: 3));}
return Transaction.abort(); //out, but will retry transaction
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
bTransactionCommittedOrGetOutAnyWay = true; //whichever of
//options below take effect, dont want to continue while loop
if (_post[STARTS_TABLE_ALL_PLAYERS] !=
startingGames[iIndexInStartingGames].allplayers) {
//someone has joined since trying to start
bCanWeStartGame = false;
//Transaction.abort() as dont want to change data
return Transaction.abort();
} else {
//Nobody has joined, so can Start. Tell others Game is starting
_post['stg'] = true;
bCanWeStartGame = true;
return Transaction.success(_post);
}
});
} //while runtransaction doesnot get at data```

Related

Flutter Firebase offline issues with await

When I try to reorder things in my reorderable list view, I run into a problem. If the user is online, awaiting the document references (as seen in the code) runs fast enough. The problem is, if the user is offline, the Firebase await function can take upwards of 20 seconds. On the other hand, if I don't await these changes, and do multiple quick reorders, the resulting writes as soon as I go back online differ greatly from the "order" state that I have in the app.
Is there anything I can do that avoids awaiting?
I have thought about checking the online status and disabling this feature while offline or disabling the reorder function while the function awaits. Both seem like really bad solutions.
Thanks for the help, the code is below:
Future<void> reorderExercises(int oldIndex, int newIndex,
CollectionReference<AddExercise> whereToUpdateRef) async {
List<DocumentReference<AddExercise>> referenceList = [];
await whereToUpdateRef.orderBy(COLUMN_DAYSINDEX).get().then(
(value) => value.docs.forEach(
(element) {
referenceList.add(element.reference);
},
),
);
void _updateIndex(int _oldIndex, int _newIndex) {
referenceList[_oldIndex].update(
{COLUMN_DAYSINDEX: _newIndex},
);
}
if (oldIndex < newIndex) {
newIndex -= 1;
_updateIndex(oldIndex, newIndex);
for (int i = oldIndex + 1; i <= newIndex; i++) {
_updateIndex(i, i - 1);
}
} else {
for (int i = oldIndex - 1; i >= newIndex; i--) {
_updateIndex(i, i + 1);
}
_updateIndex(oldIndex, newIndex);
}
}
Problem:
await is very slow when the user has no internet on
Solution:
You check if the user has internet. if not you push to a noInternet() screen with a try again button.
You can check if the user have internet with this code:
Source _source = Source.serverAndCache;
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
_source = Source.serverAndCache;
}
} on SocketException catch (_) {
_source = Source.cache;
}
firebaseDocumentReference.get(GetOptions(source: _source))...
This is very close to Liam's answer, but I cannot edit it unfortunately.
Using his code I changed the source option and now offline await works as fast as online. Also changed "google.com" to "expample.com" for it to work in China as well.
Problem:
await is very slow when the user has no internet on
Solution:
You check if the user has internet. if not you push to a noInternet() screen with a try again button.
You can check if the user have internet with this code:
var haveInternet;
...
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
haveInternet = true;
}
} on SocketException catch (_) {
haveInternet = false;
}
Hope it helps!

Flutter firestore pagination doesn't start after a document,

I am trying to implement a pagination feature on my list and I've been following this official documentation https://firebase.google.com/docs/firestore/query-data/query-cursors
Here is a main code:
Future<List<Review>> fetchReviewPaginated(int limit) async {
var ref = _firestore.collection('reviews').orderBy('creationDate', descending: true);
if (_isFetchingUser) return null;
_isFetchingUser = true;
if (cachedReview == null) {
var snapshot = await ref.limit(limit).get();
lastPage = snapshot.docs.last;
cachedReview = snapshot.docs.map((e) => Review.fromMap(e.data())).toList();
} else {
var snapshot = await ref.startAfter(lastPage["creationDate"]).limit(limit).get();
lastPage = snapshot.docs.last;
cachedReview.addAll(snapshot.docs.map((e) => Review.fromMap(e.data())).toList());
}
if (cachedReview.length < limit) hasNext = false;
_isFetchingUser = false;
notifyListeners();
return cachedReview;
}
My widget will call fetchReviewPaginated(5) and display what is in cachedReviews. If the cachedReviews is null, then it will fetch 5 of reviews, then it will startAfter what is in the last of the cachedReviews and get another 5 documents after that point.
Problem is that this function always returns the same 5 items and doesn't truly start after what I specify,
I cannot test/try your code at this moment but I think this is because startAfter(lastPage["creationDate"]) is not correct.
You need to pass a list of values to startAfter() (a list with a unique item in your case). Since lastPage is a DocumentSnapshot you therefore need to use get() or data(), like startAfter([lastPage.get("creationDate")]).
Note that you could instead use the startAfterDocument() method to which you pass the DocumentSnapshot itself. It is somehow more adapted to your case, since you have only one sorting.

How do I get the value of this variable outside the .then((){}); statement in flutter?

I have the following lines of code
bool check;
Firestore.instance.collection('tests').where('name',isEqualTo: snapshot['name'].replaceAll('.mp4','.txt')).getDocuments().then((docs){
if(docs.documents[0].exists) check = true;
else check = false;
});
debugPrint(check.toString());
This produces an error saying that a bool can't have a value of null, so how do I retrieve the value of check from inside the .then statement so that I can use it outside of the .then statement?
If you use the .then() clause you need to do all your work inside it and you can't use its values outside because you don't know when you are receiving the answer.
You could await the answer there and mark the method that encloses your code with async.
bool check;
final docs = Firestore.instance.collection('tests').where('name',isEqualTo: snapshot['name'].replaceAll('.mp4','.txt')).getDocuments();
if(docs.documents[0].exists)
check = true;
else
check = false;
debugPrint(check.toString());
or
Future<void> doSomething() async {
bool check;
final docs = Firestore.instance.collection('tests').where('name',isEqualTo:
snapshot['name'].replaceAll('.mp4','.txt')).getDocuments();
if(docs.documents[0].exists)
check = true;
else
check = false;
debugPrint(check.toString());
}

Obscure Unity Firebase Realtime Database Error: "Custom Run loops are not supported"?

I am currently attemping to build a custom Editor tool for Unity that utilizes the Firebase Realtime Database. This tool would allow someone to right-click on a scene asset in the inspector and select to 'lock' or 'unlock' the scene. Within our Firebase Database, this locking is represented by a dictionary, with each scene name as a key and each value being either "locked" or "unlocked". This funcitonality will be expanded later, but for now, I'm just trying to get things set up so that I can actually connect to and use the Firebase Realtime Database.
I had looked at the Firebase Quickstart Unity project for the Realtime Database (the one that functions like a leaderboard) and saw that it worked fine. I could replace the database URL in the project with the URL for my app's database, and when I entered in values, they appeared inside my realtime database.
So, I based the code for my custom editor script on the code from the quickstart. In fact, I copy-pasted most of it. I will post the script itself, and then describe the errors I receive, as well as the lines that give the errors:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Firebase;
using Firebase.Unity.Editor;
using Firebase.Database;
[CustomEditor(typeof(SceneAsset))]
[ExecuteInEditMode]
public class SceneLockingEditor : Editor
{
static string sceneName;
DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
protected virtual void OnEnable()
{
Debug.Log("OnEnable Called");
sceneName = target.name;
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available)
{
InitializeFirebase();
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + dependencyStatus);
}
});
}
// Initialize the Firebase database:
protected virtual void InitializeFirebase()
{
Debug.Log("Initializing Firebase");
FirebaseApp app = FirebaseApp.DefaultInstance;
app.SetEditorDatabaseUrl(CENSORING MY DATABASE SORRY);
if (app.Options.DatabaseUrl != null) app.SetEditorDatabaseUrl(app.Options.DatabaseUrl);
}
static TransactionResult SceneLockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if(mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach(string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if(((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "locked")
{
Debug.Log("Scene is already locked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was not locked. Locking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "locked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as locked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "locked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static TransactionResult SceneUnlockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if (mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach (string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if (((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "unlocked")
{
Debug.Log("Scene is already unlocked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was locked. Unlocking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "unlocked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as unlocked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "unlocked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static public void AddSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneLockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
static public void RemoveSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneUnlockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
[MenuItem("CONTEXT/SceneAsset/Lock Scene", false, 0)]
public static void LockScene()
{
Debug.Log("LockScene Called for scene " + sceneName + ".");
AddSceneLock();
}
[MenuItem("CONTEXT/SceneAsset/Unlock Scene", false, 0)]
public static void UnlockScene()
{
Debug.Log("UnlockScene Called for scene " + sceneName + ".");
RemoveSceneLock();
}
}
The errors always come from this line:
FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Any line that has to do with "FirebaseDatabase.DefaultInstance" will throw one of the following two errors
Error 1:
InvalidOperationException: SyncContext not initialized.
Firebase.Unity.UnitySynchronizationContext.get_Instance ()
Firebase.Platform.PlatformInformation.get_SynchronizationContext ()
Firebase.FirebaseApp.get_ThreadSynchronizationContext ()
Firebase.Database.DotNet.DotNetPlatform+SynchronizationContextTarget..ctor ()
Firebase.Database.DotNet.DotNetPlatform.NewEventTarget (Firebase.Database.Internal.Core.Context c)
Firebase.Database.Internal.Core.Context.EnsureEventTarget ()
Firebase.Database.Internal.Core.Context.InitServices ()
Firebase.Database.Internal.Core.Context.Freeze ()
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
Error 2:
Exception: Custom Run loops are not supported!
Firebase.Database.Internal.Core.Context.GetExecutorService ()
Firebase.Database.Internal.Core.Context.GetConnectionContext ()
Firebase.Database.Internal.Core.Context.NewPersistentConnection (Firebase.Database.Internal.Connection.HostInfo info, IDelegate delegate_)
Firebase.Database.Internal.Core.Repo..ctor (Firebase.Database.Internal.Core.RepoInfo repoInfo, Firebase.Database.Internal.Core.Context ctx, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
It's always one error or the other, and I can't determine what causes one error to appear rather than the other. Both errors stop whatever operation I'm trying to do on the database, which means I can't interact with my database at all.
I've taken a look at the quickstart projects and watched some videos of people setting up Firebase to work with their projects, and I can't seem to determine what I have messed up during the process. I have imported my google-services.json into the unity project. The quickstart projects worked just fine interacting with my database. It's just this particular script that won't work. I cannot find any mention of these two errors anywhere on Google. I even contacted the official Firebase support and they couldn't give me any advice on what the errors mean or what could be causing them.
I considered one problem might be in my initialization function. Instead of doing:
FirebaseApp app = FirebaseApp.DefaultInstance;
I figured that maybe I am supposed to use FirebaseApp.Create() with a custom name passed in, but that resulted in the same errors being thrown on the same line. I am at a loss for how to proceed with this problem. I don't know of anyone else who has had these particular errors, and I've done very much experimentation with different ways to access the database over the past several days. If anybody has an idea of what I am doing wrong here, or what causes these errors (and, how to fix them), I would really appreciate it.
At first you should initialize firebase with new instance of FirebaseApp with unique name. I do it like this:
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
The second is setup references (DatabaseReference, StorageReference etc.) with this firebaseApp instance and use it only after FirebaseApp.CheckAndFixDependenciesAsync()
Overall code will look like this:
public static void Initialize(bool isEditor = false)
{
if (isEditor)
{
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
firebaseApp.SetEditorDatabaseUrl("https://project.firebaseio.com/");
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
if (task.Result == DependencyStatus.Available)
{
database = FirebaseDatabase.GetInstance(firebaseApp).RootReference;
storage = FirebaseStorage.GetInstance(firebaseApp).RootReference;
auth = FirebaseAuth.GetAuth(firebaseApp);
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + task.Result);
}
});
}
else
{
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://project.firebaseio.com/");
database = FirebaseDatabase.DefaultInstance.RootReference;
storage = FirebaseStorage.DefaultInstance.RootReference;
auth = FirebaseAuth.DefaultInstance;
}
IsInitialized = true;
}
I had the same errors. I spent several hours solving this and it worked for me

How to perform Firebase Mutable Transaction Data with Flutter?

I have Swift code where I reduce or increase an int value for certain conditions. How can I replicate this with Flutter? Here is my Swift code..
// 2. Decrease Value -= from NumberOnes
let decreaseRef = self.ref.child("NumberOnes/\(myfav1)/Value")
decreaseRef.runTransactionBlock { (currentData: FIRMutableData) -> FIRTransactionResult in
if var data = currentData.value as? Int
{
var count = data
count -= 1
data = count
currentData.value = data
return FIRTransactionResult.success(withValue: currentData)
}
return FIRTransactionResult.success(withValue: currentData)
}
Also, if there's documentation/tutorials for this out there, please point me to it.
* UPDATE *
Here is my Flutter code...
fb.child('UserVideo/${userid}/Vid1').onValue.listen((Event event){
if (event.snapshot != null){
final vidID = event.snapshot.value["videoID"];
fb.child('NumberOnes/${vidID}/Value').onValue.listen((Event newEvent){
int vidValue = newEvent.snapshot.value;
vidValue;
print(vidValue);
//vidValue = value;
final value = vidValue--;
fb.child('NumberOnes/${vidID}').update({
'Value': value,
});
});
The problem with my Flutter code is that it doesn't stop decreasing. Is there a way around this?
Here is my solution. I worked it out from this answer here... Flutter Firebase update will not stop updating node?
Basically, I'm isolating the value once, manipulating it, then updating node with new info. I think this is much less efficient than the runTransactionBlock from the Firebase Swift SDK that brings back snapshot value as MutableData. If anyone finds a work around for this in the future please add answer.
if (vidRank == 1) {
var event = await fb.child('UserVideo/${userid}/Vid1').once();
if (event.snapshot != null){
var vid1id = event.snapshot.value['videoID'].toString();
var onesEvent = await fb.child('NumberOnes/${vid1id}').once();
if (onesEvent.snapshot != null){
var onesValue = (onesEvent.snapshot.value['Value'] as int);
final vidValue = onesValue - 1;
print("Inside ${vidValue}");
fb.child('NumberOnes/${vid1id}').update({
'Value': vidValue
});
}
}
}

Resources