RobertH
RobertH

Reputation: 365

Save and Load Data from Application.persistentDataPath works in Unity, but not on iOS

I'm building an app that needs to save and load user created data to the persistent data path. When I play it in the Unity Editor it works perfectly, but when I try it on iOS, I get this error:

Uploading Crash Report
NullReferenceException: Object reference not set to an instance of an object.
  at CustomModeScript.LoadSavedModes () [0x00000] in <00000000000000000000000000000000>:0 
  at CustomModeScript.Initialize (System.String fromPanel) [0x00000] in <00000000000000000000000000000000>:0 
  at MainSettingsPanelScript.OnControlModes () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.Events.UnityAction.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1].Invoke (T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchEvents () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0 
UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchPress(PointerEventData, Boolean, Boolean)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchEvents()
UnityEngine.EventSystems.StandaloneInputModule:Process()

It used to be an ioexception: Sharing violation on path error, but now it's the error above after I refactored my Load function. The issue is currently happening when I try to load the data (I can't really tell if it is saving or not)

Here is the functions for Save and Load (Retrieve)

public void SaveModes()
    {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "/UserCustomModes.jl");
        List<Mode> data = new List<Mode>();

        data = savedModes;
        
        bf.Serialize(file , data);
        file.Close();
    }

    // Retrieve saved modes if they exist 
    public List<Mode> RetrieveSavedModes()
    {
        if(File.Exists(Application.persistentDataPath + "/UserCustomModes.jl"))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/UserCustomModes.jl", FileMode.Open);
            List<Mode> storedModesFile = bf.Deserialize(file) as List<Mode>;

            savedModes = storedModesFile;
            file.Close();
        }

        return savedModes;
    }

Here is the code that is calling the load function:

// Populate mode view with saved modes
    private void PopulateModeView()
    {
        ClearCustomModes();
        Debug.Log("Loading Modes: " + Manager.savedModes.Count);
        if (Manager.savedModes.Count > 0)
        {
            //Debug.Log("More than 0 modes present");
            var index = 0;
            foreach (var savedMode in Manager.savedModes)
            {
                var modeItem = Instantiate(customModeItemPrefab, customModeSectionTransform);
                modeItem.GetComponent<CustomModeItemScript>().Initialize(savedMode, index, this, mainSettingsPanelScript);
                generatedModeButtons.Add(modeItem);
            }
        }
    }

Here is the LoadSavedModes Function:

private void LoadSavedModes()
    {
        RemoveAllModeButtons();
        Manager.savedModes = Manager.RetrieveSavedModes();
        Debug.Log("[UNITY] Retrieve Modes Count: " + Manager.savedModes.Count);
        PopulateModeView();
    }

Thank you in advance!

Upvotes: 3

Views: 14299

Answers (1)

TEEBQNE
TEEBQNE

Reputation: 6275

Here is my personal templated Save/Load script. I know it works for all platforms currently listed in the function GetFilePath.

using UnityEngine;
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections.Generic;

/// <summary>
/// Saves, loads and deletes all data in the game
/// </summary>
/// <typeparam name="T"></typeparam>
public static class SaveLoad<T>
{
    /// <summary>
    /// Save data to a file (overwrite completely)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="data"></param>
    /// <param name="folder"></param>
    /// <param name="file"></param>
    public static void Save(T data, string folder, string file)
    {
        // get the data path of this save data
        string dataPath = GetFilePath(folder, file);

        string jsonData = JsonUtility.ToJson(data, true);
        byte[] byteData;
        
        byteData = Encoding.ASCII.GetBytes(jsonData);

        // create the file in the path if it doesn't exist
        // if the file path or name does not exist, return the default SO
        if (!Directory.Exists(Path.GetDirectoryName(dataPath)))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(dataPath));
        }

        // attempt to save here data
        try
        {
            // save datahere
            File.WriteAllBytes(dataPath, byteData);
            Debug.Log("Save data to: " + dataPath);
        }
        catch (Exception e)
        {
            // write out error here
            Debug.LogError("Failed to save data to: " + dataPath);
            Debug.LogError("Error " + e.Message);
        }
    }
    
    /// <summary>
    /// Load all data at a specified file and folder location
    /// </summary>
    /// <param name="folder"></param>
    /// <param name="file"></param>
    /// <returns></returns>
    public static T Load(string folder, string file)
    {
        // get the data path of this save data
        string dataPath = GetFilePath(folder, file);

        // if the file path or name does not exist, return the default SO
        if (!Directory.Exists(Path.GetDirectoryName(dataPath)))
        {
            Debug.LogWarning("File or path does not exist! " + dataPath);
            return default(T);
        }

        // load in the save data as byte array
        byte[] jsonDataAsBytes = null;

        try
        {
            jsonDataAsBytes = File.ReadAllBytes(dataPath);
            Debug.Log("<color=green>Loaded all data from: </color>" + dataPath);
        }
        catch (Exception e)
        {
            Debug.LogWarning("Failed to load data from: " + dataPath);
            Debug.LogWarning("Error: " + e.Message);
            return default(T);
        }

        if (jsonDataAsBytes == null)
            return default(T);

        // convert the byte array to json
        string jsonData;

        // convert the byte array to json
        jsonData = Encoding.ASCII.GetString(jsonDataAsBytes);

        // convert to the specified object type
        T returnedData = JsonUtility.FromJson<T>(jsonData);

        // return the casted json object to use
        return (T)Convert.ChangeType(returnedData, typeof(T));
    }
    
    /// <summary>
    /// Create file path for where a file is stored on the specific platform given a folder name and file name
    /// </summary>
    /// <param name="FolderName"></param>
    /// <param name="FileName"></param>
    /// <returns></returns>
    private static string GetFilePath(string FolderName, string FileName = "")
    {
        string filePath;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        // mac
        filePath = Path.Combine(Application.streamingAssetsPath, ("data/" + FolderName));

        if (FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        // windows
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_ANDROID
        // android
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_IOS
        // ios
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#endif
        return filePath;
    }
}

Here is an example to use the script

[System.Serializable]
public class TestClass
{
    public TestClass()
    {
         testData = new List<int>();
    }

    public List<int> testData = new List<int>();
}

public class TestMono : MonoBehaviour
{
     private string testFolder = "folder";
     private string testFile = "file";

     private List<int> myList = new List<int>();

     private void Awake()
     {
          // the ?? is a null comparison so if the value returns null, it generates new data
          TestClass loadedData = SaveLoad<TestClass>.Load(folder, file) ?? new TestClass();

          myList = loadedData.testData;
     }

     private void SaveData()
     {
         TestClass dataToSave = new TestClass
         {
              testData = myList
         };
          
         SaveLoad<TestClass>.Save(dataToSave, folder, file);
     }
}

The above snippet is untested but is the general use-case for the Save/Load script.

Upvotes: 4

Related Questions