首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Flutter 与 Firebase 集成:认证、数据库、云存储实战

Flutter 与 Firebase 集成:认证、数据库、云存储实战

作者头像
爱吃大芒果
发布2025-12-24 14:08:33
发布2025-12-24 14:08:33
2780
举报

作者:爱吃大芒果

个人主页 爱吃大芒果

本文所属专栏 Flutter

更多专栏

前言

在移动应用开发中,用户认证、数据存储、文件上传是核心功能模块,手动搭建后端服务不仅耗时耗力,还需应对高并发、安全性等诸多挑战。Firebase 作为 Google 推出的一站式后端云服务平台,提供了认证、实时数据库、云存储等开箱即用的功能,能够快速补齐 Flutter 应用的后端能力,大幅提升开发效率。

本教程将以实战为导向,从环境准备入手,逐步实现 Flutter 与 Firebase 的集成,重点讲解用户认证(邮箱密码登录、Google 第三方登录)、实时数据库(数据增删改查)、云存储(图片上传与展示)三大核心功能,并通过一个完整的示例应用将三者串联,帮助开发者快速掌握 Flutter + Firebase 的开发流程与最佳实践。

第一章 环境准备

1.1 前置条件

  • 已安装 Flutter 开发环境(Flutter SDK 3.0+,Dart 2.17+),并配置好 Android Studio / VS Code 开发工具;
  • 拥有 Google 账号(用于登录 Firebase 控制台);
  • 已配置好 Android 模拟器/真机、iOS 模拟器/真机(用于应用调试)。

1.2 Firebase 控制台配置

首先需要在 Firebase 控制台创建项目,并为 Flutter 应用添加平台配置(Android + iOS),步骤如下:

  1. 访问 Firebase 控制台,点击「添加项目」,输入项目名称(如「FlutterFireDemo」),勾选「同意 Firebase 服务条款」,点击「继续」;
  2. 禁用「Google Analytics」(初学者可先禁用,后续需用到时再开启),点击「创建项目」,等待项目创建完成后点击「继续」;
  3. 添加 Android 应用:点击控制台左侧「项目设置」,在「您的应用」区域点击「Android 图标」,输入应用包名(需与 Flutter 项目中 android/app/build.gradle 中的 applicationId 一致,如「com.example.flutter_fire_demo」),输入应用别名(可选),点击「注册应用」;
  4. 下载 google-services.json 文件,将其复制到 Flutter 项目的 android/app 目录下,点击「下一步」;
  5. 按照提示配置 Android 项目依赖(修改 build.gradle 文件),完成后点击「下一步」,最后点击「继续到控制台」;
  6. 添加 iOS 应用:同样在「您的应用」区域点击「iOS 图标」,输入 Bundle ID(需与 Flutter 项目中 ios/Runner.xcodeproj/project.pbxproj 中的 PRODUCT_BUNDLE_IDENTIFIER 一致),输入应用别名(可选),点击「注册应用」;
  7. 下载 GoogleService-Info.plist 文件,将其添加到 Flutter 项目的 ios/Runner 目录下(需在 Xcode 中添加,直接复制可能无效),点击「下一步」;
  8. 按照提示配置 iOS 项目依赖,完成后点击「下一步」,最后点击「继续到控制台」。

1.3 Flutter 项目依赖配置

在 Flutter 项目的 pubspec.yaml 文件中,添加 Firebase 相关依赖包,本教程核心依赖如下:

添加完成后,执行 flutter pub get 命令安装依赖。

1.4 Firebase 初始化

在 Flutter 应用启动时,需要初始化 Firebase,修改 lib/main.dart 文件,代码如下:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';  // 自动生成的文件
import 'screens/login_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化 Firebase
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Fire Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const LoginScreen(), // 初始页面为登录页
      debugShowCheckedModeBanner: false,
    );
  }
}

注意:firebase_options.dart 文件会在执行 flutter pub get 后自动生成,若未生成,可执行 flutter pub run firebase_core:generate_config 命令手动生成。

第二章 用户认证:实现邮箱密码登录与 Google 登录

Firebase Authentication 支持多种认证方式,本教程重点实现「邮箱密码登录」和「Google 第三方登录」,覆盖主流的账号登录场景。

2.1 启用 Firebase 认证方式

首先需要在 Firebase 控制台启用对应的认证方式:

  1. 登录 Firebase 控制台,进入当前项目,点击左侧「Authentication」;
  2. 点击「登录方法」选项卡,找到「电子邮件/密码」,点击「启用」,勾选「电子邮件/密码」,点击「保存」;
  3. 找到「Google」,点击「启用」,输入项目支持邮箱(需与 Google 账号一致),点击「保存」。

2.2 封装认证工具类

为了便于代码复用,创建 lib/services/auth_service.dart 文件,封装 Firebase 认证相关操作:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  // 获取当前登录用户
  User? get currentUser => _firebaseAuth.currentUser;

  // 监听用户登录状态变化
  Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();

  // 邮箱密码注册
  Future<UserCredential?> signUpWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      final UserCredential userCredential =
          await _firebaseAuth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      return userCredential;
    } on FirebaseAuthException catch (e) {
      // 处理注册错误(如邮箱已存在、密码过短等)
      print('注册失败:${e.message}');
      return null;
    }
  }

  // 邮箱密码登录
  Future<UserCredential?> signInWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      final UserCredential userCredential =
          await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      return userCredential;
    } on FirebaseAuthException catch (e) {
      // 处理登录错误(如邮箱不存在、密码错误等)
      print('登录失败:${e.message}');
      return null;
    }
  }

  // Google 登录
  Future<UserCredential?> signInWithGoogle() async {
    try {
      // 触发 Google 登录流程
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      if (googleUser == null) return null; // 用户取消登录

      // 获取登录凭证
      final GoogleSignInAuthentication googleAuth =
          await googleUser.authentication;
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // 用凭证登录 Firebase
      final UserCredential userCredential =
          await _firebaseAuth.signInWithCredential(credential);
      return userCredential;
    } catch (e) {
      print('Google 登录失败:$e');
      return null;
    }
  }

  // 退出登录
  Future<void> signOut() async {
    await _firebaseAuth.signOut();
    await _googleSignIn.signOut(); // 退出 Google 登录
  }
}

2.3 实现登录页面

创建 lib/screens/login_screen.dart 文件,实现登录界面与交互逻辑:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  // 获取当前登录用户
  User? get currentUser => _firebaseAuth.currentUser;

  // 监听用户登录状态变化
  Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();

  // 邮箱密码注册
  Future<UserCredential?> signUpWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      final UserCredential userCredential =
          await _firebaseAuth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      return userCredential;
    } on FirebaseAuthException catch (e) {
      // 处理注册错误(如邮箱已存在、密码过短等)
      print('注册失败:${e.message}');
      return null;
    }
  }

  // 邮箱密码登录
  Future<UserCredential?> signInWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      final UserCredential userCredential =
          await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      return userCredential;
    } on FirebaseAuthException catch (e) {
      // 处理登录错误(如邮箱不存在、密码错误等)
      print('登录失败:${e.message}');
      return null;
    }
  }

  // Google 登录
  Future<UserCredential?> signInWithGoogle() async {
    try {
      // 触发 Google 登录流程
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      if (googleUser == null) return null; // 用户取消登录

      // 获取登录凭证
      final GoogleSignInAuthentication googleAuth =
          await googleUser.authentication;
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // 用凭证登录 Firebase
      final UserCredential userCredential =
          await _firebaseAuth.signInWithCredential(credential);
      return userCredential;
    } catch (e) {
      print('Google 登录失败:$e');
      return null;
    }
  }

  // 退出登录
  Future<void> signOut() async {
    await _firebaseAuth.signOut();
    await _googleSignIn.signOut(); // 退出 Google 登录
  }
}

2.4 实现注册页面

创建 lib/screens/register_screen.dart 文件,实现用户注册功能,逻辑与登录页面类似:

代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:flutter_fire_demo/services/auth_service.dart';
import 'package:flutter_fire_demo/screens/login_screen.dart';

class RegisterScreen extends StatefulWidget {
  const RegisterScreen({super.key});

  @override
  State<RegisterScreen> createState() => _RegisterScreenState();
}

class _RegisterScreenState extends State<RegisterScreen> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final AuthService _authService = AuthService();
  bool _isLoading = false;

  void _handleRegister() async {
    setState(() => _isLoading = true);
    final String email = _emailController.text.trim();
    final String password = _passwordController.text.trim();

    if (email.isEmpty || password.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入邮箱和密码')),
      );
      setState(() => _isLoading = false);
      return;
    }

    final UserCredential? userCredential =
        await _authService.signUpWithEmailAndPassword(
      email: email,
      password: password,
    );

    setState(() => _isLoading = false);
    if (userCredential != null) {
      // 注册成功,返回登录页面
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('注册成功,请登录')),
      );
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const LoginScreen()),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('注册失败,请重试')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('注册')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: '邮箱',
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: '密码(至少6位)',
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _isLoading ? null : _handleRegister,
                child: _isLoading
                    ? const CircularProgressIndicator(color: Colors.white)
                    : const Text('注册'),
              ),
            ),
            const SizedBox(height: 16),
            TextButton(
              onPressed: _isLoading
                  ? null
                  : () {
                      Navigator.pop(context);
                    },
              child: const Text('已有账号?返回登录'),
            ),
          ],
        ),
      ),
    );
  }
}

2.5 登录状态监听与首页保护

为了避免未登录用户直接访问首页,需要在 main.dart 中监听用户登录状态,根据状态动态切换页面:

代码语言:javascript
复制
代码语言:javascript
复制
// 修改 main.dart 中的 MyApp 组件
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Fire Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: StreamBuilder<User?>(
        stream: AuthService().authStateChanges,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            // 加载中
            return const Scaffold(
              body: Center(child: CircularProgressIndicator()),
            );
          }
          // 根据登录状态切换页面
          return snapshot.hasData ? const HomeScreen() : const LoginScreen();
        },
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}

第三章 实时数据库:实现数据增删改查

Firebase Realtime Database 是一个云托管的 NoSQL 数据库,数据以 JSON 格式存储,支持实时同步,非常适合构建需要实时更新数据的应用(如聊天、社交、协作工具等)。本章节将实现用户个人信息的增删改查功能。

3.1 启用实时数据库并配置规则

  1. 登录 Firebase 控制台,进入当前项目,点击左侧「Realtime Database」;
  2. 点击「创建数据库」,选择数据库位置(建议选择离用户最近的区域,如「asia-east1」),点击「下一步」;
  3. 设置数据库规则(开发阶段可先设置为测试模式,生产环境需严格配置):选择「测试模式」,点击「启用」;
  4. 数据库创建完成后,点击「规则」选项卡,可看到默认规则(测试模式下任何人可读写,有效期30天),生产环境需修改为基于用户认证的规则,例如: { "rules": { ".read": "auth != null", // 已登录用户可读取 ".write": "auth != null" // 已登录用户可写入 } }

3.2 封装数据库工具类

创建 lib/services/database_service.dart 文件,封装实时数据库的增删改查操作:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';

class DatabaseService {
  final FirebaseDatabase _database = FirebaseDatabase.instance;
  final String? _uid;

  // 构造函数,传入用户ID
  DatabaseService({this._uid});

  // 获取用户信息节点的引用
  DatabaseReference get _userRef => _database.ref('users/$_uid');

  // 保存用户信息
  Future<void> saveUserInfo({
    required String name,
    required String phone,
  }) async {
    try {
      await _userRef.set({
        'name': name,
        'phone': phone,
        'uid': _uid,
        'createTime': DateTime.now().millisecondsSinceEpoch,
      });
    } catch (e) {
      print('保存用户信息失败:$e');
      rethrow;
    }
  }

  // 获取用户信息
  Stream<DataSnapshot> getUserInfo() {
    return _userRef.onValue;
  }

  // 更新用户信息
  Future<void> updateUserInfo({
    String? name,
    String? phone,
  }) async {
    try {
      final Map<String, dynamic> updates = {};
      if (name != null) updates['name'] = name;
      if (phone != null) updates['phone'] = phone;
      await _userRef.update(updates);
    } catch (e) {
      print('更新用户信息失败:$e');
      rethrow;
    }
  }

  // 删除用户信息
  Future<void> deleteUserInfo() async {
    try {
      await _userRef.remove();
    } catch (e) {
      print('删除用户信息失败:$e');
      rethrow;
    }
  }
}

3.3 实现用户信息管理页面

创建 lib/screens/profile_screen.dart 文件,实现用户信息的展示、添加、修改和删除功能:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_fire_demo/services/auth_service.dart';
import 'package:flutter_fire_demo/services/database_service.dart';

class ProfileScreen extends StatefulWidget {
  const ProfileScreen({super.key});

  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  final AuthService _authService = AuthService();
  late DatabaseService _databaseService;
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _phoneController = TextEditingController();
  Map<String, dynamic> _userInfo = {};
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    final User? user = _authService.currentUser;
    if (user != null) {
      _databaseService = DatabaseService(uid: user.uid);
      // 监听用户信息变化
      _databaseService.getUserInfo().listen((snapshot) {
        if (snapshot.value != null) {
          setState(() {
            _userInfo = Map<String, dynamic>.from(snapshot.value as Map);
            _nameController.text = _userInfo['name'] ?? '';
            _phoneController.text = _userInfo['phone'] ?? '';
          });
        }
      });
    }
  }

  // 保存/更新用户信息
  void _handleSaveUserInfo() async {
    setState(() => _isLoading = true);
    final String name = _nameController.text.trim();
    final String phone = _phoneController.text.trim();

    if (name.isEmpty || phone.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入姓名和手机号')),
      );
      setState(() => _isLoading = false);
      return;
    }

    try {
      if (_userInfo.isEmpty) {
        // 新增用户信息
        await _databaseService.saveUserInfo(name: name, phone: phone);
      } else {
        // 更新用户信息
        await _databaseService.updateUserInfo(name: name, phone: phone);
      }
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('操作成功')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('操作失败:$e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 删除用户信息
  void _handleDeleteUserInfo() async {
    if (_userInfo.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('暂无用户信息可删除')),
      );
      return;
    }

    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认删除'),
        content: const Text('确定要删除用户信息吗?此操作不可恢复。'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () async {
              Navigator.pop(context);
              setState(() => _isLoading = true);
              try {
                await _databaseService.deleteUserInfo();
                setState(() => _userInfo.clear());
                _nameController.clear();
                _phoneController.clear();
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('删除成功')),
                );
              } catch (e) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('删除失败:$e')),
                );
              } finally {
                setState(() => _isLoading = false);
              }
            },
            child: const Text('删除'),
          ),
        ],
      ),
    );
  }

  // 退出登录
  void _handleSignOut() async {
    await _authService.signOut();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('个人中心'),
        actions: [
          TextButton(
            onPressed: _isLoading ? null : _handleSignOut,
            child: const Text(
              '退出登录',
              style: TextStyle(color: Colors.white),
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(
                labelText: '姓名',
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _phoneController,
              keyboardType: TextInputType.phone,
              decoration: const InputDecoration(
                labelText: '手机号',
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
            ),
            const SizedBox(height: 24),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isLoading ? null : _handleSaveUserInfo,
                    child: _isLoading
                        ? const CircularProgressIndicator(color: Colors.white)
                        : Text(_userInfo.isEmpty ? '保存信息' : '更新信息'),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: OutlinedButton(
                    onPressed: _isLoading ? null : _handleDeleteUserInfo,
                    child: const Text('删除信息'),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 32),
            // 展示用户信息
            if (_userInfo.isNotEmpty)
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        '用户信息',
                        style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(height: 16),
                      Text('姓名:${_userInfo['name']}'),
                      Text('手机号:${_userInfo['phone']}'),
                      Text(
                        '创建时间:${DateTime.fromMillisecondsSinceEpoch(_userInfo['createTime']).toString().substring(0, 19)}',
                      ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

第四章 云存储:实现图片上传与展示

Firebase Cloud Storage 用于存储和检索用户生成的内容(如图片、视频、音频等),提供了高可用性和安全性。本章节将实现图片选择、上传到云存储,并展示已上传的图片功能。

4.1 启用云存储并配置规则

  1. 登录 Firebase 控制台,进入当前项目,点击左侧「Storage」;
  2. 点击「开始使用」,选择存储位置(与实时数据库位置一致即可),点击「下一步」;
  3. 设置存储规则(开发阶段可先设置为测试模式),点击「完成」;
  4. 存储创建完成后,点击「规则」选项卡,可修改规则(生产环境需配置基于用户认证的规则): { "rules": { ".read": "auth != null", ".write": "auth != null" } }

4.2 封装云存储工具类

创建 lib/services/storage_service.dart 文件,封装图片上传、获取图片URL、删除图片等操作:

代码语言:javascript
复制
import 'dart:io';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';

class StorageService {
  final FirebaseStorage _storage = FirebaseStorage.instance;
  final String? _uid;

  StorageService({this._uid});

  // 获取图片存储引用(按用户ID分目录,避免文件名冲突)
  Reference get _imageRef => _storage.ref('user_images/$_uid');

  // 上传图片
  Future<String?> uploadImage(File imageFile) async {
    try {
      // 生成唯一的文件名(时间戳 + 原文件名)
      final String fileName =
          '${DateTime.now().millisecondsSinceEpoch}_${imageFile.path.split('/').last}';
      // 创建存储引用
      final Reference ref = _imageRef.child(fileName);
      // 上传文件
      final UploadTask uploadTask = ref.putFile(imageFile);
      // 等待上传完成,获取下载URL
      final TaskSnapshot taskSnapshot = await uploadTask;
      final String downloadUrl = await taskSnapshot.ref.getDownloadURL();
      return downloadUrl;
    } catch (e) {
      print('图片上传失败:$e');
      return null;
    }
  }

  // 获取用户上传的所有图片URL
  Future<List<String>> getImageUrls() async {
    try {
      final ListResult result = await _imageRef.listAll();
      final List<String> urls = [];
      for (final Reference ref in result.items) {
        final String url = await ref.getDownloadURL();
        urls.add(url);
      }
      return urls;
    } catch (e) {
      print('获取图片URL失败:$e');
      return [];
    }
  }

  // 删除图片
  Future<bool> deleteImage(String imageUrl) async {
    try {
      // 从URL中获取图片引用
      final Reference ref = _storage.refFromURL(imageUrl);
      await ref.delete();
      return true;
    } catch (e) {
      print('图片删除失败:$e');
      return false;
    }
  }
}

4.3 实现图片上传与展示页面

创建 lib/screens/image_upload_screen.dart 文件,使用 image_picker 插件选择图片,结合存储工具类实现上传与展示:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_fire_demo/services/storage_service.dart';
import 'dart:io';

class ImageUploadScreen extends StatefulWidget {
  const ImageUploadScreen({super.key});

  @override
  State<ImageUploadScreen> createState() => _ImageUploadScreenState();
}

class _ImageUploadScreenState extends State<ImageUploadScreen> {
  final StorageService _storageService =
      StorageService(uid: FirebaseAuth.instance.currentUser?.uid);
  final ImagePicker _picker = ImagePicker();
  File? _selectedImage;
  List<String> _imageUrls = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    // 初始化时获取已上传的图片
    _getUploadedImages();
  }

  // 获取已上传的图片
  Future<void> _getUploadedImages() async {
    setState(() => _isLoading = true);
    final List<String> urls = await _storageService.getImageUrls();
    setState(() {
      _imageUrls = urls;
      _isLoading = false;
    });
  }

  // 选择图片(从相册)
  Future<void> _pickImage() async {
    final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);
    if (pickedFile != null) {
      setState(() {
        _selectedImage = File(pickedFile.path);
      });
    }
  }

  // 上传图片
  Future<void> _uploadImage() async {
    if (_selectedImage == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请先选择图片')),
      );
      return;
    }

    setState(() => _isLoading = true);
    final String? downloadUrl = await _storageService.uploadImage(_selectedImage!);
    setState(() => _isLoading = false);

    if (downloadUrl != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('图片上传成功')),
      );
      // 上传成功后重新获取图片列表
      _getUploadedImages();
      // 清空选择的图片
      setState(() => _selectedImage = null);
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('图片上传失败')),
      );
    }
  }

  // 删除图片
  Future<void> _deleteImage(String imageUrl) async {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认删除'),
        content: const Text('确定要删除这张图片吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () async {
              Navigator.pop(context);
              setState(() => _isLoading = true);
              final bool success = await _storageService.deleteImage(imageUrl);
              setState(() => _isLoading = false);

              if (success) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('图片删除成功')),
                );
                // 删除成功后重新获取图片列表
                _getUploadedImages();
              } else {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('图片删除失败')),
                );
              }
            },
            child: const Text('删除'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('图片上传与展示')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 选择并预览图片
            if (_selectedImage != null)
              Column(
                children: [
                  Image.file(
                    _selectedImage!,
                    height: 200,
                    fit: BoxFit.cover,
                  ),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: _isLoading ? null : _uploadImage,
                    child: _isLoading
                        ? const CircularProgressIndicator(color: Colors.white)
                        : const Text('上传图片'),
                  ),
                ],
              )
            else
              ElevatedButton(
                onPressed: _isLoading ? null : _pickImage,
                child: const Text('选择图片'),
              ),
            const SizedBox(height: 32),
            // 展示已上传的图片
            const Text(
              '已上传图片',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            if (_isLoading)
              const CircularProgressIndicator()
            else if (_imageUrls.isEmpty)
              const Text('暂无上传的图片')
            else
              Expanded(
                child: GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    crossAxisSpacing: 16,
                    mainAxisSpacing: 16,
                  ),
                  itemCount: _imageUrls.length,
                  itemBuilder: (context, index) {
                    final String url = _imageUrls[index];
                    return Stack(
                      children: [
                        Image.network(
                          url,
                          height: double.infinity,
                          width: double.infinity,
                          fit: BoxFit.cover,
                        ),
                        Positioned(
                          top: 8,
                          right: 8,
                          child: IconButton(
                            icon: const Icon(Icons.delete, color: Colors.red),
                            onPressed: _isLoading
                                ? null
                                : () => _deleteImage(url),
                          ),
                        ),
                      ],
                    );
                  },
                ),
              ),
          ],
        ),
      ),
    );
  }
}

第五章 整合三大功能:实现首页导航

为了将认证、数据库、云存储三大功能整合起来,创建首页 home_screen.dart,使用 BottomNavigationBar 实现页面切换:

代码语言:javascript
复制
代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:flutter_fire_demo/screens/profile_screen.dart';
import 'package:flutter_fire_demo/screens/image_upload_screen.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _currentIndex = 0;

  // 导航页面列表
  final List<Widget> _screens = [
    const ProfileScreen(),
    const ImageUploadScreen(),
  ];

  // 导航标题列表
  final List<String> _titles = [
    '个人中心',
    '图片管理',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(_titles[_currentIndex])),
      body: _screens[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) => setState(() => _currentIndex = index),
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '个人中心',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.photo),
            label: '图片管理',
          ),
        ],
      ),
    );
  }
}

第六章 测试与部署注意事项

6.1 功能测试

  1. 登录测试:测试邮箱密码注册、登录,Google 登录,退出登录功能是否正常;
  2. 数据库测试:测试用户信息的保存、更新、删除、实时同步功能是否正常;
  3. 云存储测试:测试图片选择、上传、展示、删除功能是否正常;
  4. 异常测试:测试空输入、网络异常、权限不足等场景下的错误提示是否友好。

6.2 权限配置

在 Android 和 iOS 平台,需要配置对应的权限(如相册访问权限):

  1. Android:修改 android/app/src/main/AndroidManifest.xml,添加相册访问权限: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  2. iOS:修改 ios/Runner/Info.plist,添加相册访问权限描述: <key>NSPhotoLibraryUsageDescription</key> <string>需要访问相册以选择图片</string>

6.3 生产环境配置

部署到生产环境前,必须修改 Firebase 服务的规则,避免数据泄露:

  1. 实时数据库规则:限制用户只能读写自己的信息:
  1. 云存储规则:限制用户只能读写自己目录下的文件:

总结

本教程通过实战案例,详细讲解了 Flutter 与 Firebase 集成的核心流程,包括环境准备、用户认证、实时数据库、云存储四大核心模块,并通过首页导航将三者整合,形成了一个功能完整的应用。通过 Firebase 的集成,开发者无需关注后端服务的搭建与维护,只需专注于前端业务逻辑的实现,大幅提升了开发效率。

后续可进一步扩展功能,如添加邮箱验证、密码重置、实时聊天、推送通知等,充分利用 Firebase 生态的强大能力。同时,在生产环境中,需重点关注数据安全与权限控制,确保应用的稳定性与安全性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 第一章 环境准备
    • 1.1 前置条件
    • 1.2 Firebase 控制台配置
    • 1.3 Flutter 项目依赖配置
    • 1.4 Firebase 初始化
  • 第二章 用户认证:实现邮箱密码登录与 Google 登录
    • 2.1 启用 Firebase 认证方式
    • 2.2 封装认证工具类
    • 2.3 实现登录页面
    • 2.4 实现注册页面
    • 2.5 登录状态监听与首页保护
  • 第三章 实时数据库:实现数据增删改查
    • 3.1 启用实时数据库并配置规则
    • 3.2 封装数据库工具类
    • 3.3 实现用户信息管理页面
  • 第四章 云存储:实现图片上传与展示
    • 4.1 启用云存储并配置规则
    • 4.2 封装云存储工具类
    • 4.3 实现图片上传与展示页面
  • 第五章 整合三大功能:实现首页导航
  • 第六章 测试与部署注意事项
    • 6.1 功能测试
    • 6.2 权限配置
    • 6.3 生产环境配置
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档