Flutter未定义功能插件driven的使用
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的普通Directory
或File
对象上传文件到用户的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是非常棒的,而且有关于如何做到这一点的大量相互矛盾的信息。以下是我的步骤:
- 在Google Cloud控制台(https://console.cloud.google.com/apis/dashboard)上创建一个新的项目。
- 启用Google Drive API。
- 在Firebase上创建相同的应用。
- 下载适当的应用程序的.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
更多关于Flutter未定义功能插件driven的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,如果你遇到关于未定义功能插件(如driven
)的使用问题,通常意味着这个插件可能不存在于Flutter的官方插件库中,或者你可能没有正确地安装和导入这个插件。由于driven
并不是Flutter官方插件库中的一个已知插件,我假设你可能指的是某个特定项目或第三方库中的自定义插件。
不过,为了说明如何在Flutter中正确安装和使用一个插件(假设driven
是一个有效的插件名称,且存在于某处,比如pub.dev或者其他源),以下是一个通用的步骤和代码示例:
-
在
pubspec.yaml
文件中添加依赖:首先,你需要在你的
pubspec.yaml
文件中添加这个插件作为依赖。假设driven
插件在pub.dev上存在,你可以这样做:dependencies: flutter: sdk: flutter driven: ^x.y.z # 替换为实际的版本号
然后,运行
flutter pub get
来安装依赖。 -
导入插件并在代码中使用:
一旦依赖安装完成,你就可以在你的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
是假设的。你需要根据实际的插件文档来替换这些部分。 -
处理未定义错误:
如果你遇到“未定义功能插件”的错误,并且确认插件名称和导入路径都是正确的,那么可能的原因包括:
- 插件名称拼写错误。
- 插件没有正确安装(运行
flutter pub get
)。 - 插件的源代码中确实缺少某些功能或类。
- 你的Flutter环境可能有缓存问题,尝试运行
flutter clean
后重新构建项目。
由于driven
不是一个已知的Flutter官方插件,如果你确实需要这个特定的功能,你可能需要联系插件的开发者或查找相关的文档和资源。如果这是一个自定义插件或内部插件,确保你有正确的访问权限和安装指南。