I have a Unity project with Firebase for google Authentication. I am storing user information in a realtime database. They are stored as:
UnityProject
1->
Name: "Something"
Email: "Something"
2->
Name: "some other thing"
Email: "Something else"
Now, these 1,2 are to be given by me (to be used as a primary key). I would start by giving the first user ID 1 and the second used ID 2 and so on.
But I need to get back from the firebase the last ID which was stored. For example,
idToInsert = GetLastUsedID() + 1;
I have used this code but it doesn't work. The screen just freezes until I force close Unity.
public int GetLastUsedID()
{
int currentID = 1;
bool continueSearch = true;
while (continueSearch)
{
FirebaseREST.DatabaseReference reference = FirebaseREST.FirebaseDatabase.Instance.GetReference(""+currentID);
string value = "";
currentID++;
reference.GetValueAsync(10, (res) =>
{
if (res.success)
{
value = res.data.GetRawJsonValue();
Debug.Log("Success fetched data : " + value);
if(value == "")
{
continueSearch = false;
Debug.Log(currentID);
}
}
else
{
Debug.Log("Fetch data failed : " + res.message);
continueSearch = false;
}
});
}
return currentID;
}
Basically I am just trying to iterate from 1 till whenever I get the empty string. The empty string means no data exists under that ID.
I'm not familiar with FirebaseREST (I would recommend using the official Firebase plugin if you're able, it does much more than just call REST endpoints), but I think I can see your issue.
GetValueAsync likely runs asynchronously in the background. So the logic inside the block (the (res)=> part) is firing off many times (basically infinitely with that while loop). Then, depending on how this is implemented, either continueSearch never goes to false because it isn't marked volatile or the callback logic never gets a chance to run (say if FirebaseREST tries to dispatch to the main thread, which is locked in an infinite while loop).
If GetValueAsync returns a Task, you can use some of the tips I cover in this article. My recommendation would be to try to use async/await, so your logic would look more like:
async public int GetLastUsedID()
{
volatile int currentID = 1;
volatile bool continueSearch = true;
while (continueSearch)
{
FirebaseREST.DatabaseReference reference = FirebaseREST.FirebaseDatabase.Instance.GetReference(""+currentID);
string value = "";
currentID++;
await reference.GetValueAsync(10, (res) =>
{
if (res.success)
{
value = res.data.GetRawJsonValue();
Debug.Log("Success fetched data : " + value);
if(value == "")
{
continueSearch = false;
Debug.Log(currentID);
}
}
else
{
Debug.Log("Fetch data failed : " + res.message);
continueSearch = false;
}
});
}
return currentID;
}
This is probably not the case, in which case you'll probably want to turn this into a recursive call. You'd want some function like:
public void GetLastUsedID(currentId)
{
FirebaseREST.DatabaseReference reference = FirebaseREST.FirebaseDatabase.Instance.GetReference(""+currentID);
reference.GetValueAsync(10, (res) =>
{
if (res.success)
{
value = res.data.GetRawJsonValue();
Debug.Log("Success fetched data : " + value);
if(value == "")
{
continueSearch = false;
Debug.Log(currentID);
}
else
{
// recurse
GetLastID(currentId+1);
}
}
else
{
Debug.Log("Fetch data failed : " + res.message);
continueSearch = false;
}
});
}
You will have to figure out how to pass in your own callback to bubble a success or failure like this (exercise to the reader).
Finally, I would be a little cautious with what you're doing entirely. If you're authenticating users, I'd recommend using Firebase Authentication which ties directly into Realtime Database. At most you'd only store user id's in there, and use security rules to ensure that only that user can write into it (for instance). By using Firebase Authentication to manage user data and tying that to RTDB rules, you avoid the same mistakes this author made.
Related
Whenever I reopen the game and start scoring again it replaces the previous data. Please help me with this and tell me where I'm doin it wrong.
I'm using Firebase Realtime Database.
This is my score script down below:
public static int cashValue;
Text cash;
void Start()
{
cash = GetComponent();
}
void Update()
{
cash.text = "" + cashValue;
}
This is where I save the score in Firebase Realtime Database:
private IEnumerator UpdateKills(int _kills)
{
//Set the currently logged in user kills
var DBTask = DBreference.Child("users").Child(User.UserId).Child("kills").SetValueAsync(_kills);
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else
{
//Kills are now updated
}
}
And This is Where I Retrieve the data from Firebase Realtime Database:
private IEnumerator LoadUserData()
{
//Get the currently logged in user data
var DBTask = DBreference.Child("users").Child(User.UserId).GetValueAsync();
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else if (DBTask.Result.Value == null)
{
//No data exists yet
xpField.text = "0";
killsField.text = "0";
deathsField.text = "0";
}
else
{
//Data has been retrieved
DataSnapshot snapshot = DBTask.Result;
xpField.text = snapshot.Child("xp").Value.ToString();
killsField.text = snapshot.Child("kills").Value.ToString();
deathsField.text = snapshot.Child("deaths").Value.ToString();
}
}
It works perfectly, but the only problem is that it replacing the Score instead of updating it or start from where I left.
The flow should go like this:
int myData;
bool isMyDataLoaded = false;
void Start() {
LoadMyData();
}
void LoadMyData() {
StartCoroutine(IELoadMyData());
}
void SetMyData() {
if(isMyDataLoaded) {
// Set only if the data from database was retreived
StartCoroutine(IESetMyData());
}
}
IEnumerator IELoadMyData() {
// Load your data from database here
if(<fetch was successful>) {
myData = <the result>;
isMyDataLoaded = true; // Set data loaded as true
}
}
IEnumerator IESetMyData() {
// Update your data here
}
Now whenever you want to update, Call SetMyData. It will only set the new data in the DB if the data was first fetched in the local game successfully.
Something to watch out for is that you might be falling victim to caching. Basically, GetValueAsync typically returns whatever's cached locally (unless you disable persistence -- which you probably shouldn't) and asks the server for an update. SetValueAsync writes to the local cache and eventually syncs it up in the background.
Therefore, to update a value, you should always use a Transaction. From https://firebase.google.com/docs/database/unity/save-data#save_data_as_transactions
private void UpdateKills(int _kills) {
DBreference.Child("users").Child(User.UserId).Child("kills").RunTransaction(mutableData => {
// if the data isn't an int or is null, just make it 0
// then add the new number of kills
var kills = ((mutableData.value as? int) ?? 0) + _kills;
return TransactionResult.Success(mutableData);
});
}
This would require reworking your logic so that rather than saving the total kills, you record the delta. Then this transaction would run repeatedly until it succeeds (usually once, unless your data is out of sync in which case usually twice).
For getting data, you're best off registering ValueChanged listeners and updating your UI directly from those. This way you're always in sync with your server, and it's smart enough to use a local cache so you don't have to worry about always blocking on a network access (you'll get a null if there's nothing cached). Also, as the local cache updates in the Transaction, you will get ValueChanged events with the latest data.
Obviously, not every game can be stable around randomly receiving value updates from a server. But if you can move your data to be more reactive around Realtime Database, you'll make the best use of its inbuilt local caching and sync logic.
I have the following scenario. My ultimate goal is to return my "trackingId" if I have (serviceResults.Status == ServiceStatus.Success). First I'm getting a pending status and my loop will work correctly then it keep looping until I get a success status and my variable are not set correctly.
My only issue is visual studio does not recognize this property on the first call serviceResults.partnerReference.trackingID . I'm trying to come with a better logic. Any help will be appreciated...
var finished = false;
var trackingId = ""
do {
var serviceResults = await client.Services.GetServiceOrderStatusAsync.....
//This is correct
if (serviceResults.Status == ServiceStatus.Pending) {
finished = false;
}
// This is not correct because this serviceResults.partnerReference.trackingID was never
//initialized on the first call.
if (serviceResults.Status == ServiceStatus.Success) {
trackingId = serviceResults.partnerReference.trackingID -> this never exist
finished = true;
}
}
while (!finished);
Here is my two postman call:
Pending
then success
Maybe one of my solutions was this??
if (serviceResults.Status == ServiceStatus.Success) {
dynamic serviceResulys = await
client.Services.GetServiceOrderStatusAsync("11158174", transactionId).ConfigureAwait(false);
trackingId = serviceResulys.serviceResults.trackingID;
finished = true;
}
if you want to keep waiting until you get the result you could simply run while loop infinite times, once you get the results you can break the loop something like below rather introducing so many variables for controlling the looping process.
while (true)
{
var serviceResults = await client.Services.GetServiceOrderStatusAsync.....
if (serviceResults.Status == ServiceStatus.Success)
{
trackingId = serviceResults.partnerReference.trackingID;
break;
}
}
I'm trying to create a Class to manage my Cloud Firestore requests (like any SQLiteHelper Class). However, firebase uses async calls and I'm not able to return a value to other scripts.
Here an example (bool return):
public bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
docRef.GetSnapshotAsync().ContinueWithOnMainThread(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Debug.Log(String.Format("Document {0} exist!", snapshot.Id));
return true; //Error here
}
else
{
Debug.Log(String.Format("Document {0} does not exist!", snapshot.Id));
}
});
}
Unfortunately, since Firestore is acting as a frontend for some slow running I/O (disk access or a web request), any interactions you have with it will need to be asynchronous. You'll also want to avoid blocking your game loop if at all possible while performing this access. That is to say, there won't be a synchronous call to GetSnapshotAsync.
Now there are two options you have for writing code that feels synchronous (if you're like me, it's easier to think like this than with callbacks or reactive structures).
First is that GetSnapshotAsync returns a task. You can opt to await on that task in an async function:
public async bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
// this is equivalent to `task.Result` in the continuation code
DocumentSnapshot snapshot = await docRef.GetSnapshotAsync()
return snapshot.Exists;
}
The catch with this is that async/await makes some assumptions about C# object lifecycle that aren't guaranteed in the Unity context (more information in my related blog post and video). If you're a long-time Unity developer, or just want to avoid this == null ever being true, you may opt to wrap your async call in a WaitUntil block:
private IEnumerator CheckIfIsFullyRegisteredInCoroutine() {
string idUtente;
// set idUtente somewhere here
var isFullyRegisteredTask = CheckIfIsFullyRegistered(idUtente);
yield return new WaitUntil(()=>isFullyRegisteredTask.IsComplete);
if (isFullyRegisteredTask.Exception != null) {
// do something with the exception here
yield break;
}
bool isFullyRegistered = isFullyRegisteredTask.Result;
}
One other pattern I like to employ is to use listeners instead of just retrieving a snapshot. I would populate some Unity-side class with whatever the latest data is from Firestore (or RTDB) and have all my Unity objects ping that MonoBehaviour. This fits especially well with Unity's new ECS architecture or any time you're querying your data on a per-frame basis.
I hope that all helps!
This is how i got it to work, but excuse my ignorance as i've only been using FBDB for like a week.
Here is a snippet, I hope it helps someone.
Create a thread task extension to our login event
static Task DI = new System.Threading.Tasks.Task(LoginAnon);
Logging in anon
DI = FirebaseAuth.DefaultInstance.SignInAnonymouslyAsync().ContinueWith(result =>
{
Debug.Log("LOGIN [ID: " + result.Result.UserId + "]");
userID = result.Result.UserId;
FirebaseDatabase.DefaultInstance.GetReference("GlobalMsgs/").ChildAdded += HandleNewsAdded;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/infodata/nickname/").ValueChanged += HandleNameChanged;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/staticdata/status/").ValueChanged += HandleStatusChanged;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildAdded += HandleLobbyAdded;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildRemoved += HandleLobbyRemoved;
loggedIn = true;
});
And then get values.
DI.ContinueWith(Task =>
{
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/" + selectedLobbyID + "/players/").GetValueAsync().ContinueWith((result) =>
{
DataSnapshot snap2 = result.Result;
Debug.Log("Their nickname is! -> " + snap2.Child("nickname").Value.ToString());
Debug.Log("Their uID is! -> " + snap2.Key.ToString());
//Add the user ID to the lobby list we have
foreach (List<string> lobbyData in onlineLobbies)
{
Debug.Log("Searching for lobby:" + lobbyData[0]);
if (selectedLobbyID == lobbyData[0].ToString()) //This is the id of the user hosting the lobby
{
Debug.Log("FOUND HOSTS LOBBY ->" + lobbyData[0]);
foreach (DataSnapshot snap3 in snap2.Children)
{
//add the user key to the lobby
lobbyData.Add(snap3.Key.ToString());
Debug.Log("Added " + snap3.Child("nickname").Value.ToString() + " with ID: " + snap3.Key.ToString() + " to local lobby.");
currentUsers++;
}
return;
}
}
});
});
Obviously you can alter it how you like, It does not really need loops but i'm using them to test before i compact the code into something less readable and more intuitive.
One of my iOS applications seems to have the symptoms of a classic Heisenbug. The application tracks a user's home location so certain events happen when the user enters and exits their home location.
While I'm testing the application, it works great. I walk in and out of a CLCircularRegion and it works every which way I try it. It works with the application in the background. It works with the application closed. It works with the application in the foreground. It works with green eggs and ham.
Unfortunately, users are reporting issues where it will be delayed by 15 minutes or so. The users will enter their homes, but the event will not occur until later. In some cases, the event does not occur at all. The pattern seems to be that when the user first starts using the application, it works great. After a day or so, the application doesn't seem to work as well. The events are delayed.
I'll be the first to admit that I'm no expert on the inner workings of CLLocationManager and CLCircularRegion. I believe I have everything set up properly though and I'm having a really hard time trying to figure out how I can debug something like this.
At any rate, I'll show some of my code here. Keep in mind this is developed with Xamarin so it's in C#.
AppDelegate.cs
public static AppDelegate self;
private CLLocationManager locationManager;
private CLCircularRegion[] locationFences;
private void initializeLocationManager()
{
this.locationManager = new CLLocationManager();
// iOS 8 additional permissions requirements
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
locationManager.RequestAlwaysAuthorization();
}
locationManager.AuthorizationChanged += (sender, e) =>
{
var status = e.Status;
// Location services was turned off or turned off for this specific application.
if (status == CLAuthorizationStatus.Denied)
{
stopLocationUpdates();
}
else if (status == CLAuthorizationStatus.AuthorizedAlways &&
iOSMethods.getKeyChainBool(OptionsViewController.GENERIC, OptionsViewController.SERVICE_GEOLOCATION_ENABLED))
{
startLocationUpdates();
}
};
if (CLLocationManager.IsMonitoringAvailable(typeof(CLCircularRegion)))
{
locationManager.RegionEntered += (sender, e) =>
{
setRegionStatus(e, "Inside");
};
locationManager.RegionLeft += (sender, e) =>
{
setRegionStatus(e, "Outside");
};
locationManager.DidDetermineState += (sender, e) =>
{
setRegionStatus(e);
};
}
else
{
// cant do it with this device
}
init();
}
public void init()
{
var data = SQL.query<SQLTables.RoomLocationData>("SELECT * FROM RoomLocationData").ToArray();
int dLen = data.Length;
if (dLen > 0)
{
locationFences = new CLCircularRegion[dLen];
for (int x = 0; x < dLen; x++)
{
var d = data[x];
CLCircularRegion locationFence = new CLCircularRegion(new CLLocationCoordinate2D(d.Latitude, d.Longitude), d.Radius, d.SomeID.ToString() + ":" + d.AnotherID.ToString());
locationFence.NotifyOnEntry = true;
locationFence.NotifyOnExit = true;
locationFences[x] = locationFence;
}
}
}
private void setRegionStatus(CLRegionEventArgs e, string status, bool calledFromDidDetermineState = false)
{
string identifier = e.Region.Identifier;
string lastStatus = iOSMethods.getKeyChainItem(OptionsViewController.GENERIC, OptionsViewController.SERVICE_LAST_GEO_STATUS);
if (lastStatus == status + ":" + identifier)
{
return;
}
iOSMethods.setKeychainItem(OptionsViewController.GENERIC, OptionsViewController.SERVICE_LAST_GEO_STATUS, status + ":" + identifier);
string[] split = identifier.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries);
if (split.Length == 2)
{
try
{
int someID = Convert.ToInt32(split[0]);
int anotherID = Convert.ToInt32(split[1]);
// Notifies our API of a change.
updateGeofenceStatus(someID, anotherID, status);
if (iOSMethods.getKeyChainBool(OptionsViewController.GENERIC, OptionsViewController.SERVICE_GEOLOCATION_NOTIFICATIONS) &&
(status == "Inside" || status == "Outside" || status == "Unknown"))
{
var rm = SQL.query<SQLTables.KeyRoomPropertyData>("SELECT * FROM KeyRoomPropertyData WHERE SomeID ID = ? AND AnotherID = ?",
new object[] { someID, anotherID }).ToArray();
if (rm.Length > 0)
{
if (status == "Unknown")
{
return;
}
var rmD = rm[0];
UILocalNotification notification = new UILocalNotification();
notification.AlertAction = "Geolocation Event";
notification.AlertBody = status == "Inside" ? "Entered " + rmD.SomeName + ": " + rmD.AnotherName :
status == "Outside" ? "Exited " + rmD.SomeName + ": " + rmD.AnotherName :
"Geolocation update failed. If you would like to continue to use Geolocation, please make sure location services are enabled and are allowed for this application.";
notification.SoundName = UILocalNotification.DefaultSoundName;
notification.FireDate = NSDate.Now;
UIApplication.SharedApplication.ScheduleLocalNotification(notification);
}
}
}
catch (Exception er)
{
// conversion failed. we don't have ints for some reason.
}
}
}
private void setRegionStatus(CLRegionStateDeterminedEventArgs e)
{
string state = "";
if (e.State == CLRegionState.Inside)
{
state = "Inside";
}
else if (e.State == CLRegionState.Outside)
{
state = "Outside";
}
else
{
state = "Unknown";
}
CLRegionEventArgs ee = new CLRegionEventArgs(e.Region);
setRegionStatus(ee, state, true);
}
public void startLocationUpdates()
{
if (CLLocationManager.LocationServicesEnabled)
{
init();
if (locationFences != null)
{
foreach (CLCircularRegion location in locationFences)
{
locationManager.StartMonitoring(location);
Timer t = new Timer(new TimerCallback(delegate(object o) { locationManager.RequestState(location); }), null, TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
}
}
}
}
public void stopLocationUpdates(bool isRestarting = false)
{
if (locationFences != null)
{
foreach (CLCircularRegion location in locationFences)
{
locationManager.StopMonitoring(location);
}
}
if (!isRestarting)
{
var rooms = SQL.query<SQLTables.KeyRoomPropertyData>("SELECT * FROM KeyRoomPropertyData").ToArray();
foreach (SQLTables.KeyRoomPropertyData room in rooms)
{
// notifies our API of a change
updateGeofenceStatus(room.SomeID, room.AnotherID, "Unknown");
}
}
}
I know it's a lot of code for anyone to sift through, but I really have no good theory at this point as to what is causing this bug or if it is even possible to fix with the limitations of iOS.
A few theories that I have are if the CLLocationManager.PausesLocationUpdatesAutomatically property may have something to do with it, or some other property of CLLocationManager such as ActivityType, DesiredAccuracy, or DistanceFilter. I've left all of these at their defaults which I would assume would be fine, but I'm not really sure.
Another theory is that there is an uncaught exception being thrown some time after the "service" has been running in the background for some time. If that is the case, is there anything iOS does that would give me a stack trace or something? In all of my tests, I never ran across any exceptions being thrown from this code so I kind of doubt that's the issue. At this point though, I'm willing to entertain any ideas or suggestions.
Also, please keep in mind that in order for this application to work the way it was intended, the location update events MUST occur as soon as the user enters or exist the CLCircularRegion (within a minute or so at least). Obviously I have to leave it to the user to keep their location services enabled and allow the app to have the appropriate permissions.
You are most likely right on target with your diagnosis - it is classic observer effect.
When you test the app, when users play with a new app, the iphone is being actively used. It is not given a chance to fall asleep. What happens on a next day, when users return home - their phones most likely are not in use for extended time right before reaching home location: normally we do not use phones during "last mile" walk after leaving public transportation, or while driving back home. iOS notices this extended inactivity period and adjusts its own behavior to optimize battery life.
The easiest way to observe this is to put together a simple breadcrumbs app - set geofence at your location and keep doing that every time you get exit event. Depending on the way you use (or not use) your phone results will be very different while walking the same route.
And when you get home, the phone is usually the last thing you reach for as well.
You may want to ask users to give more details on how exactly they used phones last 15 minutes before and after entering home, what other apps they use, if they drive do they keep turn by turn navigation app running etc. You will spot the pattern.
re. Also, please keep in mind that in order for this application to
work the way it was intended, the location update events MUST occur as
soon as the user enters or exist the CLCircularRegion (within a minute
or so at least).
You can't do this with geofencing only, especially taking into account different arrival/departure patterns - walking vs driving, "descend" paths (e.g. arrivals with U-turns). You have to anticipate both delays longer than 1 minute and "premature" triggering. I am afraid there is no workaround.
Some things to check:
What are some typical values for radius? You may want to consider reducing that.
iOS Location Services will provide a quicker response if the device has WiFi enabled even if the user is not connected to a network. Check if the problem users have wifi disabled and if you haven't done so already maybe test your device w/o wifi.
Is there a delay in the notification? That is, does the region event occur correctly but for some reason there is a delay in the notification?
How many RoomLocationData entries are there? iOS limits each app to 20 regions max.
Presuming the users are driving to/from their house, you may want to try the following settings (code is Swift):
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest // or kCLLocationAccuracyBestForNavigation
locationManager.pausesLocationUpdatesAutomatically = true // try false if nothing else works
locationManager.allowsBackgroundLocationUpdates = true
locationManager.activityType = CLActivityType.AutomotiveNavigation
So the problem is as follows: I'm using a third party client class to issue commands to an external system to retrieve data (fairly standard). The problem is that when I issue commands via an instance of this class, it uses a callback reference based on the function name in my code and the line number within that function from which it was called, it then serializes this and other information into JSON and transmits to the external system for processing, with the data return being identified by the reference. This works "well" until we get to iteration, then the callback reference remains the same and I only receive data for one iteration. The third party isn't going to alter their code so I need a way of generating unique references in my code, but I'm unsure of how I can do this within C#. I can't edit their class as it is provided as a DLL, I can only access this system by using it (it is part of their SDK).
Any ideas greatly appreciated!
Example code:
[Note: actual code is part of a Windows Form Application]
The last part client.IsUserInGroup is the problem.
using thirdParty.Client;
class TestProgram
{
static void Main(string[] args)
{
//Area and user objects defined within third party class
List<Area> Areas = new List<Area>();
List<User> myUsers = new List<User>();
int publicAreaID = 0;
bool isConnected=false;
client.Connect("user", "pass",
(connstatus) =>
{
switch (connstatus)
{
case ConnectionStatus.Success:
isConnected = true;
Console.WriteLine("Connected");
break;
case ConnectionStatus.InvalidCredentials:
Console.WriteLine("InvalidCredentials");
break;
case ConnectionStatus.Timeout:
Console.WriteLine("Timeout");
break;
}
});
if (isConnected)
{
client.GetAreas(
(result) =>
{
Areas = result;
});
//Get ID of public area
foreach (Area myArea in Areas)
{
if (myArea.Name.Equals("Public"))
{
publicAreaID = myArea.ID;
}
}
//Get all keyholders in Public area and store in list
client.GetUsersInArea(publicAreaID,
(result) =>
{
myUsers = result;
});
//Iterate over all users in list and verify they are in the everyone group
foreach (User myUser in myUsers)
{
User tempUser = myUser;
client.IsUserInGroup(tempUser.ID, 0,
(result) =>
{
if (result) //this is a bool
{
//This only returns one result..
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser.Name));
}
});
}
client.Disconnect();
}
}
}
UPDATE
I've been doing more testing by removing the foreach loop and just calling client.IsUserInGroup twice to generate alternative callback references; the results are interesting. As expected there are unique references, but there is still only one result displayed, the only way to get both is to create two User objects rather than reuse one. As mentioned above, the "real" code is used in a Windows forms app, could this be something to do with object referencing? Example code below:
new User tempUser1 = myUsers[0];
client.IsUserInGroup(tempUser1.ID, 0,
(result) =>
{
if (result) //this is a bool
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser1.Name));
}
});
new User tempUser2 = myUsers[1];
client.IsUserInGroup(tempUser2.ID, 0,
(result) =>
{
if (result) //this is a bool
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser2.Name));
}
});
Answer moved from OP's original question:
Ok so I was playing with this a lot over the last few hours and kind of made it work by keeping the iterative loop but doing two things; firstly I assumed that the third party class would synchronize information requests and not allow my code to continue until it had a result returned - this seemingly is not the case as the output from recursion with an extra Console.WriteLine(iterationCount) in it shows the count increasing with no data returned; therefore I am forced to slow down the code by Thread.Sleep (I'm investigating better ways of doing this). Secondly any code within the lambda that could be moved outside, was. Instead a temp bool outside of the lambda was assigned the value of the result bool. The code looks like:
//Iterate over all users in list and verify they are in the everyone group
foreach (User myUser in myUsers)
{
User tempUser = myUser;
bool tempRes = false;
client.IsUserInGroup(tempUser.ID, 0,
(result) =>
{
tempRes = result;
});
if (tempRes)
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser.Name));
}
System.Threading.Thread.Sleep(75); //Not a good way of enforcing sync!
}