乐趣区

关于前端:使用-Flutter-和-Firebase-制作计数器应用程序

应用 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
  • 参考网站
  • 参考网站

<div id=”01″>1️⃣Flutter 概述和特点 </div>

什么是 Flutter?

Flutter 是一个由谷歌开发的开源应用程序框架。

Flutter 官网

Flutter 的特点

  • <span> 只需一段代码就能为多个平台创立应用程序,包含 Android, iOS, Web, Windows, MacOS 和 Linux。</span>
  • <span> 轻松拜访 Material Design</span>
  • <span>UI 是应用小工具的组合来构建的 </span>
  • <span> 应用 Dart 作为开发语言 </span>
  • <span> 热重载性能实现了疾速开发 </span>

Flutter 的文档

Flutter/Dart有一套残缺的官网文档。
这里有一些例子

对于如何开发 Flutter 应用程序的文档。
Flutter 官网文档

Flutter API 参考

FlutterAPI 参考

Dart 包搜寻站点

Dart 包搜寻站点


此外,Flutter 一年比一年受欢迎,除了官网文档外,许多开发者在其余网站上整顿了一些通俗易懂的文章,能够作为开发的参考。

<div id=”02″>2️⃣Firebase 概览和服务列表 </div>

什么是 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 计划 随用随付 用于大规模的产品

无关每个计划的限度和具体价格,请参见官方网站。

<div id=”03″>3️⃣开发环境 </div>

对于开发此计数器应用程序的环境。

对于与以下不同的环境,代码可能会有所不同。

我的项目 内容
PC Macbook Air(M1)
Flutter 3.0.4
Firebase CLI 11.2.2
FlutterFire 0.2.4
模拟器 Android 12(API 31), Chrome

<div id=”04″>4️⃣筹备编码 </div>

装置 Flutter

要装置 Flutter,请参考官方网站。

创立计数器应用程序

首先,初始化 Flutter 应用程序并创立一个计数器应用程序。

flutter create counter_firebase

Firebase CLI 的设置

参照官网文档,装置 Firebase CLI

这里有几种装置办法,但你也能够实用 npm 来进行装置

npm install -g firebase-tools

尔后,依照官网文件进行

首先,登录到 firebase,全局启用 flutterfire_cli

firebase login
dart pub global activate flutterfire_cli

从 Firebase Console 创立一个我的项目

此时应启用Google Analytics

将你的应用程序连贯到 Firebase

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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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 <Widget>[
          _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<NormalCounterPage> {
  @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: <Widget>[
            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),
      ),
    );
  }
}

<div id=”05″>5️⃣Firebase Analytics 编 </div>

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<void> 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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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 <Widget>[
          _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<void> logPage(String screenName) async {
    await FirebaseAnalytics.instance.logEvent(
      name: 'screen_view',
      parameters: {'firebase_screen': screenName,},
    );
  }
}

查看 Firebase Console 中的内容

<div id=”06″>6️⃣Firebase Crashlytics 编 </div>

Firebase Crashlytics 概述

Firebase Crashlytics是一个跟踪利用问题的解体报告工具

Firebase Crashlytics可用于 AndroidiOS设施

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<Future<void>>(() 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<Future<void>>(() 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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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: <Widget>[
          _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<void> 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: <Widget>[
          TextButton(onPressed: () => throw Exception(),
            child: const Text("抛出测试异样"),
          ),
        ],
      ),
    );
  }
}

<div id=”07″>7️⃣Firebase Remote Config</div>

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 确定的键,在默认值中输出新的值,而后 ’ 公布更改 ’

过了一会儿,我可能确认文本已更改为 8

小结总结

这是与上次相比的变动

  • 减少了 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<Future<void>>(() 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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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: <Widget>[
          _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<void> 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<RemoteConfigPage> {
  @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: <Widget>[
            /// 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();}
}

<div id=”08″>8️⃣Firebase Authentication</div>

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("janeq@example.com");

通过电子邮件地址进行认证,但也能够通过电话号码和 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<Future<void>>(() 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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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: <Widget>[
          /// 显示用户信息
          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<void> 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<User?>((ref) => null);
final userEmailProvider = StateProvider<String>((ref) => '未登录');

/// 页面设置
class AuthPage extends ConsumerStatefulWidget {const AuthPage({Key? key}) : super(key: key);

  @override
  AuthPageState createState() => AuthPageState();
}

class AuthPageState extends ConsumerState<AuthPage> {
  @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: <Widget>[
          /// 输出你的电子邮件地址
          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 = '登录或创立一个账户';
}

<div id=”09″>9️⃣Cloud Firestore</div>

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 的数据模型由文档、汇合等组成,反对的数据类型包含 boolintMap类型,以及日期和地理坐标

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<String, dynamic> 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 页面
  • 扭转页面转换的按钮的色彩
  • 其余代码批改

<div id=”10″>🔟Firebase Realtime Database</div>

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 查看数据库是否曾经被扭转

小结总结

在这一小结中咱们实现了一下性能

  • 减少了实时数据库页面。
  • 其余代码批改

<div id=”11″>▶️Cloud Storage for Firebase</div>

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<Uint8List?>((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();}

小结总结

在这一小结中咱们实现了一下性能

  • 减少了云存储页面
  • 其余代码批改

<div id=”12″>⬇️Firebase Cloud Messaging</div>

Firebase Cloud Messaging概述

Firebase Cloud Messaging(FCM)是一项容许向客户端应用程序发送推送告诉的服务。

官方网站

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<void> main() async {FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(...}

Future<void> _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 注册令牌中。

如果你的设施上收到推送告诉,你就胜利了

小结总结

在这一小结中咱们实现了一下性能

  • 其余代码批改
  • 例如,有些键是用类来暗藏的

<div id=”13″>⬅️Firebase In-App Messaging</div>

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 的施行

<div id=”14″>◀️Firebase ML</div>

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_kitgoogle_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 页面
  • 其余代码批改

<div id=”15″>⏬Cloud Functions for Firebase</div>

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<void> 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<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {await Firebase.initializeApp();
  print('Handling a background message: ${message.messageId}');
}

void main() async {runZonedGuarded<Future<void>>(() 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<Counter, int>((ref) {return Counter();
});

class Counter extends StateNotifier<int> {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<MyHomePage> {
  @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: <Widget>[
          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<void> 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<CloudFunctionsPage> {

  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: <Widget>[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),
      ),
    );
  }
}

<div id=”16″>⏫Firebase Hosting</div>

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 <head> 设置

web/manifest.json 中,配置网络应用的行为和图标

能够通过笼罩 web/icons 目录下的文件(如 Icon-192.png)来扭转应用程序的图标。
在这一点上要留神不要弄错尺寸。

调试完应用程序后,在 Flutter 端构建应用程序,而后用 Firebase 部署它

flutter build web
firebase deploy --only hosting

拜访部署后呈现的 URL,如果应用程序被验证,就能够应用了

留神,如果你想应用一个自定义的域名,你能够从 Firebase Console 进行设置

<div id=”17″>↩️Firebase Performance Monitoring</div>

Firebase Performance Monitoring概述

Firebase Performance Monitoring 是一项容许您掂量您的 Flutter 利用性能的服务

点击这里查看 Performance Monitoring 官网文件

应用办法

要在我的项目中引入 firebase_performance,请在 pubspec.yaml 中增加以下内容

dependencies:
  firebase_performance: ^0.8.2

为了确保 Firebase 的配置是最新的,在我的项目根目录下关上一个终端,运行flutterfire configure

为了显示最后的性能数据,运行该我的项目并查看它是否显示在 Firebase 控制台。

如果显示了图表,你就胜利了。

自定义性能测量容许你增加自定义属性以及通用属性。

<div id=”18″>🔽Firebase 其余服务 </div>

本章概述

本章总结了前几章中没有介绍的 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<void> 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 打算(按需付费)

<div id=”19″>🔀Firebase 平安规定 </div>

什么是 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;}
  }
}

<div id=”20″>🆗 参考网站 </div>

在编写本文时参考了一些网站

在此表示感谢

本文由 mdnice 多平台公布

退出移动版