UnityEngine.UI.Text is not updating inside async method - c#

i have a script MenuManager.cs amd i this script i get tow doc in the firebase firestone, and this works well but when i will set the result in a text, dont work
using Firebase.Firestore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Json;
using UnityEngine;
public class MenuManager : MonoBehaviour
{
private Usuario usuario;
private Rank rank;
private CollectionReference usuarioCollection;
private CollectionReference rankCollection;
public GameObject rankNomeLabel;
public bool infoCarregadas;
public GameObject botaoInfo;
void Start()
{
this.rank = new Rank();
this.botaoInfo.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => this.CarregaInfos());
//this.infoCarregadas = false;
}
void Update()
{
/*if (db != null && infoCarregadas == false)
{
this.CarregaInfos();
} else
{
if (infoCarregadas == false)
{
db = FirebaseAuthenticator.instance.fsDB;
}
}*/
}
private void CarregaInfos()
{
try
{
FirebaseFirestore db = FirebaseAuthenticator.instance.fsDB;
var usuarioJson = PlayerPrefs.GetString("usuario");
this.usuario = usuarioJson != "" ? JsonUtility.FromJson<Usuario>(usuarioJson) : null;
if (this.usuario != null && this.usuario.UID != null)
{
this.usuarioCollection = db.Collection("usuario");
DocumentReference docRef = this.usuarioCollection.Document(usuario.UID);
docRef.GetSnapshotAsync().ContinueWith(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Dictionary<string, object> usuario = snapshot.ToDictionary();
foreach (KeyValuePair<string, object> pair in usuario)
{
if (pair.Key == "rank")
{
this.rankCollection = db.Collection("rank");
DocumentReference docRef = this.rankCollection.Document(pair.Value.ToString());
docRef.GetSnapshotAsync().ContinueWith(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Dictionary<string, object> rank = snapshot.ToDictionary();
foreach (KeyValuePair<string, object> pair in rank)
{
if (pair.Key == "nome")
{
this.rankNomeLabel.GetComponent<UnityEngine.UI.Text>().text = pair.Value.ToString();
}
}
}
else
{
Debug.Log("Erro ao carregar o rank: " + snapshot.Id);
}
});
}
}
}
else
{
Debug.Log("Erro ao carregar o usuário: " + snapshot.Id);
}
});
}
else
{
Debug.Log("Erro ao pegar usuário do PlayerPrefs");
}
}
catch (Exception e)
{
Debug.LogError("Erro: " + e);
}
}
}
the text is linked to the variable (print bellow)
this.rankNomeLabel.GetComponent<UnityEngine.UI.Text>().text = pair.Value.ToString(); in this line, the reference is null
think the problem is in setting the value within the async method but I don't know how to fix it
does anyone know what's going on?

Most of the Unity API can only be used on the Unity main thread (the exception are pure structs since they are only data containers and don't immediately rely on or influence the actual scene).
ContinueWith is not guaranteed to be executed in the main thread.
Therefore Firebase provides the Extension method ContinueWithOnMainThread.
Replacing both or at least the inner ContinueWith by ContinueWithOnMainThreae should solve your issue or at least make it clearer where the actual issue is.
Instead of using GetComponent over and over again make your live easier and directly use the correct types
using UnityEngine.UI;
...
public Text rankNomeLabel;
public Button botaoInfo;
this also makes sure you can only drag in objects that actually have the according component attached and is less error prone on runtime since it makes already sure it isn't null - and if it is (e.g. if the object was destroyed) you can also see it immediately in the Inspector

I solved a similar issue by checking a boolean in an update method. It's not fancy but it works.
public class MyClass : MonoBehaviour
{
private bool updateUI = false;
private async Task function() {
updateUI = true;
}
private void Update() {
if (updateUI) {
// update your ui here
...
updateUI = false;
}
}
}

Related

Why does myObjectPool.Clear() not work? It deletes only the first object

I wanted to play arround with object pooling. In my case, I'm pooling projectiles (monoBehaviours) and to allow external code to remove projectiles, I'm using delegates.
The problem: when I call myObjectPool.Clear(), it destroys only the first object in the pool.
public Projectile ProjPrefab { get; private set; }
private ObjectPool<Projectile> _projectilePool;
private Dictionary<int, Projectile.GetDestroyed> _destroyDelegates = new();
public Gun(GunData data ): base(data)
{
ProjPrefab = data.ProjectilePrefab;
#region Pool SetUp
_projectilePool = new(
()=> {
Projectile obj = GameObject.Instantiate(ProjPrefab);
obj.SetSenderID(_parentEntity);
Projectile.GetDestroyed destrDelegate = () => _projectilePool.Release(obj);
_destroyDelegates.Add(obj.GetInstanceID() , destrDelegate);
obj.DestroyWish += destrDelegate;
obj.gameObject.SetActive(false);
return obj;
},
proj => {
Vector2 worldPos = GunInstance.TransformPoint(GunLocalShootingSpot.position);
proj.transform.SetPositionAndRotation(worldPos, GunInstance.rotation);
proj.gameObject.SetActive(true);
},
proj => {
proj.gameObject.SetActive(false);
},
proj => {
int projID = proj.GetInstanceID();
Projectile.GetDestroyed destrDelegate = _destroyDelegates[projID];
proj.DestroyWish -= destrDelegate;
_destroyDelegates.Remove(projID);
GameObject.Destroy(proj.gameObject);
},
collectionCheck: true,/*not sure what this does*/
defaultCapacity: 10,
maxSize: 15
);
#endregion
}
public override void OnRemove()
{
_projectilePool.Clear();
}
Gun is a simple class, that doesn't derive from any builtIn classes, it is stored in a centralized script called gameEntity (in a list). When the GameEntity (which is a MonoBehaviour) is destroyed, it calls gun.OnRemove() in SelfDestroy():
public class GameEntity : MonoBehaviour
{
List<Property> _properties = new();
//code
//****
//code
public void SelfDestroy()
{
foreach (var prop in _properties)
prop.OnRemove();
Destroy(gameObject);
}

How to create loading in Unity until load information?

hi,i found a code for loading scene in unity3D, I want to take when data via PHP (through WWW), Loading displayed, please help me.
Also, how to recognize and change the keyboard language for Android Device?
void Update(){
if (loadScene == false)
{
loadScene = true;
loadingText.text = "Is loading your information...";
StartCoroutine(LoadNewScene());
}
if (loadScene == true)
{
loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
}
}
}
void Start(){
StartCoroutine(rankGet());
}
IEnumerator LoadNewScene() {
yield return new WaitForSeconds(3);
AsyncOperation async = Application.LoadLevelAsync(scene);
while (!async.isDone) {
yield return null;
}
}
IEnumerator rankGet()
{
txtUsername.text = PlayerPrefs.GetString("username");
WWW connection = new WWW("http://127.0.0.1/scoregame/userscorerank.php?uss=" + txtUsername.text);
yield return (connection);
if (connection.text == "401")
{
//Debug.Log("username do not exits!");
}
else
{
ranktxt.text = connection.text;
}
}
The easiest way to use Async operation for any other purpose then loading new scene, is to
write your own method that produces an IEnumerable instance.
As described here by msknapp user.
public System.Collections.IEnumerable coroutineFunction()
{
while (!doneYourPersistentProcess())
// ... wait for it ...
yield return "";
// now it is done.
doPostProcessing();
}
public void doPostProcessing()
{
// Loading progressbar here
}
public void Update()
{
if (userStartsPersistentProcess())
StartCoroutine(coroutineFunction());
}
public bool userStartsPersistentProcess()
{
// your code here.
}
public bool doneYourPersistentProcess()
{
// your code here.
}
And the last step is to prepare graphic for progress bar.
The easiest way is to change Fill Amount value in the Image object, with properties as shown below:

Threads conflict in some cases, in loop conditions

I am working on a project that uses Threads. In some cases, I have these problems:
Here is some piece of my code :
List<EmailAddress> lstEmailAddress = new List<EmailAddress>();
private void TimerCheckInternetConnection_Tick(object sender, EventArgs e)
{
lock (TicketLock)
{
if (UtilityManager.CheckForInternetConnection())
{
if (ApplicationRunStatus == Enum_ApplicationRunStatus.UnknownDisconnect || ApplicationRunStatus == Enum_ApplicationRunStatus.IsReady)
{
// Connect
ThreadPool.QueueUserWorkItem((o) =>
{
for (int i = 0; i < lstEmailAddress.Count; i++)
{
lstEmailAddress[i].IsActive = lstEmailAddress[i].Login();
}
this.BeginInvoke(new Action(() =>
{
// some code
}));
});
}
}
}
}
and this is EmailAddress class :
class EmailAddress
{
private Imap4Client imap = new Imap4Client();
private object objectLock = new object();
public bool IsActive;
public string Address;
public string Password;
public string RecieveServerAddress;
public int RecieveServerPort;
public bool Login()
{
lock (objectLock)
{
try
{
imap.ConnectSsl(RecieveServerAddress, RecieveServerPort);
}
catch (Exception)
{
}
try
{
imap.Login(Address, Password);
return true;
}
catch (Exception)
{
return false;
}
}
}
}
And my problem is this:
When I want to use Login procedure that belongs to EmailAddress Class, it has some conflict. As you can see, I used Lock but any thing changed.
For more details:
If I have 3 items in lstEmailAddress , the Login procedure has to be called 3 times by this code. but every time, the login procedure will work on same username and password. So all my emails cannot login correctly.
If I remove threadpool, it will be ok.
Your code is very confusing:
If you add the lock in your code, it will run synchroniously, only one thread at the time, which will lead to performance loss.
If you queue work via QueueUserWorkItem - it will run in other thread, and not inside TicketLock
You should incapsulate locks inside your class, and should not lock entire logic in your program.
You start work for a loop variable i, which is being closured for it's last value, which lead for a problem you state in last sentence.
lock object in Email class isn't static so it's being created for each instance, and doesn't actually lock anithing.
As you are using Invoke method, your code is being started from UI, and you need to pass the synchronization context. I suggest you to use TPL code for this, and do not directly work with ThreadPool
So I suggest you this solution:
List<EmailAddress> lstEmailAddress = new List<EmailAddress>();
private void TimerCheckInternetConnection_Tick(object sender, EventArgs e)
{
// remove this lock as we have another in Email class
//lock (TicketLock)
if (UtilityManager.CheckForInternetConnection())
{
if (ApplicationRunStatus == Enum_ApplicationRunStatus.UnknownDisconnect
|| ApplicationRunStatus == Enum_ApplicationRunStatus.IsReady)
{
for (int i = 0; i < lstEmailAddress.Count; i++)
{
// use local variable to store index
int localIndex = i;
// Connect
ThreadPool.QueueUserWorkItem((o) =>
{
// if you add a lock here, this will run synchroniosly,
// and you aren't really need the ThreadPool
//lock (TicketLock)
lstEmailAddress[localIndex].IsActive = lstEmailAddress[localIndex].Login();
this.BeginInvoke(new Action(() =>
{
// some code
}));
});
}
}
}
}
class EmailAddress
{
// if you have to login only for one user simultaneosly
// use static variables here, other wise simply remove the lock as it is useless
private static Imap4Client imap;
private static object objectLock;
// static constructor for only one initialization for a static fields
static EmailAddress()
{
objectLock = new object();
imap = new Imap4Client();
}
public bool IsActive;
public string Address;
public string Password;
public string RecieveServerAddress;
public int RecieveServerPort;
public bool Login()
{
// aquire a static lock
lock (objectLock)
{
try
{
imap.ConnectSsl(RecieveServerAddress, RecieveServerPort);
}
catch (Exception)
{
// STORE THE EXCEPTION!!!
// return as you haven't connected
return false;
}
try
{
imap.Login(Address, Password);
return true;
}
catch (Exception)
{
// STORE THE EXCEPTION!!!
return false;
}
}
}
}
Change your Code as and try . you code is queing item from lstEmailAddress where it will always go and hit last item from the list. change your code to inquie each item in threadpool. that should fix. it.
for (int i = 0; i < lstEmailAddress.Count; i++)
{
ThreadPool.QueueUserWorkItem((o) =>
{
lstEmailAddress[i].IsActive = lstEmailAddress[i].Login();
}
}

Prism NavigationService get previous view name

Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName

Subclass a class that has Pointers to other classes

I want to subclass a Class in Parse that has Pointers to other classes.
Here is my class:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Parse;
using System.Threading.Tasks;
[ParseClassName("UserCars")] public class UserCars : ParseObject {
[ParseFieldName("car")]
public Car car
{
get { return GetProperty<Car>("car"); }
set { SetProperty<Car>(value, "car"); }
}
[ParseFieldName("isBumpers")]
public bool isBumpers
{
get { return GetProperty<bool>("isBumpers"); }
set { SetProperty<bool>(value, "isBumpers"); }
}
When I execute this query below isBumpers returns an accurate bool value, but car is always null:
var query = new ParseQuery<UserCars>();
query.WhereEqualTo("user", ParseUser.CurrentUser);
query.FindAsync().ContinueWith(t =>
{
IEnumerable<UserCars> userCars = t.Result;
foreach (UserCars u in userCars)
{
Debug.Log(u.car);
}
});
I tried adding this with no change:
IEnumerable<UserCars> userCars = t.Result;
foreach (UserCars u in userCars)
{
Task<Car> fCar = u.car.FetchIfNeededAsync();
Debug.Log(u.car);
}
What am I missing?
fCar is a Task, so basicly you should wait for it to complete.
Try writing this instead:
foreach (UserCars u in userCars )
{
u.car.FetchIfNeededAsync().ContinueWith(result =>
{
Debug.Log(u.car);
};
}
This will make sure you wait for the data to be correctly fetched.

Categories