Flutter 的 Parse!
大家好,这是一个 Flutter 插件,允许与 Parse Server(https://parseplatform.org)进行通信,该服务器可以托管在您自己的服务器上,也可以托管在其他服务器上,例如(http://Back4App.com)。
这是一个正在进行中的项目,我们正在持续更新它。如果您认为有任何需要更改/添加的地方,请告知我们,并且最重要的是,请加入这个项目。(即使只是为了改进我们的文档)
入门
要安装,请将 依赖添加到您的 pubspec.yaml 文件。
将库添加到项目后,在您的应用程序首次调用时(类似于您的应用程序类),添加以下内容...
await Parse().initialize(
keyApplicationId,
keyParseServerUrl);
如果您想使用安全存储或使用 Flutter Web/桌面 SDK,请切换到下面的 CoreStorage 实例,因为它不依赖于 Flutter。
CoreStoreSembastImp 不会在 Web 上加密数据!(Web 本身就不安全。请根据需要手动加密字段。)
await Parse().initialize(
keyParseApplicationId,
keyParseServerUrl,
coreStore: await CoreStoreSembastImp.getInstance());
可以添加其他参数来配合您的 Parse Server 实例使用:
await Parse().initialize(
keyApplicationId,
keyParseServerUrl,
clientKey: keyParseClientKey, // Required for some setups
debug: true, // When enabled, prints logs to console
liveQueryUrl: keyLiveQueryUrl, // Required if using LiveQuery
autoSendSessionId: true, // Required for authentication and ACL
securityContext: securityContext, // Again, required for some setups
coreStore: await CoreStoreSharedPrefsImp.getInstance()); // Local data storage method. Will use SharedPreferences instead of Sembast as an internal DB
⚠️ 请注意,主密钥只能在安全的环境中使用,绝不能在客户端使用 ‼️
Web 支持
由于跨源资源共享 (CORS) 的限制,这需要在 parse-server 中将 `X-Parse-Installation-Id` 添加为允许的标头。当通过 docker 直接运行时,请设置环境变量 `PARSE_SERVER_ALLOW_HEADERS=X-Parse-Installation-Id`。当通过 express 运行时,请设置 ParseServerOptions 中的 `allowHeaders: ['X-Parse-Installation-Id']`。
网络客户端
默认情况下,此 SDK 使用 `ParseHTTPClient`。另一个选项是使用 `ParseDioClient`。此客户端支持大多数功能(例如文件上传时的进度回调),但基准测试表明,在 Web 上 dio 比 http 慢。
如果您想使用 `ParseDioClient`(它使用 dio 网络库),您可以在初始化 SDK 时提供自定义的 `ParseClientCreator`。
await Parse().initialize(
//...
clientCreator: ({bool? sendSessionId, SecurityContext? securityContext}) => ParseDioClient(sendSessionId: sendSessionId, securityContext: securityContext),
);
对象
您可以通过调用以下方法创建自定义对象:
var dietPlan = ParseObject('DietPlan')
..set('Name', 'Ketogenic')
..set('Fat', 65);
await dietPlan.save();
或通过其 objectId 更新现有对象:
var dietPlan = ParseObject('DietPlan')
..objectId = 'R5EonpUDWy'
..set('Fat', 70);
await dietPlan.save();
使用以下方法验证对象是否已成功保存:
var response = await dietPlan.save();
if (response.success) {
dietPlan = response.results.first;
}
支持的类型
- 字符串
- Double(双精度浮点数)
- Int(整数)
- Boolean(布尔值)
- DateTime(日期时间)
- File(文件)
- Geopoint(地理点)
- ParseObject/ParseUser (Pointer)(Parse 对象/Parse 用户(指针))
- 地图
- List (all types supported)(列表(所有类型都支持))
然后,您就可以对该对象执行以下操作:可用的功能包括:
- Get(获取)
- GetAll(获取所有)
- 创建
- Save(保存)
- Query – By object Id(按 objectId 查询)
- Delete(删除)
- Complex queries as shown above(复杂的查询,如上所示)
- Pin(固定)
- Plenty more!(更多!)
- Counters(计数器)
- Array Operators(数组运算符)
Custom Objects(自定义对象)
您可以创建自己的 `ParseObjects` 或通过以下方式将现有对象转换为 Parse Objects:
class DietPlan extends ParseObject implements ParseCloneable {
DietPlan() : super(_keyTableName);
DietPlan.clone(): this();
/// Looks strangely hacky but due to Flutter not using reflection, we have to
/// mimic a clone
@override clone(Map map) => DietPlan.clone()..fromJson(map);
static const String _keyTableName = 'Diet_Plans';
static const String keyName = 'Name';
String get name => get<String>(keyName);
set name(String name) => set<String>(keyName, name);
}
当从 SDK 接收到 `ParseObject` 时,您通常可以提供自定义对象实例作为复制对象。要始终使用自定义对象类,您可以在 SDK 初始化时注册子类。
Parse().initialize(
...,
registeredSubClassMap: <String, ParseObjectConstructor>{
'Diet_Plans': () => DietPlan(),
},
parseUserConstructor: (username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress),
);
此外,您可以在 SDK 初始化后注册 `SubClasses`。
ParseCoreData().registerSubClass('Diet_Plans', () => DietPlan());
ParseCoreData().registerUserSubClass((username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress));
如上所述,提供 `ParseObject` 即使您注册了不同的 `SubClass`,也应该仍然有效。
对于自定义文件类,请参考 此处。
向对象添加新值
要向对象添加变量并检索它,请调用:
dietPlan.set<int>('RandomInt', 8);
var randomInt = dietPlan.get<int>('RandomInt');
使用 Pin 保存对象
您现在可以通过调用对象实例上的 `.pin()` 来保存对象。
dietPlan.pin();
并检索它:
var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT');
存储
我们现在有两种存储方式:安全和不安全。我们目前依赖于两个第三方选项:
- SharedPreferences(共享偏好设置)
- Sembast Sembast 提供安全存储,而 SharePreferences 则封装了 iOS 上的 NSUserDefaults 和 Android 上的 SharedPreferences。
存储方法在 `Parse().initialize` 的 `coreStore` 参数中定义。
请查看示例代码了解选项。
在对象中增加计数器值
检索它,调用:
var response = await dietPlan.increment("count", 1);
或与 save 函数一起使用:
dietPlan.setIncrement('count', 1);
dietPlan.setDecrement('count', 1);
var response = dietPlan.save()
对象中的数组运算符
检索它,调用:
var response = await dietPlan.add("listKeywords", ["a", "a","d"]);
var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]);
var response = await dietPlan.remove("listKeywords", ["a"]);
或与 save 函数一起使用:
dietPlan.setAdd('listKeywords', ['a','a','d']);
dietPlan.setAddUnique('listKeywords', ['a','a','d']);
dietPlan.setRemove('listKeywords', ['a']);
var response = dietPlan.save()
Queries(查询)
设置好项目并初始化实例后,您可以通过调用以下方法从服务器检索数据:
var apiResponse = await ParseObject('ParseTableName').getAll();
if (apiResponse.success){
for (var testObject in apiResponse.result) {
print(ApplicationConstants.APP_NAME + ": " + testObject.toString());
}
}
或者,您可以按 objectId 获取对象:
var dietPlan = await DietPlan().getObject('R5EonpUDWy');
if (dietPlan.success) {
print(ApplicationConstants.keyAppName + ": " + (dietPlan.result as DietPlan).toString());
} else {
print(ApplicationConstants.keyAppName + ": " + dietPlan.exception.message);
}
要查询用户对象,您可以使用 `ParseUser.forQuery()` 函数,如下所示:
var queryBuilder = QueryBuilder<ParseUser>(ParseUser.forQuery())
..whereEqualTo('activated', true);
var response = await queryBuilder.query();
if (response.success) {
print(response.results);
} else {
print(response.exception.message);
}
替代查询方法
标准的 `query()` 方法返回一个 `ParseResponse`,其中包含结果或错误。作为替代,您还可以使用 `Future<List<T>> find()` 来接收选项。此方法返回一个 `Future`,该 `Future` 要么解析为错误(等同于 `ParseResponse` 中的错误),要么解析为包含查询对象的 `List`。您应该注意的一个区别是,`Future<List<T>> find()` 在没有匹配您查询的对象时会返回一个空列表,而不是您在 `ParseResponse` 中收到的“未找到结果”错误。
选择 `query()` 和 `find()` 取决于个人喜好。这两种方法都可以用于查询 `ParseQuery`,只是输出方法不同。
与 `find()` 类似,`QueryBuilder` 还有一个名为 `Future<T>? first()` 的函数。与 `find()` 一样,`first()` 只是一个方便的方法,可以更轻松地查询满足查询的第一个对象。`first()` 返回一个 `Future`,它会解析为错误或匹配查询的第一个对象。如果没有对象满足查询,结果将为 `null`。
复杂的查询
您可以创建复杂的查询来真正考验您的数据库。
var queryBuilder = QueryBuilder<DietPlan>(DietPlan())
..startsWith(DietPlan.keyName, "Keto")
..greaterThan(DietPlan.keyFat, 64)
..lessThan(DietPlan.keyFat, 66)
..equals(DietPlan.keyCarbs, 5);
var response = await queryBuilder.query();
if (response.success) {
print(ApplicationConstants.keyAppName + ": " + ((response.results as List<dynamic>).first as DietPlan).toString());
} else {
print(ApplicationConstants.keyAppName + ": " + response.exception.message);
}
如果您想查找满足多个查询中任何一个的对象,您可以使用 **QueryBuilder.or** 方法来构建一个 OR 查询。例如,如果您想查找胜场很多或胜场很少的玩家,您可以这样做:
ParseObject playerObject = ParseObject("Player");
QueryBuilder<ParseObject> lotsOfWins =
QueryBuilder<ParseObject>(playerObject))
..whereGreaterThan('wins', 50);
QueryBuilder<ParseObject> fewWins =
QueryBuilder<ParseObject>(playerObject)
..whereLessThan('wins', 5);
QueryBuilder<ParseObject> mainQuery = QueryBuilder.or(
playerObject,
[lotsOfWins, fewWins],
);
var apiResponse = await mainQuery.query();
可用的功能包括:
- Equals(等于)
- Contains(包含)
- LessThan(小于)
- LessThanOrEqualTo(小于等于)
- GreaterThan(大于)
- GreaterThanOrEqualTo(大于等于)
- NotEqualTo(不等于)
- StartsWith(以...开头)
- EndsWith(以...结尾)
- Exists(存在)
- Near(附近)
- WithinMiles(在...英里范围内)
- WithinKilometers(在...公里范围内)
- WithinRadians(在...弧度范围内)
- WithinGeoBox(在地理框范围内)
- WithinPolygon(在多边形范围内)
- MatchesQuery(匹配查询)
- DoesNotMatchQuery(不匹配查询)
- MatchesKeyInQuery(在查询中匹配键)
- DoesNotMatchKeyInQuery(在查询中不匹配键)
- Regex(正则表达式)
- 订单
- Limit(限制)
- Skip(跳过)
- Ascending(升序)
- Descending(降序)
- Plenty more!(更多!)
关系查询
如果您想检索某个字段包含匹配另一个查询的对象的对象,您可以使用 **whereMatchesQuery** 条件。例如,假设您有一个 Post 类和一个 Comment 类,其中每个 Comment 都指向其父 Post。您可以通过以下方式查找具有图像的帖子的评论:
QueryBuilder<ParseObject> queryPost =
QueryBuilder<ParseObject>(ParseObject('Post'))
..whereValueExists('image', true);
QueryBuilder<ParseObject> queryComment =
QueryBuilder<ParseObject>(ParseObject('Comment'))
..whereMatchesQuery('post', queryPost);
var apiResponse = await queryComment.query();
如果您想检索某个字段包含不匹配另一个查询的对象,您可以使用 **whereDoesNotMatchQuery** 条件。假设您有一个 Post 类和一个 Comment 类,其中每个 Comment 都指向其父 Post。您可以通过以下方式查找没有图像的帖子的评论:
QueryBuilder<ParseObject> queryPost =
QueryBuilder<ParseObject>(ParseObject('Post'))
..whereValueExists('image', true);
QueryBuilder<ParseObject> queryComment =
QueryBuilder<ParseObject>(ParseObject('Comment'))
..whereDoesNotMatchQuery('post', queryPost);
var apiResponse = await queryComment.query();
您可以使用 **whereMatchesKeyInQuery** 方法来获取其键值与另一查询结果集中的键值匹配的对象。例如,如果您有一个包含体育队伍的类,并在用户类中存储了用户的家乡,您可以发出一个查询来查找家乡队伍有获胜记录的用户列表。查询将如下所示:
QueryBuilder<ParseObject> teamQuery =
QueryBuilder<ParseObject>(ParseObject('Team'))
..whereGreaterThan('winPct', 0.5);
QueryBuilder<ParseUser> userQuery =
QueryBuilder<ParseUser>ParseUser.forQuery())
..whereMatchesKeyInQuery('hometown', 'city', teamQuery);
var apiResponse = await userQuery.query();
相反,要获取其键值与另一查询结果集中的键值不匹配的对象,请使用 **whereDoesNotMatchKeyInQuery**。例如,查找家乡队伍战绩不佳的用户:
QueryBuilder<ParseObject> teamQuery =
QueryBuilder<ParseObject>(ParseObject('Team'))
..whereGreaterThan('winPct', 0.5);
QueryBuilder<ParseUser> losingUserQuery =
QueryBuilder<ParseUser>ParseUser.forQuery())
..whereDoesNotMatchKeyInQuery('hometown', 'city', teamQuery);
var apiResponse = await losingUserQuery.query();
要根据第二个表中的指针的 objectId 来过滤行,您可以使用点表示法。
QueryBuilder<ParseObject> rolesOfTypeX =
QueryBuilder<ParseObject>(ParseObject('Role'))
..whereEqualTo('type', 'x');
QueryBuilder<ParseObject> groupsWithRoleX =
QueryBuilder<ParseObject>(ParseObject('Group')))
..whereMatchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
var apiResponse = await groupsWithRoleX.query();
计算对象数量
如果您只关心某个玩家玩过的游戏数量:
QueryBuilder<ParseObject> queryPlayers =
QueryBuilder<ParseObject>(ParseObject('GameScore'))
..whereEqualTo('playerName', 'Jonathan Walsh');
var apiResponse = await queryPlayers.count();
if (apiResponse.success && apiResponse.result != null) {
int countGames = apiResponse.count;
}
实时查询
此工具允许您订阅您感兴趣的 QueryBuilder。订阅后,服务器将在 ParseObject 匹配 QueryBuilder 时创建或更新时实时通知客户端。
Parse LiveQuery 包含两个部分:LiveQuery 服务器和 LiveQuery 客户端。要使用实时查询,您需要同时设置它们。
服务器上的 Parse Server 配置指南可以在此处找到:https://docs.parseplatform.org/parse-server/guide/,不属于本文档。
通过在 `Parse().initialize` 中输入 `liveQueryUrl` 参数来初始化 Parse Live Query。
Parse().initialize(
keyApplicationId,
keyParseServerUrl,
clientKey: keyParseClientKey,
debug: true,
liveQueryUrl: keyLiveQueryUrl,
autoSendSessionId: true);
声明 LiveQuery
final LiveQuery liveQuery = LiveQuery();
设置将由 LiveQuery 监视的 QueryBuilder。
QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('TestAPI'))
..whereEqualTo('intNumber', 1);
创建订阅您将通过此订阅获取 LiveQuery 事件。第一次调用 `subscribe` 时,我们将尝试为您打开到 LiveQuery 服务器的 WebSocket 连接。
Subscription subscription = await liveQuery.client.subscribe(query);
事件处理我们定义了几种您将通过订阅对象获取的事件类型:
Create event(创建事件)当创建一个新的 ParseObject 并满足您订阅的 QueryBuilder 时,您将收到此事件。该对象是已创建的 ParseObject。
subscription.on(LiveQueryEvent.create, (value) {
print('*** CREATE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
Update event(更新事件)当满足您订阅的 QueryBuilder 的现有 ParseObject 更新时(在更改之前和之后都满足 QueryBuilder),您将收到此事件。该对象是已更新的 ParseObject。其内容是 ParseObject 的最新值。
subscription.on(LiveQueryEvent.update, (value) {
print('*** UPDATE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
Enter event(进入事件)当现有 ParseObject 的旧值不满足 QueryBuilder,但其新值满足 QueryBuilder 时,您将收到此事件。该对象是进入 QueryBuilder 的 ParseObject。其内容是 ParseObject 的最新值。
subscription.on(LiveQueryEvent.enter, (value) {
print('*** ENTER ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
Leave event(离开事件)当现有 ParseObject 的旧值满足 QueryBuilder,但其新值不满足 QueryBuilder 时,您将收到此事件。该对象是离开 QueryBuilder 的 ParseObject。其内容是 ParseObject 的最新值。
subscription.on(LiveQueryEvent.leave, (value) {
print('*** LEAVE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
Delete event(删除事件)当满足您订阅的 QueryBuilder 的现有 ParseObject 被删除时,您将收到此事件。该对象是被删除的 ParseObject。
subscription.on(LiveQueryEvent.delete, (value) {
print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
Unsubscribe(取消订阅)如果您想停止接收来自 QueryBuilder 的事件,您可以取消订阅。之后,您将不会收到订阅对象的任何事件,并且将关闭到 LiveQuery 服务器的 WebSocket 连接。
liveQuery.client.unSubscribe(subscription);
Disconnection(断开连接)如果客户端与服务器的连接中断,LiveQuery 将自动尝试重新连接。LiveQuery 将在重连尝试之间等待递增的间隔。默认情况下,这些间隔设置为移动设备的 `[0, 500, 1000, 2000, 5000, 10000]`,Web 设备的 `[0, 500, 1000, 2000, 5000]`。您可以通过在 `Parse.initialize()` 中使用 `liveListRetryIntervals` 参数提供自定义列表来更改这些间隔(“-1”表示“不尝试重新连接”)。
ParseLiveList
ParseLiveList 使实现动态列表尽可能简单。
通用用法
它附带了 ParseLiveList 类本身,该类管理列表的所有元素,对它们进行排序,保持自身最新并通知您更改。
ParseLiveListWidget 是一个处理与 ParseLiveList 所有通信的小部件。使用 ParseLiveListWidget,您只需提供一个 QueryBuilder 即可创建动态列表。
ParseLiveListWidget<ParseObject>(
query: query,
);
要自定义列表元素,您可以提供一个 `childBuilder`。
ParseLiveListWidget<ParseObject>(
query: query,
reverse: false,
childBuilder:
(BuildContext context, ParseLiveListElementSnapshot<ParseObject> snapshot) {
if (snapshot.failed) {
return const Text('something went wrong!');
} else if (snapshot.hasData) {
return ListTile(
title: Text(
snapshot.loadedData.get("text"),
),
);
} else {
return const ListTile(
leading: CircularProgressIndicator(),
);
}
},
);
与标准的 ListView 类似,您可以提供 `reverse` 或 `shrinkWrap` 等参数。通过提供 `listLoadingElement`,您可以向用户显示一些内容,同时列表正在加载。
ParseLiveListWidget<ParseObject>(
query: query,
childBuilder: childBuilder,
listLoadingElement: Center(
child: CircularProgressIndicator(),
),
);
通过提供 `duration` 参数,您可以更改动画速度。
ParseLiveListWidget<ParseObject>(
query: query,
childBuilder: childBuilder,
duration: Duration(seconds: 1),
);
包含的子对象
默认情况下,ParseLiveQuery 将为您提供您在查询中包含的所有对象,如下所示:
queryBuilder.includeObject(/*List of all the included sub-objects*/);
默认情况下,ParseLiveList 不会监听这些对象的更新。要激活对所有包含的子对象的更新监听,请在 ParseLiveListWidgets 的构造函数中添加 `listenOnAllSubItems: true`。如果您想让 ParseLiveList 监听部分子对象的更新,请使用 `listeningIncludes: const
延迟加载
默认情况下,ParseLiveList 会延迟加载内容。您可以通过设置 `lazyLoading: false` 来避免这种情况。如果您想使用 lazyLoading 但需要预加载某些列,您可以提供一个 `preloadedColumns` 列表。通过使用点表示法支持预加载指针的字段。您可以在 `ParseLiveListElementSnapshot` 的 `preLoadedData` 字段中访问预加载的数据。
ParseLiveListWidget<ParseObject>(
query: query,
lazyLoading: true,
preloadedColumns: ["test1", "sender.username"],
childBuilder:
(BuildContext context, ParseLiveListElementSnapshot<ParseObject> snapshot) {
if (snapshot.failed) {
return const Text('something went wrong!');
} else if (snapshot.hasData) {
return ListTile(
title: Text(
snapshot.loadedData.get<String>("text"),
),
);
} else {
return ListTile(
title: Text(
"loading comment from: ${snapshot.preLoadedData?.get<ParseObject>("sender")?.get<String>("username")}",
),
);
}
},
);
注意: 要使用这些功能,您必须首先启用 实时查询。
Users(用户)
您可以使用此 SDK 像平常一样创建和控制用户。
要注册用户,请先创建一个用户:
var user = ParseUser().create("TestFlutter", "TestPassword123", "[email protected]");
然后让用户注册:
var response = await user.signUp();
if (response.success) user = response.result;
您也可以使用用户登录:
var response = await user.login();
if (response.success) user = response.result;
您也可以使用用户登出:
var response = await user.logout();
if (response.success) {
print('User logout');
}
此外,登录后您可以管理会话令牌。此功能可以在启动时调用 `Parse().init()` 之后检查是否有已登录用户。
user = ParseUser.currentUser();
为用户添加额外的列:
var user = ParseUser("TestFlutter", "TestPassword123", "[email protected]")
..set("userLocation", "FlutterLand");
其他用户功能包括:
- 请求密码重置
- 请求验证电子邮件
- 获取所有用户
- Save(保存)
- 删除用户
- Queries(查询)
Facebook、OAuth 和第三方登录/用户
通常,每个提供商都会提供自己的登录库,但 `ParseUser` 上的 `loginWith` 方法接受提供商名称,然后是一个 `Map<String, dynamic>`,其中包含所需的身份验证详细信息。对于 Facebook 和下面的示例,我们使用了 https://pub.dev/packages/flutter_facebook_login 提供的库。
Future<void> goToFacebookLogin() async {
final FacebookLogin facebookLogin = FacebookLogin();
final FacebookLoginResult result = await facebookLogin.logInWithReadPermissions(['email']);
switch (result.status) {
case FacebookLoginStatus.loggedIn:
final ParseResponse response = await ParseUser.loginWith(
'facebook',
facebook(result.accessToken.token,
result.accessToken.userId,
result.accessToken.expires));
if (response.success) {
// User is logged in, test with ParseUser.currentUser()
}
break;
case FacebookLoginStatus.cancelledByUser:
// User cancelled
break;
case FacebookLoginStatus.error:
// Error
break;
}
}
对于 Google 和下面的示例,我们使用了 https://pub.dev/packages/google_sign_in 提供的库。
class OAuthLogin {
final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email', 'https://www.googleapis.com/auth/contacts.readonly'] );
signInGoogle() async {
GoogleSignInAccount account = await _googleSignIn.signIn();
GoogleSignInAuthentication authentication = await account.authentication;
await ParseUser.loginWith(
'google',
google(authentication.accessToken!, _googleSignIn.currentUser!.id,
authentication.idToken!));
}
}
对象安全 – ParseACL
对于任何对象,您可以指定允许哪些用户读取该对象,以及允许哪些用户修改该对象。为了支持这种安全性,每个对象都有一个访问控制列表,由 **ParseACL** 类实现。
如果未指定 ParseACL(ParseUser 类除外),则所有对象都设置为公共读写。使用 ParseACL 的最简单方法是指定对象只能由单个用户读取或写入。要创建这样的对象,必须先有一个已登录的 ParseUser。然后,`new ParseACL(user)` 会生成一个 ParseACL,将访问限制在该用户。对象的 ACL 在保存对象时更新,就像任何其他属性一样。
ParseUser user = await ParseUser.currentUser() as ParseUser;
ParseACL parseACL = ParseACL(owner: user);
ParseObject parseObject = ParseObject("TestAPI");
...
parseObject.setACL(parseACL);
var apiResponse = await parseObject.save();
权限也可以按用户授予。您可以使用 **setReadAccess** 和 **setWriteAccess** 将权限单独添加到 ParseACL。
ParseUser user = await ParseUser.currentUser() as ParseUser;
ParseACL parseACL = ParseACL();
//grant total access to current user
parseACL.setReadAccess(userId: user.objectId, allowed: true);
parseACL.setWriteAccess(userId: user.objectId, allowed: true);
//grant read access to userId: 'TjRuDjuSAO'
parseACL.setReadAccess(userId: 'TjRuDjuSAO', allowed: true);
parseACL.setWriteAccess(userId: 'TjRuDjuSAO', allowed: false);
ParseObject parseObject = ParseObject("TestAPI");
...
parseObject.setACL(parseACL);
var apiResponse = await parseObject.save();
您还可以使用 `setPublicReadAccess` 和 `setPublicWriteAccess` 一次性授予所有用户权限。
ParseACL parseACL = ParseACL();
parseACL.setPublicReadAccess(allowed: true);
parseACL.setPublicWriteAccess(allowed: true);
ParseObject parseObject = ParseObject("TestAPI");
...
parseObject.setACL(parseACL);
var apiResponse = await parseObject.save();
被禁止的操作,例如删除您没有写入访问权限的对象,会产生一个 ParseError,代码为 101:“ObjectNotFound”。出于安全原因,这可以防止客户端区分哪些 objectId 存在但已受保护,与哪些 objectId 完全不存在。
您可以使用以下方法检索对象的 ACL 列表:
ParseACL parseACL = parseObject.getACL();
Config(配置)
SDK 支持 Parse Config。可以通过调用以下方法从服务器获取所有配置的映射:
var response = await ParseConfig().getConfigs();
并添加配置:
ParseConfig().addConfig('TestConfig', 'testing');
Cloud Functions(云函数)
SDK 支持调用云函数。
执行返回 ParseObject 类型的云函数。
final ParseCloudFunction function = ParseCloudFunction('hello');
final ParseResponse result =
await function.executeObjectFunction<ParseObject>();
if (result.success) {
if (result.result is ParseObject) {
final ParseObject parseObject = result.result;
print(parseObject.className);
}
}
执行带参数的云函数。
final ParseCloudFunction function = ParseCloudFunction('hello');
final Map<String, String> params = <String, String>{'plan': 'paid'};
function.execute(parameters: params);
Relation(关联)
SDK 支持 Relation。
向对象添加关联:
dietPlan.addRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
移除对象的关联:
dietPlan.removeRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
要检索用户关联实例,请调用:
final relation = dietPlan.getRelation('fruits');
然后您可以向传入的对象添加关联:
relation.add(dietPlan);
final result = await user.save();
要检索作为父对象 Relation 字段成员的对象:
QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('Fruits'))
..whereRelatedTo('fruits', 'DietPlan', DietPlan.objectId);
File(文件)
此 SDK 中有三种不同的文件类:
ParseFileBase是一个抽象类,是此 SDK 可以处理的每个文件类的基础。ParseFile(以前是 SDK 中唯一的文件类)扩展了 `ParseFileBase`,并且默认用作除 Web 以外的每个平台的文件类。此类使用 `dart:io` 中的 `File` 来存储原始文件。ParseWebFile是 Flutter Web 中使用的 `ParseFile` 的等效类。此类使用 `Uint8List` 来存储原始文件。
这些类默认用于表示文件,但您也可以构建自己的类,扩展 `ParseFileBase` 并提供自定义的 `ParseFileConstructor`,这与 `SubClasses` 类似。
请查看示例应用程序了解一个(非 Web)示例。
在上传或下载文件时,您可以使用 `progressCallback` 参数来跟踪 http 请求的进度。
//A short example for showing an image from a ParseFileBase
Widget buildImage(ParseFileBase image){
return FutureBuilder<ParseFileBase>(
future: image.download(),
builder: (BuildContext context,
AsyncSnapshot<ParseFileBase> snapshot) {
if (snapshot.hasData) {
if (kIsWeb) {
return Image.memory((snapshot.data as ParseWebFile).file);
} else {
return Image.file((snapshot.data as ParseFile).file);
}
} else {
return CircularProgressIndicator();
}
},
);
}
//A short example for storing a selected picture
//libraries: image_picker (https://pub.dev/packages/image_picker), image_picker_for_web (https://pub.dev/packages/image_picker_for_web)
PickedFile pickedFile = await ImagePicker().getImage(source: ImageSource.gallery);
ParseFileBase parseFile;
if (kIsWeb) {
//Seems weird, but this lets you get the data from the selected file as an Uint8List very easily.
ParseWebFile file = ParseWebFile(null, name: null, url: pickedFile.path);
await file.download();
parseFile = ParseWebFile(file.file, name: file.name);
} else {
parseFile = ParseFile(File(pickedFile.path));
}
someParseObject.set("image", parseFile);
//This saves the ParseObject as well as all of its children, and the ParseFileBase is such a child.
await someParseObject.save();
此库的其他功能
Main
- 安装(查看示例应用程序)
- GeoPoints(地理点)(查看示例应用程序)
- Persistent storage(持久化存储)
- Debug Mode – Logging API calls(调试模式 – 记录 API 调用)
- Manage Session ID’s tokens(管理会话 ID 令牌)
用户
- Queries(查询)
- Anonymous(匿名)(查看示例应用程序)
- 3rd Party Authentication(第三方身份验证)
对象
- Create new object(创建新对象)
- Extend Parse Object and create local objects that can be saved and retrieved(扩展 Parse Object 并创建可以保存和检索的本地对象)
- Queries(查询)
Author(作者):
本项目由 Phill Wiggins 编写。您可以通过 [email protected] 与我联系。
