Flutter文件选择插件reactive_file_selector的使用

Flutter文件选择插件reactive_file_selector的使用

reactive_file_selector 是一个用于在 Flutter 中选择文件的插件。它基于 file_selector 插件,并与 reactive_forms 集成以实现更方便的表单管理。

示例代码

下面是一个完整的示例代码,展示了如何使用 reactive_file_selector 插件来选择多个文件并验证文件数量。

import 'package:flutter/material.dart';
import 'package:reactive_file_selector/reactive_file_selector.dart';
import 'package:reactive_forms/reactive_forms.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // 构建表单
  FormGroup buildForm() => fb.group({
        'input': FormControl<MultiFile<String>>(
            value: const MultiFile(),
            validators: [
              Validators.required,
              FileSelectorValidators.limit(min: 5, max: 10),
            ]),
      });

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: ReactiveFormBuilder(
              form: buildForm,
              builder: (context, form, child) {
                return Column(
                  children: [
                    Expanded(
                      child: ReactiveFileSelector<String>(
                        formControlName: 'input',
                        allowMultiple: true,
                        distinctPickedFiles: true,
                        filePickerBuilder: (pickImage, files, onChange) {
                          final items = [
                            ...files.files
                                .asMap()
                                .map((key, value) => MapEntry(
                                    key,
                                    ListTile(
                                      onTap: () {
                                        onChange(files.copyWith(
                                            files:
                                                List<String>.from(files.files)
                                                  ..removeAt(key)));
                                      },
                                      leading: const Icon(Icons.delete),
                                      title: FileListItem(value).build(context),
                                    )))
                                .values,
                            ...files.platformFiles
                                .asMap()
                                .map((key, value) => MapEntry(
                                      key,
                                      ListTile(
                                        onTap: () {
                                          onChange(files.copyWith(
                                              platformFiles: List<XFile>.from(
                                                  files.platformFiles)
                                                ..removeAt(key)));
                                        },
                                        leading: const Icon(Icons.delete),
                                        title: PlatformFileListItem(value)
                                            .build(context),
                                      ),
                                    ))
                                .values,
                          ];

                          return Column(
                            children: [
                              Expanded(
                                child: ListView.builder(
                                  itemCount: items.length,
                                  itemBuilder: (_, i) {
                                    return items[i];
                                  },
                                ),
                              ),
                              ElevatedButton(
                                onPressed: pickImage,
                                child: const Text("Pick images"),
                              ),
                            ],
                          );
                        },
                        validationMessages: {
                          ValidationMessage.min: (error) {
                            if (error is! Map<String, Object?>) {
                              return ValidationMessage.min;
                            }
                            return "应至少有 ${error['min']} 个文件,实际有: ${error['actual']}";
                          },
                          ValidationMessage.max: (error) {
                            if (error is! Map<String, Object?>) {
                              return ValidationMessage.max;
                            }
                            return "应最多有 ${error['max']} 个文件,实际有: ${error['actual']}";
                          }
                        },
                        decoration: const InputDecoration(
                          labelText: '多文件选择器',
                          border: OutlineInputBorder(),
                          helperText: '',
                        ),
                      ),
                    ),
                    ElevatedButton(
                      child: const Text('提交'),
                      onPressed: () {
                        if (form.valid) {
                          debugPrint(form.value.toString());
                        }
                      },
                    ),
                  ],
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}

// 抽象类,定义列表项接口
abstract class ListItem {
  Widget build(BuildContext context);
}

// 文件列表项
class FileListItem extends ListItem {
  final String url;

  FileListItem(this.url);

  [@override](/user/override)
  Widget build(context) {
    return Text(url);
  }
}

// 平台文件列表项
class PlatformFileListItem extends ListItem {
  final XFile platformFile;

  PlatformFileListItem(this.platformFile);

  [@override](/user/override)
  Widget build(context) {
    return Text(platformFile.name);
  }
}

代码解释

  1. 导入必要的库

    import 'package:flutter/material.dart';
    import 'package:reactive_file_selector/reactive_file_selector.dart';
    import 'package:reactive_forms/reactive_forms.dart';
    
  2. 构建表单

    FormGroup buildForm() => fb.group({
          'input': FormControl<MultiFile<String>>(
              value: const MultiFile(),
              validators: [
                Validators.required,
                FileSelectorValidators.limit(min: 5, max: 10),
              ]),
        });
    
  3. 构建UI

    [@override](/user/override)
    Widget build(BuildContext context) {
      return MaterialApp(
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: Scaffold(
          appBar: AppBar(),
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: ReactiveFormBuilder(
                form: buildForm,
                builder: (context, form, child) {
                  return Column(
                    children: [
                      Expanded(
                        child: ReactiveFileSelector<String>(
                          formControlName: 'input',
                          allowMultiple: true,
                          distinctPickedFiles: true,
                          filePickerBuilder: (pickImage, files, onChange) {
                            // 构建文件列表
                          },
                          validationMessages: {
                            ValidationMessage.min: (error) {
                              // 自定义最小文件数错误信息
                            },
                            ValidationMessage.max: (error) {
                              // 自定义最大文件数错误信息
                            }
                          },
                          decoration: const InputDecoration(
                            labelText: '多文件选择器',
                            border: OutlineInputBorder(),
                            helperText: '',
                          ),
                        ),
                      ),
                      ElevatedButton(
                        child: const Text('提交'),
                        onPressed: () {
                          if (form.valid) {
                            debugPrint(form.value.toString());
                          }
                        },
                      ),
                    ],
                  );
                },
              ),
            ),
          ),
        ),
      );
    }
    
  4. 自定义列表项

    abstract class ListItem {
      Widget build(BuildContext context);
    }
    
    class FileListItem extends ListItem {
      final String url;
    
      FileListItem(this.url);
    
      [@override](/user/override)
      Widget build(context) {
        return Text(url);
      }
    }
    
    class PlatformFileListItem extends ListItem {
      final XFile platformFile;
    
      PlatformFileListItem(this.platformFile);
    
      [@override](/user/override)
      Widget build(context) {
        return Text(platformFile.name);
      }
    }
    

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

1 回复

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


reactive_file_selector 是一个 Flutter 插件,用于在 Flutter 应用程序中选择文件。它提供了一个响应式的文件选择器,可以与 Flutter 的响应式编程模型(如 StreamRxDart)无缝集成。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 reactive_file_selector 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  reactive_file_selector: ^1.0.0  # 请使用最新版本

然后,运行 flutter pub get 来安装依赖。

使用 reactive_file_selector

1. 导入插件

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

import 'package:reactive_file_selector/reactive_file_selector.dart';

2. 创建文件选择器

你可以使用 ReactiveFileSelector 小部件来创建一个文件选择器。它通常与 StreamControllerBehaviorSubject 一起使用,以便在文件选择时触发事件。

import 'package:flutter/material.dart';
import 'package:reactive_file_selector/reactive_file_selector.dart';
import 'package:rxdart/rxdart.dart';

class FileSelectorExample extends StatefulWidget {
  @override
  _FileSelectorExampleState createState() => _FileSelectorExampleState();
}

class _FileSelectorExampleState extends State<FileSelectorExample> {
  final BehaviorSubject<File> _fileSubject = BehaviorSubject<File>();

  @override
  void dispose() {
    _fileSubject.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Reactive File Selector Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ReactiveFileSelector(
              onFileSelected: _fileSubject.add,
              child: ElevatedButton(
                onPressed: () {
                  // 这里不需要手动处理按钮点击事件,ReactiveFileSelector 会自动处理
                },
                child: Text('Select File'),
              ),
            ),
            StreamBuilder<File>(
              stream: _fileSubject.stream,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text('Selected File: ${snapshot.data!.path}');
                } else {
                  return Text('No file selected');
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

3. 处理文件选择事件

在上面的代码中,ReactiveFileSelector 小部件会在用户选择文件时触发 onFileSelected 回调,并将选中的文件添加到 _fileSubject 中。然后,StreamBuilder 会监听 _fileSubject 的变化,并在 UI 中显示选中的文件路径。

4. 自定义文件选择器

你可以通过传递不同的参数来自定义文件选择器的行为。例如,你可以指定允许选择的文件类型:

ReactiveFileSelector(
  onFileSelected: _fileSubject.add,
  allowedExtensions: ['.txt', '.pdf'],  // 只允许选择 .txt 和 .pdf 文件
  child: ElevatedButton(
    onPressed: () {},
    child: Text('Select File'),
  ),
),

5. 处理错误

你还可以通过 onError 回调来处理文件选择过程中可能发生的错误:

ReactiveFileSelector(
  onFileSelected: _fileSubject.add,
  onError: (error) {
    print('File selection error: $error');
  },
  child: ElevatedButton(
    onPressed: () {},
    child: Text('Select File'),
  ),
),
回到顶部