Weird Bug with Json save system

I’ve recently been having issues with saving json data. On Unity Editor it works perfectly but once built for android it throw me a null exception from SetData and GetData which is false error to me since this not happening on pc at all.
Even if i try to see what is going on there there’s absolutely no errors in there or at least i think.
It’s literally weird because this work for me for 3 months straight and i don’t changed anything in there since this is the only data i’m saving right now.
I double checek all reference in SetData and GetData they’re basically scriptableObjects which they’re all assigned properly :frowning:

This is the code that i’m using to SetData:

private void SetData()
    {
        dataStats.playerGoods.gold = configurationBasePlayer.gold;
        dataStats.playerGoods.diamond = configurationBasePlayer.diamond;
        dataStats.playerGoods.metal = configurationBasePlayer.metal;
        dataStats.playerGoods.brilliant = configurationBasePlayer.brilliant;

        for (int i = 0; i < heroConf.Length; i++)
        {
            if (heroConf.Length > dataStats.startHeroStats.Count)
            {
                dataStats.startHeroStats.Add(new StartHeroStats());
            }
            if (heroConf.Length == dataStats.startHeroStats.Count)
            {
                dataStats.startHeroStats[i].isUnlocked = heroConf[i].isUnlockedHero;
                dataStats.startHeroStats[i].isSelected = heroConf[i].isSelected;
                dataStats.startHeroStats[i].damage = heroConf[i].damage;
                dataStats.startHeroStats[i].rangeAttack = heroConf[i].rangeAttack;
                dataStats.startHeroStats[i].startHp = heroConf[i].startHealth;
                dataStats.startHeroStats[i].ownSpeedHero = heroConf[i].OwnSpeedNavMesh;
                dataStats.startHeroStats[i].currentXp = heroConf[i].currentXp;
                dataStats.startHeroStats[i].levelHero = heroConf[i].level;
            }
        }
        for (int i = 0; i < turretsConf.Length; i++)
        {
            if (turretsConf.Length > dataStats.turrets.Count)
            {
                dataStats.turrets.Add(new Turrets());
            }
            if (turretsConf.Length == dataStats.turrets.Count)
            {
                dataStats.turrets[i].isTurretPayed = turretsConf[i].isPayed;
                dataStats.turrets[i].isTurretSelected = turretsConf[i].isSelected;
                dataStats.turrets[i].isTurretUnlocked = turretsConf[i].isUnlocked;
            }
        }
        for (int i = 0; i < configurationAbility.Length; i++)
        {
            if (configurationAbility.Length > dataStats.upgrader.Count)
            {
                dataStats.upgrader.Add(new Upgrades());
            }
            if (configurationAbility.Length == dataStats.upgrader.Count)
            {
                dataStats.upgrader[i].unlockedUpgrade = configurationAbility[i].itemsUpgrade[i].currentUpgradeLevel;
                dataStats.upgrader[i].isUnlocked = configurationAbility[i].itemsUpgrade[i].isUnlocked;
            }
        }
        for (int i = 0; i < levelsData.Length; i++)
        {
            if (levelsData.Length > dataStats.levelItemArray.Count)
            {
                dataStats.levelItemArray.Add(new LevelItem());
            }
            if (levelsData.Length == dataStats.levelItemArray.Count)
            {
                dataStats.levelItemArray[0].unlocked = true;
                dataStats.lastUnlockedLevel = CurrentLevel;
                dataStats.levelItemArray = Items;
            }
        }

        dataStats.timeData.lastRewardClaimed = DailyRewards.lastReward;
        dataStats.timeData.availableRewardNow = DailyRewards.availableReward;
        dataStats.timeData.FirstTime = DailyRewards.checkFirstTime;
        dataStats.timeData.LastRewardTime = RewardBox.rewardsTimeInString;
        dataStats.timeData.currentRewardnum = RewardBox.currentRewardIndex;
    }

This is to GetData:

private void GetData()
    {
        configurationBasePlayer.gold = dataStats.playerGoods.gold;
        configurationBasePlayer.diamond = dataStats.playerGoods.diamond;
        configurationBasePlayer.metal = dataStats.playerGoods.metal;
        configurationBasePlayer.brilliant = dataStats.playerGoods.brilliant;

        for (int i = 0; i < heroConf.Length; i++)
        {
            if (heroConf.Length > dataStats.startHeroStats.Count)
            {
                dataStats.startHeroStats.Add(new StartHeroStats());
            }
            if (heroConf.Length == dataStats.startHeroStats.Count)
            {
                heroConf[i].isUnlockedHero = dataStats.startHeroStats[i].isUnlocked;
                heroConf[i].isSelected = dataStats.startHeroStats[i].isSelected;
                heroConf[i].damage = dataStats.startHeroStats[i].damage;
                heroConf[i].rangeAttack = dataStats.startHeroStats[i].rangeAttack;
                heroConf[i].startHealth = dataStats.startHeroStats[i].startHp;
                heroConf[i].OwnSpeedNavMesh = dataStats.startHeroStats[i].ownSpeedHero;
                heroConf[i].currentXp = dataStats.startHeroStats[i].currentXp;
                heroConf[i].level = dataStats.startHeroStats[i].levelHero;
            }
        }
        for (int i = 0; i < turretsConf.Length; i++)
        {
            if (turretsConf.Length > dataStats.turrets.Count)
            {
                dataStats.turrets.Add(new Turrets());
            }
            if (turretsConf.Length == dataStats.turrets.Count)
            {
                turretsConf[i].isPayed = dataStats.turrets[i].isTurretPayed;
                turretsConf[i].isSelected = dataStats.turrets[i].isTurretSelected;
                turretsConf[i].isUnlocked = dataStats.turrets[i].isTurretUnlocked;
            }
        }
        for (int i = 0; i < levelsData.Length; i++)
        {
            if (levelsData.Length > dataStats.levelItemArray.Count)
            {
                dataStats.levelItemArray.Add(new LevelItem());
            }
            if (levelsData.Length == dataStats.levelItemArray.Count)
            {
                dataStats.levelItemArray[0].unlocked = true;
                CurrentLevel = dataStats.lastUnlockedLevel;
                Items = dataStats.levelItemArray;
            }
        }
        for (int i = 0; i < configurationAbility.Length; i++)
        {
            if (configurationAbility.Length > dataStats.upgrader.Count)
            {
                dataStats.upgrader.Add(new Upgrades());
            }
            if (configurationAbility.Length == dataStats.upgrader.Count)
            {
                configurationAbility[i].itemsUpgrade[i].currentUpgradeLevel = dataStats.upgrader[i].unlockedUpgrade;
                configurationAbility[i].itemsUpgrade[i].isUnlocked = dataStats.upgrader[i].isUnlocked;
            }
        }
        DailyRewards.lastReward = dataStats.timeData.lastRewardClaimed;
        DailyRewards.availableReward = dataStats.timeData.availableRewardNow;
        DailyRewards.checkFirstTime = dataStats.timeData.FirstTime;
        RewardBox.rewardsTimeInString = dataStats.timeData.LastRewardTime;
        RewardBox.currentRewardIndex = dataStats.timeData.currentRewardnum;
    }

This is how i save data to json:

public void SaveJsonLocal()
    {
        DirPath = Path.Combine(Application.persistentDataPath, FileNameJson);
        if (!Directory.Exists(DirPath))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(DirPath));
        }
        SetData();

        if (useNewtonsoftJson)
        {
            mSavedGameFileContent = JsonConvert.SerializeObject(dataStats);
        }
        else
            mSavedGameFileContent = JsonUtility.ToJson(dataStats);

        if (useEncryption)
        {
            mSavedGameFileContent = protectorJsonFiles.Encrypts(mSavedGameFileContent);
        }

        using FileStream streamFile = new FileStream(DirPath, FileMode.Create, FileAccess.Write);
        using StreamWriter sr = new StreamWriter(streamFile);
        sr.Write(mSavedGameFileContent);
        sr.Flush();
        sr.Close();
        streamFile.Close();
    }

This is how i load data:

public void LoadJsonLocal()
    {
        if (File.Exists(DirPath))
        {
            using FileStream fileStream = new FileStream(DirPath, FileMode.Open, FileAccess.Read);
            using StreamReader sr = new StreamReader(fileStream);
            mSavedGameFileContent = sr.ReadToEnd();
            sr.Close();
            fileStream.Close();

            Debug.Log("Json exist and readed successfully");
            if (useEncryption)
            {
                mSavedGameFileContent = protectorJsonFiles.Decrypts(mSavedGameFileContent);
            }
            if (useNewtonsoftJson)
            {
                dataStats = JsonConvert.DeserializeObject<GameDataStorage>(mSavedGameFileContent);
            }
            else
            {
                dataStats = JsonUtility.FromJson<GameDataStorage>(mSavedGameFileContent);
            }

            GetData();
        }
        else
        {
            SaveJsonLocal();
        }
    }

First thing to do is to display the json strings when loading and saving. mSavedGameFileContent in your case.

Hey it displaying it correctly:

{"playerGoods":{"gold":33450,"diamond":15,"metal":48450,"brilliant":505},"startHeroStats":[{"levelHero":1,"currentXp":0.0,"isUnlocked":true,"isSelected":true,"damage":1.5,"ownSpeedHero":4.0,"rangeAttack":10.0,"startHp":154.0},{"levelHero":1,"currentXp":0.0,"isUnlocked":false,"isSelected":false,"damage":1.93,"ownSpeedHero":2.5,"rangeAttack":10.5,"startHp":205.0},{"levelHero":1,"currentXp":0.0,"isUnlocked":true,"isSelected":false,"damage":4.5,"ownSpeedHero":5.0,"rangeAttack":3.75,"startHp":512.0},{"levelHero":1,"currentXp":0.0,"isUnlocked":true,"isSelected":false,"damage":7.5,"ownSpeedHero":3.0,"rangeAttack":3.75,"startHp":304.0},{"levelHero":1,"currentXp":0.0,"isUnlocked":false,"isSelected":false,"damage":4.5,"ownSpeedHero":3.5,"rangeAttack":2.5,"startHp":425.0}],"turrets":[{"isTurretUnlocked":true,"isTurretPayed":false,"isTurretSelected":true},{"isTurretUnlocked":true,"isTurretPayed":false,"isTurretSelected":true},{"isTurretUnlocked":true,"isTurretPayed":false,"isTurretSelected":true},{"isTurretUnlocked":true,"isTurretPayed":false,"isTurretSelected":true},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false},{"isTurretUnlocked":false,"isTurretPayed":false,"isTurretSelected":false}],"lastUnlockedLevel":0,"levelItemArray":[{"unlocked":true,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0},{"unlocked":false,"starAchieved":0}],"upgrader":[{"unlockedUpgrade":0,"isUnlocked":true},{"unlockedUpgrade":0,"isUnlocked":false},{"unlockedUpgrade":0,"isUnlocked":false}],"timeData":{"lastRewardClaimed":1,"availableRewardNow":0,"FirstTime":"01/05/2023 15:07:19","LastRewardTime":"02/05/2023 12:54:24","currentRewardnum":9},"timeBuilder":{"timeStart":"02/05/2023 13:35:36","timeEnd":"02/05/2023 13:36:36"}}

My guess is that since you use NewtonSoft in one case and not in the other, one of the two is broken. Wether or not you use NewtonSoft should go in a #define directive then using #if rather than coding like that because you might need some specific attributes.

I tried both variant that’s why i set boolean for that JsonUtility and Newtonsoft work fine on Unity.
Already tried to define directive and set #if … even disabled stripping code and set to minimal in case. Then tried to debug every line of SetData and GetData no issues on Unity Editor on Android both of them SetData and GetData result in Null reference exception which is false in my opinion. Then I tried to set JsonProperty for Newtonsoft usage.
Well result is the same… I have no idea of what is going on.
I tried to save only 2 integers works fine on Unity Editor on Android it throwing null reference exception which is impossible because I’m setting this integers inside the same script like this:

public class SaveData: MonoBehaviour
{


void SetData()
{
      DataStore data = new DataStore
{
  data.lives = 15,
  data.goods = 50
};
}
}

[Serializable]
public class DataStore
{
    public int lives;
    public int goods;
}

This code won’t do. Try this instead:

            DataStore data = new DataStore
            {
                lives = 15,
                goods = 50
            };

VisualStudio should have flagged the errors though, it’s weird.

Tried. Still showing null reference exception on SetData bringing me to DataStore data = new DataStore line which make no sense.
Tried to use Try and catch still showing the same issues on android but in UnityEditor never reach catch so basically try method work as expected

However, since you have lines like this:

dataStats.timeData.lastRewardClaimed = DailyRewards.lastReward;

You can figure out which member is null by checking both sides:

if (dataStats == null) {
    Debug.Log("dataStats is null") ;

}
else if (dataStats.timeData == null) {
    Debug.Log("dataStats.timeData is null") ;
}
if (DailyRewards == null) {
    Debug.Log("DailyRewards i null") ;
}

This is just an example, you only need to test the line that throws the exception.

I’ve had some weird errors like this because I was using stuff before initializing localization. One of your members is a getter maybe?

Building your addressables might help.

I never use get set while I’m saving data to Json. I always try to avoid that.

Thank you for trying to help me out with this. I’m gonna test it right now.

This is basically a static variables which DailyRewards class is a singleton so I’m not destryoing this class since i need to access it through all scenes in some scenario. The same as SaveSystem which is another class where I’m keeping SetData/GetData and save json logic as I mentioned previously which is a singleton as well.

Tried on both Android and Unity Editor I’m not getting none of them as a null reference but on Android even if they’re not null it keep saying that SetData and GetData null reference exception… start from this line

which is a scriptable object that is assigned properly to saveSystem class :frowning:

Don’t just try on editor, make a PC build. Aside from rebuilding addressables you can also check if the constructor ends correctly.

I can’t make a PC build since my game is for Android and have many of dlls which is for android only… I did build it on Android and tried it on Unity Editor

On both I’m receiving back dataStats which i revert them instead

if(dataStats != null)
{
     Debug.Log("dataStats exists");
}

etc…
DailyRewards exists as well
But if i try to save or load a null reference pop up

Actually this is even more weird if I go to Android folder and search for my game the actual file is saved but on the game it keep throwing null reference exception which is seems to me to be fake error but load is actually not loading and save is not saving

Well this can happen if a constructor crashes I think but don’t quote me on that.

Sorry I was thinking you could help me

Is this guy is actually you?

I have some experience with Unity and Android but not both together. However I did have a bug like yours.

Well you doing great. Thank you I needed actually.
I subscribed to you! :slight_smile: