Flutter Planning Center API集成插件planningcenter_api的使用

Flutter Planning Center API 集成插件 planningcenter_api 的使用

概述

Planning Center 是一个在线平台,用于教会管理。它提供了多个应用程序,包括签到、服务规划、志愿者管理、CRM、捐赠等。此外,它还提供了一个强大的 API 来远程访问其系统的几乎所有方面。

特性

该包提供了对 JSON:API 规范文档中描述的整个 API 的完全覆盖:

https://api.planningcenteronline.com/[APPLICATION_NAME]/v2/documentation

大多数代码已自动生成,以在 PlanningCenter API 周围创建 Dart 抽象,并详细记录每个 API 调用。此库实现了截至 2022-03-01 的最新 PlanningCenter API 版本。

该包支持通过开发人员密钥和密钥进行身份验证:

https://api.planningcenteronline.com/oauth/applications

或者通过 OAuth2 身份验证凭据(请参阅示例以了解详情)。

开始使用

安装包:

dart pub add planningcenter_api

在 Dart 文件中导入包:

import 'package:planningcenter_api/planningcenter_api.dart';

使用

在可以访问 Planning Center API 之前,需要初始化库。有三种初始化方式:

/// 选项 1: 使用开发人员级别的身份验证,指定 `appId` 和 `secret`。
PlanningCenter.init(appId, secret);

/// 选项 2: 使用 [PlanningCenterCredentials] 对象(可能从文件加载)
PlanningCenter.initWithCredentials(credentials);

/// 选项 3: 等待 OAuth 授权流程
/// [redirector] 是返回 Future<String> 的函数,应该解析为授权码
/// 请参阅示例了解如何在命令行应用程序中执行此操作
await PlanningCenter.authorize(clientId, clientSecret, redirectUrl, scopesList, redirector);

可以通过检查 initialized 静态成员来确定库是否已初始化:

if (PlanningCenter.initialized) {
  // 你的代码在这里
}

一旦库初始化完成,你可以直接使用静态成员 PlanningCenter.instance 访问 API 对象,并通过 PlanningCenter.instance.call 发起 API 调用:

/// [data] 对象应为 JSON 字符串或可序列化为 JSON 的对象,将发送到端点。[call] 函数还通过 [PlanningCenterApiQuery] 包装器支持查询变量。
///
/// 此示例将在具有 id [personId] 的人员上附加电子邮件地址 [personEmail]
PlanningCenterApiResponse res = await PlanningCenter.instance.call(
  '/people/v2/people/${personId}/emails', {
  verb: 'post',
  data: {
    'data': {
      'attributes': {
        'address': personEmail,
        'location': 'Home',
        'primary': true
      }
    }
  },
  apiVersion: '2022-01-28',
});

PlanningCenter.instance.call 几乎与 http 客户端的工作方式相同,但它在后台提供了有用的包装器来处理查询变量和身份验证。具体来说,如果需要刷新授权令牌,它会在继续其余 API 调用之前先处理此问题。

虽然可以直接使用 PlanningCenter.instance 发起 API 调用,但推荐的方法是使用库中提供的类关联的方法。这样,你可以在 IDE 中获得代码建议(Intellisense)和文档。

上述向人员添加电子邮件地址的示例应重写如下:

var email = PcoPeopleEmail(personId, address: 'email@example.com', location: 'Home', isPrimary: true);
email.save();

文件上传

你可以将文件上传到 PlanningCenter 以便在其他资源中使用:

var r3 = await PlanningCenterApiFile.upload('myImage.jpg');
if (r3.isError) {
  print(r3.errorMessage);
} else {
  var f = (r3.data.first);
  print('File was successfully uploaded... it can now be attached to other objects by using its UUID');
  print('UUID: ${f.id}');
  print('CONTENT-TYPE: ${f.contentType}');
}

应用程序资源类

库中的类命名遵循 PlanningCenter 开头的惯例,而资源对象类则遵循 Pco + 应用名称 + 资源名称的惯例。例如:PcoPeopleEmailPcoServicesPlan

通过输入 Pco,你的 IDE 应该能够获取可用的资源类。

静态方法

每个可以检索的 PlanningCenter 资源都可通过相关类上的静态方法提供给你。

静态方法始终返回 Future<PcoCollection<T>>

PcoCollection<PcoPeopleEmail> emails = await PcoPeopleEmail.getFromPeople(personId);
print(emails.items);

实例关系方法

一旦你拥有 PlanningCenter 资源,你可以通过实例方法获取相关资源。

实例方法始终返回 Future<PcoCollection<T>>

PcoCollection<PcoPeopleEmail> emails = await PcoPeopleEmail.getFromPeople(personId);
var myEmail = emails.items.first;
PcoCollection<PcoPeoplePerson> myProfile = await myEmail.getPerson();
print(myProfile.items.first);

包含相关对象

每当 API 对象允许请求相关对象时,这些选项会通过相关方法暴露出来,并且可以通过类型安全的 getter 获取包含的关系。如果 API 将包含作为复数(即 ‘emails’)暴露,则响应将是列表,否则,响应将是可为空的对象。

var collection = await PcoPeoplePerson.get(id: '000000001', includeEmails: true);
var person = collection.items.first;
List<PcoPeopleEmail> emails = person.includedEmails;
PcoPeopleCampus? primaryCampus = person.includedPrimaryCampus;

实例动作方法

每个 PlanningCenter 资源都可以有不同的操作与其关联,并且这些操作可以通过实例方法访问。

  • 可以创建或更新的资源暴露了 save 方法。
  • 可以删除的资源暴露了 delete 方法。
  • 其他操作可能会或可能不会作为相关类的实例方法暴露。

注意:操作应有文档说明,但没有助手代码。你需要发布文档中指定的确切数据。发布的数据应遵循 JSON:API 规范。为此提供了一个 PcoData 类。

var res = await PcoServicesPlan.getFromServiceType('1234567');
if (!res.isError) {
  var plan = res.data.first;
  var r2 = await plan.itemReorder(
    PcoData(
      'PlanItemReorder',
      attributes: {'sequence': ['5', '1', '3']},
    ),
  );
  if (r2.isError) {
    print(r2.errorMessage);
  } else {
    print('success');
  }
} else {
  print(res.errorMessage);
}

提示

你可以始终通过打印 res.errorMessage 查看 API 请求的错误。

如果你想从头开始获取一些 PlanningCenter 数据,你可能想要调用表示所需数据的类上的静态方法。如果你已经有了一些数据并想获取相关数据,请调用对象本身的方法。

代码应有良好的文档说明。如果你有任何疑问,请创建 GitHub 问题。

示例

import 'dart:async';
import 'dart:io'; // 为了更快地退出脚本
import 'dart:convert'; // 为了美观地打印 JSON

import 'package:planningcenter_api/planningcenter_api.dart';

/// 这里存储我的 [appid], [secret], [oAuthClientId], 和 [oAuthClientSecret] 常量
import '../secrets.dart';

void debug(Object o) {
  try {
    print(JsonEncoder.withIndent('  ').convert(o));
  } on JsonUnsupportedObjectError catch (e) {
    print('DEBUG AS STRING BECAUSE: $e');
    print(o);
  }
}

Future checkResponseError(PlanningCenterApiResponse res) async {
  if (res is PlanningCenterApiError) {
    debug(res.message);
    debug(res.responseBody);
  } else {
    debug('Success!');
  }
}

/// 这里的实际示例代码
void main() async {
  // 首先初始化 PlanningCenter API
  // 这种初始化将使用开发者 appid 和密钥,从而让你访问开发者可以访问的所有内容
  // PlanningCenter.init(appid, secret);

  // 这种初始化将使用 OAuth
  var credentialsFile = File('credentials.json');
  if (await credentialsFile.exists()) {
    try {
      var credentials = json.decode(await credentialsFile.readAsString());
      var creds = PlanningCenterCredentials.fromJson(credentials);
      PlanningCenter.initWithCredentials(oAuthClientId, oAuthClientSecret, creds);
    } catch (e) {
      print(e);
      print('无法读取凭证文件');
    }
  }

  if (!PlanningCenter.initialized) {
    print('正在使用 OAuth 授权 PlanningCenter');
    await PlanningCenter.authorize(
      oAuthClientId,
      oAuthClientSecret,
      PlanningCenter.oAuthScopes,
      redirectUri: 'http://localhost:64738/pco_callback',
    );
  }

  if (!PlanningCenter.initialized) {
    print('PlanningCenter 身份验证失败');
    exit(1);
  }

  // 现在,所有以 Pco 开头的类都可供使用

  /// 获取默认组织的服务类型(默认抓取 25 个)
  /// 将返回 List<PcoServicesServiceType>
  var collection = await PcoServicesServiceType.get();
  debug(collection.response);
  if (!collection.isError) {
    var service = collection.items.first;
    print('找到服务类型: ${service.name}');

    /// 大多数类实例都有方法允许你获取相关项目
    /// 这次,我们还使用查询对象按降序请求计划的排序日期
    var plans = await service.getPlans(
      query: PcoServicesPlanQuery(orderBy: PcoServicesPlanOrder.sortDate, reverse: true),
    );
    if (!plans.isError) {
      var plan = plans.items.first;
      print('找到计划: ${plan.seriesTitle} - ${plan.title} - ${plan.lastTimeAt}');
      var items = await plan.getItems();
      for (var item in items.items) {
        print('计划项目: ${item.title}\n${item.description}\n');
        if (item.title == 'CHANGE ME') {
          print('尝试更新此项目');
          item.title = 'CHANGED';
          var result = await item.save();
          print(result.isError ? '失败' : '成功');
        }
      }
    }
  } else {
    print(collection.error!.message);
  }

  // 直接调用 API,你可以这样做,但不会返回
  // 类型化的数据... 只是一个适度解析的 PlanningCenterApiResponse 对象
  var res = await PlanningCenter.instance.call('/services/v2/songs');
  checkResponseError(res);
  debug(res.toJson());

  // 一旦我们完成了客户端,保存凭证文件。这确保了
  // 如果在使用客户端期间自动刷新了凭证,新的凭证将在下一次程序运行中可用。
  if (PlanningCenter.instance.oAuthCredentials != null) {
    await credentialsFile.create(recursive: true);
    await credentialsFile.writeAsString(json.encode(PlanningCenter.instance.oAuthCredentials));
  }

  // var email = PcoPeopleEmail('1', address: 'email@example.com', location: 'Home', isPrimary: true);
  // email.save();
  // PcoCollection<PcoPeopleEmail> emails = await PcoPeopleEmail.getFromPeople('1');
  // var myEmail = emails.data.first;
  // var myProfile = await myEmail.getPerson();
  // print(myProfile.data.first);

  var r = await PcoServicesPlan.getFromServiceType('1234567');
  if (!r.isError) {
    var plan = r.items.first;
    var r2 = await plan.itemReorder(PlanningCenterApiData('PlanItemReorder', attributes: {
      'sequence': ['5', '1', '3']
    }));
    checkResponseError(r2);
    debug(r2.toJson());
  }

  var r3 = await PlanningCenterApiFile.upload('myImage.jpg');
  if (r3.isError) {
    debug(r3.errorMessage);
    debug(r3.responseBody);
  } else {
    var f = (r3.data.first);
    print('文件成功上传...现在可以通过其 UUID 附加到其他对象');
    print('UUID: ${f.id}');
    print('CONTENT-TYPE: ${f.contentType}');
  }

  /// 这里是如何向捐赠模块添加捐赠的一个示例

  // 首先,添加一个批次
  var batch = PcoGivingBatch();
  await batch.save();

  // 现在创建一个捐赠对象
  var don = PcoGivingDonation(
    batchId: batch.id!,
    personId: '1234',
    paymentMethod: 'cash',
    paymentCheckNumber: 1234,
    paymentSourceId: '289',
    receivedAt: DateTime.now(),
  );

  // 现在使用两个指定项将其保存到服务器
  // 注意,不带指定项保存捐赠将失败。
  var r4 = await don.saveWithDesignations([
    PcoGivingDesignation(
      amountCents: 1234,
      withRelationships: {
        'fund': [PcoGivingFund(id: '1')]
      },
    ),
    PcoGivingDesignation(
      amountCents: 5678,
      withRelationships: {
        'fund': [PcoGivingFund(id: '2')]
      },
    )
  ]);
  checkResponseError(r4);
  debug(r4.toJson());

  exit(0);
}

更多关于Flutter Planning Center API集成插件planningcenter_api的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Planning Center API集成插件planningcenter_api的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中集成和使用planningcenter_api插件的示例代码。这个插件假设你已经有一个有效的Planning Center账户和API令牌。请注意,为了简化示例,这里假设你已经设置好Flutter开发环境并创建了一个新的Flutter项目。

步骤 1: 添加依赖

首先,在你的pubspec.yaml文件中添加planningcenter_api依赖。

dependencies:
  flutter:
    sdk: flutter
  planningcenter_api: ^最新版本号  # 请替换为实际的最新版本号

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

步骤 2: 配置API密钥

为了与Planning Center API进行通信,你需要提供一个有效的API密钥。出于安全考虑,建议不要在代码中硬编码API密钥。你可以使用Flutter的SharedPreferences或环境变量来管理敏感信息。

步骤 3: 初始化API客户端

在你的Flutter应用中初始化PlanningCenterApi客户端。这里是一个简单的示例,展示了如何初始化并使用它来获取一些数据。

import 'package:flutter/material.dart';
import 'package:planningcenter_api/planningcenter_api.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PlanningCenterScreen(),
    );
  }
}

class PlanningCenterScreen extends StatefulWidget {
  @override
  _PlanningCenterScreenState createState() => _PlanningCenterScreenState();
}

class _PlanningCenterScreenState extends State<PlanningCenterScreen> {
  late PlanningCenterApi _apiClient;

  @override
  void initState() {
    super.initState();
    _initializeApiClient();
  }

  Future<void> _initializeApiClient() async {
    final SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    String apiToken = sharedPreferences.getString('PLANNING_CENTER_API_TOKEN') ?? '';

    if (apiToken.isEmpty) {
      // Handle the case where the API token is not found
      print('API token not found. Please configure it.');
      return;
    }

    _apiClient = PlanningCenterApi(apiToken: apiToken);

    // Example: Fetch some data (e.g., people)
    _fetchPeople();
  }

  Future<void> _fetchPeople() async {
    try {
      final people = await _apiClient.people.list();
      print('Fetched people: $people');
      // You can update your UI with the fetched data here
    } catch (error) {
      print('Error fetching people: $error');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Planning Center API Integration'),
      ),
      body: Center(
        child: Text('Loading...'), // You can replace this with actual UI widgets once you have the data
      ),
    );
  }
}

注意事项

  1. API端点和方法:上面的示例使用了people.list()方法,这只是一个假设的方法。你需要查阅planningcenter_api插件的文档,了解可用的端点和方法。

  2. 错误处理:在实际应用中,你需要更细致地处理API调用中的错误,比如网络错误、认证错误等。

  3. 安全性:确保你的API密钥安全存储,不要在客户端代码中硬编码敏感信息。

  4. UI更新:上面的示例中并没有更新UI,因为示例重点在于API集成。在实际应用中,你需要在获取数据后更新UI以显示数据。

  5. 文档:查阅Planning Center API文档planningcenter_api插件的文档,以获取更多信息和示例。

这个示例提供了一个基本的框架,你可以在此基础上根据你的需求进行扩展和定制。

回到顶部