0

I'm working in a Unity 3D app where I have 2 methods that connects to a service by using Oauth: one retrieves the token, and the other retrieves the JSON output from the service, based on the token. Once the JSON is ready then I change the text on a TextMesh. Everything running in the start method of the TextMesh script. Something like this:

void Start()
{
    string authToken = getAuthToken();
    CustomerGroups data = getCustGroups(authToken);

    TextMesh curText = (TextMesh)gameObject.GetComponent(typeof(TextMesh));
    curText.text = data.value[0].Field1+ "-" + data.value[0].Field2;
}

Oddly enough, this works great when I use "Attach to Unity" in Visual Studio and run the GameObject in the Unity player, but when I run it without debugging (like running a couple steps) I always get:

NullReferenceException: Object reference not set to an instance of an object
HelloWorldInteractive.getAuthToken () (at Assets/HelloWorldInteractive.cs:106)
HelloWorldInteractive.Start () (at Assets/HelloWorldInteractive.cs:15)

The actual method is:

public string getAuthToken()
{
    string token = string.Empty;
    Dictionary<string, string> content = new Dictionary<string, string>();
    TokenClassName json = null;

    content.Add("tenant_id", "https//...");
    content.Add("grant_type", "client_credentials");
    content.Add("client_id", "xxxx");
    content.Add("client_secret", "xxxx");
    content.Add("resource", "https://...");

    UnityWebRequest www = UnityWebRequest.Post("https://login...", content);

    //Send request
    www.SendWebRequest();

    if (!www.isNetworkError)
    {
        string resultContent = www.downloadHandler.text;
        json = JsonUtility.FromJson<TokenClassName>(resultContent);

        //Return result
        token = json.access_token;
    }

    return token;
}

And again, if I don't debug it, it fails, but when I debug while running it, it works great. I'm assuming that it could be related to executing them in the Start method...maybe I need to do it somewhere else? I just need to have the JSON data ready so I can change the value of the TextMesh right at the start.

Uwe Keim
  • 36,867
  • 50
  • 163
  • 268
saman0suke
  • 698
  • 1
  • 8
  • 21
  • Possible duplicate of [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Uwe Keim Feb 20 '19 at 05:58
  • @UweKeim The thread above is very generic, my issue is with Unity player, and only when I'm not debugging. – saman0suke Feb 20 '19 at 06:05
  • where is "HelloWorldInteractive.getAuthToken () (at Assets/HelloWorldInteractive.cs:106)" ? – Heejae Kim Feb 20 '19 at 06:13
  • @HeejaeKim those 2 methods are in the HelloWorldInteractive class, and being called within Start(). Please keep in mind that this works while debugging, and TextMesh is changed, but when I just run it, it fails. – saman0suke Feb 20 '19 at 06:17
  • @saman0suke I just want to know the line that "HelloWorldInteractive.cs:106" means. Anyway, as I know, the web request should be done on Coroutine. Refer to https://docs.unity3d.com/2017.4/Documentation/Manual/UnityWebRequest-SendingForm.html. – Heejae Kim Feb 20 '19 at 06:24
  • @HeejaeKim My apologies, it fails exactly here if I'm not mistaken (not in front of the error right now) token = json.access_token, where it retrieves the access_token from the obtained JSON, and it fails because it returns null while not debugging. – saman0suke Feb 20 '19 at 13:12

1 Answers1

6

Your problem is called a "race condition".

When you are debugging you are "slow enough" so your UnityWebRequest very probably has a result until you get to the part of the code where it is needed.

While not debugging: You are not waiting until the webrequest finishes.

so www.downloadHandler.text; will still have the value null when the method reaches the line

json = JsonUtility.FromJson<TokenClassName>(resultContent);

I don't know what exctaly JsonUtility.FromJson does for a null as input value but I'ld guess either the error is already thrown in there or it might return null so the next line

token = json.access_token;

trying to access json with the value null is throwing the exception.


You have to use a Coroutine and yield until you have a result (see UnityWebRequest.Post).

I would use a callback method like

private IEnumerator getAuthToken(Action<string> onSuccess)
{
    string token = string.Empty;
    Dictionary<string, string> content = new Dictionary<string, string>();
    TokenClassName json = null;

    content.Add("tenant_id", "https//...");
    content.Add("grant_type", "client_credentials");
    content.Add("client_id", "xxxx");
    content.Add("client_secret", "xxxx");
    content.Add("resource", "https://...");

    UnityWebRequest www = UnityWebRequest.Post("https://login...", content);

    //Send request
    // !! wait until request finishes
    yield return www.SendWebRequest();

    if (!www.isNetworkError && !www.isHttpError)
    {
        string resultContent = www.downloadHandler.text;
        json = JsonUtility.FromJson<TokenClassName>(resultContent);

        //Return result
        token = json.access_token;

        // this should only be done on success
        // execute the callback
        onSuccess?.Invoke(token);
    }
    else
    {
        Debug.LogErrorFormat(this, "Downlaod failed with: {0} - \"{1}\"", www.responseCode, www.error);
    }
}

and use it either with a callback method like

private Start()
{
    StartCoroutine(getAuthToken(OnReceivedToken));
}

privtae void OnReceivedToken(string authToken)
{
    CustomerGroups data = getCustGroups(authToken);

    TextMesh curText = (TextMesh)gameObject.GetComponent(typeof(TextMesh));
    curText.text = data.value[0].Field1+ "-" + data.value[0].Field2;     
}

or as a lambda expression

private Start()
{
    StartCoroutine(getAuthToken(
        (authToken) =>
        {
            CustomerGroups data = getCustGroups(authToken);

            TextMesh curText = (TextMesh)gameObject.GetComponent(typeof(TextMesh));
            curText.text = data.value[0].Field1+ "-" + data.value[0].Field2;
        }
    ));
}
derHugo
  • 49,310
  • 9
  • 37
  • 73
  • I used your approach and applied "yield" in the second method as well (getCustGroups) and it worked as expected, thanks! – saman0suke Feb 21 '19 at 03:36