persistent_file_access 0.1.1
persistent_file_access: ^0.1.1 copied to clipboard
Persist and restore access to user-selected files and directories across app restarts.
import 'dart:async';
import 'dart:io';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:persistent_file_access/persistent_file_access.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final access = await PersistentFileAccessSet.open(
'persistent-file-access-example',
);
runApp(PersistentFileAccessExample(access: access));
}
class PersistentFileAccessExample extends StatefulWidget {
final PersistentFileAccessSet access;
const PersistentFileAccessExample({required this.access, super.key});
@override
State<PersistentFileAccessExample> createState() =>
_PersistentFileAccessExampleState();
}
class _PersistentFileAccessExampleState
extends State<PersistentFileAccessExample> {
StreamSubscription<PersistentAccessSnapshot>? _snapshotSubscription;
String? _message;
var _busy = false;
@override
void initState() {
super.initState();
_snapshotSubscription = widget.access.snapshots.listen((final _) {
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
unawaited(_snapshotSubscription?.cancel());
unawaited(_closeAccess());
super.dispose();
}
Future<void> _closeAccess() async {
try {
await widget.access.close();
} catch (error, stackTrace) {
FlutterError.reportError(
FlutterErrorDetails(
exception: error,
stack: stackTrace,
library: 'persistent_file_access example',
context: ErrorDescription('while closing persistent file access'),
),
);
}
}
Future<void> _chooseFile() async {
setState(() => _busy = true);
try {
final selection = await openFile();
if (mounted && selection != null) {
await _addEntity(File(selection.path));
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not choose a file: $error');
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
Future<void> _chooseDirectory() async {
setState(() => _busy = true);
try {
final selectedPath = await getDirectoryPath();
if (mounted && selectedPath != null) {
await _addEntity(Directory(selectedPath));
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not choose a folder: $error');
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
Future<void> _addEntity(final FileSystemEntity entity) async {
try {
final result = await widget.access.addDetailed(entity);
if (mounted) {
setState(() {
_message = switch (result.disposition) {
PersistentFileAccessAddDisposition.added
when result.entry.isAvailable =>
'Access saved.',
PersistentFileAccessAddDisposition.added =>
'Access saved, but the item is currently unavailable.',
PersistentFileAccessAddDisposition.replaced => 'Access updated.',
PersistentFileAccessAddDisposition.recovered => 'Access recovered.',
PersistentFileAccessAddDisposition.alreadyPresent
when !result.entry.isAvailable =>
'The saved item is still unavailable.',
PersistentFileAccessAddDisposition.alreadyPresent =>
'That item is already saved.',
};
});
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not save access: $error');
}
}
}
Future<void> _removeEntry(final PersistentAccessEntry entry) async {
setState(() => _busy = true);
try {
final removed = await widget.access.removeEntry(entry);
if (mounted) {
setState(() {
_message =
removed ? 'Saved access removed.' : 'Item already removed.';
});
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not remove access: $error');
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
Future<void> _retryEntry(final PersistentAccessEntry entry) async {
setState(() => _busy = true);
try {
final retried = await widget.access.retryEntry(entry);
if (mounted) {
setState(() {
_message = switch (retried?.status) {
PersistentAccessStatus.available => 'Access recovered.',
PersistentAccessStatus.temporarilyUnavailable =>
'The item is still unavailable.',
PersistentAccessStatus.invalid => 'The saved access is invalid.',
null => 'Item already removed.',
};
});
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not retry access: $error');
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
Future<void> _inspectEntry(final PersistentAccessEntry entry) async {
setState(() => _busy = true);
try {
final message = await widget.access.withAccess(entry, (
final entity,
) async {
if (entity is File) {
return 'Read ${await entity.length()} bytes.';
}
final count = await (entity as Directory).list().length;
return 'Read $count folder entries.';
});
if (mounted) {
setState(() => _message = message);
}
} catch (error) {
if (mounted) {
setState(() => _message = 'Could not access the item: $error');
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
@override
Widget build(final BuildContext context) {
final entries = widget.access.snapshot.entries;
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Persistent File Access')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Wrap(
spacing: 12,
runSpacing: 12,
children: [
FilledButton.icon(
onPressed: _busy ? null : _chooseFile,
icon: const Icon(Icons.insert_drive_file_outlined),
label: const Text('Choose file'),
),
FilledButton.tonalIcon(
onPressed: _busy ? null : _chooseDirectory,
icon: const Icon(Icons.folder_open_outlined),
label: const Text('Choose folder'),
),
],
),
if (_busy) ...[
const SizedBox(height: 12),
const LinearProgressIndicator(),
],
if (_message case final message?) ...[
const SizedBox(height: 12),
Text(message),
],
const SizedBox(height: 24),
Expanded(
child: ListView(
children: [
for (final entry in entries)
ListTile(
leading: Icon(
entry.kind == PersistentAccessEntityKind.directory
? Icons.folder_outlined
: Icons.insert_drive_file_outlined,
),
title: Text(entry.lastKnownPath ?? 'Unknown path'),
subtitle: Text(entry.status.name),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (entry.isAvailable)
IconButton(
tooltip: 'Access item',
icon: const Icon(Icons.visibility_outlined),
onPressed:
_busy ? null : () => _inspectEntry(entry),
),
if (!entry.isAvailable)
IconButton(
tooltip: 'Retry access',
icon: const Icon(Icons.refresh),
onPressed:
_busy ? null : () => _retryEntry(entry),
),
IconButton(
tooltip: 'Remove saved access',
icon: const Icon(Icons.delete_outline),
onPressed:
_busy ? null : () => _removeEntry(entry),
),
],
),
),
],
),
),
],
),
),
),
);
}
}