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以外)を考える必要があるのだと思うのですが、
こういう場合のセオリー、もっと適したウィジェットなどありますでしょうか?
「どうにかしたい」というのは、すでに表示されたファイルに関しては常に動画再生ボタンを押したい時は押せる、
いつでも動画再生画面に行ける、という風にできればいいなあ、と考えています。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/09/28 06:04
2020/09/28 07:31
2020/09/28 12:03