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

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

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

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

Dart

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

Q&A

1回答

2563閲覧

Flutter : streamを使ってイベント取得(リスト取得)する都度リストを表示する処理に関して。

moriman

総合スコア615

Flutter

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

Dart

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

0グッド

0クリップ

投稿2020/09/27 02:51

Flutter、Firebaseを用いて画像・動画投稿アプリを作っています。

今までstorageに保存してあるファイルの全件リスト表示に関して、
「ホーム画面」の問い合わせボタン押下
→全件リスト取得完了
→「全件リスト表示画面」へページ遷移(遷移時に全件リストを引数として渡す)
としていました。リスト取得時にサムネイルを作っているのですが、
サムネイル生成がかなり時間がかかっており、4割くらい生成できない(例外発生)
状況です。多分そこで時間がかかっていると思うのですが、
結局リリースして利用者が増えてstorageに保存してあるファイル件数が100件、1000件と増えた場合、
全件リストを取得して、全件リスト表示画面に遷移するまでに途方もない時間が
かかることが予想されます。
ですので以下のような方針でやってみました。

「ホーム画面」の問い合わせボタン押下時にstreamController生成。(ホーム画面)

streamController.add([])実行(一番最初nullだと、一番最初のイベント発生までの間ページ表示がエラー表示(赤い画面)になるため。)

「全件リスト表示画面」へページ遷移

ファイル1件のデータ取得ごとにstreamController.add()実行。(ホーム画面)

「全件リスト表示画面」では、StreamBuilderウィジェットを使用して、前ページで生成したstreamのイベントが発生する度に(1件ずつのファイルデータが取得される度に)リスト表示が増えていく(全件表示されるまで続く)。

具体的なコードの一部が下記↓

//↓IconButtonが問い合わせボタン(押したら全件リスト表示画面へ遷移する) , IconButton( icon: const Icon(Icons.call), tooltip:'ファイル一覧画面へ', onPressed: //_tasks.isNotEmpty ? () => setState(() => _tasks.clear()) : null, () async { List<String> refList2 = []; //←抽出したドキュメントID(storageのパス)を格納するrefList2 var tempQS=await Firestore.instance.collection('movies') .where('pref', isEqualTo: area[0][0]) .where('city', isEqualTo: area[1][0]) .getDocuments(); List<DocumentSnapshot> tempDS=tempQS.documents; for(var ds in tempDS){ refList2.add(ds.reference.documentID); } if (refList2.isEmpty) { showDialog( context: context, builder: (BuildContext context) { return SimpleDialog( title: const Text('ファイルがありません。'), children: <Widget>[ SimpleDialogOption( onPressed: () { Navigator.pop(context); }, child: const Text( 'アップロードファイルがありません。'), ), ], ); } ); return; } //取得したリストを日付でソート↓ refList2.sort(); refList2=refList2.reversed.toList(); List<Map<String, dynamic>> refAll2 = List<Map<String, dynamic>>(); StreamController<List<Map<String, dynamic>>> sCML=StreamController(); sCML.add([]); Navigator.pushNamed( context, '/third', arguments: sCML.stream, ); for (var prop in refList2) { var tempRef = widget.storage.ref() .child(area[0][0]) .child(area[1][0]) .child(prop); var tempTime = await tempRef.getMetadata(); var tempurl = await tempRef.getDownloadURL(); var uint8list; var file; var bytes; Widget sumb = Container( child:SizedBox( height:200.0, width:100.0, ), ); //↓サムネイル生成して、それ以外のデータも含めて次ページへ引数として送るmapを作る。 //↓サムネイル生成でエラーが出る時があるので例外処理。エラー時は空のContainerをセットしておく。 try { uint8list = await VideoThumbnail.thumbnailFile( video: tempurl, thumbnailPath: (await getTemporaryDirectory()).path, imageFormat: ImageFormat.WEBP, maxHeight: 200, // specify the height of the thumbnail, let the width auto-scaled to keep the source aspect ratio quality: 75, ); file = File(uint8list); bytes = file.readAsBytesSync(); sumb = Image.memory(bytes); var tempMap = { "ref": tempRef, "sumb": sumb, "path": prop, "time": tempTime.creationTimeMillis, "customeMeta": tempTime.customMetadata, }; refAll2.add(tempMap); sCML.add(refAll2); refAll2.sort((a, b) { var aa = a['time']; var bb = b['time']; return bb - aa; }); } catch (e) { print("????????エラーエラーエラー"); sumb = Container(); var tempMap = { "ref": tempRef, "sumb": sumb, "path": prop, "time": tempTime.creationTimeMillis, "customeMeta": tempTime.customMetadata, }; refAll2.add(tempMap); sCML.add(refAll2); //2020/9/26/ここでソートする必要はなくなったのでとりあえずコメントアウト↓ /* refAll2.sort((a, b) { var aa = a['time']; var bb = b['time']; return bb - aa; }); */ } } })
//↓全件リスト表示画面 class ThirdRoute extends StatefulWidget { @override State<ThirdRoute> createState() => ThirdRouteState(); } class ThirdRouteState extends State<ThirdRoute>{ bool isCreating = false; @override Widget build(BuildContext context) { //↓前ページから送られてきたstreamを受け取る。 Stream streamOfMap=ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text("Third Route(All files)"), ), body: LoadingOverlay( child:StreamBuilder( stream:streamOfMap, builder:(BuildContext context,AsyncSnapshot snapshot){ List<Widget> childrenAll=[]; var no=0; for (var ref in snapshot.data){ //var path1=mt_getPath(ref); //↓ここを変えれば表示件数を変えられる。 /* if(no>15){ break; } */ no += 1; final buttun= Container( padding: const EdgeInsets.all(8.0), color: Colors.lightGreenAccent, alignment: Alignment.center, child: Column( children:[ //↓アップロード日時表示 Text('${DateTime.fromMillisecondsSinceEpoch(ref['time'])}'), Text('${ref['customeMeta']['activity']}'), ref['sumb'], //↓動画再生ボタン表示 RaisedButton( child: Text("no$no動画${ref['path']}"), onPressed: () { //今回の質問とは関係ないので_downloadFile関数の宣言は省略してます。 _downloadFile(ref['ref']); } ), ] ), transform: Matrix4.rotationZ(0.1), ); childrenAll.add(buttun); } return ListView( children: childrenAll, ); }, ), isLoading:isCreating, opacity: 0.5, progressIndicator: CircularProgressIndicator(), ), ); } }

上記のコードで一応、最新のファイルが一番上に表示され、その後次々と古いファイルが取得されるごとに一覧ページに追加される挙動にはなりました。

ただ例えば一番最初のファイルが表示されてから、次のファイルが表示されるまでの時間の内、かなりの時間がボタン押下やスワイプができない状態になります。多分streamのイベント取得(1件のファイルデータ取得)してbuildメソッドが実行されている時間のような気がします。
こういう状況で頭に浮かんだのがStreamBuilderだったのでStreamBuilderを使用していますが、
多分この事象をどうにかしたいのであれば別の方法(StreamBuilder以外)を考える必要があるのだと思うのですが、
こういう場合のセオリー、もっと適したウィジェットなどありますでしょうか?

「どうにかしたい」というのは、すでに表示されたファイルに関しては常に動画再生ボタンを押したい時は押せる、
いつでも動画再生画面に行ける、という風にできればいいなあ、と考えています。

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

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

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

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

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

guest

回答1

0

まず、なぜ全件リスト表示画面ではなくホーム画面でStreamControllerを生成していたり、addする処理を行っているのかが気になりました。
今の設計だと例えば、動画がアップロードされても、Firestoreの変更がFlutterにリアルタイムで伝わらないような気がします。

せっかくFirebaseのパッケージを使っているのであれば、StreamBuilderのstreamにはFirestoreのSnapshotを流して、builderの中で処理をしてListViewを返します。
サムネイルを作成する処理は、ListViewの各アイテム内で個別に行えば、無駄なリビルドはなくせるのではないでしょうか?

ざっとした説明ですが、私もやってみなければわからない部分もあります。

投稿2020/09/28 05:43

tepci

総合スコア419

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

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

moriman

2020/09/28 06:04

回答を頂きましてありがとうございます。 なぜ、ということなんですが、私自身Firebaseの諸々の機能についてまさに手探りでドキュメントを見ながらいろいろ実験しているような状態でして、特に理由は無くてですね(笑)、ドキュメントで見つけたメソッドを試してみて、意図通りに動いたので、その次を考える、ということを繰り返していたらこういうコードができた、という感じです。 すみません。私の理解力が低く、頂いた回答の文章では具体的にイメージできません。 「streamにはFirestoreのSnapshotを流して」の部分なんですが、具体的にStreamBuilderのstreamプロパティに何を設定するのがセオリーでしょうか? query.dartなど見てみたのですが、 Stream<QuerySnapshot> snapshots({bool includeMetadataChanges = false}) ↑のメソッドを使う、ということでしょうか? snapshotsメソッドはドキュメントでチラッと見たような気がするのですが、 ちょっと調べてみないと使い方もよくわからないので、頂いた回答についての検討も、 その後ということになり、今すぐ頂いた提案に対してコメントできません。 ちょっとお時間頂きたいと思います。
tepci

2020/09/28 07:31

すこし論点がずれてしまいましたね。すみません。。 現状だと、addするたびにリスト内のアイテムすべてがリビルドされてしまっている可能性があり、そこがネックかなと思います。 サムネイルを作成する処理は下位ウィジェットに託し、上位ではFirestoreから受け取ったデータをただ流すだけにした方が良いと思います。
moriman

2020/09/28 12:03

いえいえ、いつもありがとうございます。 自分で作っているにもかかわらず忘れていましたが、 確かfirestoreからリストを取得するメソッドが非同期 (サムネイル作るメソッドも非同期)なので、それを 実行できる場所がonPressedなどのコールバックの中だけだったような 気がします。(コールバックを非同期関数にしてその中で取得する。) かなりうろ覚えですが。 と言っても私がいろいろ試した結果できなかっただけで、実際方法はあるのかもしれません。 ということで 遷移元のリスト問い合わせボタン押下時のコールバックでfirestoreのリスト取得 ↓ 遷移先に引数として渡す という流れになったような気がします。 なので 遷移先でボタンなど設置してそのコールバックでfirestoreのリスト取得 (つまり遷移先でボタンを押したら一覧表示 というのはできるかな、という気はしますが、 サムネイル生成を下位ウィジェットに託しても(どこでサムネイル作っても) 時間がかかることに変わりはないので、リスト表示に時間がかかる場所が 遷移元から遷移先に変わるのかな、と思います。 実際やってみたわけではないので、間違っていたらすみません。 ただ、firebaseを使うと、非同期メソッドは頻繁に出てくるので、それの扱いについて、 現在の私の認識で正しいのか、何か方法があるのか、は非常に気になるところです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問