Flutter×Firebase2_Providerを使用してPageとModelに分割【Mac】

今週は不具合の週でした。。。
ストレージやコードが正しいのに反映されない？現象が起こってましたが
なんとか解決できましたので
引き続きFlutter関連の投稿を進めようと思います！！

備忘録として使用する予定ですが
皆さんの解決策にもなれば幸いです。
今回はFirebaseのproviderをインストールしてPageとModelに分割します。


## 目次
[-完成イメージ](#完成イメージ)
[-1.構成](#1構成)
[-2.Providerを使用してPageとModelに分割①_Providerをインストール](#2providerを使用してpageとmodelに分割_providerをインストール)
[-3.Providerを使用してPageとModelに分割②_ディレクトリの整理](#3providerを使用してpageとmodelに分割_ディレクトリの整理)
[-4.Providerを使用してPageとModelに分割③_Bookクラスの作成](#4providerを使用してpageとmodelに分割_bookクラスの作成)
[-5.Providerを使用してPageとModelに分割④_ChangeNotifierProviderを記載](#5providerを使用してpageとmodelに分割_changenotifierproviderを記載)
[-6.Providerを使用してPageとModelに分割⑤_データの追加](#6providerを使用してpageとmodelに分割_データの追加)
[-7.全体コード](#7全体コード)
[-参考文献](#参考文献)
[-最後に](#最後に)


## 完成イメージ
・前回の記事からFlutterアプリにProvider[^1]を追加
　※下記前回記事
[^1]:親Widgetから子Widgetにデータを受け渡すことが出来る処理のこと

https://qiita.com/ds0743/items/f95654854ea2fd01bd1b

・ディレクトリの整理
・Modelの作成
・bookクラスの作成
・ChangeNotifierProvider[^2]の記載
[^2]:ChangeNotifier のインスタンス（モデル）をその子孫に提供するウィジェット

・データを一冊分追加

<img width="40%" alt="provider①.png" img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121562/4fefe31a-293e-267a-b883-2590166d3cc4.png">


## 1.構成
・Providerをpubspec.yamlに追加
・libフォルダ内でbook_listフォルダを作成
・book_listフォルダにbook_list_page.dartを移動し
　book_list_model.dartを作成する
 
・libフォルダ内でdomainフォルダを作成
・domainフォルダにbook.dartを作成
・book_list_page.dartにChangeNotifierProviderを記載
・Firebaseサイトより一冊分データを追加


## 2.Providerを使用してPageとModelに分割①_Providerをインストール
・下記を参考に最新のProviderをpubspec.yamlに追加する

https://pub.dev/packages/provider/install

```php:pubspec.yaml
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6
  firebase_core: ^3.1.0
  cloud_firestore: ^5.0.1
  provider: ^6.1.2//←こちらを追加
```

## 3.Providerを使用してPageとModelに分割②_ディレクトリの整理
・libフォルダにbook_listフォルダを作成
・book_listフォルダにbook_list_page.dartを移動
・book_listフォルダにbook_list_model.dartを作成
![provider②.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121562/a03f97ce-a882-d66b-83e8-5630cd8f9500.png)
※現状のイメージ画像

・book_list_model.dartにBookListModel class作成
・fetchBookList関数を作成
・book_list_page.dartからfinal Stream<QuerySnapshot> 〜を切り取りし
　book_list_model.dartに貼り付け
・book_list_page.dartからsnapshot.data!docs.map〜をコピーして
　book_list_model.dartに貼り付け。少し加工
・firestoreをインポート
・material.dartをインポート
・データを返す準備処理を記載

```php:book_list_model.dart
import 'package:cloud_firestore/cloud_firestore.dart';//←firestoreをインポート
import 'package:flutter/material.dart';//←material.dartをインポート

class BookListModel extends ChangeNotifier {
  final Stream<QuerySnapshot> _usersStream =
      FirebaseFirestore.instance.collection('books').snapshots();//final Stream<QuerySnapshot> 〜をbook_list_page.dartから切り取りし貼り付け


  void fetchBookList() {//←fetchBookList関数を作成
    _usersStream.listen((QuerySnapshot snapshot) {
      final List<Book> books = snapshot.docs.map((DocumentSnapshot document) {//←snapshot.data!docs.map〜をbook_list_page.dartからコピーし貼り付け。少し加工。
        Map<String, dynamic> data = document.data()! as Map<String, dynamic>;//データを返す準備処理を記載

        
      }).toList();
    });
  }
}
```





## 4.Providerを使用してPageとModelに分割③_Bookクラスの作成
・libフォルダにdomainフォルダを作成
・book_listフォルダにbook.dartを作成
![provider③.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121562/510360c8-4885-e47a-4d40-563e6fa833af.png)

※現状のイメージ画像


・book.dartにBook classを作成しtitleとauthorを定義する
```php:book.dart
class Book {
  Book(this.title, this.author);
  String title;
  String author;
}
```
・book_list_model_dartのfetchBookList関数の中にBookへ変換するための値を入れる
・booksへの代入処理を行う
・Bookリストのインスタンスが生成された際にfetchBookListメソッドを呼び出すように処理
・UIやWidgetに通知する処理を記載

```php:book_list_model.dart
List<Book>? books;//←booksへの代入処理

  BookListModel() {//Bookリストのインスタンスが生成された際にfetchBookListメソッドを呼び出すように処理
    fetchBookList(); 
  }


void fetchBookList() {
    _usersStream.listen((QuerySnapshot snapshot) {
      final List<Book> books = snapshot.docs.map((DocumentSnapshot document) {
        Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
        final String title = data['title'];//←Bookへ変換するための値
        final String author = data['author'];//←Bookへ変換するための値
        return Book(title, author);//←Bookへ変換するための値
      }).toList();
      this.books = books;//←booksへの代入処理
      notifyListeners();//←UIやWidgetに通知する処理
    });
  }
```
※fetchbooklist関数の処理前はList<Book>の中身はnullとなるためnull許容の?を入れて
 　List<　Book　>？ books;としている

## 5.Providerを使用してPageとModelに分割④_ChangeNotifierProviderを記載
・book_list_page.dartでCenter Widget内のStreamBuilderから全て削除
・下記を参考にConsumer〜をCenter　Widgetに代入
・providerをインポート
・下記記事を参考にScaffoldにWidgetを囲みChangeNotifierProviderを代入

https://github.com/flutteruniv/flutter_provider_samples/blob/master/lib/count_page.dart

・CountModelをBookListModelへ変更
・booksを定義しListviewで返すためにWidgetへ変換する
　※ListViewはWidgetで返せるので変換が必要
・bookがnullの場合の処理（Loading中となる）を記載

```php:book_list_page.dart
import 'package:firebase1_firebase_add/book_list/book_list_model.dart';
import 'package:firebase1_firebase_add/domain/book.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';//←providerをインポート

class BookListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<BookListModel>(//←Widgetで囲み、ChangeNotifierProviderを代入・CountModelをBookListModelへ変更
      create: (_) => BookListModel(),//←CountModelをBookListModelへ変更
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text('本一覧'),
        ),
        body: Center(//CenterWidget内を削除し添付記事からConsumer~を代入
          child: Consumer<BookListModel>(builder: (context, model, child) {//←CountModelをBookListModelへ変更
            final List<Book>? books = model.books;//←booksを定義

            if (books == null) {//←bookがnullの場合の処理（Loading中となる）を記載
              return CircularProgressIndicator();
            }

            final List<Widget> widgets = books//←booksをWidgetに変換
                .map(
                  (book) => ListTile(
                    title: Text(book.title),
                    subtitle: Text(book.author),
                  ),//←booksをWidgetに変換
                )
                .toList();
            return ListView(//←widgets(books)をListviewに返すように処理
              children: widgets,
            );
          }),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: null,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

```


## 6.Providerを使用してPageとModelに分割⑤_データの追加
・下記リンクにログインする
　<font color="Red">※ログインは先ほどログインしたものと同様のアカウント</font>
　https://firebase.google.com/
・右上の「Go to Console」をクリック
・作成したプロジェクトをクリック
・左のタブの「構築」→「Firestore Database」をクリック
 <img width="30%" alt="firebase_add⑨.png" img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121562/dd5f208b-8bd2-6b97-2449-0261a7c62aae.png">
・「ドキュメントを追加」をクリック
・ドキュメントIDの「自動ID」をクリック
・フィールドに「title」、値に「エコノミー革命」を入力
・フィールドの追加→フィールドに「サクッとわかる ビジネス教養　行動経済学」、
　値に「阿部　誠」を入力
・保存をクリック
<img width="30%" alt="firebase_add⑨.png" img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121562/dd5f208b-8bd2-6b97-2449-0261a7c62aae.png">

## 7.全体コード
```php:book_list_page.dart
import 'package:firebase1_firebase_add/book_list/book_list_model.dart';
import 'package:firebase1_firebase_add/domain/book.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class BookListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<BookListModel>(
      create: (_) => BookListModel(),
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text('本一覧'),
        ),
        body: Center(
          child: Consumer<BookListModel>(builder: (context, model, child) {
            final List<Book>? books = model.books;

            if (books == null) {
              return CircularProgressIndicator();
            }

            final List<Widget> widgets = books
                .map(
                  (book) => ListTile(
                    title: Text(book.title),
                    subtitle: Text(book.author),
                  ),
                )
                .toList();
            return ListView(
              children: widgets,
            );
          }),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: null,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
```
```php:book_list_model.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase1_firebase_add/domain/book.dart';
import 'package:flutter/material.dart';

class BookListModel extends ChangeNotifier {
  final Stream<QuerySnapshot> _usersStream =
      FirebaseFirestore.instance.collection('books').snapshots();

  List<Book>? books;

  BookListModel() {
    fetchBookList(); 
  }

  void fetchBookList() {
    _usersStream.listen((QuerySnapshot snapshot) {
      final List<Book> books = snapshot.docs.map((DocumentSnapshot document) {
        Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
        final String title = data['title'];
        final String author = data['author'];
        return Book(title, author);
      }).toList();
      this.books = books;
      notifyListeners();
    });
  }
}
```
```php:book.dart
class Book {
  Book(this.title, this.author);
  String title;
  String author;
}
```
pubspec.yamlは変更点のあった35~40行目付近のみを記載
```php:pubspec.yaml
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6
  firebase_core: ^3.1.0
  cloud_firestore: ^5.0.1
  provider: ^6.1.2
```

## 参考文献
Flutter大学. 「【2021年8月最新】Providerを使って画面をPageとModelに分割しよう」.　YouTube.　2021/08,https://www.youtube.com/watch?v=MvNyZrMr-3c&list=PLuLRJz1UnJzEYLFd1ihJit1E6dYol8VC5&index=3&t=0, (参照 2024-07-08)

pub.dev. 「provider 6.1.2」.　provider| Flutter packege.　
https://pub.dev/packages/provider, (参照 2024-07-08)

flutteruniv. 「flutter_provider_samples」.　Github.　
https://github.com/flutteruniv/flutter_provider_samples/blob/master/lib/count_page.dart, (参照 2024-07-08)


## 最後に
・今回こちらを作成しビルドするとbooksがnullとなり
　画面がLoading中となっておりました。
・動画を５回程見返しましたがコード自体に誤りはなく路頭に迷っていましたが
　VScodeを数回更新すると解決しました笑
・皆さんも定期的な更新を心がけてください！