Flutter未定义功能插件driven的使用

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

Flutter未定义功能插件driven的使用

Driven

An easier Google Drive experience

特性:

  • 使用你熟悉的路径构造在Google Drive中操作。
  • 将文件复制到Google Drive。
  • 递归地将文件夹复制到Google Drive。
  • 利用Dart扩展方法构建在Google Drive API的基础上,因此你仍然可以使用原始的Google Drive API来处理特定情况。
  • 将文件从Google Drive递归地复制到本地设备(即将推出)。
  • 从本地文件夹到Google Drive的一次性同步(即将推出)。

未来计划:

  • 上面所有标注为“即将推出”的功能。
  • 可以将文件复制到应用存储而不是用户的驱动器。
  • 测试 🔬。
Why?

尝试使用官方的Google库将文件和文件夹复制到Google Drive是一次真正的体验。它们提供了难以置信的灵活性和几乎无限的选择。然而,这会导致开发人员(读作:我)像“好吧,但我只是想将一个文件夹复制到用户的Google Drive?”。

Driven旨在通过让你使用Dart的普通DirectoryFile对象上传文件到用户的Google Drive来解决这个问题。一旦用户允许你的应用程序与其Drive交互,你就可以将文件复制到任何位置。

此外,Driven会将文件复制到用户的Google Drive。它不使用Google Drive提供的“应用存储”功能。原因是似乎很难导航在应用存储中创建的文件和文件夹。所以,如果一个应用使用了Google Drive中的这个隐藏存储,并且该应用变得无人维护或被放弃,那么你该怎么办?显然,这种权衡是,直接将文件复制到Google Drive意味着用户可以手动修改文件并可能破坏事情。将来,将在Driven中添加将文件复制到Google Drive内的应用存储的功能。

Caveats

我没有发现Driven有任何立即的世界性问题,我在我的应用程序中使用它。但是,与用户的Google Drive交互,那里存放着他们的东西,是一件严肃的事情。根据你如何配置你的应用程序,你可能会覆盖或删除用户的Drive上的文件。这是非常糟糕的体验。不要这样做。

你也应该阅读并在项目中使用前熟悉代码,至少给你一种保证这个库以安全方式运行的方式。不要只将其添加到pubspec.yaml中并希望一切顺利——如果你与用户的Drive进行交互,他们期望你负责任地去做。所以,做正确的事,浏览一下库,确保它不会删除所有内容。

Usage

为了使用Driven,你需要做一些令人困惑的设置。在Flutter中配置你的应用程序与Firebase是非常棒的,而且有关于如何做到这一点的大量相互矛盾的信息。以下是我的步骤:

  1. 在Google Cloud控制台(https://console.cloud.google.com/apis/dashboard)上创建一个新的项目。
  2. 启用Google Drive API。
  3. 在Firebase上创建相同的应用。
  4. 下载适当的应用程序的.json或.plist文件,并将其复制到应用程序中的适当位置。或者,

将来,我计划制作一个视频展示这些步骤以及如何在一个示例项目中使用Driven。

API

Driven主要提供的是一些对Google Drive API提供的DriveApi对象的扩展。目前有以下函数:

Future<drive.File> createFolder(final String folderName, {final String? parent}) 

在Google Drive上创建一个空文件夹。如果指定了parent变量,则在指定的父文件夹内创建文件夹。(注意:父文件夹是Google Drive上的ID)

Future<List<FolderPathBit>> createFoldersRecursively(final String folderPath)

在用户的Google Drive上创建一个path/like/this。返回创建的文件夹及其ID的列表。

Future<drive.File> pushFile(final File file, final String path, {final String? mimeType})

将本地设备上的文件复制到用户的Google Drive中的path变量指定的位置。如果指定的路径不存在,则创建路径,然后将文件复制到该路径。你可以手动指定文件的mimeType,如果不指定,Driven将使用mime包尝试通过文件扩展名猜测mime类型。

Stream<FolderPushProgress> pushFolder(final Directory directory, final String destinationDirectory)

递归地将本地Directory对象复制到Google Drive。这个函数目前有点慢,但正在努力提高速度。

Future<List<FolderPathBit>> getFolderPathAsIds(final String path) 

如果你有一个path/like/this,此函数将为你检索这些文件夹的ID。你可以使用这些ID来确定文件夹是否已经存在。

Query Helpers

Driven还附带了一些帮助你编写Google Drive API查询的“查询助手”。目前只有一个fileQuery,它可以帮助你编写一个未被删除的文件的查询,并检查文件所在的目录。

Support

创建像Driven这样的插件需要很多时间,所以如果你有几美元可以捐赠,可以在GitHub上赞助我:https://github.com/sponsors/flutterfromscratch

你也可以买杯咖啡给我:https://www.buymeacoffee.com/flutterscratch

或者,你可以订阅我的YouTube频道:https://www.youtube.com/channel/UCYZ6TTC9EgfDTFGz4UC-GJw

完整示例代码

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

import 'package:driven/driven.dart';
import 'package:driven/querybuilder/driveExtensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:googleapis/drive/v3.dart' as drive;
import 'package:google_sign_in/google_sign_in.dart' as auth;
import 'package:path_provider/path_provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _driven = Driven(iAcceptTheRisksOfUsingDriven: true);
  String? statusUpdate;

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: StreamBuilder<auth.GoogleSignInAccount?>(
        initialData: null,
        stream: _driven.signedInStream.stream,
        builder: (context, account) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              if (account.data == null)
                ElevatedButton(
                  onPressed: () async {
                    showLoading(context);
                    _driven.authenticateWithGoogle().then((value) {
                      Navigator.of(context).pop();
                    });
                  },
                  child: const Text('SIGN INTO GOOGLE SERVICES'),
                ),
              if (account.data != null)
                Column(
                  children: [
                    if (account.data?.photoUrl != null)
                      Card(
                        child: Column(
                          children: [
                            Text(
                              'Signed in as...',
                              style: Theme.of(context).textTheme.headline5,
                            ),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: [
                                CircleAvatar(
                                  child: Image.network(account.data!.photoUrl!),
                                  radius: 100,
                                ),
                                Text(account.data!.displayName ?? 'No display name?')
                              ],
                            ),
                          ],
                        ),
                      ),
                    ElevatedButton(
                        onPressed: () {
                          showLoading(context);
                          _driven.signOut().then((value) => Navigator.of(context).pop());
                        },
                        child: const Text('SIGN OUT')),
                    ElevatedButton(
                      onPressed: () {
                        showDialog<String>(
                            context: context,
                            builder: (builder) {
                              String path = "";
                              return AlertDialog(
                                title: const Text('Create a new folder'),
                                content: Column(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    const Text('What path should we create? It must be a path/like/this.'),
                                    TextField(
                                      onChanged: (newPath) => path = newPath,
                                    ),
                                    ElevatedButton(
                                      child: const Text('CREATE'),
                                      onPressed: () {
                                        showLoading(context);

                                        drive.DriveApi(GoogleAuthClient())
                                            .createFoldersRecursively(path)
                                            .then((value) => Navigator.of(context).pop())
                                            .onError((error, stackTrace) {
                                          Navigator.pop(context);
                                          showDialog(
                                              context: context,
                                              builder: (context) => AlertDialog(
                                                    title: const Text('Error'),
                                                    content: Text(error.toString()),
                                                  ));
                                        });
                                      },
                                    )
                                  ],
                                ),
                              );
                            });
                      },
                      child: const Text('CREATE A FOLDER'),
                    ),
                    ElevatedButton(
                      onPressed: () async {
                        showDialog(
                          context: context,
                          builder: (context) {
                            String? filePath;
                            String? fileContents;
                            return AlertDialog(
                              title: const Text('Create a text file'),
                              content: Column(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  TextField(
                                    decoration: const InputDecoration(hintText: 'File Path on Google Drive (like /path/to/file.txt)'),
                                    onChanged: (val) {
                                      filePath = val;
                                    },
                                  ),
                                  TextField(
                                    decoration: const InputDecoration(
                                      hintText: 'Contents of file',
                                    ),
                                    onChanged: (val) {
                                      fileContents = val;
                                    },
                                  ),
                                  ElevatedButton(
                                      onPressed: () async {
                                        if (filePath == null || fileContents == null) {
                                          showDialog(
                                              context: context,
                                              builder: (context) => const AlertDialog(
                                                    title: Text('Please fill out the fields.'),
                                                  ));
                                        } else {
                                          final tempFile = await getTemporaryDirectory();
                                          final file = File(tempFile.path + '/tempfile.txt');
                                          file.writeAsString(fileContents ?? 'Test from driven!');
                                          await drive.DriveApi(GoogleAuthClient()).pushFile(file, filePath!); // already checked for null
                                        }
                                      },
                                      child: const Text('CREATE FILE')),
                                ],
                              ),
                            );
                          },
                        );
                      },
                      child: const Text('CREATE A PLAIN TEXT FILE'),
                    ),
                    ElevatedButton(
                      onPressed: () async {
                        showDialog(
                          context: context,
                          builder: (context) {
                            return SimpleDialog(
                              contentPadding: const EdgeInsets.all(20),
                              title: const Text('Create a folder with contents'),
                              children: [
                                const Text(
                                    'This will create a folder on your local device with some files in it, and then send that folder to Google Drive.'),
                                ElevatedButton(
                                  onPressed: () async {
                                    final tempDirectory = await getTemporaryDirectory();
                                    final tempPath = (await tempDirectory.createTemp()).path;
                                    for (int i = 0; i < 10; i++) {
                                      final newDir = Directory('$tempPath/Driven Test/${i.toString()}/${i.toString()}/${i.toString()}/${i.toString()}');
                                      await newDir.create(recursive: true);
                                      final testFile = File('${newDir.path}/testfile.txt');
                                      await testFile.writeAsString('Its just test content.');
                                      print('created test file ${testFile.path} with some content.');
                                    }
                                    final created =
                                        await drive.DriveApi(GoogleAuthClient()).pushFolder(Directory(tempPath), 'Driven Folder Test').toList();
                                    showDialog(
                                      context: context,
                                      builder: (context) => AlertDialog(
                                        title: const Text('Created some files and folders.'),
                                        content: Text(
                                          created.last.toString(),
                                        ),
                                      ),
                                    );
                                  },
                                  child: const Text('MAKE IT SO'),
                                )
                              ],
                            );
                          },
                        );
                      },
                      child: const Text('CREATE AND COPY FOLDER'),
                    ),
                    ElevatedButton(
                      onPressed: () async {
                        final localStorage = await getApplicationDocumentsDirectory();
                        final remoteFolderIds = await drive.DriveApi(GoogleAuthClient()).getFolderPathAsIds('Driven Test');
                        final localFolder = await Directory(localStorage.path + '/Driven Test').create();
                        if (remoteFolderIds.last.id == null) {
                          showDialog(
                            context: context,
                            builder: (context) => const SimpleDialog(
                              title: Text('Please create a folder called "Driven Test" on remote first.'),
                            ),
                          );
                        } else {
                          showDialog(
                            context: context,
                            builder: (context) {
                              return SimpleDialog(
                                children: [
                                  const Text('Copying to local...'),
                                  StreamBuilder<FolderTransferProgress>(
                                    builder: (context, progress) {
                                      if (progress.hasData) {
                                        return Text(progress.data!.status.name);
                                      } else {
                                        return Text('Waiting...');
                                      }
                                    },
                                    stream: drive.DriveApi(GoogleAuthClient()).receiveFolder(remoteFolderIds.last.id!, localFolder),
                                  )
                                ],
                              );
                            },
                          );
                        }
                      },
                      child: const Text('COPY FOLDER FROM REMOTE TO LOCAL'),
                    )
                  ],
                )
            ],
          );
        },
      ),
    );
  }

  void showLoading(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) => Center(
              child: const Card(
                child: Padding(
                  padding: EdgeInsets.all(32.0),
                  child: const CircularProgressIndicator(),
                ),
              ),
            ));
  }
}

更多关于Flutter未定义功能插件driven的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter未定义功能插件driven的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,如果你遇到关于未定义功能插件(如driven)的使用问题,通常意味着这个插件可能不存在于Flutter的官方插件库中,或者你可能没有正确地安装和导入这个插件。由于driven并不是Flutter官方插件库中的一个已知插件,我假设你可能指的是某个特定项目或第三方库中的自定义插件。

不过,为了说明如何在Flutter中正确安装和使用一个插件(假设driven是一个有效的插件名称,且存在于某处,比如pub.dev或者其他源),以下是一个通用的步骤和代码示例:

  1. pubspec.yaml文件中添加依赖

    首先,你需要在你的pubspec.yaml文件中添加这个插件作为依赖。假设driven插件在pub.dev上存在,你可以这样做:

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

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

  2. 导入插件并在代码中使用

    一旦依赖安装完成,你就可以在你的Dart代码中导入并使用这个插件了。例如:

    import 'package:driven/driven.dart';  // 假设这是正确的导入路径
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text('Driven Plugin Example'),
            ),
            body: Center(
              child: DrivenWidget(),  // 假设这是插件提供的一个Widget
            ),
          ),
        );
      }
    }
    
    // 假设DrivenWidget是插件提供的一个组件
    class DrivenWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 使用插件提供的功能
        return Text('Driven Plugin is working!');
      }
    }
    

    请注意,上面的代码中的DrivenWidget和导入路径package:driven/driven.dart是假设的。你需要根据实际的插件文档来替换这些部分。

  3. 处理未定义错误

    如果你遇到“未定义功能插件”的错误,并且确认插件名称和导入路径都是正确的,那么可能的原因包括:

    • 插件名称拼写错误。
    • 插件没有正确安装(运行flutter pub get)。
    • 插件的源代码中确实缺少某些功能或类。
    • 你的Flutter环境可能有缓存问题,尝试运行flutter clean后重新构建项目。

由于driven不是一个已知的Flutter官方插件,如果你确实需要这个特定的功能,你可能需要联系插件的开发者或查找相关的文档和资源。如果这是一个自定义插件或内部插件,确保你有正确的访问权限和安装指南。

回到顶部