Flutter文件处理插件flutter_handle_file的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter文件处理插件flutter_handle_file的使用

该插件用于帮助你的应用程序与文件关联,并处理打开这些文件的操作。

安装

要使用该插件,在你的pubspec.yaml文件中添加flutter_handle_file作为依赖项。

dependencies:
  flutter_handle_file: ^x.x.x

权限

在Android上,需要读写本地存储的权限。因此,你需要在AndroidManifest.xml文件中添加以下权限:

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

设置

该插件可以自动在AndroidManifest.xmlInfo.plist文件中添加必要的条目。为了实现这一点,你需要在pubspec.yaml文件中添加一个特定的flutter_handle_file部分:

flutter_handle_file:
  bundle_identifier: $(PRODUCT_BUNDLE_IDENTIFIER)
  bundle_type_name: Portable Document Format
  extensions:
    - pdf: application/pdf

在大多数情况下,你可以使用$(PRODUCT_BUNDLE_IDENTIFIER)作为bundle_identifier键的值。

配置完成后,你可以运行以下命令来让flutter_handle_file添加相应的条目:

flutter pub run flutter_handle_file:main

特定平台配置

对于iOS,你还可以指定in_place选项。默认情况下,LSSupportsOpeningDocumentsInPlace键将被创建为false。更多详情请参阅此页面

使用

你的应用程序可以通过两种方式接收文件:从冷启动或热启动。

初始文件

返回应用程序启动时所用的链接(如果有)。

import 'dart:async';
import 'dart:io';

import 'package:flutter_handle_file/flutter_handle_file.dart';

Future<void> initHandleFile() async {
  try {
    String initialFile = await getInitialFile();
    if (initialFile != null) {
      // 处理文件
    }
  } on PlatformException {
    // 异常处理
  }
}

变更事件(字符串)

通常你会检查getInitialFile并监听变化。

import 'dart:async';
import 'dart:io';

import 'package:flutter_handle_file/flutter_handle_file.dart';

StreamSubscription? _sub;

Future<void> initHandleFile() async {
  try {
    String initialFile = await getInitialFile();
    if (initialFile != null) {
      // 处理文件
    }
  } on PlatformException {
    // 异常处理
  }

  // 添加监听器
  _sub = getFilesStream()?.listen((String link) {
    if (link != null) {
      // 处理文件
    }
  }, onError: (err) {
    // 异常处理
  });
  
  // 注意:在dispose方法中取消订阅
}

更多关于从链接启动应用

如果应用被终止(或者在后台未运行),并且操作系统必须重新启动它,这是冷启动。在这种情况下,getInitialFile将包含启动应用的链接,而流不会产生链接(此时)。

相反,如果应用正在后台运行,并且操作系统必须将其带到前台,流将是生成链接的地方,而getInitialFile将为空或包含启动应用的初始链接。

因此,你应该始终检查初始链接,并订阅链接流。

文件启动工具

Android

在Android上,你需要使用adb将本地文件推送到设备。

adb push <local_file> /sdcard/

然后,你可以使用设备上的Files应用点击文件。

iOS

假设你已经安装了Xcode:

/usr/bin/xcrun simctl openurl booted "file://<local_file>"

如果你有xcrunsimctl在路径中,可以直接调用它们。

标志booted假设有一个打开的模拟器(你可以通过open -a Simulator启动),带有已启动的设备。你可以通过指定其UUID(通过xcrun simctl listflutter devices找到)来针对特定设备,替换booted标志。

示例代码

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_handle_file/flutter_handle_file.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  String? _latestFile = 'Unknown';
  Uri? _latestUri;

  StreamSubscription? _sub;

  final List<String> _cmds = getCmds();
  final TextStyle _cmdStyle = const TextStyle(
    fontFamily: 'Courier',
    fontSize: 12.0,
    fontWeight: FontWeight.w700,
  );
  final _scaffoldKey = new GlobalKey<ScaffoldState>();

  [@override](/user/override)
  initState() {
    super.initState();
    initPlatformState();
  }

  [@override](/user/override)
  dispose() {
    _sub?.cancel();
    super.dispose();
  }

  // 平台消息异步,所以我们初始化在异步方法中。
  initPlatformState() async {
    await initPlatformStateForStringHandleFile();
    await initPlatformStateForUriHandleFile();
  }

  /// 使用[String]链接的实现
  initPlatformStateForStringHandleFile() async {
    // 将监听器附加到链接流
    _sub = getFilesStream()?.listen((String file) {
      if (!mounted) return;
      setState(() {
        _latestFile = file;
      });
    }, onError: (err) {
      if (!mounted) return;
      setState(() {
        _latestFile = 'Failed to get latest link: $err.';
      });
    });

    // 将第二个监听器附加到流
    getFilesStream()?.listen((String link) {
      print('got link: $link');
    }, onError: (err) {
      print('got err: $err');
    });

    // 获取最新的链接
    String? initialFile;
    Uri? initialUri;
    // 平台消息可能会失败,所以我们使用try/catch PlatformException。
    try {
      initialFile = await getInitialFile();
      initialUri = await getInitialUri();
      print('initial link: $initialFile');
    } on PlatformException {
      initialFile = 'Failed to get initial link.';
      initialUri = null;
    } on FormatException {
      initialFile = 'Failed to parse the initial link as Uri.';
      initialUri = null;
    }

    // 如果小部件在异步平台消息飞行期间从树中移除,我们希望丢弃回复而不是调用setState更新我们的不存在的外观。
    if (!mounted) return;

    setState(() {
      _latestFile = initialFile;
      _latestUri = initialUri;
    });
  }

  /// 使用[Uri]方便助手的实现
  initPlatformStateForUriHandleFile() async {
    // 将监听器附加到Uri链接流
    _sub = getUriFilesStream()?.listen((Uri uri) {
      if (!mounted) return;
      setState(() {
        _latestUri = uri;
      });
    }, onError: (err) {
      if (!mounted) return;
      setState(() {
        _latestUri = null;
      });
    });

    // 将第二个监听器附加到流
    getUriFilesStream()?.listen((Uri uri) {
      print('got uri: ${uri.path} ${uri.queryParametersAll}');
    }, onError: (err) {
      print('got err: $err');
    });

    // 获取最新的Uri
    Uri? initialUri;
    String? initialFile;
    // 平台消息可能会失败,所以我们使用try/catch PlatformException。
    try {
      initialUri = await getInitialUri();
      print('initial uri: ${initialUri?.path} ${initialUri?.queryParametersAll}');
      initialFile = await getInitialFile();
    } on PlatformException {
      initialUri = null;
      initialFile = 'Failed to get initial uri.';
    } on FormatException {
      initialUri = null;
      initialFile = 'Bad parse the initial link as Uri.';
    }

    // 如果小部件在异步平台消息飞行期间从树中移除,我们希望丢弃回复而不是调用setState更新我们的不存在的外观。
    if (!mounted) return;

    setState(() {
      _latestUri = initialUri;
      _latestFile = initialFile;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        key: _scaffoldKey,
        appBar: new AppBar(
          title: new Text('插件示例应用'),
        ),
        body: new ListView(
          shrinkWrap: true,
          padding: const EdgeInsets.all(8.0),
          children: <Widget>[
            new ListTile(
              title: const Text('String'),
              subtitle: new Text('$_latestFile'),
            ),
            new ListTile(
              title: const Text('Uri Path'),
              subtitle: new Text('${_latestUri.toString()}'),
            ),
            _cmdsCard(_cmds),
          ],
        ),
      ),
    );
  }

  Widget _cmdsCard(commands) {
    Widget platformCmds;

    if (commands == null) {
      platformCmds = const Center(
        child: const Text('不支持的平台'),
      );
    } else {
      platformCmds = new Column(
        children: <List<Widget>>[
          [
            const Text(
                '要填充上述字段,请打开终端并运行以下命令:\n'),
          ],
          intersperse(
            commands.map<Widget>(
              (cmd) =>
                  new InkWell(
                    onTap: () => _printAndCopy(cmd),
                    child: new Text(
                      '\n$cmd\n',
                      style: _cmdStyle,
                    ),
                  ),
            ),
            const Text('或'),
          ),
          [
            new Text(
                '(点击任何上述命令以打印到控制台/日志并复制到设备剪贴板。)',
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.bodySmall),
          ]
        ].expand((el) => el).toList(),
      );
    }

    return new Card(
      margin: const EdgeInsets.only(top: 20.0),
      child: new Padding(
        padding: const EdgeInsets.all(10.0),
        child: platformCmds,
      ),
    );
  }

  _printAndCopy(String cmd) async {
    print(cmd);
    await Clipboard.setData(new ClipboardData(text: cmd));
    ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
      content: const Text('已复制到剪贴板'),
    ));
  }
}

List<String> getCmds() {
  if (Platform.isIOS) {
    return [
      '/usr/bin/xcrun simctl openurl booted "file://\$(pwd)/data/test.pdf"'
    ];
  } else if (Platform.isAndroid) {
    return ['adb push ./data/test.pdf /sdcard', '使用设备上的文件应用'];
  } else {
    return [];
  }
}

List<Widget> intersperse(Iterable<Widget> list, Widget item) {
  List<Widget> initialValue = [];
  return list.fold(initialValue, (all, el) {
    if (all.length != 0) all.add(item);
    all.add(el);
    return all;
  });
}

更多关于Flutter文件处理插件flutter_handle_file的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter文件处理插件flutter_handle_file的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,flutter_handle_file 是一个用于 Flutter 应用中文件处理的插件。虽然这个插件的具体实现和功能可能会因版本和作者的不同而有所差异,但通常文件处理插件会包括读取、写入、复制、删除文件等基本功能。以下是一个假设性的示例代码,展示了如何使用一个类似 flutter_handle_file 的插件进行文件操作。

请注意,由于 flutter_handle_file 不是一个广为人知的官方或广泛使用的 Flutter 插件,以下代码是基于一个假设性的插件接口编写的。如果你正在使用特定的插件,请参考该插件的官方文档。

首先,确保在 pubspec.yaml 文件中添加依赖(假设插件名为 flutter_handle_file):

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

然后,运行 flutter pub get 来获取依赖。

接下来,是一个使用假设性的 flutter_handle_file 插件的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_handle_file/flutter_handle_file.dart';  // 假设的导入路径

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FileHandlerScreen(),
    );
  }
}

class FileHandlerScreen extends StatefulWidget {
  @override
  _FileHandlerScreenState createState() => _FileHandlerScreenState();
}

class _FileHandlerScreenState extends State<FileHandlerScreen> {
  final FileHandler _fileHandler = FileHandler();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('File Handler Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                String content = "Hello, Flutter!";
                String filePath = await _fileHandler.writeFile("example.txt", content);
                print("File written to: $filePath");
              },
              child: Text('Write File'),
            ),
            ElevatedButton(
              onPressed: () async {
                String content = await _fileHandler.readFile("example.txt");
                print("File content: $content");
              },
              child: Text('Read File'),
            ),
            ElevatedButton(
              onPressed: () async {
                bool success = await _fileHandler.copyFile("example.txt", "example_copy.txt");
                print("File copy success: $success");
              },
              child: Text('Copy File'),
            ),
            ElevatedButton(
              onPressed: () async {
                bool success = await _fileHandler.deleteFile("example.txt");
                print("File delete success: $success");
              },
              child: Text('Delete File'),
            ),
          ],
        ),
      ),
    );
  }
}

// 假设的 FileHandler 类,用于文件操作
class FileHandler {
  Future<String> writeFile(String fileName, String content) async {
    // 这里是假设的文件写入逻辑
    // 在实际插件中,你会调用插件提供的写入方法
    String filePath = "/path/to/$fileName";  // 假设的文件路径
    // 写入文件内容(这里仅作示例,实际应使用插件的API)
    // await File(filePath).writeAsString(content);
    return filePath;  // 返回文件路径(假设)
  }

  Future<String> readFile(String fileName) async {
    // 这里是假设的文件读取逻辑
    // 在实际插件中,你会调用插件提供的读取方法
    String filePath = "/path/to/$fileName";  // 假设的文件路径
    // 读取文件内容(这里仅作示例,实际应使用插件的API)
    // String content = await File(filePath).readAsString();
    return "Hello, Read File!";  // 返回读取的内容(假设)
  }

  Future<bool> copyFile(String sourceFileName, String destinationFileName) async {
    // 这里是假设的文件复制逻辑
    // 在实际插件中,你会调用插件提供的复制方法
    // bool success = await someCopyFunction(source, destination);
    return true;  // 返回复制是否成功(假设)
  }

  Future<bool> deleteFile(String fileName) async {
    // 这里是假设的文件删除逻辑
    // 在实际插件中,你会调用插件提供的删除方法
    // bool success = await someDeleteFunction(filePath);
    return true;  // 返回删除是否成功(假设)
  }
}

注意

  1. 上述代码中的 FileHandler 类及其方法(writeFile, readFile, copyFile, deleteFile)是假设性的,用于演示目的。
  2. 在实际使用中,你应该参考 flutter_handle_file 插件的官方文档,调用插件提供的实际API。
  3. 文件路径在 Flutter 中通常是相对于应用沙盒的,而不是绝对路径。在实际应用中,你需要处理文件路径的获取和权限问题。

由于 flutter_handle_file 不是一个广为人知的插件,如果你正在使用特定的插件,请参考该插件的官方文档和示例代码。

回到顶部