[SOLVED]Android Build: NullReferenceException in adb logcat

In the editor all is working okay.
On my android game build on real device, different story

Basically the thing is, I’m trying to acces a singleton that is attached on a gameobject in my first scene,
and in the adb logcat I can see a NullRefecenceException pointing on the exact line where I’m trying to acces it

so in my scene, say, “main” has my singleton
a few scenes away, the “BattleScene” is referencing it, and… BOOM ! NRE

adb logcat -s "Unity"

E/SMD     (  271): DCD OFF
I/Unity   (17988): NullReferenceException: Object reference not set to an instance of an object
I/Unity  (17988): at BattleState.ItemDrops () [0x0004e] in D:\UnityProjects\Gquest\Assets\Scripts\BattleState.cs:123
I/Unity  (17988): at BattleState+<Battle>c__Iterator0.MoveNext () [0x0010c] in D:\UnityProjects\Gquest\Assets\Scripts\BattleState.cs:69
I/Unity  (17988): at UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) [0x00028] in /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17

and a few lines above, I can see

Unloading 10 unused Assets to reduce memory usage. Loaded Objects now: 356.

yes the above line is caused by UnloadUnusedAssets(); (I guess)
so maybe it has something to do with that

I’m done

1 Like

Well seems like you pretty much found the source of your exception. Try preventing the desctruction of your singleton gameobject so you can continue using it in other scenes… Have you set it to DontDestroyOnLoad ? (Don’t know if this saves the objects from automatically unloading, but at least it won’t be destroyed if you change scenes)

Yes, I’ve done that

public static ItemDatabase instance;
    void Awake()
    {
        if (instance == null)
        {
            DontDestroyOnLoad(gameObject);
            instance = this;
        }
        else if (instance != null)
            Destroy(gameObject);
    }

doesn’t make sense to me why it can’t access it

Can I implement something like Resources.Load() right before I reference it ?

Unloading 10 unused Assets to reduce memory usage. Loaded Objects now: 356.

Happens anyways. It should not be the reason for your null exception. Show some code, what happens at line 123 of your battleScript?

some code:

public class BattleState : MonoBehaviour {

    //public static BattleState instance;
    //public int i = 5;
    //private void awake()
    //{
    //    if (instance == null)
    //    {
    //        DontDestroyOnLoad(gameObject);
    //        instance = this;
    //    }
    //    else if (instance != null)
    //        Destroy(gameObject);
    //    else if (EnemyControler.instance.enemyToFight.CURRHP <= 0 || player.CURRHP <= 0)
    //        Destroy(gameObject);
      
    //}

    public BaseHero player;
    // public BaseEnemy mob;
    public BaseEnemy enemy;
      
    void Start()
    {
        player = HeroDefine.instance.player;
        print(EnemyControler.instance.enemyToFight.NAME);
        resetHp();
        StartBattle();
      
    }

    private IEnumerator Battle()
    {
        yield return new WaitForSeconds(0f);
        EnemyHpReferences();
        PlayerHpReferences();

    Start:
  
            if (EnemyControler.instance.enemyToFight.CURRHP > 0 && player.CURRHP > 0) // Player attacks
        {
            yield return new WaitForSeconds(0.5f);
            player.Attack();
            EnemyHpReferences();
            if (EnemyControler.instance.enemyToFight.CURRHP <= 0)
            {
                ItemDrops();  
                SceneManager.LoadScene("Victory");
            }

            if (player.CURRHP > 0 && EnemyControler.instance.enemyToFight.CURRHP > 0) // Enemy attacks
            {
                yield return new WaitForSeconds(0.5f);
                player.CURRHP -= EnemyControler.instance.enemyToFight.DMG;
                PlayerHpReferences();
                if (player.CURRHP <= 0)
                {
                    LoadGameOverScene();
                }
                    goto Start;
              
             
            }
        }
    }

    public void StartBattle()
    {
        StartCoroutine(Battle());
    }
    private void IsEnemyDead()
    {
        if (EnemyControler.instance.enemyToFight.CURRHP <= 0)
        {
          
            //SceneManager.LoadScene(7);
        }
    }
    private void ItemDrops()
    {
      
        for (int i = 0; i < EnemyControler.instance.enemyToFight.DROPID.Length; i++)
        {
            int common = Random.Range(0, 3);
            int uncommon = Random.Range(0, 4);
            int rare = Random.Range(0, 5);
            int legend = Random.Range(0, 6);
            int epic = Random.Range(0, 7);

            if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "common")
            {
                print("common "+common);
                if (common ==1 )
                {
                    DroppedItemsList.instance.DroppedItems.Add(ItemDatabase.instance.FetchItemByID
                        (EnemyControler.instance.enemyToFight.DROPID[i]));
                }
            }
            if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "uncommon")
            {
                print("uncommon " + uncommon);
                if (uncommon == 1)
                {
                    DroppedItemsList.instance.DroppedItems.Add(ItemDatabase.instance.FetchItemByID
                        (EnemyControler.instance.enemyToFight.DROPID[i]));
                }
            }
            if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "rare")
            {
                print("rare " + rare);
                if (rare == 1)
                {
                    DroppedItemsList.instance.DroppedItems.Add(ItemDatabase.instance.FetchItemByID
                        (EnemyControler.instance.enemyToFight.DROPID[i]));
                }
            }
            if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "legend")
            {
                print("legend " + legend);
                if (legend == 1)
                {
                    DroppedItemsList.instance.DroppedItems.Add(ItemDatabase.instance.FetchItemByID
                        (EnemyControler.instance.enemyToFight.DROPID[i]));
                }
            }
            if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "epic")
            {
                print("epic " + epic);
                if (epic == 1)
                {
                    DroppedItemsList.instance.DroppedItems.Add(ItemDatabase.instance.FetchItemByID
                        (EnemyControler.instance.enemyToFight.DROPID[i]));
                }
            }
        }
    }
    private void resetHp()
    {
        EnemyControler.instance.enemyToFight.CURRHP = EnemyControler.instance.enemyToFight.MAXHP;
        player.CURRHP = player.MAXHP;
    }
    private void EnemyHpReferences()
    {
        GameObject.Find("EnemyHP").GetComponent<Image>().fillAmount = EnemyControler.instance.enemyToFight.CURRHP / EnemyControler.instance.enemyToFight.MAXHP;
        GameObject.Find("EHpAmount").GetComponent<Text>().text = EnemyControler.instance.enemyToFight.CURRHP.ToString();
    }
    private void PlayerHpReferences()
    {
        GameObject.Find("HeroHP").GetComponent<Image>().fillAmount = player.CURRHP / player.MAXHP;
        GameObject.Find("HHpAmount").GetComponent<Text>().text = player.CURRHP.ToString();
    }
    private void LoadVictoryScene()
    {
        SceneManager.LoadScene(7);
    }
    private void LoadGameOverScene()
    {
        SceneManager.LoadScene("GameOver");
    }
}

changes have been made since, now it’s line 47 which leads to line 90 : when I try to acces ItemDatabase, which is:

public class ItemDatabase : MonoBehaviour {

    public static ItemDatabase instance;
    void Awake()
    {
        if (instance == null)
        {
            DontDestroyOnLoad(gameObject);
            instance = this;
        }
        else if (instance != null)
            Destroy(gameObject);
    }
    private List<Item> database = new List<Item>();
    private JsonData itemData;

    void Start () {
        // open Items.json file, read it, copy it to itemData, close it
        itemData = JsonMapper.ToObject(File.ReadAllText(Application.dataPath + "/StreamingAssets/Items.json"));
        ConstructItemDatabase();
       // database[0].SPRITE = Resources.Load<Sprite>("Sprites/Items" + database[0].SLUG);
        //print(database[0].SPRITE);
    }

     public Item FetchItemByID(int id)
    {
        for (int i = 0; i < database.Count; i++)
            if (database[i].ID == id)
                return database[i];
            return null;
    }

    void ConstructItemDatabase()
    {
        for(int i=0; i < itemData.Count; i++)
        {
            database.Add(new Item((int)itemData[i]["id"], itemData[i]["title"].ToString(),
                                       itemData[i]["slug"].ToString(),(int)itemData[i]["dmg"],
                                       itemData[i]["rarity"].ToString() ));
        }
    }

    private void Update()
    {
       
    }
}
//Defining Items
public class Item
{
    public int ID { get; set; }
    public string TITLE { get; set; }
    public string SLUG { get; set; }
    public int DMG { get; set; }
    public int DEFENCE { get; set; }

    public string RARITY { get; set; }
    public Sprite SPRITE { get; set; }

    public Item(int id, string title, string slug,int dmg, string rarity)
    {
        ID = id;
        TITLE = title;
        SLUG = slug;
        DMG = dmg;
        RARITY = rarity;

        SPRITE = Resources.Load<Sprite>("Sprites/Items/" + slug);

    }
    public Item()
    {
        ID = -1;
       
    }
}

Just tested it building to .exe on win10 and works fine there.
still not on android.

Maybe if I make it a ScriptableObject it might work, but it seems quite challenging to me since I’ve never done any SO yet.

I just tried to achieve the same result by referencing through ItemDatabase prefabs (no singleton)
the problem remains. Editor okay, android isn’t.

Bonus … :

I/Unity   (28723): ItemDatabase isActiveAndEnabled True

I/Unity   (28723):
I/Unity   (28723): ItemDatabase enabled True

I/Unity   (28723):
I/Unity   (28723): ItemDatabase active True

I/Unity   (28723):
I/Unity   (28723): ItemDatabase activeInHierarchy True

I/Unity   (28723):
I/Unity   (28723): ItemDatabase activeSelf True

I/Unity   (28723): (Filename: D Line: 0)
I/Unity   (28723):
I/Unity   (28723): NullReferenceException: Object reference not set to an instance of an object
I/Unity   (28723):   at BattleState.ItemDrops () [0x0004e] in D:\UnityProjects\Gquest\Assets\Scripts\BattleState.cs:90
I/Unity   (28723):   at BattleState+<Battle>c__Iterator0.MoveNext () [0x0019c] in D:\UnityProjects\Gquest\Assets\Scripts\BattleState.cs:47
I/Unity   (28723):   at UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) [0x00028] in /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17
I/Unity   (28723):
I/Unity   (28723): (Filename: D Line: 0)

And just to make sure, but your FetchItemById isn’t returning null by chance?

if I’m doing it right, yes it does, only in the app not in the editor

if(ItemDatabase.instance.FetchItemByID(0)==null)
        print("null");

and another thing I didn’t mention, I have an Inventory scene accessible right after the main scene (which hold ItemDatabase) that has a reference to ItemDatabase, and it works fine.
but in any other scene, it wont, even though I’m calling it the same way.

Ok, so here is what I’m asking

if (ItemDatabase.instance.FetchItemByID(EnemyControler.instance.enemyToFight.DROPID[i]).RARITY == "common")

This line has a lot going on, so it’s possible ItemDatabase isn’t the null value.
The reason I’m asking if FetchItemByID will return null is because if it returns null, you are still trying to access the RARITY field on it. But if it is null, there is no RARITY field, which should also give you a null error.

I also believe if you do a debug.Log(ItemDatabase.instance); it will not return null.

Debug.Log(ItemDatabase.instance);
Debug.Log(ItemDatabase.instance.FetchItemByID(1));

you’re right, only the second statement returns null, doesn’t make sense to me.
so yes, everything that comes after will return null too.

when debugging the game process on the device with monodevelop


Unknown members

Well that message just tells you that you don’t have access to these variables inside your method, which is correct unless they are statics.

Seems like fynesia found your Exception, you need to check for null when you fetch your item, so whenever the id is not found inside your method and it returns null, then you can’t access RARITY property, and therefor have to do other tasks to proceed. You can not access a property from an object that is null. so make sure it is not null, or check for null and perform another action instead.

It seems like the ConstructItemDatabase(); (which loads my items to database from json) method in Start() of ItemDatabase isn’t happening

when I add an item manually in to the FetchItemByID, it works

public Item FetchItemByID(int id)
    {
        database.Add (new Item (0, "myitem", "bronze_sword", 10, "common"));

        for (int i = 0; i < database.Count; i++)
            if (database[i].ID == id)
                return database[i];
            return null;
    }

so either it’s not deserializing the json file or the ConstructItemDatabase(); is not called in start
(you can see these in the above ItemDatabase class code)

I digged a little deeper and it seems like it can’t find the path to my Item.json file

I/Unity   (31496): IsolatedStorageException: Could not find a part of the path "/data/app/com.myCompany.GquestV2-1/base.apk/StreamingAssets/Items.json".

so that’s why FetchItemByID(); is returning null

Well, you should still check for null if you use that method and not simply access the property without knowing if it retrieved/found an item for the given id, except you want that exception to be thrown and cancel your game.

For the resouce exception: How do you load your json file, where is it located? Or have you already solved your problem?

Nah, it didn’t find anything, until I add items without loading them from the json file, like this:
(as opposed to the commented part)

void ConstructItemDatabase()
    {
        database.Add(new Item(0, "Bronze Sword", "bronze_sword", 5, "common"));
        database.Add(new Item(1, "Silver Sword", "silver_sword", 7, "uncommon"));
        database.Add(new Item(2, "Golden Sword", "golden_sword", 10, "rare"));

        //for (int i = 0; i < itemData.Count; i++)
        //{
        //    database.Add(new Item((int)itemData[i]["id"], itemData[i]["title"].ToString(),
        //                               itemData[i]["slug"].ToString(), (int)itemData[i]["dmg"],
        //                               itemData[i]["rarity"].ToString()));
        //}
    }

The json file is located at /StreamingAssets/Items.json
inside the default Assets folder
3017747--225359--json.PNG

and the way I’m loading it :

private JsonData itemData;

    void Start () {
       
        itemData = JsonMapper.ToObject(File.ReadAllText(Application.dataPath + "/StreamingAssets/Items.json"));
        ConstructItemDatabase();
      
    }

That’s not what I meant, of course it won’t find any items if you don’t add them to the list it fetches from. The point here is to make a solid scripted game, and unless you want it to crash and throw an exception, I would always check for null or other conditions to make sure everything runs smooth. If I for example get null somewhere I have to decide what happens, rather I crash the game showing an error message, or keep running and just do other actions. Like loading an image for example, if you can’t load it and it returns null, the game can still run with an placeholder image instead and it won’t affect gameplay. So an exception and crashing the game is not of use here. But if you need to load the player’s units so he can attack others with them, and you get null, it would be better to stop the pvp session or even stop the game so no further errors/hacks can occur.

For your problem it seems like File.ReadAllText isn’t working properly with android. Try this instead:

  • Move your Items.json file to Assets/Resources folder (you can also create a subfolder for “StreamingAssets”) and then load the file with this code:
var json = Resources.Load("<PathToResourcesFolder>/StreamingAssets/Items.json") as TextAsset;
var data = JsonMapper.ToObject(json.text);