If you want registerUser to run as a coroutine, you need to call StartCoroutine(), not simply call registerUser().
If a function contains “yield return” and you run it normally, it returns a lazily-evaluated collection. If you want it to run as a regular synchronous function, get rid of “yield return”.
And how do I get the return value?
Debug.Log("result is " + cd.coroutine); ==> result is UnityEngine.Coroutine
Debug.Log("result is " + cd.result); ==> result is UnityEngine.Networking.UnityWebRequestAsyncOperation
You need to learn about callbacks (a particular use case of delegates). A delegate is basically a reference to a function that you can pass around like a variable. The simplest way to use delegates is to use System.Action<>, which is a delegate type for a “void” function with any parameters you want. In this case, you want one with an int parameter, and you can use that parameter as your “return value”.
Something like:
StartCoroutine(RegisterUser(user, OnComplete) );
...
IEnumerator RegisterUser(User user, System.Action<int> callbackOnFinish)
{
Debug.Log("a register user");
string action = "insertUser";
WWWForm form = new WWWForm();
form.AddField("action", action);
using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
callbackOnFinish(1);
}
else
{
Debug.Log(www.downloadHandler.text);
callbackOnFinish(0);
}
}
}
public void OnComplete(int didError) {
Debug.Log("Did we get an error?" + didError);
}
Once you grasp this concept, you can use something called a lambda function to keep the code more organized in cases like this - it’s a function without a name that gets defined inline as you need it. The syntax is easy to mess up though which is why I started with the other syntax.
StartCoroutine(RegisterUser(user, (didError) => {
Debug.Log("Did we get an error?" + didError);
});
In either case it’s important to remember that that code won’t be executed until callbackOnFinish(x) is called in the coroutine, which may be many frames later. The code is just sort of stashed away until then.
Delegates and callbacks are super cool and super useful tools.
(And if you’re wondering, this is extremely common in networking code like this)
Thank you very much for such a good explanation. I have a doubt refering callbacks…
My coroutine is within a method which is the one I want to return a value to the original called, like this :
public int registerUser(User user)
{
CoroutineWithData cd = new CoroutineWithData(this, RegisterUser(user));
return 1;
Debug.Log("result is " + cd.result); // 'success' or 'fail'
}
Now with the callback I made :
public int registerUser(User user)
{
int result = -1;
StartCoroutine(RegisterUser(user, (didError) => {
Debug.Log("Did we get an error?" + didError);
result = didError;
});
return result;
}
Which as you may know it doesn’t work… as you said the method returns a value before the callback is call so always returns “-1”.
So how can I make the callback return the correct value for registerUser method?
that’s because StartCouroutine is like ‘fire and forget’ so the next line (return result) is being executed before even Ienumerator started so it has a value -1.
So in general your approach is wrong.
You want to StartCoroutine and then wait for the result but inside an int calculation which is the main thread
this has no logic
My approach - not tested but should work
StartCoroutine(RegisterUser(new User()));
IEnumerator RegisterUser(User user) {
int userID = -1;
yield return StartCoroutine(GetRegisterID(user, value => userID = value));
//now you hava a result and you may continue with registration
Debug.Log(userID);
}
IEnumerator GetRegisterID(User user, System.Action<int> result) {
yield return new WaitForSeconds(1); //do something long...
int regID = 12345;
result(regID);
}