构建 Flutter 后端分步指南
 
 本文将讨论 Flutter,这是 Google 开发的一款开源 UI 软件开发工具包。
我们将探讨使用 Flutter 的优缺点,并重点介绍 Flutter 应用程序的不同后台选项。
最后,您将学习如何使用 Back4apps 的后端即服务(BaaS)功能为 Flutter 应用程序构建一个可用的后端。
Contents
什么是Flutter?
Flutter 是一个跨平台开发框架,可让您快速构建原生感的 Android、iOS、Web、Linux、macOS 和 Windows 应用程序。
有了 Flutter,您会发现构建特定平台应用程序的许多复杂问题都迎刃而解了。Flutter 还以其快速的性能和漂亮的 UI 部件而闻名。
Flutter 团队自己将这项技术定义为
Google的一个开源框架,用于从单一代码库中构建美观的、本地编译的多平台应用程序。
Flutter 的三大优势和局限
| 优势 | 局限性 | 
|---|---|
| Flutter 是一个快速框架,具有大量即用型功能,可帮助您加快应用程序的开发时间。 | Flutter 应用程序往往比使用 React Native 等其他框架构建的应用程序更大。 | 
| Flutter 应用程序是跨平台的,可在 Android 和 iOS 上运行,为您节省开发时间和资金。 | Flutter 是一个相对较新的框架,社区规模相对较小。这可能会使您在需要时很难找到帮助和支持。 | 
| Flutter 易于学习,即使是初学者也不例外。精彩的文档让你轻松上手。 | Flutter 使用 Dart 编程语言,这种语言不像 Java 或 JavaScript 等其他语言那样广为人知。这可能会限制您可用的软件包。 | 
后台类型有哪些?
在构建任何应用程序时,选择正确的后端选项来处理服务器端任务是非常重要的。
每种类型的后端都有自己的强项,您的应用程序的特定需求应指导您选择后端。
下面的小节将重点介绍你可以使用的不同后台选项、它们是什么、如何工作以及它们如何最适合你的应用程序的需要。
IaaS
如果您要处理的是一个拥有众多用户的大型应用程序,那么IaaS是您应该考虑采用的后端选项。IaaS 是一种云计算模式,代表基础设施即服务。
云计算是一种由第三方供应商通过互联网提供计算资源(如服务器、存储和应用程序)的模式。
这些资源可按需访问,按需付费。使用 IaaS,云提供商将维护主要的底层计算基础设施,而您则可以完全控制应用程序的数据配置、操作系统和所有其他后端要求。
这使它成为需要增加或减少计算资源的应用程序的良好后端选择。
PaaS
平台即服务(PaaS)和基础设施即服务(IaaS)是两种不同的云计算模式,各有不同的用途。虽然二者都具有灵活性和可扩展性,但它们在开发和部署应用程序方面满足了不同的需求。
PaaS 后端将为您提供完整的开发环境,抽象出管理服务器、网络和存储的复杂性。如果您想专注于构建和维护应用程序,而无需担心基础设施管理,那么 PaaS 将是您的最佳选择。
BaaS
后端即服务(BaaS)后端可为您提供随时可用的后端服务和功能,使他们能够专注于构建和增强应用程序的用户体验,而无需管理后端基础设施。
有了 BaaS,开发人员可以通过应用程序接口访问用户验证、数据库和存储等功能,无需设置和维护后端服务器。
这就像使用一块现成的拼图,完全适合您的应用程序,省时省力。
从本质上讲,BaaS 简化了开发流程,使开发人员能够专注于应用程序面向用户的方面,同时依靠预构建的后端服务来处理繁重的工作。
如何使用后端即服务构建 Flutter 后端
本节将讨论如何开始构建 Flutter 应用程序的后端。Back4app 平台是构建可扩展、安全、灵活且易于部署的后端应用程序的绝佳选择。
Back4app 概览
Back4App 是一个后台即服务(BaaS)平台,通过提供完整的后台基础设施简化移动和网络应用程序开发。
使用 Back4App,您就可以专注于构建应用程序的前端功能。
该平台提供各种随时可用的后端服务,包括数据库、应用程序接口和云功能。
Back4app 的主要功能/优势包括
- 关系数据库和非关系数据库
- REST 和 GraphQL 应用程序接口
- 实时查询
- 多种身份验证选项
- 可扩展托管
- 推送和电子邮件通知
Back4App 使用 Parse 服务器,这是一个用于开发应用程序服务器端组件的开源工具包。支持多种技术。本参考指南列出了 Back4app 支持的不同技术。
项目介绍
本指南旨在构建一个 Back4app 后端,以支持 Flutter 应用程序。应用程序将使用 Parse 服务器 SDK 与设置好的后端进行连接和交互。
您要创建的应用程序是一个简单的联系人应用程序,允许用户创建和读取联系人。这些联系人将使用 Back4app 的 PostgreSQL 数据库支持进行存储。
用户只需在应用程序中输入联系人信息,即可添加新联系人。
在本指南结束时,您将对如何做有一个扎实的了解:
- 在 Back4app 平台上构建后端应用程序
- 写入结构化的 Back4app 数据库
- 处理 Parse 后台安全的基础知识。
先决条件
要学习本指南,您必须满足以下前提条件:
- 熟悉 Dart 语言和 Flutter 框架
- 在 Flutter 中操作状态的知识
- 运行应用程序的设备模拟器或模拟器。iOS 的 Xcode 或 Android 的 Android Studio。
- 基本了解关系型和非关系型数据库
创建应用程序
您需要有一个 Back4app 帐户来创建您的后台。如果您没有账户,可以通过注册免费的 账户来创建。
如果您已经拥有 Back4app 账户,请登录并创建新应用程序。选择 “后台即服务 “作为服务选项。

这将构建一个可扩展的 BaaS 后端。别忘了给你的应用程序起一个唯一的名字。对于数据库的选择,Back4app 提供 PostgreSQL 作为数据库选项。
PostgreSQL 是一种流行的开源关系数据库管理系统(RDBMS)。它以可扩展性、可靠性、高性能和安全性著称。
使用 Back4app 中的 PostgreSQL 测试版选项,您就可以利用这些功能和优势。

构建阶段应该很快就会结束。完成后,您将进入应用程序仪表板。
PostgreSQL 数据库
在 Back4App 中,您可以定义表示应用程序数据的类和字段。PostgreSQL 使用关系数据库,这是一种为应用程序用户存储数据的结构化方法。
如前所述,我们的 Flutter 应用程序将是一个联系人簿,可以存储多个联系人。PostgreSQL 数据库保存关系。该应用程序的合理数据模型是联系人类和联系人的城市邮编。
让我们从联系人模型开始。联系人表需要不同的字段;
- 联系人 ID/对象 ID
- 名称
- 电话号码
- 邮政编码
您的数据模型表将如下所示;
联系方式
| 现场 | 数据类型 | 制约因素 | 
|---|---|---|
| contactId | 整数 | 主键 | 
| 名字 | 字符串 | 非空 | 
| 电话号码 | 整数 | 非空 | 
| 邮编 | POINTER | 非空 | 
邮政编码
| 现场 | 数据类型 | 制约因素 | 
|---|---|---|
| 对象标识 | 整数 | 主键 | 
| 邮编 | 整数 | 非空 | 
联系人对象将存储联系人信息,并以contactId作为主键。姓名、电话号码和邮政编码字段必须填写(NOT NULL)、
该数据模型在联系人应用的表之间创建了一种关系,使您能够管理联系人信息,并将多个字段与每个联系人关联起来。
要在 Back4app 应用程序上创建这两个班级,请点击控制面板左上角的 “创建班级“按钮。

将类别命名为 “联系人”,并将 “是否为必填字段“选项切换为 “打开”。

为Contact 中的数据字段创建列,然后为ZipCode类创建同样的列。Back4app 允许您自定义创建的每一列。
管理面板
Back4App 提供直观的 GUI(图形用户界面),称为管理面板,可轻松管理应用程序的后台。
管理面板是监控、编辑和分析数据的强大工具,是应用程序管理的宝贵资产。
您可以在应用程序中访问应用程序的管理面板。在应用程序的仪表板上,您会在 “更多“下找到一个标有“管理面板“的按钮或链接,它将引导您进入管理面板。您可以在那里切换并启用管理应用程序。
您将提供一个用户名和密码来访问管理应用程序。
设置管理应用程序的最后一步是选择一个用于访问面板的域名。
在本指南中,一个很好的例子是:flutter-backend.admin.back4app.com。
现在,您可以在浏览器上打开提供的域,登录管理仪表板。
要了解有关 Back4app 管理应用程序的更多信息,请访问官方文档。
确保应用程序的安全
目前,您的后台应用程序是完全脆弱的,其中的信息可以从客户端进行篡改。
为了防止在生产阶段出现这种情况,可以配置相关规定和访问权限。
要控制类的创建方式并禁止从客户端创建类,请导航至控制面板>应用程序设置并找到客户端类创建。将此选项切换为关闭,因为您的应用程序目前只需要您创建的两个类。
切换关闭时会出现一个弹出窗口。这是为了确认和保存应用程序的更改。

使用 Flutter 进行 CRUD API 测试
在本节中,您将使用自己构建的 Flutter 应用程序测试应用程序的后台 API 功能和数据库。首先,您将为 Flutter 应用程序构建用户界面。
您必须在计算机上安装 Flutter SDK 才能初始化 Flutter 应用程序。
按照Flutter 的官方 入门 文档,在 Windows 或 Mac 机器上安装 Flutter SDK 和构建 Flutter 应用程序所需的工具。
要初始化 Flutter 应用程序,请在终端中运行以下命令:
#Initialize your Flutter app
flutter create my_flutter_app
#Run your Flutter app
flutter run
这些命令将搭建并启动一个简单的 Flutter 应用程序,供您构建。
为了保持本指南的简洁性,main.dart文件将包含运行 Flutter 应用程序的大部分代码。我们不会在此讨论 Flutter 代码,因为这不是本指南的目的。
用以下代码覆盖main.dart中的所有代码:
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk_flutter.dart';
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final keyApplicationId = 'appID';
  final keyClientKey = 'clientKey';
  final keyParseServerUrl = '<https://parseapi.back4app.com>';
  await Parse().initialize(keyApplicationId, keyParseServerUrl,
      clientKey: keyClientKey, debug: true);
  runApp(MaterialApp(
    title: 'Contacts',
    theme: ThemeData(
      primaryColor: Colors.white,
    ),
    home: ContactsApp(),
  ));
}
class ContactsApp extends StatefulWidget {
  const ContactsApp({Key? key}) : super(key: key);
  @override
  // ignore: library_private_types_in_public_api
  _ContactsAppState createState() => _ContactsAppState();
}
class _ContactsAppState extends State<ContactsApp> {
  List<Contact> contacts = [];
  String? selectedZipCode; // Initialize with a default value
  @override
  void initState() {
    super.initState();
    selectedZipCode = '1234'; // Initialize with a default value
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Contacts'),
      ),
      body: ListView.builder(
        itemCount: contacts.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(contacts[index].name),
            subtitle: Text(contacts[index].phoneNumber),
            trailing: IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () {
                setState(() {
                  contacts.removeAt(index);
                });
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _showAddContactDialog();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
  void _showAddContactDialog() async {
    showDialog(
      context: context,
      builder: (context) {
        String name = '';
        String phoneNumber = '';
        return AlertDialog(
          title: const Text('Add Contact'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                decoration: const InputDecoration(labelText: 'Name'),
                onChanged: (value) {
                  name = value;
                },
              ),
              TextField(
                decoration: const InputDecoration(labelText: 'Phone Number'),
                onChanged: (value) {
                  phoneNumber = value;
                },
              ),
              // Checkbox for Zip Code
              ListTile(
                title: const Text('Select Zip Code'),
                subtitle: Column(
                  children: [
                    RadioListTile(
                      title: const Text('1234'),
                      value: '1234',
                      groupValue: selectedZipCode,
                      onChanged: (value) {
                        setState(() {
                          selectedZipCode = value;
                        });
                        print(selectedZipCode);
                      },
                    ),
                    RadioListTile(
                      title: const Text('4321'),
                      value: '4321',
                      groupValue: selectedZipCode,
                      onChanged: (value) {
                        setState(() {
                          selectedZipCode = value;
                        });
                        print(selectedZipCode);
                      },
                    ),
                  ],
                ),
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () async {
                setState(() {
                  contacts.add(Contact(
                    name: name,
                    phoneNumber: phoneNumber,
                    zipCode: selectedZipCode as String,
                  ));
                });
                // Save the contact to Back4App
                final contact = ParseObject('Contact');
                contact.set('name', name);
                contact.set('phoneNumber', phoneNumber);
                contact.set(
                    'zipCode',
                    (ParseObject('ZipCode')..objectId = selectedZipCode.objectId)
                        .toPointer());
                await contact.save();
                // ignore: use_build_context_synchronously
                Navigator.of(context).pop();
              },
              child: const Text('Save'),
            ),
          ],
        );
      },
    );
  }
}
class Contact {
  final String name;
  final String phoneNumber;
  final String zipCode;
  Contact({
    required this.name,
    required this.phoneNumber,
    required this.zipCode,
  });
}
class ZipCode {
  final String zipCode;
  ZipCode({
    required this.zipCode,
  });
}
这样,您的 Flutter 应用程序的基本 UI 就可以运行了。



现在你可以开始实施 Back4app 了。Back4app 使用 Parse Flutter SDK 将 Parse 服务器集成到 Flutter 应用程序中。
Parse Server 是一个开源后端平台,为管理应用程序数据提供了一个即用型基础架构。
有了 SDK,您就可以通过 Flutter 应用程序与 Parse Server API 通信,从而更轻松地对数据执行 CRUD 操作(创建、读取、更新、删除)、管理用户会话以及处理其他服务器端功能。
要使用 Parse SDK,请将其作为依赖项安装到 Flutter 项目的pubspec.yaml文件中。
指定Parse服务器:
dependencies:
	# Parse SDK
  parse_server_sdk_flutter: ^5.1.1
  flutter:
    sdk: flutter
确保缩排准确。.yaml文件对大小写和缩进非常敏感。
运行命令安装指定的依赖项:
flutter pub get
前往main.dart,导入添加的 Parse SDK:
import 'package:parse_server_sdk_flutter/parse_server_sdk_flutter.dart';
void main() async{
  WidgetsFlutterBinding.ensureInitialized();
	// code for runApp()
  const keyApplicationId = 'YOUR_APPLICATION_ID_HERE';
  const keyClientKey = 'YOUR_CLIENT_KEY_HERE';
  const keyParseServerUrl = '<https://parseapi.back4app.com>';
  await Parse().initialize(keyApplicationId, keyParseServerUrl,
      clientKey: keyClientKey, debug: true);
}
在这里,你要将 ParseSDk 导入你的main.dart文件。你还需要在main()中调用Parse().initialize()来初始化Parse。由于这是一个异步程序,所以要用 async 关键字标记main()。
必须用实际值替换Application_Id和Client Key的占位符。这些是与后台通信的应用程序凭据。
要获取这些密钥,请从 Back4app 面板导航至应用程序设置>安全与密钥。然后用相应的密钥替换占位符。
运行应用程序时,main()将被触发,Parse 将初始化您的应用程序,您的 Flutter 应用程序将连接到 Back4app 后端。
写入联系人和邮政编码类
在这里,您将学习如何从应用程序的客户端创建数据库中的类并写入这些类。要创建 “联系人 “和 “生日 “类,您需要修改main.dart 文件。
在高架按钮部件的onPressed()方法中,您将使用ParseObject()创建一个联系人 类的新实例:
final contact = ParseObject('Contact');
contact.set('name', name);
contact.set('phoneNumber', phoneNumber);
final ParseResponse parseResponse = await contact.save();
if (parseResponse.success) {
   final contactId = (parseResponse.results!.first as ParseObject).objectId!;
		print('Object created: $contactId');
	} else {
      print('Object created with failed: ${parseResponse.error.toString()}');
    }
Parse Flutter SDK 会使用ParseObject()创建一个新实例,该实例与您作为参数传递的类名相同。使用对象字段的名称及其值调用Contact.set()将写入并更新这些字段。
您可以创建一个ParseResponse对象来保存保存操作的结果。ParseResponse将包含一个success属性,如果保存操作成功,则该属性为true;如果保存操作失败,则该属性为false。
然后,代码会将变量contactId初始化为空。该变量将用于存储自动生成的已保存联系人对象的objectId。然后进行一些错误处理。
在上述代码下方
final zipCodeObject = ParseObject('ZipCode')
    ..objectId = selectedZipCode as String;
zipCodeObject.set('zipCode', selectedZipCode);
contact.set('zipCode', zipCodeObject.toPointer());
await contact.save();
await zipCodeObject.save();
在此创建邮编 ParseObject。当通过对话框添加新联系人时,代码将正确创建一个ZipCode对象,并将其与联系人对象的zipCode字段关联。
它使用所选邮编的objectId作为指针建立关联。使用.save()方法将两个对象保存到后台。
从数据库中查询/读取数据
下面我们将讨论如何从 Flutter 应用程序向数据库发送查询。您可以进行查询,检索具有相同邮政编码的所有联系人。在非关系型数据库系统中,要实现这一点相当复杂。
如果检索成功,就可以显示 Flutter 应用程序中所有联系人的列表。
检索具有相同邮政编码的联系人列表:
Future<void> _loadContacts() async {
  final queryBuilder = QueryBuilder<ParseObject>(ParseObject('Contact'))
    ..whereEqualTo('zipCode', selectedZipCode) // Filter by zip code
    ..orderByAscending('name');
  final response = await queryBuilder.query();
  if (response.success && response.results != null) {
    final List<Contact> loadedContacts = response.results!.map((parseObject) {
      return Contact(
        name: parseObject.get('name'),
        phoneNumber: parseObject.get('phoneNumber'),
        zipCode: parseObject.get('zipCode')!.objectId,
      );
    }).toList();
    setState(() {
      //set your contacts to loadedContacts
    });
  }
}
@override
  void initState() {
    super.initState();
   
    _loadContacts(); // Load contacts when the app starts
  }
_loadContacts方法向联系人类建立一个查询,查找具有指定邮政编码的联系人对象。
然后,该方法会返回联系人列表,同时根据 “姓名 “字段按姓名升序排列。
应用程序启动时(initState),会调用_loadContacts方法来获取所选邮编的联系人。
结论
拥有一个可控的后端绝对是必要的,在这里您可以正确构建应用程序中的数据流。使用 Back4app,您可以随时随地建立移动应用程序的后台。
您还可以使用 Back4app 平台,通过设置用户模型数据库来实现用户身份验证。
什么是Flutter?
Flutter是一个开发框架,可以在多个平台上创建类似原生的应用程序。它简化了构建特定平台应用程序的过程,并以其快速的开发速度和美丽的用户界面而闻名。
有哪些后端部署选项?
– IaaS
– PaaS
– BaaS 
如何构建Flutter应用的后端?
1. 规划应用的数据库数据模型结构
2. 设置一个与该结构对应的数据库
3. 编写UI视图逻辑并连接到后端
4. 测试后端的安全漏洞并执行CRUD操作
5. 选择像Back4app这样的部署平台
6. 将应用部署到后端 

