🌎
riverpodを使ってFireStoreの値を取得する
riverpodとは?
今流行りの、Flutterの状態管理ライブラリーというやつですね。
今回、FireStoreの値を取得することに成功したので、記事を書いて記録を残しておきます。
*開発環境
flutter2.10.3
Android OS
設定はこんな感じでしました。動作は保証できません😇
app/src/build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.book_list"
minSdkVersion 23 // 23に修正
targetSdkVersion 31 // 31に修正
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation platform('com.google.firebase:firebase-bom:30.0.1')
implementation "androidx.multidex:multidex:2.0.1"
}
pabspec.yaml
name: book_list
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.16.1 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
# riverpod用のパッケージ
flutter_riverpod: ^1.0.3
# Firebase用のパッケージ
cloud_firestore: ^3.1.13
firebase_core: ^1.15.0
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
フォルダ構成
.
├── generated_plugin_registrant.dart
├── main.dart
├── model
│ ├── book_data.dart
│ ├── book_state.dart
│ └── book_store.dart
├── provider
│ └── global_provider.dart
└── view
└── book_data_page.dart
modelフォルダのファイル
book_data.dart
// 本のタイトルクラス
class BookData {
String documentId;
String title;
String auther;
// コンストラクター
BookData({this.documentId = '', required this.title, required this.auther});
// factoryを利用する場合、インスタンスは処理内で作成して返却する必要がある
// 今回は引数(Firestoreの情報)からインスタンスを生成する
// また引数については、処理内で再設定されないようfinalを追記
factory BookData.toModel(
final String docId, final Map<String, dynamic> data) {
// ここでインスタンスを生成し返却する
return BookData(
documentId: docId,
title: data['title'],
auther: data['auther']
);
}
// toJsonメソッドは
// 呼び出されるインスタンスの各フィールド値をMap形式に変換します
Map<String, Object?> toJson() {
return {
'title': title,
'auther': auther
};
}
}
book_state.dart
// 状態管理用のクラス
// BookData情報をリスト型で保持する
import 'package:book_list/model/book_data.dart';
class BookDataState {
List<BookData> books = [];
}
book_store.dart
// 状態管理クラス
import 'package:book_list/model/book_data.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class BookStore extends StateNotifier<List<BookData>> {
BookStore() : super([]) {
// これは初期化処理1度だけ実行されます
// ここでFirestoreの情報を取得
debugPrint('init call📞');
_readFirebaseDocument();
}
// Firebaseの読み込みを行う関数
// 対象DocumentはBooks
Future<void> _readFirebaseDocument() async {
// まずはFirebaseを利用するためにinstanceを取得
// 以降設定することがないのでfinalで定義
final store = FirebaseFirestore.instance;
final document = await store
.collection('books')
.withConverter<BookData>(
fromFirestore: (snapshot, _) =>
BookData.toModel(snapshot.id, snapshot.data()!),
toFirestore: (book, _) => book.toJson(),
)
.get();
// 取得したDocument情報からTodo情報を取得して
// stateに設定する
// document.docs.map((bookData) => todoData.data()).toList()
// ではdocument.docsの中身をtodoDataで受けて
// bookData.data()でBook.toModelを実行した結果にアクセスする
// その戻り値を.toList()でList形式に変換する
final List<BookData> list =
document.docs.map((bookData) => bookData.data()).toList();
state = list; // mapで変換したデータが入った変数
}
}
providerフォルダのファイル
global_provider.dart
import 'package:book_list/model/book_data.dart';
import 'package:book_list/model/book_store.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 状態管理用のプロバイダー
// StateNotifierProvider<[状態管理クラス:extends StateNotifier<状態クラス>],状態クラス>を指定する
// 今回でいえばStateNotifierProvider<BookStore, List<BookData>
// final bookDataStateProvider =
// StateNotifierProvider<BookStore, List<BookData>>((ref) => BookStore()); // 状態管理クラスを返却する
// このように書き換えることもできる
final bookDataStateProvider =
StateNotifierProvider<BookStore, List<BookData>>((ref) {
return BookStore();
});
viewフォルダのファイル
book_data_page.dart
import 'package:book_list/model/book_data.dart';
import 'package:book_list/provider/global_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class BookDataPage extends ConsumerWidget {
BookDataPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text("BOOK-APP"),
),
body: Column(
children: <Widget>[
// Expandedを使えばColumnやRowなどの空いたスペースをレスポンシブに埋めることができます!
Expanded(
child: _listViewBuilder(ref),
),
],
),
);
}
/// 引数が存在する場合はこんな形で[]で囲むとよい
/// [ref] WidgetRef
/// create List View
Widget _listViewBuilder(WidgetRef ref) {
return ListView.builder(
itemCount: ref.watch(bookDataStateProvider).length,
itemBuilder: (BuildContext context, int index) {
return _bookItem(context, ref, index);
});
}
Widget _bookItem(BuildContext context, WidgetRef ref, int index) {
// リストの各要素をWidget化
// 今回は基本的に状態リスト自体を書き換えているので、ここはreadで問題ない
// 状態リストが書き換わると、
// 本メソッドの呼び出し元 itemCount: ref.watch(bookDataStateProvider).length
// でリビルドが発生するため、中身の各要素に応じたWidgetもリビルド対象となる
BookData bookData = ref.read(bookDataStateProvider)[index];
return Container(
child: ListTile(
subtitle: Text('著者: ${bookData.auther}'),
title: Text('本のタイトル: ${bookData.title}'),
),
);
}
}
main.dart
import 'package:book_list/view/book_data_page.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
// Riverpodでデータを受け渡しが可能な状態にするために必要
ProviderScope(
child: MaterialApp(
title: 'Book App',
theme: ThemeData(
primaryColor: Colors.blueAccent
),
home: BookDataPage(),
),
),
);
}
generated_plugin_registrant.dart
//
// Generated file. Do not edit.
//
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars
import 'package:cloud_firestore_web/cloud_firestore_web.dart';
import 'package:firebase_core_web/firebase_core_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
FirebaseFirestoreWeb.registerWith(registrar);
FirebaseCoreWeb.registerWith(registrar);
registrar.registerMessageHandler();
}
最後に
単純なサンプルですが、誰かの役にお役に立てればと思いこの記事を書きました。私も最近エンジニアになったばかりでわからないことだらけです。
毎日outputしております🧑💻
Discussion
質問です。
このコードだと新規追加があった時はリアルタイムで更新されますか?
こちらだと、されてなかった気がしますね。
随分前に勉強用に書き換えていたコードなので、あまりわかってないです😅
StreamProvider使ったアプリはリアルタイムで更新されてましたよ!
ありがとうございます!