Flutter目录书签管理插件directory_bookmarks的使用

Flutter目录书签管理插件directory_bookmarks的使用

目录书签

directory_bookmarks 是一个用于跨平台目录书签管理和安全文件操作的 Flutter 插件。此插件提供了一致的 API 来处理目录访问和文件操作,并且特别支持平台特定的安全特性。

平台支持

平台 状态 实现细节
macOS 支持 安全范围书签用于持久目录访问
Android 开发中 存储访问框架(部分实现)
iOS 计划中 将使用安全范围书签
Windows 计划中 未来实现
Linux 计划中 未来实现

注意:目前,该包主要集中在 macOS 支持上。在其他平台上使用会导致不支持平台错误。我们正在积极开发扩展平台支持。

特性

  • 安全目录访问:平台特定的安全目录访问机制
    • macOS:安全范围书签
    • Android:存储访问框架
  • 目录书签:保存并恢复对用户选择目录的访问
  • 文件操作:在书签目录中读取、写入和列出文件
  • 持久访问:跨应用重启保持对目录的访问
  • 权限管理:内置权限管理和验证
  • 资源管理:自动清理系统资源

入门指南

将插件添加到你的 pubspec.yaml 文件:

dependencies:
  directory_bookmarks: ^0.1.0

平台特定设置

macOS (支持)

  1. 在你的 macOS 应用中启用 App Sandbox 和所需的权限。在你的权限文件中添加以下内容:
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
  1. 在你的 AppDelegate.swift 中注册插件:
import directory_bookmarks

class AppDelegate: FlutterAppDelegate {
  override func applicationDidFinishLaunching(_ notification: Notification) {
    guard let mainWindow = mainFlutterWindow else { return }
    guard let controller = mainWindow.contentViewController as? FlutterViewController else { return }
    DirectoryBookmarksPlugin.register(with: controller.registrar(forPlugin: "DirectoryBookmarksPlugin"))
    super.applicationDidFinishLaunching(notification)
  }
}

Android (开发中)

注意:Android 支持目前处于开发阶段。实现是部分的,可能不会按预期工作。

在你的 AndroidManifest.xml 中添加以下权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

其他平台 (计划中)

iOS、Windows 和 Linux 的支持正在计划中。目前在这些平台上使用此插件会导致 UnsupportedError

API 参考

目录书签操作

  • saveBookmark(String directoryPath, {Map<String, dynamic>? metadata}): 保存目录书签,可选元数据
  • resolveBookmark(): 解析并返回当前目录书签信息

文件操作

  • saveFile(String fileName, List<int> data): 将原始数据保存到书签目录中的文件
  • saveStringToFile(String fileName, String content): 将文本内容保存到文件
  • saveBytesToFile(String fileName, Uint8List bytes): 将二进制数据保存到文件
  • readFile(String fileName): 从文件中读取原始数据
  • readStringFromFile(String fileName): 从文件中读取文本内容
  • readBytesFromFile(String fileName): 从文件中读取二进制数据
  • listFiles(): 列出书签目录中的所有文件

目录操作

// 在书签位置创建目录
final success = await DirectoryBookmarkHandler.createDirectory('images');

// 创建嵌套目录
await DirectoryBookmarkHandler.createDirectory('images/thumbnails');

// 在子目录中保存文件(如果不存在则创建)
final imageBytes = await File('path/to/image.jpg').readAsBytes();
await DirectoryBookmarkHandler.saveBytesToFileInPath(
  'images/thumbnails/image1.jpg',
  imageBytes,
);

// 在子目录中保存文本文件
await DirectoryBookmarkHandler.saveStringToFileInPath(
  'docs/notes/note1.txt',
  'Hello, World!',
);

// 列出特定子目录中的文件
final imageFiles = await DirectoryBookmarkHandler.listFilesInPath('images');
if (imageFiles != null) {
  for (final file in imageFiles) {
    print('Image file: $file');
  }
}

示例:带子目录的图像库

class ImageGalleryWithFolders extends StatefulWidget {
  const ImageGalleryWithFolders({super.key});

  @override
  State<ImageGalleryWithFolders> createState() => _ImageGalleryWithFoldersState();
}

class _ImageGalleryWithFoldersState extends State<ImageGalleryWithFolders> {
  String _currentPath = '';
  List<String> _items = [];
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    _loadCurrentDirectory();
  }

  Future<void> _loadCurrentDirectory() async {
    try {
      final files = await DirectoryBookmarkHandler.listFilesInPath(_currentPath);
      if (files != null) {
        setState(() {
          _items = files;
          _errorMessage = null;
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Error loading directory: $e';
      });
    }
  }

  Future<void> _createNewFolder(String folderName) async {
    try {
      final path = _currentPath.isEmpty 
          ? folderName 
          : '$_currentPath/$folderName';
      
      final success = await DirectoryBookmarkHandler.createDirectory(path);
      if (success) {
        _loadCurrentDirectory();
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Error creating folder: $e';
      });
    }
  }

  Future<void> _navigateToFolder(String folderName) async {
    setState(() {
      _currentPath = _currentPath.isEmpty 
          ? folderName 
          : '$_currentPath/$folderName';
    });
    _loadCurrentDirectory();
  }

  Future<void> _navigateUp() async {
    if (_currentPath.isEmpty) return;
    
    final lastSlash = _currentPath.lastIndexOf('/');
    setState(() {
      _currentPath = lastSlash == -1 ? '' : _currentPath.substring(0, lastSlash);
    });
    _loadCurrentDirectory();
  }

  @override
  Widget build(BuildContext context) {
    if (_errorMessage != null) {
      return Center(child: Text(_errorMessage!));
    }

    return Column(
      children: [
        // 目录导航栏
        Container(
          padding: const EdgeInsets.all(8),
          child: Row(
            children: [
              IconButton(
                icon: const Icon(Icons.arrow_upward),
                onPressed: _currentPath.isEmpty ? null : _navigateUp,
              ),
              Text('Current path: ${_currentPath.isEmpty ? '/' : _currentPath}'),
              IconButton(
                icon: const Icon(Icons.create_new_folder),
                onPressed: () async {
                  final name = await showDialog<String>(
                    context: context,
                    builder: (context) => NewFolderDialog(),
                  );
                  if (name != null) {
                    _createNewFolder(name);
                  }
                },
              ),
            ],
          ),
        ),
        // 目录内容
        Expanded(
          child: ListView.builder(
            itemCount: _items.length,
            itemBuilder: (context, index) {
              final item = _items[index];
              final isDirectory = !item.contains('.');
              
              return ListTile(
                leading: Icon(isDirectory ? Icons.folder : Icons.image),
                title: Text(item),
                onTap: isDirectory 
                    ? () => _navigateToFolder(item)
                    : null,
              );
            },
          ),
        ),
      ],
    );
  }
}

class NewFolderDialog extends StatelessWidget {
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Create New Folder'),
      content: TextField(
        controller: controller,
        decoration: const InputDecoration(
          labelText: 'Folder Name',
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, controller.text),
          child: const Text('Create'),
        ),
      ],
    );
  }
}

写入文件

// 保存任何类型的文件(基本方法)
final fileData = await File('path/to/source/file').readAsBytes();
final success = await DirectoryBookmarkHandler.saveFile(
  'destination.file',
  fileData,
);

// 保存文本文件
final textSuccess = await DirectoryBookmarkHandler.saveStringToFile(
  'example.txt',
  'Hello, World!',
);

// 保存二进制文件(如图像、PDF等)
final imageBytes = await File('path/to/image.jpg').readAsBytes();
final imageSuccess = await DirectoryBookmarkHandler.saveBytesToFile(
  'image.jpg',
  imageBytes,
);

读取文件

// 读取任何类型的文件(基本方法)
final fileData = await DirectoryBookmarkHandler.readFile('myfile.dat');
if (fileData != null) {
  // 使用文件数据(List<int>)
}

// 读取文本文件
final textContent = await DirectoryBookmarkHandler.readStringFromFile('example.txt');
if (textContent != null) {
  print('File content: $textContent');
}

// 读取二进制文件
final imageBytes = await DirectoryBookmarkHandler.readBytesFromFile('image.jpg');
if (imageBytes != null) {
  // 使用图像字节(Uint8List)
  final image = Image.memory(imageBytes);
}

示例:将文件复制到书签目录

import 'package:directory_bookmarks/directory_bookmarks.dart';
import 'package:file_picker/file_picker.dart';

Future<void> copyFileToBookmark() async {
  try {
    // 选择要复制的文件
    final result = await FilePicker.platform.pickFiles();
    if (result == null) return;

    final file = result.files.first;
    if (file.bytes == null) return;

    // 保存到书签目录
    final success = await DirectoryBookmarkHandler.saveBytesToFile(
      file.name,
      file.bytes!,
    );

    if (success) {
      print('File copied successfully');
    } else {
      print('Failed to copy file');
    }
  } catch (e) {
    print('Error copying file: $e');
  }
}

权限管理

  • hasWritePermission(): 检查书签目录是否授予写权限
  • requestWritePermission(): 请求书签目录的写权限

使用

基本示例

import 'package:directory_bookmarks/directory_bookmarks.dart';

void main() async {
  // 检查平台支持
  if (!(defaultTargetPlatform == TargetPlatform.macOS ||
        defaultTargetPlatform == TargetPlatform.android)) {
    print('Platform not supported');
    return;
  }

  try {
    // 选择并书签一个目录
    final path = await FilePicker.platform.getDirectoryPath(
      dialogTitle: 'Select a directory to bookmark',
    );
    
    if (path == null) {
      print('No directory selected');
      return;
    }

    // 保存书签
    final success = await DirectoryBookmarkHandler.saveBookmark(
      path,
      metadata: {'lastAccessed': DateTime.now().toIso8601String()},
    );

    if (success) {
      print('Directory bookmarked successfully');
    } else {
      print('Failed to bookmark directory');
      return;
    }

    // 解析书签
    final bookmark = await DirectoryBookmarkHandler.resolveBookmark();
    if (bookmark != null) {
      print('Bookmarked directory: ${bookmark.path}');
      
      // 检查写权限
      final hasPermission = await DirectoryBookmarkHandler.hasWritePermission();
      if (!hasPermission) {
        print('No write permission');
        return;
      }

      // 列出文件
      final files = await DirectoryBookmarkHandler.listFiles();
      if (files != null) {
        print('Files in directory: $files');
      }

      // 写文件
      final writeSuccess = await DirectoryBookmarkHandler.saveStringToFile(
        'test.txt',
        'Hello, World!',
      );
      if (writeSuccess) {
        print('File written successfully');
      }

      // 读文件
      final content = await DirectoryBookmarkHandler.readStringFromFile('test.txt');
      if (content != null) {
        print('File content: $content');
      }
    }
  } catch (e) {
    print('Error: $e');
  }
}

错误处理

插件包括全面的错误处理:

try {
  // 首先检查平台支持
  if (!(defaultTargetPlatform == TargetPlatform.macOS ||
        defaultTargetPlatform == TargetPlatform.android)) {
    print('Platform ${defaultTargetPlatform.name} is not supported yet');
    return;
  }

  // 尝试解析现有的书签
  final bookmark = await DirectoryBookmarkHandler.resolveBookmark();
  if (bookmark == null) {
    print('No bookmark found, selecting new directory...');
    
    final path = await FilePicker.platform.getDirectoryPath();
    if (path == null) {
      print('No directory selected');
      return;
    }
    
    final success = await DirectoryBookmarkHandler.saveBookmark(path);
    if (!success) {
      print('Failed to bookmark directory');
      return;
    }
  }

  // 检查权限
  if (!await DirectoryBookmarkHandler.hasWritePermission()) {
    print('No write permission for bookmarked directory');
    return;
  }

  // 执行文件操作...
} on PlatformException catch (e) {
  print('Platform error: ${e.message}');
} catch (e) {
  print('Unexpected error: $e');
}

更多关于Flutter目录书签管理插件directory_bookmarks的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter目录书签管理插件directory_bookmarks的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用directory_bookmarks插件来管理目录书签的一个基本示例。请注意,directory_bookmarks插件可能是一个虚构的插件名,因为Flutter官方插件集合中并没有直接名为directory_bookmarks的插件。但我会基于一个假设的API来展示如何使用它。

首先,确保你已经将directory_bookmarks插件添加到你的pubspec.yaml文件中:

dependencies:
  flutter:
    sdk: flutter
  directory_bookmarks: ^x.y.z  # 替换为实际的版本号

然后,运行flutter pub get来安装插件。

接下来,在你的Flutter项目中,你可以按照以下步骤使用directory_bookmarks插件来管理目录书签。

  1. 导入插件

在你的Dart文件中导入插件:

import 'package:directory_bookmarks/directory_bookmarks.dart';
  1. 请求权限(如果需要)

如果你的应用需要访问文件系统,你可能需要请求相应的权限。这通常涉及Android和iOS的权限管理。

// Android权限请求示例(在实际应用中,你可能需要使用更复杂的权限管理逻辑)
if (Platform.isAndroid) {
  // 检查并请求权限(这里只是一个示例,实际权限名称可能会有所不同)
  Permission.storage.request().then((status) {
    if (!status.isGranted) {
      // 处理权限被拒绝的情况
    }
  });
}
  1. 使用插件API

以下是一个使用directory_bookmarks插件来添加、获取和删除书签的示例代码:

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<String> bookmarks = [];

  @override
  void initState() {
    super.initState();
    // 获取现有的书签
    _getBookmarks();
  }

  Future<void> _addBookmark(String path) async {
    try {
      await DirectoryBookmarks.addBookmark(path);
      // 更新书签列表
      _getBookmarks();
    } catch (e) {
      // 处理错误
      print("Error adding bookmark: $e");
    }
  }

  Future<void> _getBookmarks() async {
    try {
      List<String> result = await DirectoryBookmarks.getBookmarks();
      setState(() {
        bookmarks = result;
      });
    } catch (e) {
      // 处理错误
      print("Error getting bookmarks: $e");
    }
  }

  Future<void> _deleteBookmark(String path) async {
    try {
      await DirectoryBookmarks.deleteBookmark(path);
      // 更新书签列表
      _getBookmarks();
    } catch (e) {
      // 处理错误
      print("Error deleting bookmark: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Directory Bookmarks'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            children: [
              // 显示书签列表
              Expanded(
                child: ListView.builder(
                  itemCount: bookmarks.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(bookmarks[index]),
                      trailing: IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () => _deleteBookmark(bookmarks[index]),
                      ),
                    );
                  },
                ),
              ),
              // 添加书签按钮
              ElevatedButton(
                onPressed: () async {
                  // 这里应该有一个UI来选择目录,这里只是一个示例
                  var path = "/path/to/directory"; // 替换为实际选择的路径
                  await _addBookmark(path);
                },
                child: Text('Add Bookmark'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

请注意,这个示例代码假设DirectoryBookmarks插件提供了addBookmark, getBookmarks, 和 deleteBookmark方法。如果实际的插件API有所不同,你需要根据实际的API文档进行调整。

此外,选择目录的UI部分(在示例中用/path/to/directory表示)需要你自己实现,可以使用Flutter的文件选择器插件来完成。

回到顶部