ByteNoteByteNote

字节笔记本

2026年2月19日

Field Trippa:使用 Google Photos API 的 Flutter 照片分享应用

API中转
¥120

本文介绍 Flutter Codelab "Field Trippa",一个使用 Google Photos API 实现照片分享功能的 Flutter 应用开发教程。

Field Trippa 简介

Field Trippa 是一个让用户之间互相分享照片的 Flutter 应用,通过调用 Google Photos Library API 实现照片上传和共享影集功能。每个旅行影集对应 Google Photos 上的一个共享影集,可通过 URL 分享给未安装应用的用户。

教程来源:Flutter 官方 Codelab(中文本地化版本) 源码仓库https://github.com/googlecodelabs/photos-sharing

学习目标

完成本教程后,你将学会:

  • 使用 Google Photos Library API 上传媒体资料和共享影集
  • 在 Flutter 中集成 Google 登录
  • 在 Flutter 中调用 Google API

开发准备

环境要求

  • Flutter 开发环境(已安装 Flutter SDK)
  • Android Studio 或 VS Code
  • 两台登录不同 Google 账户的设备/模拟器(用于测试分享功能)

Google Cloud 配置

  1. 创建 Google Cloud 项目
  2. 启用 Google Photos Library API
  3. 配置 OAuth 2.0 客户端 ID
  4. 下载 google-services.json(Android)或配置 iOS 客户端

核心架构

数据层架构

组件职责
PhotosLibraryApiModel数据层,抽象 Google Photos API 调用
PhotosLibraryApiClient执行 HTTPS REST 调用
google_sign_in处理 OAuth 2.0 认证流程

认证流程

使用 google_sign_in 插件处理 OAuth 2.0 认证:

dart
import 'package:google_sign_in/google_sign_in.dart';

final GoogleSignIn _googleSignIn = GoogleSignIn(
  scopes: [
    'https://www.googleapis.com/auth/photoslibrary',
    'https://www.googleapis.com/auth/photoslibrary.sharing',
  ],
);

Future<void> _handleSignIn() async {
  try {
    final account = await _googleSignIn.signIn();
    // 获取认证令牌
    final auth = await account.authentication;
    final accessToken = auth.accessToken;
  } catch (error) {
    print('Sign in failed: $error');
  }
}

核心 API 实现

创建影集

dart
Future<Album> createAlbum(CreateAlbumRequest request) async {
  return http.post(
    Uri.parse('https://photoslibrary.googleapis.com/v1/albums'),
    body: jsonEncode(request),
    headers: await _authHeaders,
  ).then((Response response) {
    if (response.statusCode == 200) {
      return Album.fromJson(jsonDecode(response.body));
    }
    throw Exception('Failed to create album: ${response.body}');
  });
}

上传媒体(两步流程)

Google Photos API 上传需要两个步骤:

  1. 上传字节数据获取 upload token
dart
Future<String> uploadMediaItem(File image, String filename) async {
  final headers = await _authHeaders;
  headers['Content-Type'] = 'application/octet-stream';
  headers['X-Goog-Upload-Protocol'] = 'raw';
  headers['X-Goog-Upload-File-Name'] = filename;

  return http.post(
    Uri.parse('https://photoslibrary.googleapis.com/v1/uploads'),
    body: image.readAsBytesSync(),
    headers: headers,
  ).then((response) {
    if (response.statusCode == 200) {
      return response.body; // 返回 upload token
    }
    throw Exception('Upload failed: ${response.body}');
  });
}
  1. 用 token 创建媒体项
dart
Future<void> createMediaItem(String uploadToken, String albumId) async {
  final request = {
    'albumId': albumId,
    'newMediaItems': [
      {
        'description': 'Uploaded from Field Trippa',
        'simpleMediaItem': {
          'uploadToken': uploadToken,
        },
      },
    ],
  };

  return http.post(
    Uri.parse('https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate'),
    body: jsonEncode(request),
    headers: await _authHeaders,
  );
}

获取影集列表

dart
Future<List<Album>> listAlbums() async {
  // 只获取应用创建的影集
  final uri = Uri.parse(
    'https://photoslibrary.googleapis.com/v1/albums?excludeNonAppCreatedData=true'
  );

  final response = await http.get(uri, headers: await _authHeaders);
  final data = jsonDecode(response.body);

  return (data['albums'] as List)
      .map((album) => Album.fromJson(album))
      .toList();
}

分享功能实现

启用影集分享

dart
Future<ShareInfo> shareAlbum(String albumId) async {
  final response = await http.post(
    Uri.parse('https://photoslibrary.googleapis.com/v1/albums/$albumId:share'),
    headers: await _authHeaders,
  );

  final data = jsonDecode(response.body);
  return ShareInfo.fromJson(data['shareInfo']);
}

分享方式

分享方式字段说明
URL 分享shareInfo.shareableUrl浏览器访问,无需安装应用
应用内分享shareInfo.shareToken其他用户通过 token 加入影集

加入共享影集

dart
Future<void> joinSharedAlbum(String shareToken) async {
  await http.post(
    Uri.parse('https://photoslibrary.googleapis.com/v1/sharedAlbums:join'),
    body: jsonEncode({'shareToken': shareToken}),
    headers: await _authHeaders,
  );
}

合并自有和共享影集

dart
Future<List<Album>> loadAllAlbums() async {
  // 并行加载自有影集和共享影集
  final results = await Future.wait([
    _loadAlbums(),      // 自有影集
    _loadSharedAlbums(), // 共享影集
  ]);

  return [...results[0], ...results[1]];
}

数据模型

Album 模型

dart
class Album {
  final String id;
  final String title;
  final String? coverPhotoBaseUrl;
  final String? shareToken;
  final String? shareableUrl;
  final bool isShared;
  final int mediaItemsCount;

  Album({
    required this.id,
    required this.title,
    this.coverPhotoBaseUrl,
    this.shareToken,
    this.shareableUrl,
    this.isShared = false,
    this.mediaItemsCount = 0,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'],
      title: json['title'],
      coverPhotoBaseUrl: json['coverPhotoBaseUrl'],
      shareToken: json['shareInfo']?['shareToken'],
      shareableUrl: json['shareInfo']?['shareableUrl'],
      isShared: json['shareInfo'] != null,
      mediaItemsCount: json['mediaItemsCount'] ?? 0,
    );
  }
}

生产环境建议

安全提示:将 token 存放在安全性更高的后端,并利用 app 内已有的用户关系进行影集的创建和加入。

最佳实践

  1. 后端代理:敏感操作通过后端服务器代理,避免在前端暴露 access token
  2. 令牌刷新:实现自动刷新机制,处理 token 过期
  3. 错误处理:完善的错误处理和用户提示
  4. 离线支持:本地缓存影集数据,提升用户体验

相关资源

总结

Field Trippa Codelab 是一个完整的 Flutter + Google Photos API 集成教程,涵盖了:

  • OAuth 2.0 认证:Google 登录集成
  • REST API 调用:与 Google Photos Library API 交互
  • 文件上传:两步上传流程实现
  • 社交功能:影集分享和加入机制
  • 数据建模:完整的模型设计

对于希望集成 Google Photos 功能到自己的 Flutter 应用的开发者来说,这是一个极佳的入门教程。

分享: