path_manager 0.6.1 copy "path_manager: ^0.6.1" to clipboard
path_manager: ^0.6.1 copied to clipboard

Plugin for getting commonly used locations on host file system via FFI

Path Manager #

Pub Version License: MIT

A plugin providing unified access to commonly used host filesystem directories on Android, iOS, and macOS.

This plugin leverages direct native interop using Java Native Interface (JNI) on Android, and Objective-C FFI on iOS and macOS.


Platform & Method Support #

Method Android iOS macOS Description
getTemporaryDirectory() Temporary directory for cache files (subject to OS cleanup).
getApplicationSupportDirectory() Application-created directory for app state, databases, etc.
getApplicationDocumentsDirectory() User-accessible directory for persistent documents/profiles.
getCachesDirectory() Cache directory (persists longer than temporary files).
getApplicationNoBackupDirectory() Resolves a default directory that is excluded from backups.
setApplicationPathIsExcludedFromBackup(...) Programmatically toggles the backup exclusion flag.

Features #

  • Direct interop: Built completely with JNI and FFI interop—no MethodChannels.
  • Tree shakeable: Platform-specific code and dependencies are only compiled and included for their target platform.
  • Dedicated No-Backup Directories: Provides a default folder named __no_backup__ that is automatically marked for exclusion from platform backup systems.
  • Custom exclusions: Mark any arbitrary file or folder on iOS/macOS to be skipped during iCloud/iTunes backups.

Installation #

Add path_manager to your pubspec.yaml dependencies:

dependencies:
  path_manager:
    path: ^0.6.0

Then run:

flutter pub get

Configuration #

iOS & macOS #

No additional setup is required. The plugin programmatically sets the NSURLIsExcludedFromBackupKey resource key using iOS/macOS Foundation APIs when managing exclusions.

Android #

On Android, programmatic runtime backup exclusion on arbitrary filesystem entities is not supported by the OS. Instead, Android backup exclusions are configured statically via resource XML files.

To exclude the default __no_backup__ directory (which resolves to app_support_path/__no_backup__) from Android Auto Backup and Device-to-Device transfer, follow these setup steps:

1. Define Backup Rules (Android 11 and lower)

Create a file named backup_rules.xml in your Android project's resource directory (android/app/src/main/res/xml/backup_rules.xml):

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <!-- Exclude the __no_backup__ subdirectory inside the application files directory -->
    <exclude domain="file" path="__no_backup__" />
</full-backup-content>

2. Define Data Extraction Rules (Android 12 / API 31 and above)

Create a file named data_extraction_rules.xml in your Android project's resource directory (android/app/src/main/res/xml/data_extraction_rules.xml):

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
    <cloud-backup>
        <!-- Exclude from Cloud Backups -->
        <exclude domain="file" path="__no_backup__" />
    </cloud-backup>
    <device-transfer>
        <!-- Exclude from Device-to-Device Transfer -->
        <exclude domain="file" path="__no_backup__" />
    </device-transfer>
</data-extraction-rules>

3. Register XML Rules in AndroidManifest.xml

In your android/app/src/main/AndroidManifest.xml file, link the rules inside the <application> element:

<application
    android:allowBackup="true"
    android:fullBackupContent="@xml/backup_rules"
    android:dataExtractionRules="@xml/data_extraction_rules"
    ... >

Usage #

1. Retrieving Standard Directories #

import 'package:path_manager/path_manager.dart';

// Retrieve paths as standard dart:io Directory instances
final Directory temp = await PathManager.getTemporaryDirectory();
final Directory support = await PathManager.getApplicationSupportDirectory();
final Directory docs = await PathManager.getApplicationDocumentsDirectory();
final Directory caches = await PathManager.getCachesDirectory();

print('Support Dir: ${support.path}');

2. Getting the Dedicated No-Backup Directory #

The getApplicationNoBackupDirectory() method provides a default directory. If the directory does not exist, it is automatically created and (on Apple platforms) marked as excluded from backups.

import 'dart:io';
import 'package:path_manager/path_manager.dart';

try {
  final Directory noBackupDir = await PathManager.getApplicationNoBackupDirectory();
  final File localConfig = File('${noBackupDir.path}/settings.json');
  await localConfig.writeAsString('{"offline_mode": true}');
  print('Saved sensitive offline settings to: ${noBackupDir.path}');
} on BackupExclusionConflictException catch (e) {
  // Occurs if the __no_backup__ folder exists but was manually un-excluded
  print('Conflict detected: ${e.message}');
} on MissingPlatformDirectoryException catch (e) {
  print('Failed to resolve directory: ${e.message}');
}

3. Programmatic Backup Exclusion (iOS & macOS Only) #

You can programmatically exclude individual files or directories on iOS and macOS.

import 'dart:io';
import 'package:path_manager/path_manager.dart';

final Directory docsDir = await PathManager.getApplicationDocumentsDirectory();
final File localDatabase = File('${docsDir.path}/app_db.sqlite');

try {
  // Exclude the SQLite file from iCloud/iTunes backups
  await PathManager.setApplicationPathIsExcludedFromBackup(localDatabase.path, true);
  print('Successfully excluded local database from backups.');
} on UnsupportedError catch (e) {
  // Thrown on Android
  print('Programmatic exclusions are not supported on this platform: ${e.message}');
} on FileSystemException catch (e) {
  // Thrown if the target file/directory does not exist
  print('Filesystem error: ${e.message}');
}

Testing & Mocking #

Since path_manager resolves directories using platform-specific APIs, writing unit tests for code that uses PathManager can be done by providing a mock/fake implementation of PlatformPathManager.

Writing a Mock PlatformPathManager

To mock path resolution in your tests, implement PlatformPathManager and set PlatformPathManager.instance before running your tests. Remember to restore the original instance or tear it down.

import 'package:path_manager/path_manager.dart';
import 'package:test/test.dart';

class FakePlatformPathManager implements PlatformPathManager {
  @override
  Future<String?> getTemporaryPath() async => '/fake/temp';

  @override
  Future<String?> getApplicationSupportPath() async => '/fake/support';

  @override
  Future<String?> getDocumentsPath() async => '/fake/docs';

  @override
  Future<String?> getCachesPath() async => '/fake/caches';

  @override
  Future<String?> getApplicationNoBackupDirectory() async => '/fake/nobackup';

  @override
  Future<void> setApplicationPathIsExcludedFromBackup(String path, bool exclude) async {
    // Handle mock exclusion logic
  }
}

void main() {
  group('My Service Tests', () {
    PlatformPathManager? originalInstance;

    setUp(() {
      try {
        originalInstance = PlatformPathManager.instance;
      } catch (_) {
        originalInstance = null;
      }
      PlatformPathManager.instance = FakePlatformPathManager();
    });

    tearDown(() {
      if (originalInstance != null) {
        PlatformPathManager.instance = originalInstance!;
      }
    });

    test('should load app settings from document path', () async {
      final docs = await PathManager.getApplicationDocumentsDirectory();
      expect(docs.path, equals('/fake/docs'));
      // ... test your code
    });
  });
}

Contributing #

Contributions and suggestions are welcome to this project!

Setting Up For Local Development #

  1. Clone the repository and run dependencies setup:
    flutter pub get
    
  2. Generate FFI/JNI bindings:
    • For Android (requires JAVA_HOME pointing to a JDK):
      dart run tools/jnigen.dart
      
    • For iOS/macOS:
      dart run tools/ffigen.dart
      
  3. Format all files before pushing:
    dart format .
    

Running Tests #

Run the test suite on your development machine (macOS/Linux/Windows):

dart test

License #

This package is licensed under the MIT License. See LICENSE for more details.