Open5
【Firebase】Cloud Storage と Cloud Firestore を使って画像管理機能を作成
画像をアップロードする(準備)
1. Cloud Storageを準備する
Storage > 始める
を選択して、ロケーションは日本国内であるasia-northeast1
を選択する
ルールを修正する
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
2.Cloud Storage の機能を使うためのライブラリをインストール
pubspec.yaml
# ...
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
firebase_core: ^1.17.1
firebase_auth: ^3.3.19
firebase_storage: ^10.2.17 # この行を追加
画像をアップロードする
1.端末の画像ファイルを選択できるライブラリをインストール
pubspec.yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
firebase_core: ^1.17.1
firebase_auth: ^3.3.19
firebase_storage: ^10.2.17
file_picker: ^4.6.1 # この行を追加
2.アップロードする処理を追加
- 画像ファイル選択(pickFiles()でファイルを1つ選ぶ・FileType.imageで画像ファイルのみ選ぶ)
- 画像ファイルが選択された場合の処理
// 画像を追加
Future<void> _onAddPhoto() async {
// 画像ファイルを選択
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
// 画像ファイルが選択された場合
if (result != null) {
// ログイン中のユーザー情報を取得
final User user = FirebaseAuth.instance.currentUser!;
// フォルダとファイル名を指定し画像ファイルをアップロード
// 日時をエポックミリ秒に変換
final int timestamp = DateTime.now().microsecondsSinceEpoch;
// ファイルのパス
final File file = File(result.files.single.path!);
// パスを/で区切った最後の値をnameに入れる
final String name = file.path.split('/').last;
final String path = '${timestamp}_$name';
final TaskSnapshot task = await FirebaseStorage.instance
.ref()
.child('users/${user.uid}/photos') // フォルダ名
.child(path) // ファイル名
.putFile(file); // 画像ファイル
}
}
アップロードした画像を管理する(準備)
1. Cloud Firestoreを準備する
ルールを修正する
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
2.Cloud Firestore の機能を使うためのライブラリをインストール
pubspec.yaml
# ...
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
firebase_core: ^1.17.1
firebase_auth: ^3.3.19
firebase_storage: ^10.2.17
file_picker: ^4.6.1
cloud_firestore: ^3.1.17 # この行を追加
アップロードした画像を管理する
- Cloud Storage にアップロードした画像ファイルのURLを取得
- Cloud Firestore に画像管理データを保存
- Cloud Firestore に保存した画像管理データを元に画像を一覧表示
「コレクション」と「ドキュメント」と呼ばれるものを組み合わせて「データ」を保存する。
- コレクション:複数のドキュメントを保存できる
- ドキュメント:データを保存できる、複数のサブコレクションを保存できる
- データ:複数のキーと値のペア
// パス(コレクション・ドキュメント)
/users/${userID}/photos/${photoID}
// データ
{
// 画像のURL(string)
"imageURL": "https://...",
// 画像ファイルのパス(string)
"imagePath": "users/id_xyz/photos/1599835164531849_myphoto.png",
// お気に入り(bool)
"isFavorite": false,
// 作成日時(timestamp)
"createdAt": "2020年9月11日 23:39:25 UTC+9"
}
// アップロードした画像のURLを取得
final String imageURL = await task.ref.getDownloadURL();
// アップロードした画像の保存先を取得
final String imagePath = task.ref.fullPath;
画像ファイルからURLを取得し、Cloud Firestore に画像管理データを保存する処理を追加
- スナップショットから画像のURLと画像の保存先を取得
- 保存するデータを作成
- データをCloud Firestoreに保存する
Future<void> _onAddPhoto() async {
// ...
if (result != null) {
// ...
final TaskSnapshot task = await FirebaseStorage.instance
.ref()
.child('users/${user.uid}/photos')
.child(path)
.putFile(file);
// アップロードした画像のURLを取得
final String imageURL = await task.ref.getDownloadURL();
// アップロードした画像の保存先を取得
final String imagePath = task.ref.fullPath;
// データ
final data = {
'imageURL': imageURL,
'imagePath': imagePath,
'isFavorite': false, // お気に入り登録
'createdAt': Timestamp.now(), // 現在時刻
};
// データをCloud Firestoreに保存
await FirebaseFirestore.instance
.collection('users/${user.uid}/photos') // コレクション
.doc() // ドキュメント(何も指定しない場合は自動的にIDが決まる)
.set(data); // データ
}
}
}
Cloud Firestoreに保存した各画像管理データを元に画像一覧表示する処理を追加
StreamBuilderを使用して一覧を表示する
- StreamBuilder:イベントが起きるたびその情報を自動でキャッチし、アプリの表示に反映させることができる
photo_list_screen.dart
class _PhotoListScreenState extends State<PhotoListScreen> {
// ...
Widget build(BuildContext context) {
// ログインしているユーザーの情報を取得
final User user = FirebaseAuth.instance.currentUser!;
return Scaffold(
// ...
body: StreamBuilder<QuerySnapshot>(
// Cloud Firesstoreからデータを取得
stream: FirebaseFirestore.instance
.collection('users/${user.uid}/photos')
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context, snapshot) {
// Cloud Firestoreからデータを取得中の場合
if (snapshot.hasData == false) {
return Center(
child: CircularProgressIndicator(),
);
}
// Cloud Firestoreからデータを取得完了した場合
final QuerySnapshot query = snapshot.data;
// 画像のURL一覧を作成
final List<String> imageList = query.docs
.map((doc) => doc.get('imageURL') as String)
.toList();
return PageView(
controller: _controller,
onPageChanged: (int index) => _onPageChanged(index),
children: [
//「全ての画像」を表示する部分
PhotoGridView(
// Cloud Firestoreから取得した画像のURL一覧を渡す
imageList: imageList,
onTap: (imageURL) => _onTapPhoto(imageURL, imageList),
),
//「お気に入り登録した画像」を表示する部分
PhotoGridView(
// お気に入り登録した画像は、後ほど実装
imageList: [],
onTap: (imageURL) => _onTapPhoto(imageURL, imageList),
),
],
);
},
),
// ...
);
}
// ...
void _onTapPhoto(String imageURL, List<String> imageList) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => PhotoViewScreen(
imageURL: imageURL,
imageList: imageList,
),
),
);
}
}
// Widgetを新たに定義し再利用できる
class PhotoGridView extends StatelessWidget {
const PhotoGridView({
Key? key,
// 引数から画像のURL一覧を受け取る
required this.imageList,
required this.onTap,
}) : super(key: key);
final List<String> imageList;
final Function(String imageURL) onTap;
Widget build(BuildContext context) {
return GridView.count(
// ...
children: imageList.map((String imageURL) {
// ...
}).toList(),
);
}
}
photo_view_screen.dart
import 'package:flutter/material.dart';
class PhotoViewScreen extends StatefulWidget {
const PhotoViewScreen({
Key? key,
required this.imageURL,
// 引数から画像のURL一覧を受け取る
required this.imageList,
}) : super(key: key);
final String imageURL;
final List<String> imageList;
_PhotoViewScreenState createState() => _PhotoViewScreenState();
}
class _PhotoViewScreenState extends State<PhotoViewScreen> {
late PageController _controller;
void initState() {
super.initState();
// 受け取った画像一覧から、ページ番号を特定
final int initialPage = widget.imageList.indexOf(widget.imageURL);
_controller = PageController(
initialPage: initialPage,
);
}
Widget build(BuildContext context) {
return Scaffold(
// ...
body: Stack(
children: [
PageView(
// ...
// 受け取った画像一覧を表示
children: widget.imageList.map((String imageURL) {
return Image.network(
imageURL,
fit: BoxFit.cover,
);
}).toList(),
),
// ...
],
),
);
}
}