First App Update, User Data Lost (was stored in Documents directory)

IosFile IoFilesystems

Ios Problem Overview


My first app update just went live last night and I've gotten a complaint that the update caused the user-created data (some of it) to disappear. I have been able to reproduce the problem, but can't tell why.

In the Documents directory, I have saved one key file which tells me the "title" of all the user's files and their filenames (full path). Then all the user's files are also in the Documents directory.

When the update occurs, the key file is still there (at least, I think it is because the data shows up in the first screen of the app - apps do fully quit and relaunch after updating, right?), but when the user tries to navigate into the actual files, there is no data and any new data the user types in is never saved.

It's acting exactly as it did in debugging when I was accidentally using an invalid filename (with improper characters in it) - it never saved. But with the update, these files had been saved properly in the old version but are somehow failing in the new version.

This is my first app and my first update and I'm quite at a loss here. I've pulled the app from sale for the moment (didn't know you couldn't simply revert to an old version! yikes!) and would deeply appreciate any ideas as to where/how to look for the problem and how to write a GOOD update that won't lose the data. (So far, all I've found is "save data in the Documents directory", which is what I was already doing.)

I do have the original app saved in its own project and I can go back and work from that again. I copied that whole directory when I started working on the update and I do wonder if that could somehow be the problem? I did change the name of the directory that holds all the XCode files and used the Project>Rename function. Could that have this effect somehow?

The app (both original and update) is running on 4.2 and above, if that matters.

RESOLUTION:

I believe I have figured out this problem. Like I said near the beginning of my question, I was saving the FULL PATH of the user files in my key files. Apparently, the full path is NOT guaranteed to be the same after an update (I'm sure this is documented somewhere, but I hadn't run across it).

So, the user files HAD been transported to the update's new Documents directory, but I was looking for them at the old absolute path, i.e. not in my sandbox.

Fixed by putting a loop in app's didFinishLaunching to pull out the offending file of filepaths (THAT, I had always looked for locally and it was still working) and chop them down to just fileNAMES. The Documents path has to be found and added programmatically whenever file operations are performed.

For what it's worth, it was actually good for me that there is no way to revert to a previous binary in the store because re-installing the original version would not have fixed the user's problem, but I would have believed it had, at least initially. I do wish there was a way to have disallowed UPDATES, but still allowed new purchases (since the problem did not affect new purchases). Probably not a common enough situation to warrant it, though.

Ios Solutions


Solution 1 - Ios

Just to shed some more light on this for internet passer-bys. Yes, the OP answered his own question at the very end. You should never store absolute URLs for files in your documents directory and doing so may cause data loss. This is because when you update an app, by changing its version # in the .plist file, iOS creates a new directory for that app with a different hexadecimal name. Now your absolute URL is referencing the incorrect location and won't return the proper file.

You can see this on your own computer before you even deploy to a device by navigating to

~/Library/Application Support/iPhone Simulator/ios_version/Applications/

There you'll see folders with names like:

6AA4B05C-8A38-4469-B7BE-5EA7E9712510 CB43C5F3-D720-49C3-87D4-EBE93FFD428B

Inside those folders will be the standard file system layout for iOS apps. i.e.

Documents Library tmp YourApp.app

By referencing the entire URL, you'll see something like,

~/Library/Application Support/iPhone Simulator/ios_version/Applications/6AA4B05C-8A38-4469-B7BE-5EA7E9712510/Documents/MyVeryImportantUserData.txt

However when you update the app the new URL that the app will try will be:

~/Library/Application Support/iPhone Simulator/ios_version/Applications/CB43C5F3-D720-49C3-87D4-EBE93FFD428B/Documents/MyVeryImportantUserData.txt

which is empty.

Instead you should always make a dynamic reference by only saving the filepath after you've gotten the documents directory filepath prefix.

/**
 Returns the path to the application's Documents directory.
 */
- (NSString *)applicationDocumentsDirectory {
	return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

To the original poster, you could have saved your users lost data by issuing a new update where you modified your code to take the absolute url and trim everything through the documents portion of the string. Then you could prefix the filepath with the proper documents directory url and the app would have found the data.

Solution 2 - Ios

Both the question poster and the first answer were helpful in clarifying what exactly was going on.

So building on their observations, I came to realize that there was (what appears to be) a renaming of the folders within the path that lead up to the Documents folder. This lead me to a solution where I'm saving only the fileNames of the files and creating a utility class for grabbing the current fullPath string of the fileName that had been saved and is now stored in the current Documents folder after the app updates:

#import "StringUtils.h"

@implementation StringUtils

+ (NSString *)getFullDocumentUrl:(NSString *)fileName
{
    return [NSString stringWithFormat:@"%@/%@",[self applicationDocumentsDirectory],fileName];
}

+ (NSString *)applicationDocumentsDirectory
{
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

@end

Solution 3 - Ios

I ran into the same problem. Below is some example code to fix your filenames by iterating through the files in your documents directory and substituting out the filepath information while leaving the filenames. Before executing the last line to change the filenames, I would recommend using NSLog to make sure this is exactly what you want in your updatedName.

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
    NSFileManager *fm = [NSFileManager defaultManager];
    NSArray *filenames = [fm contentsOfDirectoryAtPath:documentsPath error:nil];
    
    //Match on the filepath leading up to docs directory
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^/\\S*Documents/"
                                                                           options:NSRegularExpressionCaseInsensitive
                                                                             error:nil];
    for (NSString *fileName in filenames)
    {
        NSRange fullLength = NSMakeRange(0, [fileName length]);
        //swap out the prepended directory structure with an empty string
        NSString *updatedFileName = [regex stringByReplacingMatchesInString:fileName
                                                                    options:0
                                                                      range:fullLength
                                                               withTemplate:@""];
        NSString *currentName = [NSString stringWithFormat:@"%@/%@",documentsPath,fileName];
        NSString *updatedName = [NSString stringWithFormat:@"%@/%@",documentsPath,updatedFileName];
        [fm moveItemAtPath:currentName toPath:updatedName error:nil];
    }

    

Solution 4 - Ios

To get the current Document Directory path

NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String

Solution 5 - Ios

You can user this function to find new dicrectorty of documents

ex: this is first time you build app and save photo to this url

"file:///var/mobile/Containers/Data/Application/D79E1373-883C-412B-B84E-5C1E47BD0BEC/Documents/Temp/Photo_65650205603482916.jpg"

after that you rebuild app the doc direct is chaged to

file:///var/mobile/Containers/Data/Application/E387590D-B279-4C38-A601-65C6F7BC917D/Documents/

so you can't not upload when use old url

When upload you can do this:

// you have photoURl 

func getURLtoUpload(photoUrl:URL) {
    let urlStr = photoUrl.lastPathComponent
                                  
    let fileURL = self.getDocumentsDirectory().appendingPathComponent("Temp").appendingPathComponent("\(urlStr)")
    // your url to upload here 
    let url = URL(string:fileURL)
}


func getDocumentsDirectory() -> URL {

    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory
}

Solution 6 - Ios

> If you want to save the location of a file persistently, use the bookmark capabilities of NSURL. A bookmark is an opaque data structure, enclosed in an NSData object, that describes the location of a file. Whereas path- and file reference URLs are potentially fragile between launches of your app, a bookmark can usually be used to re-create a URL to a file even in cases where the file was moved or renamed.

https://stackoverflow.com/a/53274341/945906

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionohDearView Question on Stackoverflow
Solution 1 - IosAndrewView Answer on Stackoverflow
Solution 2 - IossqlexceptionView Answer on Stackoverflow
Solution 3 - IosDanWebsterView Answer on Stackoverflow
Solution 4 - IosKrishna KiranaView Answer on Stackoverflow
Solution 5 - IosTuan NguyenView Answer on Stackoverflow
Solution 6 - IosBB9zView Answer on Stackoverflow