字
字节笔记本
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 配置
- 创建 Google Cloud 项目
- 启用 Google Photos Library API
- 配置 OAuth 2.0 客户端 ID
- 下载
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 上传需要两个步骤:
- 上传字节数据获取 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}');
});
}- 用 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 内已有的用户关系进行影集的创建和加入。
最佳实践
- 后端代理:敏感操作通过后端服务器代理,避免在前端暴露 access token
- 令牌刷新:实现自动刷新机制,处理 token 过期
- 错误处理:完善的错误处理和用户提示
- 离线支持:本地缓存影集数据,提升用户体验
相关资源
- 官方文档:https://developers.google.com/photos
- 源码仓库:https://github.com/googlecodelabs/photos-sharing
- Google Photos Partner Program:https://developers.google.com/photos/partner-program
- API 参考:智能内容过滤、多媒体共享指南
总结
Field Trippa Codelab 是一个完整的 Flutter + Google Photos API 集成教程,涵盖了:
- OAuth 2.0 认证:Google 登录集成
- REST API 调用:与 Google Photos Library API 交互
- 文件上传:两步上传流程实现
- 社交功能:影集分享和加入机制
- 数据建模:完整的模型设计
对于希望集成 Google Photos 功能到自己的 Flutter 应用的开发者来说,这是一个极佳的入门教程。
分享: