質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

Q&A

解決済

1回答

1478閲覧

Firestoreのデータを使ったListview.builderが更新されない

nugio.kutusita

総合スコア21

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

0グッド

0クリップ

投稿2022/05/28 17:01

編集2022/05/28 23:58

自作アプリで、Firestoreのデータを用いてListviewを表示させたいです。

メインページからこちらのページに飛んでくるのですが、
イニシャライザでgetRecipeList()を使ってFirestoreからデータをgetDataMapListに取得し、
これを用いてListview.builderを作っています。
データにはレシピ名とその属性(name、category、flavor)をもっています。

一度はできていたのですが、別の機能(絞り込み)を実装しようとしている中で、
上手く動かなくなり、どこがおかしいのかわからなくなってしまいました。

ページに入った際はうまくリストが出ないのですが、
ホットリロードをすると表示されます。
また、イニシャライザの書き方がよくないのかと思い、
一番下にボタンでgetRecipeList();を起動するものも用意してみましたが、これもダメでした。

関係ない部分も多く含んでしまっており、読みにくいかと思いますが、
どなたかご指導いただけると幸いです。

Dart/Flutter

1 2class RecipeAdd extends StatefulWidget { 3 const RecipeAdd({Key? key}) : super(key: key); 4 5 State<RecipeAdd> createState() => _RecipeAddState(); 6} 7 8class _RecipeAddState extends State<RecipeAdd> { 9 10 _RecipeAddState(){ 11 getRecipeList(); 12 } 13 14 Future <void> getRecipeList()async{ 15 getDataMapList=[]; 16 final colRef = db.collection("recipe"); 17 var querySnapshot = await colRef.get(); // QuerySnapshot 18 var queryDocSnapshot = querySnapshot.docs; // List<QueryDocumentSnapshot> 19 for (final snapshot in queryDocSnapshot) { 20 final data = snapshot.data(); // 21 try { 22 getDataMapList.add(data); 23 } 24 catch(e){ 25 print("Error completing: $e"); 26 } 27 } 28 } 29String _addMenu =''; 30 int _selectedIndex = -1; 31 List getDataMapList= []; 32 33 34 Widget build(BuildContext context) { 35 var addSite = ModalRoute.of(context)?.settings.arguments; 36 bool selectedCheck = false; 37 return Scaffold( 38 body: Center( 39 child: Column( 40 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 41 children: [ 42 Text("$addSiteのメニュー"), 43 SizedBox( height: 400, width: 350, 44 child: ListView.builder( 45 itemCount: getDataMapList.length, 46 itemBuilder: (BuildContext context, int index) { 47 String _flavorText=""; 48 for ( String flavor in getDataMapList[index]["flavor"]){ 49 _flavorText += flavor + "," ; 50 } 51 IconData? _catIcon; 52 if( getDataMapList[index]["category"]=="肉"){ 53 _catIcon = Icons.kebab_dining; 54 } 55 else if (getDataMapList[index]["category"]=="魚"){ 56 _catIcon = Icons.directions_boat_filled; 57 } 58 else{ 59 _catIcon = Icons.local_dining; 60 } 61 return Card( 62 child:ListTile( 63 leading: Icon(_catIcon), 64 title: Text(getDataMapList[index]["name"]), 65 selected: _selectedIndex == index ? true:false, 66 selectedTileColor: Colors.cyan.withOpacity(0.2), 67 subtitle: Text("tag: $_flavorText",style: TextStyle(fontSize: 12),), 68 onTap: (){ 69 setState(() { 70 _addMenu = getDataMapList[index]["name"]; 71 _selectedIndex = index; 72 }); 73 }, 74 ), 75 ); 76 }, 77 ), 78 ), 79 Row( 80 mainAxisAlignment: MainAxisAlignment.center, 81 children: [ 82 ElevatedButton( 83 onPressed: () => getRecipeList(), 84 child: Icon(Icons.search), 85 ), 86 Card( 87 child: Text("絞り込み項目"), 88 ) 89 ], 90 ), 91 Container( 92 width: 200, height: 80, 93 child: ElevatedButton( 94 onPressed: () { 95 Navigator.pop(context, _addMenu); 96 }, 97 child: Text("$_addMenu\nにする!",style: TextStyle(fontSize: 20), 98 ), 99 style: ElevatedButton.styleFrom(primary: Colors.deepOrange) 100 ), 101 ), 102 ElevatedButton( 103 style: ElevatedButton.styleFrom(primary: Colors.lightGreen), 104 onPressed:(){ 105 Navigator.of(context).pushNamed("/NewRecipe") 106 .then((value) => { 107 if (value!=null){ 108 Navigator.pop(context, value)} 109 }); 110 }, 111 child: Text("新しいメニュー"), 112 ), 113 ElevatedButton( 114 onPressed: (){ 115 setState(() { 116 getRecipeList(); 117 }); 118 }, 119 child: Text("手動ロード") 120 ), 121 ], 122 ), 123 ), 124 ); 125 } 126}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

非同期でgetDataMapListを生成しているので、ListView.builderで表示する前までにデータがそろっていないのが原因でしょう。

非同期でデータを収集しその結果を表示する場合にはFutureBuilderを使うのが定石です。
データが少なく関数の時間が小さいのであれば、awaitを入れて同期するというやり方もありだと思いますが、今回コンストラクタ及び手動ロード側のsetState内の匿名関数では非同期関数の同期ができないので無理だとは思いますが。

FutureBuilderを使う場合には以下の様にします。

配置場所としては、ListView.builderの上にListView.builderをラップする形のがいいのかな。
FutureBuilderのfutureタグにはgetRecipeListを。
データ収取が完了したかどうかは、関数がFuture<void>なので、snapshot.connectionState == ConnectionState.done && !snapshot.hasErrorが良いと思います。
Future <List>的なリターンに変更するのであれば、snapshot.hasDataだけでいいとは思います。

また更新処理のためgetRecipeListを呼び出している箇所は、それを外す。
コンストラクタ部分と手動ロード部分。

注意点としては、buildが呼び出されるようなケースでは毎回getRecipeListが呼び出されます。
getDataMapListを更新したくない場合は、意図的にフラグなんかを用意し更新しないようなif文をgetRecipeListに入れるのがいいと思う。
ケースとしては、ListTileのonTapあたりかな。

投稿2022/05/29 02:47

ta.fu

総合スコア1667

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nugio.kutusita

2022/05/29 05:17

ありがとうございます。 ご指摘の通りだったようで、FutureBuilderによって表示されるようになりました。 ただ、Listtileの選択時に毎回リストの再生成がかかってしまい、うまく回避できません。 提案いただいたように、boolのフラグ変数を一つ用意して、ListTileのonTapにてフラグ=falseとしました。 getRecipeList関数内で、すべての処理をif文で囲み、フラグ=trueでのみ更新するようにしています。 これでリスト内容(項目)は更新されないようになったのですが、リストカード自体は再生成されますので、 スクロールして下のほうのカードを選択した際、再生成されてスクロールが一番上に戻る=選択したカードが隠れてしまう という問題が発生します。 再生成自体を防止する処理は難しいでしょうか。
ta.fu

2022/05/29 06:36

全く難しくはないです。 「何をやったらどこまで再作成をさせるのか」をまず考えます。 そして再作成を行う個所をStatefulWidgetやStatefulBuilder系の状態管理ウィジェットでラップし局所化します。 これにより再構築するウィジェットを制御できます。 今回のソースだと更新単位が全部なので全部更新するという動きをしているだけです。 修正方針としては、上の例で言うと、ListTile.onTapをすると_addMenuと_selectedIndexを変更し、これに関するウィジェットの状態変更をしようとしています。 それに関連するのは、個々のListTileとElevatedButtonなのでそれを再構築させることにします。 なお、StatefulWidgetやStatefulBuilderは内側のsetStateで更新をするため、外側から更新させるには他の状態管理用のウィジェットを使う方がいいと思います。 (更新対象をStatefulBuilderでラップし、そのsetStateを別に取り出して管理しておいて更新対象のウィジェットのsetStateを呼び出す、という荒業でやってやれないことはないですが。) 私はGetXというパッケージの状態管理を使ってます。 それを使うことで変数の変更により、関連付けしているウィジェットを再構築してくれるので結構便利に使っています。 今回のケースだと、_addMenuとElevatedButton、_selectedIndexをちょっと変更し、ListTile単位に選択状態かそうでないかのboolを個別に持ちそれに関連づけることで、選択・非選択になったListTileのみ再構築させることもできます。 ここらあたりに関しては負の面としてオブジェクト数が多くなったりし、メモリや速度など総合的に判断してどのような設計とするのか考えていかないといけないです。
nugio.kutusita

2022/05/29 11:07

ありがとうございます。 正直、少々複雑すぎて理解が追いつかないです。 結局、FutureBuilderを使うとできないということでしょうか。 そもそも最初、これを使わずに出来ていた要因もよくわからず… 絞り込み検索の実装もうまく行っていません。 立ち上げ時や絞り込み検索など、任意タイミングでのみ、非同期でデータ取得を待ってからListView.builderが動くようにしたいのですが… StreamBuilderというものもあるようですが、こちらは差分のみ更新する性質であるということで、 これを使ったほうがよいのでしょうか。
ta.fu

2022/05/29 12:14

FutureBuilderを使うとできない、ではなく、先に書いた「何をやったらどこまで再作成をさせるのか」を考え、それを元にウィジェット構成を考え、ウィジェットクラスに落とし込んでいくことが必要です。 今回のも状態管理パッケージを使い、ListTileとElevatedButtonの上に、状態管理と連携したビルダーでラップすればできます。 StreamBuilderはStreamで「受け取る」という状態を監視して変化があればbuilderを呼び出してくれるというもので、その基本的な考えはFutureBuilderと同じです。FutureBuilderはFutureの状態を監視して変化があったらbuilderを呼び出すものです。 Streamという仕組み上、他の場所からStreamに情報を与えれば、それを基点にStreamBuilderのbuilderが動きますが、今回のケースでは使えないのではと思います。 ちなみにこのウィジェットの更新に関してFlutterを使い始めた時は理解に苦しむものでしたが、試行錯誤してアプリを作成するうち1か月ほどでとりあえず納得できる状態には行きました。 Face to faceでサンプルを用いて授業みたいなものを受ければ1~2日である程度理解できるものだと思いますが、Web上のザッピングされた情報や知識人にリアルタイムに質疑が行えない環境だと納得できるまで結構時間がかかるんじゃないかと思います。 頑張ってください。
nugio.kutusita

2022/05/31 10:05

回答遅くなりましたが、諸々学習を進めています。 状態をどのように渡すかという部分で、futurebuilderやstreambuilderの他、changenotifierとproviderを使う方法があることなど。 firebaseからデータを引っ張る段階で絞り込み機能をつけたいので、色々な関数からリストに変化を与えたいという点でstreambuilderかchangenotiifierの方向がいいのかなとか、考えています。 改めて今読むと、上でコメントいただいた内容も理解できました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問