Flutter文件选择及可写操作插件file_picker_writable的使用
Flutter 文件选择及可写操作插件 file_picker_writable
的使用
概述
file_picker_writable
是一个用于在 Flutter 应用程序中选择文件并进行读写操作的插件。它支持持久权限(在 Android 上)和安全书签(在 iOS 上),允许用户打开关联文件或处理任意 URL。
要求
iOS
- 需要 iOS 8 及以上版本,Swift 5。
- 目前仅在 iOS 13+ 上进行了测试,请告知其他版本的测试结果。
支持文件处理器
- 配置 OTI 类型:参考文档
- 在
Info.plist
文件中添加以下配置:<key>UISupportsDocumentBrowser</key> <false/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/>
Android
- 需要 Android 4.4 (API Level 19) 及以上版本。
- 仅支持插件 API v2。
支持文件处理器
- 在
AndroidManifest.xml
中添加以下配置:<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="file" /> <data android:scheme="content" /> <data android:host="*" /> <data android:mimeType="*/*" /> <!-- https://stackoverflow.com/a/52384331/109219 ?? --> <data android:pathPattern=".*\.codeux" /> <data android:pathPattern=".*\..*\codeux" /> <data android:pathPattern=".*\..*\..*\codeux" /> <data android:pathPattern=".*\..*\..*\..*\codeux" /> <data android:pathPattern=".*\..*\..*\..*\..*\codeux" /> <data android:pathPattern=".*\..*\..*\..*\.*\..*\codeux" /> </intent-filter>
MacOS
- 当前不支持。
开始使用
首先,确保你已经在项目中添加了 file_picker_writable
插件:
dependencies:
file_picker_writable: ^版本号
然后运行 flutter pub get
来获取依赖项。
示例代码
以下是一个简单的示例,展示了如何使用 file_picker_writable
插件来打开文件、创建新文件,并进行读写操作。
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:convert/convert.dart';
import 'package:file_picker_writable/file_picker_writable.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:logging_appenders/logging_appenders.dart';
import 'package:simple_json_persistence/simple_json_persistence.dart';
final _logger = Logger('main');
Future<void> main() async {
Logger.root.level = Level.ALL;
PrintAppender().attachToLogger(Logger.root);
runApp(const MyApp());
}
class AppDataBloc {
final store = SimpleJsonPersistence.getForTypeWithDefault(
(json) => AppData.fromJson(json),
defaultCreator: () => AppData(files: []),
name: 'AppData',
);
}
class AppData implements HasToJson {
AppData({required this.files});
final List<FileInfo> files;
static AppData fromJson(Map<String, dynamic> json) => AppData(
files: (json['files'] as List<dynamic>)
.where((dynamic element) => element != null)
.map((dynamic e) => FileInfo.fromJson(e as Map<String, dynamic>))
.toList(),
);
[@override](/user/override)
Map<String, dynamic> toJson() => {'files': files};
AppData copyWith({required List<FileInfo> files}) => AppData(files: files);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final AppDataBloc _appDataBloc = AppDataBloc();
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: MainScreen(
appDataBloc: _appDataBloc,
),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key? key, required this.appDataBloc}) : super(key: key);
final AppDataBloc appDataBloc;
[@override](/user/override)
MainScreenState createState() => MainScreenState();
}
class MainScreenState extends State<MainScreen> {
AppDataBloc get _appDataBloc => widget.appDataBloc;
[@override](/user/override)
void initState() {
super.initState();
final state = FilePickerWritable().init();
state.registerFileOpenHandler((fileInfo, file) async {
_logger.fine('got file info. we are mounted:$mounted');
if (!mounted) {
return false;
}
await SimpleAlertDialog.readFileContentsAndShowDialog(
fileInfo,
file,
context,
bodyTextPrefix: 'Should open file from external app.\n\n'
'fileName: ${fileInfo.fileName}\n'
'uri: ${fileInfo.uri}\n\n\n',
);
return true;
});
state.registerUriHandler((uri) {
SimpleAlertDialog(
titleText: 'Handling Uri',
bodyText: 'Got a uri to handle: $uri',
).show(context);
return true;
});
state.registerErrorEventHandler((errorEvent) async {
_logger.fine('Handling error event, mounted: $mounted');
if (!mounted) {
return false;
}
await SimpleAlertDialog(
titleText: 'Received error event',
bodyText: errorEvent.message,
).show(context);
return true;
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('File Picker Example'),
),
body: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: StreamBuilder<AppData>(
stream: _appDataBloc.store.onValueChangedAndLoad,
builder: (context, snapshot) => Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Wrap(
children: [
ElevatedButton(
onPressed: _openFilePicker,
child: const Text('Open File Picker'),
),
const SizedBox(width: 32),
ElevatedButton(
onPressed: _openFilePickerForCreate,
child: const Text('Create New File'),
),
const SizedBox(width: 32),
ElevatedButton(
onPressed: FilePickerWritable().disposeAllIdentifiers,
child: const Text('Dispose All IDs'),
),
],
),
...?(!snapshot.hasData
? null
: snapshot.data!.files.map((fileInfo) => FileInfoDisplay(
fileInfo: fileInfo,
appDataBloc: _appDataBloc,
))),
],
),
),
),
),
);
}
Future<void> _openFilePicker() async {
final fileInfo = await FilePickerWritable().openFile((fileInfo, file) async {
_logger.fine('Got picker result: $fileInfo');
final data = await _appDataBloc.store.load();
await _appDataBloc.store
.save(data.copyWith(files: data.files + [fileInfo]));
return fileInfo;
});
if (fileInfo == null) {
_logger.fine('User cancelled.');
}
}
Future<void> _openFilePickerForCreate() async {
final rand = Random().nextInt(10000000);
final fileInfo = await FilePickerWritable().openFileForCreate(
fileName: 'newfile.$rand.codeux',
writer: (file) async {
final content = 'File created at ${DateTime.now()}\n\n';
await file.writeAsString(content);
},
);
if (fileInfo == null) {
_logger.info('User cancelled.');
return;
}
final data = await _appDataBloc.store.load();
await _appDataBloc.store
.save(data.copyWith(files: data.files + [fileInfo]));
}
}
class FileInfoDisplay extends StatelessWidget {
const FileInfoDisplay({
Key? key,
required this.fileInfo,
required this.appDataBloc,
}) : super(key: key);
final AppDataBloc appDataBloc;
final FileInfo fileInfo;
[@override](/user/override)
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text('Selected File:'),
Text(
fileInfo.fileName ?? 'null',
maxLines: 4,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.apply(fontSizeFactor: 0.75),
),
Text(
fileInfo.identifier,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
'uri:${fileInfo.uri}',
style: theme.textTheme.bodyMedium
?.apply(fontSizeFactor: 0.7)
.copyWith(fontWeight: FontWeight.bold),
),
Text(
'fileName: ${fileInfo.fileName}',
style: theme.textTheme.bodyMedium
?.apply(fontSizeFactor: 0.7)
.copyWith(fontWeight: FontWeight.bold),
),
ButtonBar(
alignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
try {
await FilePickerWritable().readFile(
identifier: fileInfo.identifier,
reader: (fileInfo, file) async {
await SimpleAlertDialog.readFileContentsAndShowDialog(
fileInfo, file, context);
});
} on Exception catch (e) {
if (!context.mounted) {
return;
}
await SimpleAlertDialog.showErrorDialog(e, context);
}
},
child: const Text('Read'),
),
TextButton(
onPressed: () async {
await FilePickerWritable().writeFile(
identifier: fileInfo.identifier,
writer: (file) async {
final content =
'New Content written at ${DateTime.now()}.\n\n';
await file.writeAsString(content);
// ignore: use_build_context_synchronously
await SimpleAlertDialog(
bodyText: 'Written: $content',
).show(context);
});
},
child: const Text('Overwrite'),
),
IconButton(
onPressed: () async {
try {
await FilePickerWritable()
.disposeIdentifier(fileInfo.identifier);
} on Exception catch (e) {
if (!context.mounted) {
return;
}
await SimpleAlertDialog.showErrorDialog(e, context);
}
final appData = await appDataBloc.store.load();
await appDataBloc.store.save(appData.copyWith(
files: appData.files
.where((element) => element != fileInfo)
.toList()));
},
icon: const Icon(Icons.remove_circle_outline),
),
],
)
],
),
),
),
);
}
}
class SimpleAlertDialog extends StatelessWidget {
const SimpleAlertDialog({Key? key, this.titleText, required this.bodyText})
: super(key: key);
final String? titleText;
final String bodyText;
Future<void> show(BuildContext context) =>
showDialog<void>(context: context, builder: (context) => this);
[@override](/user/override)
Widget build(BuildContext context) {
return AlertDialog(
scrollable: true,
title: titleText == null ? null : Text(titleText!),
content: Text(bodyText),
actions: [
TextButton(
child: const Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
}),
],
);
}
static Future<void> readFileContentsAndShowDialog(
FileInfo fi,
File file,
BuildContext context, {
String bodyTextPrefix = '',
}) async {
final dataList = await file.openRead(0, 64).toList();
final data = dataList.expand((element) => element).toList();
final hexString = hex.encode(data);
final utf8String = utf8.decode(data, allowMalformed: true);
final fileContentExample = 'hexString: $hexString\n\nutf8: $utf8String';
// ignore: use_build_context_synchronously
await SimpleAlertDialog(
titleText: 'Read first ${data.length} bytes of file',
bodyText: '$bodyTextPrefix $fileContentExample',
).show(context);
}
static Future<void> showErrorDialog(Exception e, BuildContext context) async {
await SimpleAlertDialog(
titleText: 'Error',
bodyText: e.toString(),
).show(context);
}
}
更多关于Flutter文件选择及可写操作插件file_picker_writable的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter文件选择及可写操作插件file_picker_writable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
file_picker_writable
是一个 Flutter 插件,它允许用户从设备中选择文件,并且还支持在选定的文件中进行写入操作。这个插件适用于需要读取和写入文件的应用场景,如文本编辑器、文件管理器等。
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 file_picker_writable
插件的依赖:
dependencies:
flutter:
sdk: flutter
file_picker_writable: ^1.0.0 # 请查看最新版本
然后运行 flutter pub get
来安装依赖。
2. 导入插件
在你的 Dart 文件中导入插件:
import 'package:file_picker_writable/file_picker_writable.dart';
3. 请求权限
在 Android 上,你可能需要请求读写外部存储的权限。你可以使用 permission_handler
插件来请求权限:
import 'package:permission_handler/permission_handler.dart';
Future<void> requestPermissions() async {
if (await Permission.storage.request().isGranted) {
// 权限已授予
} else {
// 权限被拒绝
}
}
4. 选择文件
使用 FilePickerWritable
来让用户选择文件:
Future<void> pickFile() async {
try {
final fileInfo = await FilePickerWritable().pickFile();
if (fileInfo != null) {
// 用户选择了一个文件,fileInfo 包含文件的信息
print('Selected file: ${fileInfo.filePath}');
}
} catch (e) {
print('Error picking file: $e');
}
}
5. 写入文件
你可以使用 FilePickerWritable
来将数据写入用户选择的文件:
Future<void> writeToFile(FileInfo fileInfo, String content) async {
try {
await FilePickerWritable().writeToFile(fileInfo, content);
print('Content written to file successfully.');
} catch (e) {
print('Error writing to file: $e');
}
}
6. 示例代码
以下是一个完整的示例代码,展示了如何选择文件并将内容写入文件:
import 'package:flutter/material.dart';
import 'package:file_picker_writable/file_picker_writable.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: FilePickerDemo(),
);
}
}
class FilePickerDemo extends StatefulWidget {
[@override](/user/override)
_FilePickerDemoState createState() => _FilePickerDemoState();
}
class _FilePickerDemoState extends State<FilePickerDemo> {
FileInfo? _selectedFile;
Future<void> requestPermissions() async {
if (await Permission.storage.request().isGranted) {
print('Permissions granted');
} else {
print('Permissions denied');
}
}
Future<void> pickFile() async {
try {
final fileInfo = await FilePickerWritable().pickFile();
if (fileInfo != null) {
setState(() {
_selectedFile = fileInfo;
});
print('Selected file: ${fileInfo.filePath}');
}
} catch (e) {
print('Error picking file: $e');
}
}
Future<void> writeToFile(String content) async {
if (_selectedFile == null) return;
try {
await FilePickerWritable().writeToFile(_selectedFile!, content);
print('Content written to file successfully.');
} catch (e) {
print('Error writing to file: $e');
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('File Picker Writable Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: requestPermissions,
child: Text('Request Permissions'),
),
ElevatedButton(
onPressed: pickFile,
child: Text('Pick File'),
),
ElevatedButton(
onPressed: () => writeToFile('Hello, World!'),
child: Text('Write to File'),
),
],
),
),
);
}
}