Search Unity

Can't use json file from StreamingAssets on Android and iOS

Discussion in 'Scripting' started by vorora, May 22, 2017.

  1. vorora

    vorora

    Joined:
    Mar 17, 2015
    Posts:
    34
    Hello, everyone. Now I'm trying to implement localization in my small project by learning from Unity Live Training.

    Unity Live Training:


    It works fine in editor, but when I build and test run on an Android device, it can't find the right path and replace the text with the localized one. I researched a lot on the internet and tried to fix it by myself, but sadly it didn't work. (I have 0 experience on json and streaming assets.) The following is the script in my project. I'm really not sure how to fix it. I'll appreciate it if anyone could help me. Thank you.




    Code (CSharp):
    1.     void LoadLocalizedText (string fileName) {
    2.         localizedTextDictionary = new Dictionary<string, string> ();
    3.         string dataAsJson;
    4.  
    5.         #if UNITY_EDITOR
    6.         string filePath = Path.Combine (Application.streamingAssetsPath, fileName);
    7.  
    8.         #elif UNITY_IOS
    9.         string filePath = Path.Combine (Application.streamingAssetsPath + "/Raw", fileName);
    10.  
    11.         #elif UNITY_ANDROID
    12.         string filePath = Path.Combine ("jar:file://" + Application.streamingAssetsPath + "!assets/", fileName);
    13.  
    14.         #endif
    15.  
    16.         if (File.Exists (filePath)) {
    17.  
    18.             #if  UNITY_EDITOR || UNITY_IOS
    19.             dataAsJson = File.ReadAllText (filePath);
    20.  
    21.             #elif UNITY_ANDROID
    22.             WWW reader = new WWW (filePath);
    23.             while (!reader.isDone) {
    24.             }
    25.             dataAsJson = reader.text;
    26.             #endif
    27.  
    28.             LocalizationData loadedData = JsonUtility.FromJson<LocalizationData> (dataAsJson);
    29.  
    30.             for (int i = 0; i < loadedData.items.Length; i++) {
    31.                 localizedTextDictionary.Add (loadedData.items [i].key, loadedData.items [i].value);
    32.             }
    33.             Debug.Log ("Data loaded, dictionary contains: " + localizedTextDictionary.Count + " entries");
    34.         } else {
    35.             Debug.LogError ("Cannot find file!");
    36.         }
    37.         isReady = true;
    38.     }
     
    Lianyou4949 and DanarKayfi like this.
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    You shouldn't have to add /Raw and jar://file:// to streamingassetspath, it should already include that. (thus the purpose of using streamingassetspath)

    You may even consider putting your json in the Resource folder instead, as this will allow you to read the file with ReadAllText no matter what platform you are on. (just a suggestion, but the above should fix your issue)

    Edit: Correction on this. ReadAllText should be used for the persistantDataPath. Resources would use Resources.Load and load it in as a TextAsset (to which you can toString to get the json string out of).

    You can use log viewer on the asset store (free) https://www.assetstore.unity3d.com/en/#!/content/12047 to view your console on a device. Print out what application.streamingassetspath shows (this will also allow you to see your debugs quickly)
     
    Last edited: May 23, 2017
    Bunny83 and vorora like this.
  3. ra-martins13

    ra-martins13

    Joined:
    Aug 6, 2013
    Posts:
    2
    DanarKayfi likes this.
  4. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
  5. MarKotiKk

    MarKotiKk

    Joined:
    Jul 18, 2017
    Posts:
    1
    Hello !
    Did you solve the problem ? Because I'm trying to do it, but I don't really succeed.
    And I have one more question. Is anyone can help to make a save function of this code?
    Thank you
     
  6. gunjanapps

    gunjanapps

    Joined:
    Sep 2, 2015
    Posts:
    3
    Some how in android System.IO.File.Exists with StreamingAssets folder does not work


    Try it without File.Exists and it worked fine.

    filePath = Path.Combine(Application.streamingAssetsPath + "/", fileName);

    #if UNITY_EDITOR || UNITY_IOS
    dataAsJson = File.ReadAllText(filePath);

    #elif UNITY_ANDROID
    WWW reader = new WWW (filePath);
    while (!reader.isDone) {
    }
    dataAsJson = reader.text;
    #endif
     
    deters, triple_why, nvllap and 2 others like this.
  7. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,736
    System.IO classes can read files on a file system. StreamingAssets on Android is a directory inside APK, which is a zip file. System.IO classes can't read files from inside zip.
    UnityWebRequest and WWW can do that.
     
    limkwon18, TerraUnity, m3m02 and 7 others like this.
  8. ap_shreyas

    ap_shreyas

    Joined:
    Feb 11, 2018
    Posts:
    2
    Hi this is not working for me as well. Can someone pls tell me what is the final solution they got working? I'm trying to get this to work for Android and WebGL.
     
  9. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    As mentioned, use www or UnityWebRequest
     
  10. ap_shreyas

    ap_shreyas

    Joined:
    Feb 11, 2018
    Posts:
    2
    Hello,
    Thanks for the reply. UnityWebRequest seems to be working partially on WebGL(it removes special characters on WebGL but shows all fine in unity game editor?). But its not working on Android. I tried printing the file path and its actually showing correct file path even then its not able to read it? Please take a look at the code below
    Code (CSharp):
    1. IEnumerator LoadLocalizedText(string fileName)
    2.     {
    3.         localizedText = new Dictionary<string, string>();
    4.         string filePath;// = Path.Combine(Application.streamingAssetsPath, fileName);
    5.         filePath = Path.Combine(Application.streamingAssetsPath + "/", fileName);
    6.         string dataAsJson;
    7.         if (filePath.Contains("://") || filePath.Contains(":///"))
    8.         {
    9.             debugText.text += System.Environment.NewLine + filePath;
    10.             UnityEngine.Networking.UnityWebRequest www = UnityEngine.Networking.UnityWebRequest.Get(filePath);
    11.             yield return www.Send();
    12.             dataAsJson = www.downloadHandler.text;
    13.         }
    14.         else
    15.         {
    16.             dataAsJson = File.ReadAllText(filePath);
    17.         }
    18.         LocalizationData loadedData = JsonUtility.FromJson<LocalizationData>(dataAsJson);
    19.  
    20.         for (int i = 0; i < loadedData.items.Length; i++)
    21.         {
    22.             localizedText.Add(loadedData.items[i].key, loadedData.items[i].value);
    23.         }
    24.         isReady = true;
    25.     }
     
  11. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    So, I did a quick test to make sure I wasn't crazy and had no issues. The text printed out from the text file just fine.
    Note a couple of things I did. I used the UNITY_ANDROID platform dependent tags located here https://docs.unity3d.com/Manual/PlatformDependentCompilation.html
    A better option for you vs checking the filepath itself.

    The other thing I did was use log viewer located here https://assetstore.unity.com/packages/tools/log-viewer-12047
    Note it's a bit outdated and will yell at you about a line in the code that references unity webplayer, but you can just delete the line and save the script and it will be fine. You can bring up log viewer on the android device to see errors, debug/print calls, and some other info.

    Code (CSharp):
    1.     IEnumerator testLoad()
    2.     {
    3.         string testPath = Path.Combine(Application.streamingAssetsPath, "testText.txt");
    4.         print(testPath);
    5. #if UNITY_ANDROID
    6.         UnityWebRequest www = UnityWebRequest.Get(testPath);
    7.         yield return www.SendWebRequest();
    8.         if (www.isNetworkError || www.isHttpError)
    9.             Debug.Log(www.error);
    10.         else
    11.             print(www.downloadHandler.text);
    12. #endif
    13.     }
    14.  
     
    ow3n likes this.
  12. tzel

    tzel

    Joined:
    Jan 9, 2017
    Posts:
    1
    Thank youuuu!!! I was getting crazzy trying to open a .json file on android. Its work 100%
     
  13. Turtwiggy

    Turtwiggy

    Joined:
    Jun 18, 2017
    Posts:
    7
    Solved this for me. File.Exists does not work with apk/obb split on android.
     
  14. triple_why

    triple_why

    Joined:
    Jun 9, 2018
    Posts:
    47
    I know this is a bit old, but I wanted to post my code too. It's working on android, ios and win-editor, Unity 2019.4. After reading Json string, I use JsonUtility.FromJson.

    Error checking may be implemented by:
    if (www.isNetworkError || www.isHttpError)
    .

    Btw, code is not in an IEnumerator so loading happens in one frame. This is the reason using
    while (!www.isDone) ;


    Code (CSharp):
    1. string sFilePath = Path.Combine(Application.streamingAssetsPath, "lang_" + sLangIDs[iLangID] + ".json");
    2. string sJson;
    3. if (Application.platform == RuntimePlatform.Android)
    4. {
    5.     UnityWebRequest www = UnityWebRequest.Get(sFilePath);
    6.     www.SendWebRequest();
    7.     while (!www.isDone) ;
    8.     sJson = www.downloadHandler.text;
    9. }
    10. else sJson = File.ReadAllText(sFilePath);
    11.  
     
    vasanrouth, Braza, elfasito and 3 others like this.
  15. unity_FjylhYVUoCiUfg

    unity_FjylhYVUoCiUfg

    Joined:
    Apr 29, 2020
    Posts:
    1
    Triple_Why you sir are a life saver, I have been going around in circles for the last 48 hours trying to figure out why a JSON file is happily being read in the Editor but fails when I throw it into the Build and run it on my test Android device. Because of your code above, I can now read the JSON file.
     
  16. tanjon4530

    tanjon4530

    Joined:
    Apr 29, 2020
    Posts:
    2
    All this 4 hours waisting, finally found the solution, AAAAA Thank you :)
     
  17. Qumatz

    Qumatz

    Joined:
    Oct 28, 2019
    Posts:
    1
    triple_why
    Thank you very much for the piece of code, that you have posted, you have also helped me too much I have been looking for this Answer and this is the one that works 100% .. I can only say! I recommend this code. Thank you very much
     
  18. goodgamershow

    goodgamershow

    Joined:
    Apr 5, 2021
    Posts:
    15
    Hi! I'm quite new to Unity's webrequest and json. In my game I have a .json file, which contains data about what skins player has. Since I'm using (Application.streamingAssetsPath + "/Skins.json") as my path, it doesn't work in Android so I tried to use your code. My question is: where should I put your code? In the start method OR create a new coroutine and run it in the start method OR just simply put your method into my LoadData/SaveData methods?

    Code (CSharp):
    1.  
    2.        
    3. public void LoadData(){
    4.         skins = JsonUtility.FromJson<Skins>(File.ReadAllText(Application.streamingAssetsPath + "/SaveSkins.json"));
    5.     }
    6.     public void SaveData(){
    7.         File.WriteAllText(Application.streamingAssetsPath + "/SaveSkins.json", JsonUtility.ToJson(skins));
    8.     }
    9.     [System.Serializable]
    10.     public class Skins{
    11.         public bool[] isPurchased;
    12.     }
    13. }
    14.  
     
  19. triple_why

    triple_why

    Joined:
    Jun 9, 2018
    Posts:
    47
    Hi! The code I posted is not in an IEnumerator, making it synchronous, so loading happens in one frame. Meaning being in a coroutine or in Start() is ok, but is not necessary. I would load&save when needed, i.e. in your LoadData/SaveData methods.

    But, I was merely loading files by my code. For saving, you cannot use StreamingAssets folder, have to use persistentDataPath. For clarification;

    Application.streamingAssetsPath: Filled with files/folders from StreamingAssets folder at compile time, cannot be changed at runtime. Files/folders in this location can be changed only by app updates. This folder and subfolders are read-only. Multiplatform reading (excluding WebGL) can be done by the code I've posted.

    Application.persistentDataPath: Empty folder by default. Can be populated by files/folders at first launch of the app or at any runtime. Files/folders in this location are not erased by app updates. This folder and subfolders are readable/writable. Multiplatform reading/writing (including WebGL) can be done by using File and Directory classes from System.IO namespace.
     
  20. goodgamershow

    goodgamershow

    Joined:
    Apr 5, 2021
    Posts:
    15
    Thank you for replying! Firstly, could you please check my code below, where I tried to implement your UnityWebRequest code into my Load/Save data methods. I guess I did something incorrectly, because now my "SaveSkins.json"'s structure doesn't change at all(before it was switching isPurchased boolean from false to true).
    Before:

    Code (CSharp):
    1. public void LoadData(){
    2.         skins = JsonUtility.FromJson<Skins>(File.ReadAllText(Application.streamingAssetsPath + "/SaveSkins.json"));
    3.     }
    4. public void SaveData(){
    5.         File.WriteAllText(Application.streamingAssetsPath + "/SaveSkins.json", JsonUtility.ToJson(skins));
    6.     }
    After:

    Code (CSharp):
    1. public Skins skins;
    2.     public void LoadData(){
    3.         string sFilePath = Path.Combine(Application.streamingAssetsPath, "SaveSkins.json");
    4.         string sJson;
    5.         if (Application.platform == RuntimePlatform.Android)
    6.         {
    7.             UnityWebRequest www = UnityWebRequest.Get(sFilePath);
    8.             www.SendWebRequest();
    9.             while (!www.isDone) ;
    10.             sJson = www.downloadHandler.text;
    11.             skins = JsonUtility.FromJson<Skins>(File.ReadAllText(sJson));
    12.         }
    13.         else sJson = File.ReadAllText(sFilePath);
    14.  
    15.     }
    16.     public void SaveData(){
    17.        
    18.         string sFilePath = Path.Combine(Application.streamingAssetsPath, "SaveSkins.json");
    19.         string sJson;
    20.         if (Application.platform == RuntimePlatform.Android)
    21.         {
    22.             UnityWebRequest www = UnityWebRequest.Get(sFilePath);
    23.             www.SendWebRequest();
    24.             while (!www.isDone) ;
    25.             sJson = www.downloadHandler.text;
    26.             File.WriteAllText(sJson, JsonUtility.ToJson(skins));
    27.         }
    28.         else sJson = File.ReadAllText(sFilePath);
    29.     }
    30.     [System.Serializable]
    31.     public class Skins{
    32.         public bool[] isPurchased;
    33.     }
    Secondly, you suggested me to use Application.persistentDataPath for saving. The problem is I am a newbie in using file-related things, so I simply don't know how to create a json file inside persistentDataPath folder with the values from my SaveSkins.json file. Also, how should I detect if it's the very first launch of the game or not? On the web I found info that it could be done by using PlayerPrefs when player first enters the game and set PlayerPrefs.SetInt("firstLaunch", 1).
    Would be great if you could help me with that(I'm already learning from the links you posted in your previous reply, so thank you for that as well!)
     
  21. triple_why

    triple_why

    Joined:
    Jun 9, 2018
    Posts:
    47
    SaveData, at else condition, should use WriteAllText, not ReadAllText. File path should be input to ReadAllText and WriteAllText, not json string. There are other issues as well. See below code.

    Then again, documentation of StreamingAssets clearly states that, "On many platforms, the streaming assets folder location is read-only; you can not modify or write new files there at runtime. Use Application.persistentDataPath for a folder location that is writable." It may have worked at a platform, guessingly editor, but it will definitely fail on other platforms. In addition, when you use persistentDataPath, UnityWebRequest is no more needed. See below code.

    To detect very first launch, you can use File.Exists to check file existence. If it's not there it's very first launch, create an initial file. File.WriteAllText method creates or overwrites the file, so can be used to both create and overwrite. See below code.

    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class Skins{
    4.     public bool[] isPurchased;
    5. }
    6.  
    7. Skins skins;
    8. string sFilePath;
    9.  
    10. void Start()
    11. {
    12.     sFilePath = Path.Combine(Application.persistentDataPath, "SaveSkins.json");
    13.     if (!File.Exists(sFilePath))
    14.     {
    15.         // this is first launch after install, create new data and save to file
    16.         Skins skins = new Skins();
    17.         SaveData();
    18.     }
    19.     else
    20.     {
    21.         // this is not the first launch, load data from file
    22.         LoadData();
    23.     }
    24. }
    25.  
    26. public void SaveData()
    27. {
    28.     string sJson = JsonUtility.ToJson(skins);
    29.     File.WriteAllText(sFilePath, sJson);
    30. }
    31.  
    32. public void LoadData()
    33. {
    34.     string sJson = File.ReadAllText(sFilePath);
    35.     skins = JsonUtility.FromJson<Skins>(sJson);
    36. }
    37.  
    38.  
    If you are using Windows version of Unity editor, SaveSkins.json can be found at below path. You may view/modify it with any text editor, or delete it to simulate a fresh install.
    C:\Users\<username>\AppData\LocalLow\<companyname>\<appname>

    P.S.: You are storing purchased items' data, and json format can be opened and modified with any text editor. So one may modify your json file and own the items for free. If you want to deal with this as well, I recommend saving/loading it -with or without converting to json- in binary form, by using i.e. BinaryWriter/BinaryReader, as described in documentation examples; and preferably encrypting it in a simple way, as described in this article of mine.
     
    goodgamershow likes this.
  22. goodgamershow

    goodgamershow

    Joined:
    Apr 5, 2021
    Posts:
    15
    Thank you very much for helping me. I tested your code on Unity Editor and it works well. Hope it will work on Android/Ios devices as well. But there's one thing that disturbs me: when IF statement in Start() method works on the very first launch, it automatically creates isPurchased array and other needed data by default, i.e I didn't need to assign any text data, which is mind blowing for me. Does assigning any variables(like isPurchased[]) in Skins class automatically transfer these variables as text data into the .json file or what? And again, thank you very much for your great help!
    UPD: I tested it and yeah, when using SaveData it creates all the variables from Skins class with variables that I assigned in Unity and assign it into .json. Never knew it was that easy, because all the time I was writing the data by hand. Thank you very much!!!
     
    Last edited: Jun 9, 2021
  23. triple_why

    triple_why

    Joined:
    Jun 9, 2018
    Posts:
    47
    It will

    Yep. Exposing data from script to inspector by making them public or [Serializable] uses the same underlying mechanism; Unity serialization. So any type which can be seen in the inspector can be serialized and jsoned.

    Unity engine objects, primitive data types, enums, Unity types (like vectors), and arrays/lists of these, are supported. Full list and details can be found in Script Serialization documentation.
     
    goodgamershow likes this.
  24. Braza

    Braza

    Joined:
    Oct 11, 2013
    Posts:
    136
    Thank you! Thi reply still holds in 2022 as the documentation and examples are scarce in this area.