iosobjective-cflutterdartnskeyedarchiver

How to read file as String in Flutter that has been written to filesystem using NSKeyedArchiver in iOS?


After updating iOS native app with an app written on Flutter I want to read a file from filesystem on iOS device using Dart. The file I want to read has been previously written to filesystem using this ObjectiveC code:

- (void)setAccount:(FTAccountModel *)account {
    _account = account;
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    path = [path stringByAppendingPathComponent:AccountModelPath];
    if (account) {
        NSArray * array = @[account];
        [array writeToFile:path atomically:YES];
        [NSKeyedArchiver archiveRootObject:array toFile:path];
    }
}

I've tried the following approach in Flutter using path_provider package:

final appDocDir = await getApplicationDocumentsDirectory();
final accountDataFile = File('${appDocDir.path}/$_iosAccountDataFile');
String contents = await accountDataFile.readAsString();
print("contents: $contents");

But I get an error when calling readAsString() method:

FileSystemException: Failed to decode data using encoding 'utf-8', path = '/var/mobile/Containers/Data/Application/FBCB4862-E5EA-4C93-8C2E-3DF1F00A8645/Documents/AccountModel.data'

How to read file on iOS device using Dart and Flutter, that has been written using NSKeyedArchiver?


Solution

  • As of writing this answer, there are no plugins to read the file, that has been previously written to the filesystem using NSKeyedArchiver in iOS. The way to read the file is to write custom platform-specific code.

    So the iOS platform code on Swift will be something like the following:

    private func callGetUserIdFromNativeApp(result: @escaping FlutterResult) {
        var account: FTAccountModel?
        let fm = FileManager.default
        let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
        if (!urls.isEmpty) {
            let file = urls[0].appendingPathComponent("Accounts.data", isDirectory: false)
            
            if (fm.fileExists(atPath: file.path)) {
                if let accountArray: [FTAccountModel] = NSKeyedUnarchiver.unarchiveObject(withFile: file.path) as? [FTAccountModel] {
            
                    if (!accountArray.isEmpty) {
                        account = accountArray[0]
                    }
                }
            }
        }
        
        if let userId: Int = account?.userId {
            result(String(userId))
        } else {
            result(nil)
        }
    }
    

    And the flutter part will use MethodChannel to invoke the native code:

    static const MethodChannel _channel = const MethodChannel("CHANNEL_NAME");
    
    static Future<String> getUserIdFromNativeIos() async {
      try {
        return await _channel.invokeMethod("METHOD_NAME");
      } catch (e){
        return _failedString();
      }
    }