Я пытаюсь создать экран Flutter, который принимает List<Map>
элементов с информацией из другого экрана, который делает / импортирует фотографии. Целью этого экрана является загрузка ВСЕХ изображений, которые он получает, в Firebase Storage. Мне нужно слушать прогресс каждого изображения, и когда ВСЕ изображения будут загружены, запустите некоторую логику, чтобы решить, куда перейти к следующему (автоматически). На этом экране не должно быть никаких действий, инициированных пользователем, он будет запускаться автоматически и автоматически перемещаться.
Я могу сделать это очень успешно для одного изображения, используя один StreamBuilder
, но мне нужно одно для каждогоизображение я собираюсь загрузить. Я использую цикл for
для генерации каждого StreamBuilder, но он не работает, как я ожидаю. Я думаю, потому что StreamBuilders
перестраивает себя, они снова запрашивают индекс цикла и переназначают его, так что все они заканчиваются тем же индексом (последним), и все же, загружается только одно изображение (обычно первое),
Как мне это сделать?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/widgets.dart';
import 'package:my_surgery/components/shared_scaffold.dart';
import 'package:my_surgery/constants/styles.dart';
import 'package:my_surgery/screens/single_album.dart';
import 'package:path_provider/path_provider.dart';
// FIREBASE INSTANCES
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseStorage _storage =
FirebaseStorage(storageBucket: 'gs://mysurgery-col19.appspot.com');
final Firestore _db = Firestore.instance;
class PhotoUpload extends StatefulWidget {
PhotoUpload({
@required this.photoPaths,
// GET ALBUM IF ANY
this.album = 'none',
});
//PATHS TO TAKEN/IMPORTED PHOTOS, IN MAP FORM WITH SOME OTHER INFO (path in fireStorage, and its unique ID)
List photoPaths;
//IF IMAGES WERE UPLOADED TO AN ALBUM OR IF EMPTY, ONE NEEDS TO BE CREATED
String album;
// TO PREVENT LEAVING WITHOUT SAVING PHOTOS - NOT IMPLEMENTED
bool canLeave = false;
@override
_PhotoUploadState createState() => _PhotoUploadState();
}
class _PhotoUploadState extends State<PhotoUpload> {
List mapPaths;
List<bool> completed;
bool started = false;
double progress = 0;
StorageUploadTask _uploadTask;
int uploadCount = 0;
List<bool> currentUpload = [];
bool done = false;
@override
void initState() {
// TODO: implement initState
super.initState();
mapPaths = widget.photoPaths;
uploadCount = mapPaths.length;
//GENERATE INITIAL UPLOAD TASK TO ALLOW WIDGET TREE TO BUILD WITHOUT ERRORS
_uploadTask = _storage
.ref()
.child(mapPaths[0]['dbPath'])
.putFile(File(mapPaths[0]['localPath']));
}
@override
Widget build(BuildContext context) {
if (currentUpload.length < uploadCount) {
return MyScaffold(
iconList: <Widget>[],
body: Center(
child: Container(
height: 800.0,
child: Column(
children: <Widget>[
Container(
width: 100.0,
height: 100.0,
child: CircularProgressIndicator(
value: null,
valueColor: AlwaysStoppedAnimation(kPrimaryColor),
strokeWidth: 2.0,
),
),
SizedBox(height: 20.0),
Text('UPLOADING YOUR PHOTOS'),
Column(
children: streamList(),
),
],
),
),
),
);
} else {
return MyScaffold(
body: Center(
child: Container(
child: Text('SUCCESS!'),
),
),
);
}
}
//-----------------------------------------------------------
//•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// CLASS: PhotoUpload METHODS
//•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
//-----------------------------------------------------------
// LOOP TO CREATE THE STREAMBUILDERS FOR ALL IMAGES IN mapPaths
streamList() {
List<Widget> streams = [];
List<StorageUploadTask> _uploadTasks = [];
int index = 0;
for (var items in mapPaths) {
//CREATE LIST OF UPLOAD TASKS
_uploadTasks.add(_storage
.ref()
.child(mapPaths[index]['dbPath'])
.putFile(File(mapPaths[index]['localPath'])));
// CREATE LIST OF STREAMS
streams.add(
StreamBuilder(
stream: _uploadTasks[index].events,
builder: (context, snapshot) {
// GET SNAPSHOT DATA
var event = snapshot?.data?.snapshot;
//KEEP TRACK OF PROGRESS
double progressPercent = event != null
? event.bytesTransferred / event.totalByteCount
: 0;
if (progressPercent == 1) {
done = true;
nextUpload();
}
//END
return Container(
child: Text(
'${(progressPercent * 100).toStringAsFixed(2)} % --- $index'));
}),
);
index++;
} //end for
return streams;
}
// This was an alternate method I was using before, where inside the stream I would listen
// for progress on an upload and when done, setState of uploadTask to the nex image in the list.
// Had to ditch it because it always gave me error since one can't trigger a new build from within
// The element that is being built.
nextUpload() {
if (currentUpload.length < uploadCount) {
// THERE ARE STILL IMAGES TO UPLOAD
// GET THE NEXT IMAGE
String path = '${mapPaths[currentUpload.length]['dbPath']}';
File file = File(mapPaths[currentUpload.length]['localPath']);
setState(() {
_uploadTask = _storage.ref().child(path).putFile(file);
currentUpload.add(true);
});
} else {
// ALL IMAGES ARE UP
Timer(Duration(seconds: 1), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return SingleAlbum();
}));
});
}
}
} // CLASS ENDS```