path_manager 0.6.1
path_manager: ^0.6.1 copied to clipboard
Plugin for getting commonly used locations on host file system via FFI
Path Manager #
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 #
- Clone the repository and run dependencies setup:
flutter pub get - Generate FFI/JNI bindings:
- For Android (requires
JAVA_HOMEpointing to a JDK):dart run tools/jnigen.dart - For iOS/macOS:
dart run tools/ffigen.dart
- For Android (requires
- 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.