Wednesday, November 23, 2011

AIR iOS- Solving [Apps must follow the iOS Data Storage Guidelines]

IMPORTANT: With Adobe AIR 3.6 beta, Adobe has introduced an API for cacheDirectory. They also included a property called "preventBackup" to prevent the file from being backed up to iCloud. Please check out the beta and Release notes. You can also Discuss the feature feedback here.
Many thanks to Pahup for posting the update here.


There are some AIR for iOS applications which got rejected due to following error.
Rejection: 2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected
Recently with the release of iOS 5, Apple updated the Data Storage Guidelines which are mentioned here(Only for the registered developers). The rejection details are similar to the one mentioned at this post. Just to keep you guys reading this article I am quoting the details from the post.

We found that your app does not follow the iOS Data Storage Guidelines, which is not in compliance with the App Store Review Guidelines.

In particular, we found magazine downloads are not cached appropriately.

The iOS Data Store Guidelines specify:

"1. Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the /Documents directory and will be automatically backed up by iCloud.

2. Data that can be downloaded again or regenerated should be stored in the /Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications.

3. Data that is used only temporarily should be stored in the /tmp directory. Although these files are not backed up to iCloud, remember to delete those files when you are done with them so that they do not continue to consume space on the user’s device."

For example, only content that the user creates using your app, e.g., documents, new files, edits, etc., may be stored in the/Documents directory - and backed up by iCloud. Other content that the user may use within the app cannot be stored in this directory; such content, e.g., preference files, database files, plists, etc., must be stored in the /Library/Caches directory.

Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.

It would be appropriate to revise your app so that you store data as specified in the iOS Data Storage Guidelines.
So, to summarize AIR developers may face this problem if they are using Local Shared Objects or AIR File.applicationStorageDirectory API to write/save the data for their application.

UPDATE: I think above mentioned APIs are used on iOS for varied use-cases ranging from using them as a disk cache to using them for storing App preferences, configuration to saving user's work (Like XMLs, Game state etc.) So before you think that following solution is for you, follow this thumb rule. 


"If you are using above mentioned APIs to store any kind of file that 

  • Can not be re-downloaded or regenerated by your application 
  •  Presence of this stored file is essential for correct functioning your application(For eg. Progress a player has made in your game). 
In such case DO  NOT to use following solution, instead provide additional explanation about the nature of files and align their purposes with the reasons mentioned above."


Let me take one more para to explain why. Caches directory is supposed to be used to store files that are actually used as caches and hence those files, if deleted, should not have any impact on the functioning of your application. iOS takes liberty to

  • Empty the caches directory in case it finds that device is under low disk space.
  • If a user updates to new version of your application. Caches directory is not retained.
All these changes are made so that only the essential and necessary files are backed up to iCloud or iTunes backup. Hence, if current version of your application was uploaded before iOS 5 release. You may face a rejection in your next release (Even if you upload almost the same application.)

There is a quick and clean workaround available for this problem if you were writing to applicationStorageDirectory. Before starting you need to decide the place where the files should go as per the guidelines. I recommend scrolling up again and reading the 3 points in Apple Rejection note, they precisely define the directory you need to use based on the kind of file you are creating.

If you have used Local Shored Objects. The solution is little trickier, because we can not control the location where LSOs are stored. Hence the only way we currently have is to "Not to use LSO" or you may want to create some class like MyLSO which provides the similar functionality using File APIs(which in turn use the following solution)

Following is the code segment that will help you solve the problem. I will, as always, post the code first and then try to explain.
        
 // If the File falls under point 2 of rejection note
 public var cacheDir:File= null;
 // If the new File falls under point 3 of rejection note
 public var tempDir:File=null;
 
 //Initialize the Objects with proper paths.
 var str:String = File.applicationDirectory.nativePath;
 cacheDir= new File(str +"/\.\./Library/Caches");
 tempDir = new File(str +"/\.\./tmp");
Here, instead of using the static Directory paths populated by File class' properties, I am creating global File objects that point to the corresponding directories as per the Apple Data Storage guidelines.Now to create/write to any file you can use code that looks similar to following.

 var fr:FileStream=new FileStream();
 fr.open(cacheDir.resolvePath("myCache.txt"),FileMode.WRITE);
 fr.writeUTFBytes("works");
 fr.close();

I think this is it. You can use the code snippets at proper places in your applications.and I you should be good to go.

Just for completeness, here you can find the Apple Documentation about Data Handling Categories.  

34 comments:

  1. Thanks! This helped me out after two of my apps got rejected for this reason after a small update. Getting them approved now, will let you know if they got accepted.

    ReplyDelete
  2. Do you know if the cache directory should be app specific such as /Library/Caches/com.example.myApp

    ReplyDelete
  3. Ah, never mind. I see in the documentation you linked to that the directory is actually [Application Home]/Library/Caches

    Thanks for the blog post!

    ReplyDelete
  4. There's a fourth point not addressed here:
    https://developer.apple.com/library/ios/#qa/qa1719/_index.html

    Point 4 is Offline Data, which is supposed to go into the "/Library/Private Documents" directory. This dats will not be backed up by iCloud, but also will not be purged by iOS in a low disk space situation (unlike with the "Caches" and temporary directories, which can be a problem in some situations).

    The problem is if you use that "Private Documents" directory you must also mark the file as "do not backup" using native code, so you must use the Native Extension by Jampot:
    http://www.jampot.ie/ane/ane-ios-data-storage-set-donotbackup-attribute-for-ios5-native-extension/

    I think this should be handled natively by Adobe, instead of having to rely in third party code.

    Please fix this in AIR!

    ReplyDelete
    Replies
    1. I meant <Application_Home>/Library/Private Documents

      Delete
    2. Hi OMA;

      You should put this as enhancement request at http://ideas.adobe.com/air , IMO having an actionscript API to set file attributes should be a good idea. API just for do not backup will be very platform specific.

      Delete
    3. Hi Saumitra. Thanks for your answer! I've submited an enhancement request to both the Ideas site and the Bugbase site:

      http://ideas.adobe.com/ct/ct_a_view_idea.bix?c=9D564F43-979A-4E35-AA21-85A61B6AB8DE&idea_id=99760394-2A00-4ED6-A61C-1DC016E204F5

      https://bugbase.adobe.com/index.cfm?event=bug&id=3104319

      For now I'm using Jampot's native extension for the Do Not Backup attribute setting. I hope this can be implemented so this very needed feature for iOS applications (your app might be rejected by Apple otherwise!) doesn't solely rely in a third party native extension.

      Delete
  5. In the case of storing a flag for an in app purchase that has been purchased, where would you recommend this is saved?

    ReplyDelete
    Replies
    1. Not sure if this is needed. First problem I see is that I can be user "A" while purchasing an item and later when actually playing i can be "B" (A and B being iTunes user account).

      I think the preferred way would be to call restore transactions and let iTunes tell what all goods current user has purchased. You may want to cache that info in Caches folder but you should be dependent on it.

      Delete
  6. If I use a sharedObject variable to store data between sessions, I haven't specified any specific location, I just declared the variable. Will this sharedObject data remain or will it be overwritten if I supply an updated version of the app, and the user updates to the new version?

    ReplyDelete
    Replies
    1. It will remain there through updates because LSOs are stored in
      /Application Support whose contents are not purged but these
      files are considered to be essential for your app to function
      properly.

      So remember that whenever you are using LSOs you are adhering to the
      recommended usage of Application Support directory.

      not sure if that answers your question. does it?

      Delete
    2. It does indeed! Thanks! Just the answer I was hoping for=) Can't tell you how glad I am to hear it, because I was worrying about having to change a lot of the code. =)

      Delete
  7. My app is an educational game and includes a feature where the learners progress is tracked over sessions. I therefore need to store the data for each session somewhere. This data could contain the progress results for many sessions ranging over many months so the data needs to be stored permanently.

    I currently do this using Shared Objects but from reading this post I think this could result in my app being rejected. Would this kind of data be allowed to be stored in the documents directory? It is user generated in so much that the app stores their scores for each session. If a user installs the app on another device am I right in my assumption that this data could then be available to the new device as the data is downloaded from the cloud?

    ReplyDelete
  8. Is there anywhere a full example of this that can help? cause i'm new to this. A simple example just moving a box and saving its position and load it every time tha application starts. Thank you!

    ReplyDelete
  9. I was having problems with this as well, I'll be sure to try this out later. Thanks.
    document scanning services

    ReplyDelete
  10. So what if I'm using LSO to save user selection of language? Where should that go?

    Cheers,
    Gorka

    ReplyDelete
    Replies
    1. IMO, You can continue using LSO. If your application has been rejected already, You may try explaining to reviewers that you can not afford losing the language preference and it can not be regenerated by the app(without asking the user again) hence it is essential to be in "Application Support" directory (This is where LSOs are saved on iOS). So that its backed up on iTunes.

      I hope that helps.

      Delete
  11. Thanks I haven't submitted it yet but I will make sure to add this where needed and if it does get rejected I'll try the approach you defined above.

    Tahnks and I'll let you know...

    Cheers,
    Gorka

    ReplyDelete
  12. Hello again, I'm having a bit of trouble when compiling for App Store distribution, the Flash IDE seems to be working (and I confirm some temp files are created) but it never ends - and I actually left the pc on for more than half a day working on this.

    Does this sound normal?

    Thanks,
    Gorka

    ReplyDelete
  13. Gorka - do you find it takes a long time to compile your app for testing too? Mine used to take around 10 minutes or more to compile then I discovered that if you turn off show warnings in the publish settings, it reduced the time to less than a minute.

    ReplyDelete
  14. For testing it takes around half an hour, actually to get the projector working it takes around 20 min. I'm guessing it is because I added a folder with 100+ images, is this a good way or should I add those directly in the library?

    ReplyDelete
  15. Realized what happened.. a Static Class used for language localization strings grew too big.. reading from xml now..

    As soon as I send to App Store will come back and post some feedback.

    Cheers,
    Gorka

    ReplyDelete
  16. Got it! No rejection! Directly to app store...

    I did mention in the version notes what you stated about the necessity of the info in the selected folder.. everything worked smoothly..

    Cheers and thanks for the moral support!
    Gorka

    ReplyDelete
  17. Does this all mean I can do this:

    var str:String = File.applicationDirectory.nativePath;
    docsDir = new File(str +"/\.\./Documents");

    And whatever is saved into this folder is then auto-synced to iCloud?

    If so, could this work as the only server tech needed for saving game progress?

    ReplyDelete
    Replies
    1. Yes, Thats correct.. (If I understood your question correctly :) )

      Delete
  18. Thank-you Sumitra I did what you said but I get this error :
    "SQLError: 'Error #3125: Unable to open the database file.', details:'Connection closed.', operation:'open', detailID:'1001'"

    this is my code :
    sqlConn = new SQLConnection();
    var str:String=File.applicationDirectory.nativePath;
    var cacheDir= new File(str +"/\.\./Library/Caches");
    var dbFile:File=cacheDir.resolvePath("DB.db");
    sqlConn.open(dbFile);
    sqlStat = new SQLStatement();
    sqlStat.sqlConnection=sqlConn;

    if I change it to
    sqlConn = new SQLConnection();
    var str:String=File.applicationDirectory.nativePath;
    var cacheDir= new File(str +"/\.\./Library/Caches");
    var dbFile:File=File.applicationStorageDirectory.resolvePath("DB.db");
    sqlConn.open(dbFile);
    sqlStat = new SQLStatement();
    sqlStat.sqlConnection=sqlConn;

    it works !

    but thats the wrong directory isn't it?
    so what should I do ?
    thank you

    ps. where is you profile picture taken ? looks like an amazing place !

    ReplyDelete
    Replies
    1. Where are you testing this code? On simulator or actual device? This is expected on Simulators.

      The photo is taken in Romania Peles castle. :)

      Delete
  19. Hi mate,
    Thanks for your reply.
    Im testing on the actual devices.
    -Luke

    ReplyDelete
  20. So, if I am using a SQLite Database and have an option for it in the program to be backed up to a local file, where would I want the local file to be backed up to on iOS 5+?

    Thanks, in advance.

    ReplyDelete
  21. @ Scott - Documents Folder.

    Hey Saumitra, I just uploaded another as3 developed app, do you know of any Adobe showcase website where I can show my work?

    I used the same approach as last time, as long as you state in the developer notes that you need the info in the documents folder there shouldn't be any problem..

    Cheers,
    Gorka

    ReplyDelete
  22. All,

    With Adobe AIR 3.6 beta, Adobe has introduced an API for cacheDirectory. They also included a property called "preventBackup" to prevent the file from being backed up to iCloud.

    Please check out the beta @ http://labs.adobe.com/technologies/flashruntimes/air/
    Release notes @ http://labsdownload.adobe.com/pub/labs/flashruntimes/shared/air3-6_flashplayer11-6_releasenotes.pdf

    Discuss the feature feedback @ http://forums.adobe.com/community/labs/flashruntimes/air/

    Thanks
    -Pahup

    ReplyDelete
    Replies
    1. Thanks, I updated the article. So the developers are encouraged to use the newer API when available.

      Delete
  23. Hi,

    Small quest. Can i create a sub directory inside the application directory and then store a data.

    ReplyDelete
  24. Hi I have a data file that has everything i need stored, how do i import that into the Library/Caches folder when the app file is created

    ReplyDelete