Parse Logo Flutter Logo


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 [/*所有包含的子对象*/]`。就像 QueryBuilder 一样,ParseLiveList 也支持嵌套的子对象。

延迟加载

默认情况下,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] 与我联系。

GitHub

查看 Github