使用 Flutter 和 Firebase 制作!计数器应用程序
目录
- Flutter 概述和特点
- Firebase 概览和服务列表
- 开发环境
- 准备编码
- Firebase Analytics编
- Firebase Crashlytics编
- Firebase Remote Config
- Firebase Authentication
- Cloud Firestore
- Firebase Realtime Database
- Cloud Storage for Firebase
- Firebase Cloud Messaging
- Firebase In-App Messaging
- Firebase ML
- Cloud Functions for Firebase
- Firebase Hosting
- Firebase Performance Monitoring
- Firebase 其他服务
- Firebase Authentication
- 参考网站
- 参考网站
1️⃣Flutter 概述和特点
什么是Flutter?
Flutter是一个由谷歌开发的开源应用程序框架。
Flutter的特点
- 只需一段代码就能为多个平台创建应用程序,包括Android, iOS, Web, Windows, MacOS和Linux。
- 轻松访问 Material Design
- UI是使用小工具的组合来构建的
- 使用Dart作为开发语言
- 热重载功能实现了快速开发
Flutter的文档
Flutter/Dart
有一套完整的官方文档。
这里有一些例子
关于如何开发Flutter应用程序的文档。
Flutter官方文档
Flutter API参考
Dart 包搜索站点
此外,Flutter一年比一年受欢迎,除了官方文档外,许多开发者在其他网站上整理了一些通俗易懂的文章,可以作为开发的参考。
2️⃣Firebase 概览和服务列表
什么是Firebase?
Firebase是谷歌提供的一个移动后台服务(mBaaS)。
Firebase可以很容易地将数据存储和通过云同步、应用认证、消息通知、应用分析和性能测量等功能添加到移动应用。
Firebase 服务列表
名称 | 内容 |
---|---|
A/B Testing | 轻松运行并分析产品和营销测试 |
Analytics | 应用分析功能 |
App Check | 为应用程序数据提供保护 |
App Distribution | 将应用程序分发到测试人员 |
Firebase Authentication | 易于建立的用户认证 |
Cloud Firestore | NoSQL 数据库构建无服务器 |
Cloud Functions for Firebase | 无服务器运行后端代码 |
Firebase Cloud Messaging | 发送和接收推送消息 |
Firebase Crashlytics | 跟踪应用稳定性问题 |
Dynamic Links | 提供对本机应用程序链接内容的直接导航 |
Firebase Extensions | Firebase 扩展 |
Firebase Hosting | 网站部署 |
Firebase In-App Messaging | 发送有针对性的上下文消息 |
Firebase ML | 为应用程序提供机器学习功能 |
Firebase Performance Monitoring | 获取性能分析 |
Firebase Realtime Database | 可以保存为 JSON 格式的数据库 |
Firebase Remote Config | 允许功能的动态变化 |
Cloud Storage for Firebase | 保存用户创建的内容 |
Test lab | 在虚拟设备上验证您的应用 |
Firebase的费用
有两种收费方案
产品 | 价格 | 备注 |
---|---|---|
Spark 方案 | 免费 | 由于是小规模的产品,所以受到限制 |
Blaze 方案 | 随用随付 | 用于大规模的产品 |
有关每个方案的限制和详细价格,请参见官方网站。
3️⃣开发环境
关于开发此计数器应用程序的环境。
对于与以下不同的环境,代码可能会有所不同。
项目 | 内容 |
---|---|
PC | Macbook Air(M1) |
Flutter | 3.0.4 |
Firebase CLI | 11.2.2 |
FlutterFire | 0.2.4 |
模拟器 | Android 12(API 31), Chrome |
4️⃣准备编码
安装Flutter
要安装Flutter,请参考官方网站。
创建计数器应用程序
首先,初始化Flutter应用程序并创建一个计数器应用程序。
flutter create counter_firebase
Firebase CLI的设置
参照官方文档,安装Firebase CLI
npm install -g firebase-tools
此后,按照官方文件进行
首先,登录到firebase,全局启用flutterfire_cli
firebase login
dart pub global activate flutterfire_cli
从Firebase Console创建一个项目
此时应启用Google Analytics
flutterfire configure
选择如下
# 选择项目
? Select a Firebase project to configure your Flutter application with ›
❯ counterfirebase-*** (counterFirebase)
# 平台选择。 检查是否都打了勾
? Which platforms should your configuration support (use arrow keys & space to select)? ›
✔ android
✔ ios
✔ macos
✔ web
# android/build.gradle是否更新
? The files android/build.gradle & android/app/build.gradle will be updated to apply Firebase configuration and gradle build plugins. Do you want to continue? (y/n) › yes
在pubspe.yaml
中加入firebase_core
dependencies:
firebase_core: ^1.19.2
确保Firebase的配置是最新的
flutterfire configure
在main.dart中安装并初始化Firebase包
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
以下是已完成的应用程序的屏幕截图
小结总结
以下部分已从最初创建的计数器应用程序中更改
- 数目增加是由Riverpod完成的
- 从主页屏幕过渡到计数器屏幕
main.dart
/// Flutter导入
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Firebase导入
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
/// 导入其他页面
import 'package:counter_firebase/normal_counter_page.dart';
/// 主
void main() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
}
/// Provider初始化
final counterProvider = StateNotifierProvider((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
///
void increment() => state++;
}
/// MaterialApp的配置
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// 首页屏幕
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: const [
_PagePushButton(
buttonTitle: '计数器',
pagename: NormalCounterPage(),
),
],
),
);
}
}
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
normal_counter_page.dart
/// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// 其他页面
import 'package:counter_firebase/main.dart';
class NormalCounterPage extends ConsumerStatefulWidget {
const NormalCounterPage({Key? key}) : super(key: key);
@override
NormalCounterPageState createState() => NormalCounterPageState();
}
class NormalCounterPageState extends ConsumerState {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final counter = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Homepage'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
5️⃣Firebase Analytics编
Firebase Analytics概述
Firebase Analytics是一项服务,允许你使用Firebase将Google Analytics应用到你的应用程序中
分析允许你记录应用程序的事件,并找出应用程序的使用情况
以下是关于在Flutter中使用分析的官方文档
准备
准备工作和前几章都已完成方可开始
使用方法
要在项目中引入firebase_analytics
,将其添加到pubspec.yaml
中并导入
pubspec.yaml
dependencies:
firebase_analytics: ^9.2.0
可以记录的事件在firebase_analytics_package
网页上列出
安装
这一次,logEvent记录了屏幕转换事件
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsService {
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
Widget
端的设置如下
ElevatedButton{
child: Text(buttonTitle),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context, MaterialPageRoute(builder: (context) => pagename));
},
},
日志信息可以在Firebase
控制台找到
在分析关系中,显示了实时分析、事件分析和转换分析
小结总结
这是与上次相比的变化
- Analytics实现页面转换记录
- 其他代码更改
main.dart
/// Flutter导入
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Firebase导入
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
/// 导入其他页面
import 'package:counter_firebase/normal_counter_page.dart';
/// 主
void main() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
}
/// Provider初始化
final counterProvider = StateNotifierProvider((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
void increment() => state++;
}
/// MaterialApp的配置
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// 主屏幕
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: const [
_PagePushButton(
buttonTitle: '计数器',
pagename: NormalCounterPage(),
),
],
),
);
}
}
/// 页面过渡按钮
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
/// Analytics
class AnalyticsService {
/// 页面转换的日志
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
检查Firebase Console
中的内容
6️⃣Firebase Crashlytics编
Firebase Crashlytics概述
Firebase Crashlytics
是一个跟踪应用问题的崩溃报告工具
Firebase Crashlytics
可用于Android
和iOS
设备
Firebase Crashlytics
的官方文档
准备
准备工作和前几章都已完成方可开始
使用方法
要在项目中引入firebase_analytics
,将其添加到pubspec.yaml
中并导入
pubspec.yaml
dependencies:
firebase_crashlytics: ^2.8.5
为了确保Firebase配置是最新的,在项目根目录下打开一个终端,运行flutterfire configure
flutterfire configure
崩溃处理程序配置
准备好后,配置崩溃处理程序
FirebaseCrashlytics.instance.recordFlutterFatalError
会自动抓取Flutter框架内抛出的所有错误
您还可以使用runZonedGuarded
(需要导入dart:async)来捕捉Flutter框架没有捕捉到的错误
import 'dart:async';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() async {
/// 崩溃处理程序
runZonedGuarded>(() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// 崩溃处理程序(Flutter框架内抛出的所有错误)
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
},
/// 崩溃处理程序(Flutter框架内未捕获的错误)
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
测试碰撞
一旦配置好,在安卓或iOS设备上强制崩溃,进行测试
如果你已经添加了一个错误处理程序,调用FirebaseCrashlytics.instance.recordError(error, stack, fatal: true)
,可以在按钮的 onPressed 上使用 throw Exception () 使其崩溃
TextButton(
onPressed: () => throw Exception(),
child: const Text("Throw Test Exception"),
),
这一次,我们增加了一个新的崩溃页面,并创建了一个崩溃按钮
当崩溃发生时,Firebase Console的Crashlytics会显示一份报告。
Crashlytics现在将监测应用程序崩溃的情况
碰撞报告也可以自定义
小结总结
这是与上次相比的变化
- 增加了测试碰撞页面
- 其他代码修改
main.dart
/// Flutter导入
import 'package:flutter/material.dart';
import 'package:flutter_river:pod/flutter_riverpod.dart';
import 'dart:async';
/// Firebase导入
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
/// 导入其他页面
import 'package:counter_firebase/normal_counter_page.dart';
import 'package:counter_firebase/crash_page.dart';
void main() async {
/// 崩溃处理程序
runZonedGuarded>(() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// 崩溃处理程序(Flutter框架内抛出的所有错误)
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
},
/// 崩溃处理程序(Flutter框架内未捕获的错误)
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
/// Provider初始化
final counterProvider = StateNotifierProvider((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
void increment() => state++;
}
/// MaterialApp设置
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// 主屏幕
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
_PagePushButton(
buttonTitle: '计数器',
pagename: NormalCounterPage(),
),
_PagePushButton(
buttonTitle: '崩溃页面',
pagename: CrashPage(),
),
],
),
);
}
}
/// 页面过渡按钮
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
/// Analytics
class AnalyticsService {
/// 页面转换的日志
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
crash_page.dart
/// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CrashPage extends ConsumerWidget {
const CrashPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('崩溃页面'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
TextButton(
onPressed: () => throw Exception(),
child: const Text("抛出测试异常"),
),
],
),
);
}
}
7️⃣Firebase Remote Config
Firebase Remote Config概述
Firebase Remote Config
是一项服务,它允许你改变你的应用程序的行为和外观,而不需要发布更新和远程改变配置值。
Firebase Remote Config
的官方文档
应用案例
官方介绍了以下 Remote Config 用例。
- 通过百分比推出发布新功能
- 为您的应用定义针对具体平台和针对具体语言区域的促销横幅
准备
准备工作和前几章都已完成方可开始
使用方法
要在项目中引入firebase_remote_config
,将其添加到pubspec.yaml
中并导入
pubspec.yaml
dependencies:
firebase_remote_config: ^2.0.12
创建并执行一个方法来初始化和设置参数
检索单例对象时,控制最小获取间隔以获得最佳更新时间
使用getString()
、getBool()
等方法获取app中使用的参数
import 'package:firebase_remote_config/firebase_remote_config.dart';
/// Firebase Remote Config的初始化
class FirebaseRemoteConfigService {
void initRemoteConfig() async {
/// 实例创建
final remoteConfig = FirebaseRemoteConfig.instance;
/// 获得一个单例对象
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: const Duration(minutes: 5),
));
/// 在应用程序中设置默认参数值
await remoteConfig.setDefaults(const {
"example_param": "Hello, world!",
});
/// 取值
await remoteConfig.fetchAndActivate();
}
}
加载
初始化
remote_config_page.dart
@override
void initState() {
super.initState();
/// Firebase Remote Config初始化
FirebaseRemoteConfigService().initRemoteConfig();
}
导出到Text Widget时,可以看到输出的是设定值(0)
remote_config_page.dart
Text(FirebaseRemoteConfig.instance.getString("example_param")),
更改值
然后从 Firebase Console的Remote Config设置后端配置以更改值
在参数键中输入由setDefaults确定的键,在默认值中输入新的值,然后'发布更改'
小结总结
这是与上次相比的变化
- 增加了Remote Config页面
- 其他代码修改
main.dart
/// Flutter导入
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
/// Firebase导入
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:counter_firebase/remote_config_page.dart';
/// 导入其他页面
import 'package:counter_firebase/normal_counter_page.dart';
import 'package:counter_firebase/crash_page.dart';
void main() async {
/// 崩溃处理程序
runZonedGuarded>(() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// 崩溃处理程序(Flutter框架内抛出的所有错误)
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
},
/// 崩溃处理程序(Flutter框架内未捕获的错误)
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
/// Provider初始化
final counterProvider = StateNotifierProvider((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
void increment() => state++;
}
/// MaterialApp设置
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// 主屏幕
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
_PagePushButton(
buttonTitle: '计数器',
pagename: NormalCounterPage(),
),
_PagePushButton(
buttonTitle: '计数器',
pagename: CrashPage(),
),
_PagePushButton(
buttonTitle: 'Remote Config计数器',
pagename: RemoteConfigPage(),
),
],
),
);
}
}
/// 页面过渡按钮
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
class AnalyticsService {
/// 页面转换的日志
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
remote_config_page.dart
/// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Firebase导入
import 'package:firebase_remote_config/firebase_remote_config.dart';
/// 其他页面
import 'package:counter_firebase/main.dart';
class RemoteConfigPage extends ConsumerStatefulWidget {
const RemoteConfigPage({Key? key}) : super(key: key);
@override
RemoteConfigPageState createState() => RemoteConfigPageState();
}
class RemoteConfigPageState extends ConsumerState {
@override
void initState() {
super.initState();
/// Firebase Remote Config初始化
FirebaseRemoteConfigService().initRemoteConfig();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Homepage'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
/// Remote Config数据采集
Text(
FirebaseRemoteConfig.instance.getString("example_param"),
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
/// Firebase Remote Config的初始设置
class FirebaseRemoteConfigService {
void initRemoteConfig() async {
/// 实例创建
final remoteConfig = FirebaseRemoteConfig.instance;
/// 获得一个单例对象
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: const Duration(minutes: 5),
));
/// 在应用程序中设置默认参数值
await remoteConfig.setDefaults(const {
"example_param": "0",
});
/// 获取数值
await remoteConfig.fetchAndActivate();
}
}
8️⃣Firebase Authentication
Firebase Authentication概述
Firebase
认证是一项能够使用用户认证功能的服务
准备
准备工作和前几章都已完成方可开始
使用方法
要在项目中引入firebase_auth,请在pubspec.yaml中添加以下内容,并导入它
pubspec.yaml
dependencies:
firebase_auth: ^3.4.2
要选择你的登录方式(电子邮件地址、电话号码等),请从Firebase Console进入认证,在登录方式下选择你喜欢的登录方式。
在这种情况下,我们将使用一个电子邮件地址和密码
在Firebase Console中设置好配置后,你就可以实施了
输入TextField中输入的电子邮件地址和密码。
通过将obscureText设置为 "true "使密码不可见。
/// 输入你的电子邮件地址
TextField(
decoration: const InputDecoration(
label: Text('E-mail'),
),
controller: _idController,
),
/// 输入密码
TextField(
decoration: const InputDecoration(
label: Text('Password'),
),
controller: _passController,
obscureText: true,
),
创建一个执行按钮并调用一个允许你创建账户或登录的函数
/// 用于创建账户
Container(
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
_createAccount(ref, idController.text, passController.text);
},
child: const Text('创建账户'),
),
),
使用FirebaseAuth.instance.createUserWithEmailAndPassword
来处理账户创建。
电子邮件地址和密码被传递,如果发生错误,会产生一个错误信息。
import 'package:firebase_auth/firebase_auth.dart';
void _createAccount(String id, String pass) async {
try {
/// credential 帐户信息记录
final credential =
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: id,
password: pass,
);
}
/// 在账户失败的情况下进行错误处理
on FirebaseAuthException catch (e) {
/// 如果密码很弱的话
if (e.code == 'weak-password') {
print('请设置包含大小写字母和数字的6-18位密码');
/// 如果该电子邮件地址已经在使用中
} else if (e.code == 'email-already-in-use') {
print('该电子邮件以注册');
}
///其他错误
else {
print('账户创建错误');
}
} catch (e) {
print(e);
}
}
登录过程是使用FirebaseAuth.instance.signInWithEmailAndPassword
来处理。
它传递电子邮件地址和密码,如果发生错误,会产生一个错误信息
void _signIn(String id, String pass) async {
try {
/// credential 帐户信息记录
final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(
email: id,
password: pass,
);
}
/// 登录失败时的错误处理
on FirebaseAuthException catch (e) {
/// 无效的电子邮件地址
if (e.code == 'invalid-email') {
print('无效的电子邮件地址');
}
/// 如果该用户不存在
else if (e.code == 'user-not-found') {
print('用户不存在');
}
/// 如果密码不正确
else if (e.code == 'wrong-password') {
print('密码不正确');
}
/// 其他错误
else {
print('登录错误');
}
}
}
用于登出FirebaseAuth.instance.signOut()
auth_page.dart
void _signOut() async {
await FirebaseAuth.instance.signOut();
}
获取用户信息的三种方式。
/// 使用authStateChanges、idTokenChanges和userChanges流
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user != null) {
print(user.uid);
}
});
/// 使用由认证(signIn)方法返回的UserCredential对象
final userCredential =
await FirebaseAuth.instance.signInWithCredential(credential);
final user = userCredential.user;
print(user?.uid);
/// 使用FirebaseAuth实例的currentUser属性
if (FirebaseAuth.instance.currentUser != null) {
print(FirebaseAuth.instance.currentUser?.uid);
}
使用.update*来更新用户资料和电子邮件地址
final userCredential =
await FirebaseAuth.instance.signInWithCredential(credential);
final user = userCredential.user;
await user?.updateDisplayName("Jane Q. User");
await user?.updateEmail("[email protected]");
通过电子邮件地址进行认证,但也可以通过电话号码和OAuth进行认证
登录前的主屏幕
小结总结
这是与上次相比的变化
- 添加 Firebase 身份验证页面
- 其他代码修改
main.dart
/// Flutter导入
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
/// Firebase导入
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_auth/firebase_auth.dart';
/// 导入其他页面
import 'package:counter_firebase/normal_counter_page.dart';
import 'package:counter_firebase/crash_page.dart';
import 'package:counter_firebase/auth_page.dart';
import 'package:counter_firebase/remote_config_page.dart';
void main() async {
/// 崩溃处理程序
runZonedGuarded>(() async {
/// Firebase初始化
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
/// 崩溃处理程序(Flutter框架内抛出的所有错误)
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
},
/// 崩溃处理程序(Flutter框架内未捕获的错误)
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
/// Provider初始化
final counterProvider = StateNotifierProvider.autoDispose((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
void increment() => state++;
}
/// MaterialApp设置
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// 主屏幕
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
/// 获取用户信息
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
ref.watch(userEmailProvider.state).state = '未登录';
} else {
ref.watch(userEmailProvider.state).state = user.email!;
}
});
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
/// 显示用户信息
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.person),
Text(ref.watch(userEmailProvider)),
],
),
/// 页面过渡
const _PagePushButton(
buttonTitle: '普通计数器',
pagename: NormalCounterPage(),
),
const _PagePushButton(
buttonTitle: '崩溃页面',
pagename: CrashPage(),
),
const _PagePushButton(
buttonTitle: '远程配置计数器',
pagename: RemoteConfigPage(),
),
const _PagePushButton(
buttonTitle: '认证页面',
pagename: AuthPage(),
),
],
),
);
}
}
/// 页面过渡按钮
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
class AnalyticsService {
/// 页面转换的日志
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
auth_page.dart
/// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Firebase导入
import 'package:firebase_auth/firebase_auth.dart';
/// Auth签入状态提供者
final signInStateProvider = StateProvider((ref) => '登录或创建一个账户');
/// 登录用户的信息提供
final userProvider = StateProvider((ref) => null);
final userEmailProvider = StateProvider((ref) => '未登录');
/// 页面设置
class AuthPage extends ConsumerStatefulWidget {
const AuthPage({Key? key}) : super(key: key);
@override
AuthPageState createState() => AuthPageState();
}
class AuthPageState extends ConsumerState {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final singInStatus = ref.watch(signInStateProvider);
final idController = TextEditingController();
final passController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: const Text('Auth Page'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
/// 输入你的电子邮件地址
TextField(
decoration: const InputDecoration(
label: Text('E-mail'),
icon: Icon(Icons.mail),
),
controller: idController,
),
/// 输入密码
TextField(
decoration: const InputDecoration(
label: Text('Password'),
icon: Icon(Icons.key),
),
controller: passController,
obscureText: true,
),
/// 登录
Container(
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
/// 用于登录
_signIn(ref, idController.text, passController.text);
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey)),
child: const Text('登录'),
),
),
/// 创建账户
Container(
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
/// 用于创建账户
_createAccount(ref, idController.text, passController.text);
},
child: const Text('创建账户'),
),
),
/// 登录信息显示
Container(
padding: const EdgeInsets.all(10),
child: Text('信息 : $singInStatus'),
),
/// 登出
TextButton(
onPressed: () {
_signOut(ref);
},
child: const Text('SIGN OUT'))
],
),
);
}
}
/// 登录处理
void _signIn(WidgetRef ref, String id, String pass) async {
try {
/// 帐户信息被记录在credential
final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(
email: id,
password: pass,
);
/// 更新用户信息
ref.watch(userProvider.state).state = credential.user;
/// 在屏幕上显示
ref.read(signInStateProvider.state).state = '我已经能够登录了!';
}
/// 登录失败时的错误处理
on FirebaseAuthException catch (e) {
/// 无效的电子邮件地址
if (e.code == 'invalid-email') {
ref.read(signInStateProvider.state).state = '无效的电子邮件地址';
}
/// 该用户不存在
else if (e.code == 'user-not-found') {
ref.read(signInStateProvider.state).state = '该用户不存在';
}
/// 密码不正确
else if (e.code == 'wrong-password') {
ref.read(signInStateProvider.state).state = '密码不正确';
}
/// 其他错误
else {
ref.read(signInStateProvider.state).state = '登录错误';
}
}
}
/// 创建账户
void _createAccount(WidgetRef ref, String id, String pass) async {
try {
/// 帐户信息被记录在credential
final credential =
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: id,
password: pass,
);
/// 更新用户信息
ref.watch(userProvider.state).state = credential.user;
/// 在屏幕上显示
ref.read(signInStateProvider.state).state = '账户创建成功!';
}
/// 在账户失败的情况下进行错误处理
on FirebaseAuthException catch (e) {
/// 如果密码很弱
if (e.code == 'weak-password') {
ref.read(signInStateProvider.state).state = '请设置包含大小写字母和数字的6-18位密码');
/// 如果该电子邮件地址已经在使用中
} else if (e.code == 'email-already-in-use') {
print('该电子邮件以注册');
}
///其他错误
else {
print('账户创建错误');
}
} catch (e) {
print(e);
}
}
/// 登出
void _signOut(WidgetRef ref) async {
await FirebaseAuth.instance.signOut();
ref.read(signInStateProvider.state).state = '登录或创建一个账户';
}
9️⃣Cloud Firestore
Cloud Firestore概述
Cloud Firestore是一个用于无服务器数据存储的NoSQL数据库
类似的服务
除了Firestore之外,Firebase也有一个类似的数据库。
云存储,用于存储用户生成的数据,如照片和视频
实时数据库,用于客户与客户之间的实时通信
官方网站上有一个与实时数据库的比较,以帮助你选择哪种数据库
准备
准备工作和前几章都已完成方可开始
使用方法
从Firebase Console,选择Firestore数据库并创建数据库。
Firestore的安全规则已被设置为记录用户ID中的计数,具体如下。
请注意,安全规则在另一章中描述。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{documents=**} {
allow read, write: if request.auth != null && request.auth.uid == userId
}
}
}
Firestore的数据模型由文档、集合等组成,支持的数据类型包括bool
、int
和Map
类型,以及日期和地理坐标
在Firebase Console
设置完毕后,在pubspec.yaml
中添加以下内容,将cloud_firestore
引入项目并导入
pubspec.yaml
dependencies:
cloud_firestore: ^3.3.0
Firestore数据处理
显示了向Firestore添加、读取和删除数据的例子。
Riverpod用于写入和读取数据
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
/// Firestore数据库的定义
final db = FirebaseFirestore.instance;
/// 获取UserID
final userID = FirebaseAuth.instance.currentUser?.uid ?? 'test';
/// 数据添加
void add(WidgetRef ref) {
final Map counterMap = {
'count': ref.read(counterProvider),
};
/// 将数据添加到Firestore
try {
db.collection('users').doc(userID).set(counterMap);
} catch (e) {
print('Error : $e');
}
}
/// 数据采集
void get(WidgetRef ref) async {
try {
await db.collection('users').doc(userID).get().then(
(event) {
ref.read(counterProvider.notifier).state = event.get('count');
},
);
} catch (e) {
print('Error : $e');
}
}
/// 数据删除
void delete() async {
try {
db.collection('users').doc(userID).delete().then((doc) => null);
} catch (e) {
print('Error : $e');
}
}
实际的计数
检查Firebase Console,看看数据是否在Firestore中
小结总结
在这一小结中我们完成了一下功能
- 增加了Firestore页面
- 改变页面转换的按钮的颜色
- 其他代码修改
Firebase Realtime Database
Firebase Realtime Database概述
Firebase实时数据库是一个NoSQL数据库服务,它能够在所有的客户端进行数据存储和实时同步
数据以json格式存储,并根据数据量收费,因此它适合存储比Firestore更频繁更新的小数据
类似的服务
除了Firestore之外,Firebase也有一个类似的数据库。
云存储,用于存储用户生成的数据,如照片和视频
准备
准备工作和前几章都已完成方可开始
使用方法
从Firebase Console,选择实时数据库并创建数据库。
安全规则设置如下,因此,只有内容所有者可以访问数据库
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
在设置了Firebase Console之后,在pubspec.yaml中添加以下内容,将firebase_database导入项目中
dependencies:
firebase_database: ^9.0.19
数据操作
数据库定义
检索了用户ID,并定义了数据库
Riverpod用于阅读和写作
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_auth/firebase_auth.dart';
final userID = FirebaseAuth.instance.currentUser?.uid ?? '';
DatabaseReference dbRef = FirebaseDatabase.instance.ref('users');
写入实时数据库
有两种方法可以向实时数据库写入数据:使用设置和使用更新
这一次是由UPDATE实施的
void write(WidgetRef ref) async {
try {
await dbRef.update({
'$userID/count': ref.read(counterProvider),
});
} catch (e) {
print('Error : $e');
}
}
读取实时数据库数据
有两种读取实时数据库数据的方法:监听DatabaseReference并调用DatabaseEvent,或使用get()。
前者在每次改变数据时都会被触发,而后者只读取一次数据。
这次是用get()实现的。
void read(WidgetRef ref) async {
try {
final snapshot = await dbRef.child(userID).get();
if (snapshot.exists) {
ref.read(counterProvider.notifier).state =
snapshot.child('count').value as int;
}
} catch (e) {
print('Error : $e');
}
}
删除实时数据库数据
可以使用remove()删除数据
void remove() async {
try {
await dbRef.child(userID).remove();
} catch (e) {
print('Error : $e');
}
}
Realtime Database的计数器画面
运行后,在Firebase Console检查数据库是否已经被改变
小结总结
在这一小结中我们完成了一下功能
- 增加了实时数据库页面。
- 其他代码修改
▶️Cloud Storage for Firebase
Cloud Firestore概述
Firebase的云存储是一项用于存储用户生成的内容的服务,如照片和视频
类似的服务
除了Firestore之外,Firebase也有一个类似的数据库。
云存储,用于存储用户生成的数据,如照片和视频。
准备
准备工作和前几章都已完成方可开始
使用方法
从Firebase控制台中选择存储来开始。
安全规则已经设置好了,只有内容所有者才能访问,具体如下
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
在设置好Firebase控制台后,添加到pubspec.yaml中,并将firebase_storage导入项目中。
这一次,为了在Android上上传图片,image_picker也被一起导入。
注意,Web不支持dart:io包,所以不能使用下面的代码。
dependencies:
image_picker: ^0.8.5
firebase_storage: ^10.3.2
数据操作
上传至云存储
要将图片上传到云存储,用image_picker选择图片并使用putFile
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
final userID = FirebaseAuth.instance.currentUser?.uid ?? '';
void uploadPic() async {
try {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
File file = File(image!.path);
String uploadName = 'image.png';
final storageRef =
FirebaseStorage.instance.ref().child('users/$userID/$uploadName');
final task = await storageRef.putFile(file);
} catch (e) {
print(e);
}
管理上传
作为管理上传的一部分,你可以暂停、恢复和取消上传。
此外,你还可以监控上传的进度。
bool paused = await task.pause();
print('paused, $paused');
bool resumed = await task.resume();
print('resumed, $resumed');
bool canceled = await task.cancel();
print('canceled, $canceled');
可以从Firebase控制台检查图片是否已经上传
从云存储下载
有两种方法可以从云存储下载图像:下载到内存或直接下载到本地文件夹。
这一次,图像被下载到内存中并显示在应用程序中。
此外,Riverpod还用于阅读和写作。
final imageStateProvider = StateProvider((ref) => null);
void downloadPic(WidgetRef ref) async {
try {
String downloadName = 'image.png';
final storageRef =
FirebaseStorage.instance.ref().child('users/$userID/$downloadName');
const oneMegabyte = 1024 * 1024;
ref.read(imageStateProvider.state).state =
await storageRef.getData(oneMegabyte);
} catch (e) {
print(e);
}
}
如果显示出来,说明下载的实施是OK的。
使用delete()来删除云存储数据。
void deletePic() async {
String deleteName = 'image.png';
final storageRef =
FirebaseStorage.instance.ref().child('users/$userID/$deleteName');
await storageRef.delete();
}
小结总结
在这一小结中我们完成了一下功能
- 增加了云存储页面
- 其他代码修改
⬇️Firebase Cloud Messaging
Firebase Cloud Messaging概述
Firebase Cloud Messaging(FCM)是一项允许向客户端应用程序发送推送通知的服务。
准备
准备工作和前几章都已完成方可开始
使用方法
iOS、Android和Web的准备工作和使用条件有所不同
Android。
Android可以在运行Android4.4或更高版本的设备上运行。
iOS
查看设置说明,为在iOS上使用做准备
web
在web上使用时,有必要在Firebase控制台为 "网络推送证书 "生成一对密钥,创建并注册firebase-messaging-sw.js文件,等等
添加到导入到firebase_messaging项目中。pubspec.yaml
dependencies:
firebase_messaging: ^11.4.0
接待设置
获取令牌的ID。
测试时打印出令牌。
import 'package:firebase_messaging/firebase_messaging.dart';
// final fcmToken = await FirebaseMessaging.instance.getToken(vapidKey: 'BDdcxJZSBD...');
final fcmToken = await FirebaseMessaging.instance.getToken();
print(fcmToken);
如果你也想在后台接收信息,不管是什么平台,添加以下代码。
_firebaseMessagingBackgroundHandler
函数不能是一个匿名函数,必须被当作一个顶级函数,否则会发生错误。
Future main() async {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(...
}
Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
此外,还为web和iOS设备设置了权限
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
实际分配
测试交付给一个安卓模拟器。
在Firebase控制台的Messaging中选择一个新的活动。
从 "在设备上测试",复制并粘贴你刚刚打印出来的令牌到FCM注册令牌中。
如果你的设备上收到推送通知,你就成功了
小结总结
在这一小结中我们完成了一下功能
- 其他代码修改
- 例如,有些键是用类来隐藏的
⬅️Firebase In-App Messaging
Firebase In-App Messaging概述
Firebase In-App Messaging是一项允许你发送目标信息的服务
这项服务只在移动端(iOS、Android)提供
准备
准备工作和前几章都已完成方可开始
使用方法
要在项目中引入firebase_in_app_messaging
,请将其加入pubspec.yaml
。
由于In-App Messaging
每天只从服务器检索一次信息,我们将在测试中尝试使用Firebase安装ID(FID)
dependencies:
firebase_in_app_messaging: ^0.6.0+14
firebase_app_installations: ^0.1.0+14
在Flutter中可以通过加载firebase_in_app_messaging
来使用应用内消息
import 'package:firebase_in_app_messaging/firebase_in_app_messaging.dart';
使用FirebaseInstallations来获取FID
import 'package:firebase_app_installations/firebase_app_installations.dart';
void getFID() async {
String id = await FirebaseInstallations.instance.getId();
print('id : $id');
}
交付测试
从Firebase控制台测试交付。
当你准备好了,输入你刚刚克制的FID,进行设备交付测试
交付后,在调试设备上返回主屏幕一次,并再次打开应用,查看应用内信息
请注意,如果出现以下错误信息,说明Firebase In-App Messaging API
被禁用了,你需要访问谷歌云平台的Firebase In-App Messaging API
并启用该API,如错误文本所示
PERMISSION_DENIED: Firebase In-App Messaging API has not been used in project *** before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firebaseinappmessaging.googleapis.com/overview?project=*** then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
小结总结
在这一小结中我们完成了一下功能
- In-App Messaging的实施
◀️Firebase ML
Firebase ML概述
Firebase ML是一个使用Firebase的机器学习模型推理
Firebase x Flutter中提供的机器学习推理
使用Flutter和Firebase的机器学习推理可以在设备上或在云端完成
设备上的推理
使用Firebase进行设备上的推理意味着用Firebase ML提供自定义的TensorFlowLite模型进行本地推理
对于实际的推断,使用了tflite_flutter、ML Kit等
用Firebase提供定制的TF模型的好处是,用户可以使用最新的模型,而不需要更新他们的应用程序
云端推理
使用Firebase(谷歌云)进行云推理,意味着用云视觉AI或云自然语言进行推理。
目前,没有为Flutter提供API,所以你需要为每个操作系统组合API
除上述之外,还有其他方法可以使用来自其他服务的机器学习模型
如何用Firebase部署自定义的TensorflowLite模型分布
准备一个你自己训练的自定义TF模型的TFLite文件
在这种情况下,为了执行图像识别任务,我从TensorFlow Hub获得了Imagenet图像分类训练模型
请注意,当从TensorFlow Hub下载模型时,要注意许可证和下载的文件类型
一旦文件准备好了,从Firebase控制台的机器学习中部署该模型
Firebase官方文档推荐使用tflite_flutter和tflite进行推理,但我在开发环境中无法用导入的tflite_flutter等构建应用,所以我用ML Kit做了实验
可以使用ML工具包中的TFLite自定义模型的任务包括图像标签或物体检测和跟踪
由于我们将对图像标签进行推理,我们也将介绍google_ml_kit
和google_mlkit_image_labelling
dependencies:
google_ml_kit: ^0.11.0
google_mlkit_image_labeling: ^0.3.0
使用google_mlkit_image_labelling
包中的FirebaseImageLabelerModelManager
从Firebase ML下载模型
final bool response =
await FirebaseImageLabelerModelManager().downloadModel(modelname);
final options = FirebaseLabelerOption(
confidenceThreshold: 0.5, modelName: modelname, maxCount: 3);
_imageLabeler = ImageLabeler(options: options);
如果你知道照片的路径,例如image_picker,你只需要两行代码进行基本的标签推理
final InputImage inputImage = InputImage.fromFilePath(path);
final List labels = await _imageLabeler.processImage(inputImage);
从推断出的结果中提取标签
String labelText = '';
for (final label in labels) {
labelText += '\nLabel: ${label.label}';
}
用应用程序检查。
它似乎能够进行推论,但结果与我预期的不同,所以还有改进的余地
小结总结
在这一小结中我们完成了一下功能
- 添加ml_page页面
- 其他代码修改
⏬Cloud Functions for Firebase
Cloud Functions for Firebase概述
Cloud Functions for Firebase是一项服务,它可以对触发的事件自动执行后端代码
Firebase的云功能支持用javascript和typescript编写,允许在不管理或扩展服务器的情况下实现后端
应用案例
准备
准备工作和前几章都已完成方可开始
在JavaScript环境中运行Cloud Functions需要一个Node.js环境,如果你还没有这样做,请使用nvm安装它
使用方法
安装firebase-tools
npm install -g firebase-tools
初始化项目
在Firebase登录准备好的环境中,初始化函数和其他必要的工具。
在这种情况下,这次我选择了语言JavaScript
firebase init functions
一旦初始化,项目中就会创建一个新的函数文件夹
创建功能
在function/index.js中编写执行Cloud Functions的函数。
导入必要的模块
const functions = require("firebase-functions");
功能定义。
有三种主要的方法来调用一个函数
- 如何直接从应用程序调用
- 如何通过 HTTP 请求调用函数
- 如何调入日程设置
这一次,计数功能是直接从应用程序中调用的,所以在后端使用了onCall触发器。
当我们在做这件事的时候,我们还将试验能够调用UID
exports.functionsTest = functions.https.onCall((data, context) => {
const firstNumber = data.firstNumber;
const secondNumber = data.secondNumber;
const addNumber = firstNumber + secondNumber;
const contextUid = context.auth.uid;
return { addNumber:addNumber, contextUid:contextUid }
});
在模拟器上测试
在部署之前在本地模拟器上进行测试以检查无限循环等。
如果App Check使用,则无法运行模拟器,需要使用 App Check 调试提供程序,每个执行环境都必须使用调试提供程序
Java安装 Open JDK,因为它需要启动模拟器
安装并初始化本地模拟器,必要时用Firebase init *安装每个插件
firebase init emulators
当使用本地模拟器时,在Flutter侧的主函数中设置useFunctionsEmulator
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Ideal time to initialize
FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001);
...
安装后,用emulators:start启动模拟器,并在浏览器中打开http://localhost:4000/
(默认情况下)
firebase emulators:start
部署
如果一切顺利,就部署到生产环境
在Firebase控制台,进入功能,选择 "开始"
firebase deploy --only functions:functionsTest
从Flutter应用程序调用
要在你的Flutter项目中应用云函数,请在pubspec.yaml中引入cloud_functions
dependencies:
cloud_functions: ^3.3.2
functions编写执行的代码
import 'package:cloud_functions/cloud_functions.dart';
void addNumber() async {
try {
final result = await FirebaseFunctions.instance
.httpsCallable('functionsTest')
.call({'firstNumber': _number, 'secondNumber': 1});
_number = result.data['addNumber'];
print(result.data['contextUid']);
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
}****
一款使用 Cloud Functions 进行计数的豪华应用程序已经完成
小结总结
在这一小结中我们完成了一下功能
- 添加了Cloud Functions
- 添加了cloud_functions
- 其他代码修改
const functions = require("firebase-functions");
exports.functionsTest = functions.https.onCall(async(data, context) => {
const firstNumber = data.firstNumber;
const secondNumber = data.secondNumber;
const addNumber = firstNumber + secondNumber;
const contextUid = context.auth.uid;
return { addNumber:addNumber, contextUid:contextUid }
});
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:counter_firebase/remote_config_page.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_app_installations/firebase_app_installations.dart';
import 'package:firebase_in_app_messaging/firebase_in_app_messaging.dart';
import 'package:counter_firebase/normal_counter_page.dart';
import 'package:counter_firebase/crash_page.dart';
import 'package:counter_firebase/auth_page.dart';
import 'package:counter_firebase/firestore_page.dart';
import 'package:counter_firebase/realtime_database_page.dart';
import 'package:counter_firebase/cloud_storage.dart';
import 'package:counter_firebase/cloud_functions_page.dart';
import 'package:counter_firebase/ml_page.dart';
final isAndroid =
defaultTargetPlatform == TargetPlatform.android ? true : false;
final isIOS = defaultTargetPlatform == TargetPlatform.iOS ? true : false;
Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message: ${message.messageId}');
}
void main() async {
runZonedGuarded>(() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
// FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001);
/// runApp w/ Riverpod
runApp(const ProviderScope(child: MyApp()));
},
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
final counterProvider = StateNotifierProvider((ref) {
return Counter();
});
class Counter extends StateNotifier {
Counter() : super(0);
void increment() => state++;
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Firebase',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends ConsumerStatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends ConsumerState {
@override
void initState() {
super.initState();
FirebaseMessagingService().setting();
FirebaseMessagingService().fcmGetToken();
FirebaseInAppMessagingService().getFID();
}
@override
Widget build(BuildContext context) {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
ref.watch(userEmailProvider.state).state = '未登录';
} else {
ref.watch(userEmailProvider.state).state = user.email!;
}
});
return Scaffold(
appBar: AppBar(
title: const Text('My Homepage'),
),
body: ListView(
padding: const EdgeInsets.all(10),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.person),
Text(ref.watch(userEmailProvider)),
],
),
const _PagePushButton(
buttonTitle: '计数器',
pagename: NormalCounterPage(),
),
const _PagePushButton(
buttonTitle: '崩溃页面',
pagename: CrashPage(),
),
const _PagePushButton(
buttonTitle: 'Remote Config计数器',
pagename: RemoteConfigPage(),
),
const _PagePushButton(
buttonTitle: '机器学习页面',
pagename: MLPage(),
),
const _PagePushButton(
buttonTitle: '验证页面',
pagename: AuthPage(),
bgColor: Colors.red,
),
/// 过渡到每个页面(认证后可用)
/// 让未经授权的人无法按下按钮
FirebaseAuth.instance.currentUser?.uid != null
? const _PagePushButton(
buttonTitle: 'Firestore计数器',
pagename: FirestorePage(),
bgColor: Colors.green,
)
: const Text('Firestore验证后,即可打开柜台'),
FirebaseAuth.instance.currentUser?.uid != null
? const _PagePushButton(
buttonTitle: 'Realtime Database计数器',
pagename: RealtimeDatabasePage(),
bgColor: Colors.green,
)
: const Text('Realtime Database认证,以打开计数器'),
FirebaseAuth.instance.currentUser?.uid != null
? const _PagePushButton(
buttonTitle: 'Cloud Storage页',
pagename: CloudStoragePage(),
bgColor: Colors.green,
)
: const Text('Cloud Storage请认证以打开该页面'),
FirebaseAuth.instance.currentUser?.uid != null
? const _PagePushButton(
buttonTitle: 'Cloud Functions页',
pagename: CloudFunctionsPage(),
bgColor: Colors.green,
)
: const Text('Cloud Functions请认证以打开该页面'),
],
),
);
}
}
class _PagePushButton extends StatelessWidget {
const _PagePushButton({
Key? key,
required this.buttonTitle,
required this.pagename,
this.bgColor = Colors.blue,
}) : super(key: key);
final String buttonTitle;
final dynamic pagename;
final Color bgColor;
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(bgColor),
),
child: Container(
padding: const EdgeInsets.all(10),
child: Text(buttonTitle),
),
onPressed: () {
AnalyticsService().logPage(buttonTitle);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => pagename),
);
},
);
}
}
class AnalyticsService {
Future logPage(String screenName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': screenName,
},
);
}
}
class FirebaseMessagingService {
FirebaseMessaging messaging = FirebaseMessaging.instance;
void setting() async {
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
}
void fcmGetToken() async {
if (isAndroid || isIOS) {
final fcmToken = await messaging.getToken();
print(fcmToken);
}
else {
final fcmToken = await messaging.getToken(
vapidKey: FirebaseOptionMessaging().webPushKeyPair);
print('web : $fcmToken');
}
}
}
class FirebaseInAppMessagingService {
void getFID() async {
String id = await FirebaseInstallations.instance.getId();
print('id : $id');
}
}
/// Flutter
import 'package:flutter/material.dart';
/// Firebase
import 'package:cloud_functions/cloud_functions.dart';
class CloudFunctionsPage extends StatefulWidget {
const CloudFunctionsPage({Key? key}) : super(key: key);
@override
CloudFunctionsPageState createState() => CloudFunctionsPageState();
}
class CloudFunctionsPageState extends State {
int _number = 0;
void addNumber() async {
try {
final result = await FirebaseFunctions.instance
.httpsCallable('functionsTest')
.call({'firstNumber': _number, 'secondNumber': 1});
_number = result.data['addNumber'];
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cloud Functions页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_number',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
addNumber();
});
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
⏫Firebase Hosting
Firebase Hosting概述
Firebase Hosting是一个为网络应用、静态和动态内容以及微服务提供的托管服务
如果您想将您的Flutter应用作为一个网络应用托管,可以使用
应用案例
准备
准备工作和前几章都已完成方可开始
使用方法
有两种方法来部署到Firebase Hosting
- 通过输入命令进行部署
- 使用Github Actions进行部署
在这种情况下,我们选择通过输入命令进行部署
创建一个Firebase项目,安装Firebase CLI并初始化项目
firebase init hosting
问题和答案示例如下
? What do you want to use as your public directory? (public)
build/web
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
No
? Set up automatic builds and deploys with GitHub? (y/N)
No
为网络配置设置
在web/index.html
中,配置html
设置
在web/manifest.json
中,配置网络应用的行为和图标
可以通过覆盖web/icons
目录下的文件(如Icon-192.png)来改变应用程序的图标。
在这一点上要注意不要弄错尺寸。
调试完应用程序后,在Flutter端构建应用程序,然后用Firebase部署它
flutter build web
firebase deploy --only hosting
访问部署后出现的URL,如果应用程序被验证,就可以使用了
注意,如果你想使用一个自定义的域名,你可以从Firebase Console进行设置
↩️Firebase Performance Monitoring
Firebase Performance Monitoring概述
Firebase Performance Monitoring是一项允许您衡量您的Flutter应用性能的服务
点击这里查看Performance Monitoring官方文件
使用方法
要在项目中引入firebase_performance,请在pubspec.yaml中添加以下内容
dependencies:
firebase_performance: ^0.8.2
为了确保Firebase的配置是最新的,在项目根目录下打开一个终端,运行flutterfire configure
为了显示最初的性能数据,运行该项目并检查它是否显示在Firebase控制台。
如果显示了图表,你就成功了。
自定义性能测量允许你添加自定义属性以及通用属性。
Firebase 其他服务
本章概述
本章总结了前几章中没有介绍的Firebase服务
Firebase Dynamic Links(仅限移动端)
Firebase Dynamic Links
是一项提供 "动态链接 "的服务,可以让你直接进入移动原生应用中的链接内容
要在Flutter中构建,请将Firebase_dynamic_links
导入你的项目,并从Firebase控制台创建链接
Firebase App Check
App Check是一项保护后端资源不被滥用的服务,如计费欺诈和网络钓鱼
使用reCAPTCHA或其他方式检查设备是否被信任。
所使用的认证供应商将因平台而异
平台 | 提供者 |
---|---|
Apple platforms | DeviceCheck, App Attest |
Android | Play Integrity, SafetyNet |
web | reCAPTCHA v3, reCAPTCHA Enterprise |
它也可以与 Flutter 一起使用
如果flutterfire安装在你的环境中,从Firebase Console启用并初步配置App Check,并安装App Check库
flutter pub add firebase_app_check
调用执行 App Check 的代码,运行它,你就大功告成了
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
// Import the firebase_app_check plugin
import 'package:firebase_app_check/firebase_app_check.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate(
webRecaptchaSiteKey: 'recaptcha-v3-site-key',
);
runApp(App());
}
Firebase installations service
Firebase installations service负责管理Firebase的安装
用来检查Firebase的安装ID,用于Messaging、Analytics等
Firebase Google AdMob
Firebase Google Admob是一项移动广告服务
如果你想在Flutter中启动AdMob广告,你不能使用Firebase的Admob,必须使用移动广告SDK(Flutter)(beta)
Test Lab
Test Lab是一项允许你在云端托管的设备上测试你的应用程序的服务,当你想在各种配置上测试你的应用程序时可以使用。
你可以通过将你的应用程序部署到Test Lab,并从Firebase Console将文件上传到Robo Test,来测试你的移动应用程序。
Firebase App Distribution
Firebase App Distribution是一项促进向测试人员分发应用程序的服务
与Google Play和App Store的链接使分发应用程序变得容易
你可以从Firebase Console分发应用程序并管理测试人员
Firebase Extensions
Firebase Extensions是一项服务,它允许你使用打包的解决方案快速为你的应用程序添加功能。
在官方的Firebase Extensions中可以找到许多扩展,这些扩展是使用Cloud Functions for Firebase编写的
[
](https://firebase.google.com/p...)
请注意,Firebase Extensions的安装只适用于Blaze计划(按需付费)
Firebase 安全规则
什么是Firebase的安全规则?
Firebase安全规则是定义如何允许访问存储数据的语法
Firestore、Realtime Database、Cloud Storage下列各项的语法是不同的
Firestore的语法
Firestore安全规则的语法是基于Common Expression Language(CEL)
语言
作为基本的安全规则,在所有经过身份验证的用户都可以访问的测试环境中,编写
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
在只有内容所有者有权访问的生产环境中,编写
service cloud.firestore {
match /databases/{database}/documents {
// Allow only authenticated content owners access
match /some_collection/{userId}/{documents=**} {
allow read, write: if request.auth != null && request.auth.uid == userId
}
}
}
Realtime Database
Realtime Database使用json格式的安全规则的语法。
为确保只有内容所有者才能访问,请按以下方式进行配置。
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
Cloud Storage
云存储安全规则的语法是基于Common Expression Language(CEL)
只能由内容所有者访问的安全规则包括
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
参考网站
在编写本文时参考了一些网站
在此表示感谢
本文由mdnice多平台发布