当前位置:   article > 正文

使用 Flutter 和 Firebase 制作!计数器应用程序_fire tv构建的flutter

fire tv构建的flutter

使用 Flutter 和 Firebase 制作!计数器应用程序

目录

1️⃣Flutter 概述和特点

什么是Flutter?

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

Flutter官网

alt

Flutter的特点

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

Flutter的文档

Flutter/Dart有一套完整的官方文档。 这里有一些例子

关于如何开发Flutter应用程序的文档。 Flutter官方文档

alt

Flutter API参考

FlutterAPI参考

alt

Dart 包搜索站点

Dart 包搜索站点

alt

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

2️⃣Firebase 概览和服务列表

什么是Firebase?

Firebase是谷歌提供的一个移动后台服务(mBaaS)。

Firebase可以很容易地将数据存储和通过云同步、应用认证、消息通知、应用分析和性能测量等功能添加到移动应用。

「Firebase 服务列表」

名称内容
A/B Testing轻松运行并分析产品和营销测试
Analytics应用分析功能
App Check为应用程序数据提供保护
App Distribution将应用程序分发到测试人员
Firebase Authentication易于建立的用户认证
Cloud FirestoreNoSQL 数据库构建无服务器
Cloud Functions for Firebase无服务器运行后端代码
Firebase Cloud Messaging发送和接收推送消息
Firebase Crashlytics跟踪应用稳定性问题
Dynamic Links提供对本机应用程序链接内容的直接导航
Firebase ExtensionsFirebase 扩展
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 方案随用随付用于大规模的产品

有关每个方案的限制和详细价格,请参见官方网站

alt

3️⃣开发环境

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

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

项目内容
PCMacbook Air(M1)
Flutter3.0.4
Firebase CLI11.2.2
FlutterFire0.2.4
模拟器Android 12(API 31), Chrome

4️⃣准备编码

安装Flutter

要安装Flutter,请参考官方网站

alt

创建计数器应用程序

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

flutter create counter_firebase
  • 1

Firebase CLI的设置

参照官方文档,安装Firebase CLI

alt 这里有几种安装方法,但你也可以实用npm来进行安装

npm install -g firebase-tools
  • 1

此后,按照官方文件进行

alt

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

firebase login
dart pub global activate flutterfire_cli
  • 1

Firebase Console创建一个项目

此时应启用Google Analytics

alt 将你的应用程序连接到Firebase

flutterfire configure
  • 1

选择如下

# 选择项目
? 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
  • 1

pubspe.yaml中加入firebase_core

dependencies:
  firebase_core: ^1.19.2
  • 1

确保Firebase的配置是最新的

flutterfire configure
  • 1

在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());
}
  • 1

以下是已完成的应用程序的屏幕截图

alt

小结总结

以下部分已从最初创建的计数器应用程序中更改

  • 数目增加是由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),
        );
      },
    );
  }
}
  • 1

「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),
      ),
    );
  }
}
  • 1

5️⃣Firebase Analytics编

Firebase Analytics概述

Firebase Analytics是一项服务,允许你使用Firebase将Google Analytics应用到你的应用程序中

分析允许你记录应用程序的事件,并找出应用程序的使用情况

以下是关于在Flutter中使用分析的官方文档

alt

准备

准备工作和前几章都已完成方可开始

使用方法

要在项目中引入firebase_analytics,将其添加到pubspec.yaml中并导入

「pubspec.yaml」

dependencies:
  firebase_analytics: ^9.2.0
  • 1
点击跳转
点击跳转

可以记录的事件在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,
      },
    );
  }
}
  • 1

Widget端的设置如下

ElevatedButton{
  child: Text(buttonTitle),
  onPressed: () {
    AnalyticsService().logPage(buttonTitle);
    Navigator.push(
        context, MaterialPageRoute(builder: (context) => pagename));
  },
},
  • 1

日志信息可以在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,
      },
    );
  }
}
  • 1

检查Firebase Console中的内容

alt

6️⃣Firebase Crashlytics编

Firebase Crashlytics概述

Firebase Crashlytics是一个跟踪应用问题的崩溃报告工具

Firebase Crashlytics可用于AndroidiOS设备

Firebase Crashlytics官方文档

点击跳转
点击跳转

准备

准备工作和前几章都已完成方可开始

使用方法

要在项目中引入firebase_analytics,将其添加到pubspec.yaml中并导入

「pubspec.yaml」

dependencies:
  firebase_crashlytics: ^2.8.5
  • 1

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

flutterfire configure
  • 1

崩溃处理程序配置

准备好后,配置崩溃处理程序

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));
}
  • 1

测试碰撞

一旦配置好,在安卓或iOS设备上强制崩溃,进行测试

如果你已经添加了一个错误处理程序,调用FirebaseCrashlytics.instance.recordError(error, stack, fatal: true),可以在按钮的 onPressed 上使用 throw Exception () 使其崩溃

TextButton(
  onPressed: () => throw Exception(),
  child: const Text("Throw Test Exception"),
),
  • 1

这一次,我们增加了一个新的崩溃页面,并创建了一个崩溃按钮

alt

当崩溃发生时,Firebase Console的Crashlytics会显示一份报告。

Crashlytics现在将监测应用程序崩溃的情况

alt

碰撞报告也可以自定义

小结总结

这是与上次相比的变化

  • 增加了测试碰撞页面
  • 其他代码修改

「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,
      },
    );
  }
}
  • 1

「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("抛出测试异常"),
          ),
        ],
      ),
    );
  }
}
  • 1

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
  • 1

创建并执行一个方法来初始化和设置参数

检索单例对象时,控制最小获取间隔以获得最佳更新时间

使用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();
  }
}
  • 1

加载

初始化

「remote_config_page.dart」

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

  /// Firebase Remote Config初始化
  FirebaseRemoteConfigService().initRemoteConfig();
}
  • 1

导出到Text Widget时,可以看到输出的是设定值(0)

「remote_config_page.dart」

Text(FirebaseRemoteConfig.instance.getString("example_param")),
  • 1
alt

更改值

然后从 Firebase Console的Remote Config设置后端配置以更改值

在参数键中输入由setDefaults确定的键,在默认值中输入新的值,然后'发布更改'

alt 过了一会儿,我能够确认文本已更改为 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,
      },
    );
  }
}
  • 1

「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();
  }
}
  • 1

8️⃣Firebase Authentication

「Firebase Authentication」概述

Firebase认证是一项能够使用用户认证功能的服务

官方网站

点击跳转
点击跳转

准备

准备工作和前几章都已完成方可开始

使用方法

要在项目中引入firebase_auth,请在pubspec.yaml中添加以下内容,并导入它

「pubspec.yaml」

dependencies:
  firebase_auth: ^3.4.2
  • 1

要选择你的登录方式(电子邮件地址、电话号码等),请从Firebase Console进入认证,在登录方式下选择你喜欢的登录方式。

在这种情况下,我们将使用一个电子邮件地址和密码

alt 在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,
),
  • 1

创建一个执行按钮并调用一个允许你创建账户或登录的函数

/// 用于创建账户
Container(
  margin: const EdgeInsets.all(10),
  child: ElevatedButton(
    onPressed: () {
      _createAccount(ref, idController.text, passController.text);
    },
    child: const Text('创建账户'),
  ),
),
  • 1

使用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);
  }
}
  • 1

登录过程是使用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('登录错误');
    }
  }
}
  • 1

用于登出FirebaseAuth.instance.signOut() 「auth_page.dart」

void _signOut() async {
  await FirebaseAuth.instance.signOut();
}
  • 1

获取用户信息的三种方式。

/// 使用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);
}
  • 1

使用.update***来更新用户资料和电子邮件地址

final userCredential =
    await FirebaseAuth.instance.signInWithCredential(credential);
final user = userCredential.user;

await user?.updateDisplayName("Jane Q. User");
await user?.updateEmail("janeq@example.com");
  • 1

通过电子邮件地址进行认证,但也可以通过电话号码和OAuth进行认证

登录前的主屏幕

alt 登录页面

alt 登录后的主屏幕

alt

小结总结

这是与上次相比的变化

  • 添加 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,
      },
    );
  }
}
  • 1

「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 = '登录或创建一个账户';
}
  • 1

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
    }
  }
}
  • 1

Firestore的数据模型由文档、集合等组成,支持的数据类型包括boolintMap类型,以及日期和地理坐标

Firebase Console设置完毕后,在pubspec.yaml中添加以下内容,将cloud_firestore引入项目并导入

「pubspec.yaml」

dependencies:
  cloud_firestore: ^3.3.0
  • 1

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');
  }
}
  • 1

实际的计数

alt

检查Firebase Console,看看数据是否在Firestore中

alt

小结总结

在这一小结中我们完成了一下功能

  • 增加了Firestore页面
  • 改变页面转换的按钮的颜色
  • 其他代码修改

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/791727
推荐阅读
相关标签