How to remove picture from flutter quill document programmatically onTap method? - firebase

I have used flutter quill rich text editor in one of my project where user can create diary from flutter quill toolbar options and also can upload pictures also. So,
Q1) when user upload picture I immediately upload that picture to firesbase storage in _onImagePickCallback() method but if user hit backspace then that picture will be removed from document but it still there in firebase storage. So, how to tackle that backspace or any remove method listener inside flutter quill. (One Approach I tried is don't upload picture immediately but only at the end when user create document and want to create diary, but here I faced problem how to change all those local images to online through uploading to firebase storage.)
Q2) I have used _customEmbedBuilder() method, to draw custom design container for images in flutter quill but I also put a Cross icon on the top of picture so when user tap on that, that picture will be removed from the document regardless where the cursor is, and I don't know how to do this.
Here is the picture and cross icon I did
This is create diary screen code where I am doing all this.
class CreateDiaryScreen extends StatefulWidget {
const CreateDiaryScreen({Key? key}) : super(key: key);
#override
State<CreateDiaryScreen> createState() => _CreateDiaryScreenState();
}
class _CreateDiaryScreenState extends State<CreateDiaryScreen> {
bool isDaySelected = false;
QuillController quillController = QuillController.basic();
String imageUrl = '';
int imagesCount = 0;
final FocusNode _focusNode = FocusNode();
TextEditingController titleController = TextEditingController();
final ScrollController _scrollController = ScrollController();
final _formKey = GlobalKey<FormState>();
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
late CalendarFormatChangedCubit calendarFormatChangedCubit;
Future<String> _onImagePickCallback(File file) async {
String url =
await context.read<UploadImageCubit>().uploadDiaryImageToFirebase(file);
//print('image callback url :$url');
//
// final appDocDir = await getApplicationDocumentsDirectory();
// final copiedFile =
// await file.copy('${appDocDir.path}/${Path.basename(file.path)}');
// return copiedFile.path.toString();
return url;
}
createDiary() {
Document document = quillController.document;
var json = jsonEncode(quillController.document.toDelta().toJson());
print(
'Plain text: ${quillController.document.toPlainText().replaceAll(RegExp(r'[ \n]'), '
').trim()}');
if (_formKey.currentState!.validate()) {
///second check whether user enter some data or not in quill editor.
if (quillController.document.toPlainText().length > 1) {
if (isDaySelected) {
BlocProvider.of<CreateDiaryCubit>(context).uploadDiary(
diaryTitle: titleController.text,
diaryText: quillController.document
.toPlainText()
.replaceAll(RegExp(r'[]'), ' ')
/// to remove  and \n from string.
.trim(),
diaryImage: imageUrl,
diaryFullDocument: quillController.document,
diarySelectedDate: _selectedDay ?? DateTime.now(),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select date'),
),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter description'),
),
);
}
}
}
Widget _customEmbedBuilder(BuildContext context, Embed embed, bool boooll) {
switch (embed.value.type) {
case BlockEmbed.imageType:
return _buildImage(context, embed);
case BlockEmbed.horizontalRuleType:
return const Divider();
default:
throw UnimplementedError(
'Embed "${embed.value.type}" is not supported.');
}
}
Widget _buildImage(BuildContext context, Embed embed) {
final imageUrl = embed.value.data;
return Container(
height: 160.sp,
width: 1.sw,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.sp),
),
child: imageUrl.startsWith('http')
? ClipRRect(
borderRadius: BorderRadius.circular(12.sp),
child: CachedNetworkImage(
imageUrl: imageUrl,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
placeholder: (context, url) => Center(
child: SizedBox(
height: 50.sp,
width: 50.sp,
child: const CircularProgressIndicator(
color: AppColors.primaryColor,
),
),
),
errorWidget: (context, url, error) => const Icon(Icons.error),
),
)
: imageUrl.toString().isEmpty
? const SizedBox()
: isBase64(imageUrl)
? ClipRRect(
borderRadius: BorderRadius.circular(12.sp),
child: Image.memory(
base64.decode(imageUrl),
fit: BoxFit.cover,
),
)
: Stack(
children: [
SizedBox(
width: 1.sw,
child: ClipRRect(
borderRadius: BorderRadius.circular(12.sp),
child: Image.file(
File(imageUrl),
fit: BoxFit.cover,
),
),
),
Positioned(
right: 10.sp,
top: 5.sp,
child: Container(
width: 21.58.sp,
height: 21.58.sp,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(0.1),
),
child: Center(
child: InkWell(
onTap: () {
print('tapped');
},
child: Icon(
Icons.close,
size: 16.sp,
)),
),
),
),
],
),
);
}
#override
void initState() {
print('init called');
// TODO: implement initState
super.initState();
calendarFormatChangedCubit =
BlocProvider.of<CalendarFormatChangedCubit>(context);
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
///when focus then change calendar format to week
calendarFormatChangedCubit.onCalendarFormatChanged(CalendarFormat.week);
}
});
}
#override
void dispose() {
super.dispose();
calendarFormatChangedCubit.resetCalendarStates();
}
showDiaryPostDiscardDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Discard Diary?'),
content: const Text('If you go back, you will lose your diary post.'),
actions: <Widget>[
TextButton(
child: const Text(
'Yes',
style: TextStyle(color: AppColors.primaryColor),
),
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
}),
TextButton(
child: const Text('No',
style: TextStyle(color: AppColors.primaryColor)),
onPressed: () => Navigator.of(context).pop(false)),
]),
);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (quillController.document.toPlainText().length > 1 ||
_formKey.currentState!.validate()) {
/// if condition passed, mean that user typed something into textfield and also written
some description
/// and show alert dialog of diary discard alert.
showDiaryPostDiscardDialog(context);
return false;
} else {
Navigator.of(context).pop(true);
return true;
}
},
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: const Color(0xffFCFEFF),
appBar: CustomAppBar(
appBarTitle: 'Add diary',
diaryAppBar: true,
onTickPressed: createDiary),
body: MultiBlocListener(
listeners: [
BlocListener<CreateDiaryCubit, CreateDiaryState>(
listener: (context, state) {
if (state is DiarySubmitionSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Diary created successfully'),
duration: Duration(milliseconds: 1500),
),
);
Navigator.pop(context);
titleController.clear();
quillController.clear();
}
if (state is DiarySubmitionNoInternetError) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('No internet, please connect your internet'),
duration: Duration(milliseconds: 1500),
),
);
}
if (state is DiarySubmitionFailed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Could not create diary, please try again'),
duration: Duration(milliseconds: 1500),
),
);
}
if (state is DiarySubmitionError) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('An error occured while uploading diary'),
duration: Duration(milliseconds: 1500),
),
);
}
},
),
BlocListener<CalendarDaySelectedCubit, CalendarDaySelectedState>(
listener: (context, state) {
if (state is CalendarDaySelected) {
isDaySelected = state.isDaySelected;
_focusedDay = state.focusDay;
_selectedDay = state.selectedDay;
}
},
),
BlocListener<UploadImageCubit, UploadImageState>(
listener: (context, state) {
if (state is UploadingImage) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: SizedBox(
height: 25.sp, child: SnackBarUploadingWidget()),
),
);
}
},
),
BlocListener<UploadImageCubit, UploadImageState>(
listener: (context, state) {
if (state is UploadedImage) {
///this is to check as to only first image url upload to firestore document, so
we create counter image check.
imagesCount++;
if (imagesCount == 1) {
imageUrl = state.url;
}
}
if (state is UploadingImageFiled) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Error occur'),
),
);
}
},
),
],
child: Column(
children: [
Expanded(
flex: 20,
child: Column(
children: [
///calendar, title and description
Align(
alignment: Alignment.topCenter,
child: Container(
width: 340.sp,
// height: 360 + 15.sp,
margin: EdgeInsets.symmetric(
horizontal: 16.sp, vertical: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0.r),
color: AppColors.backgroundColor,
),
child: Stack(
children: [
DiaryCalendarWidget(),
///calendar pin
Positioned(
top: 12.sp,
left: 1.sw / 2 - 80.sp,
child: const CalendarPins(),
),
///calendar pin
Positioned(
top: 12.sp,
left: 1.sw / 2 + 40.sp,
child: const CalendarPins(),
),
],
),
),
),
///title textField
Container(
margin:
EdgeInsets.symmetric(horizontal: 16.sp, vertical: 0),
child: Form(
key: _formKey,
child: CustomTextField(
validator: (titleText) {
RegExp titleRegExp = RegExp(r"^[a-zA-Z0-9]+//$");
if (titleText!.isEmpty) {
return 'Enter Title.';
} else if (titleText.length < 20) {
return 'Title should be more than 15 characters';
} else {
final title = titleText;
}
return null;
},
controller: titleController,
keyboardType: TextInputType.text,
textHeight: 20.sp,
),
),
),
///text customization section
Expanded(
flex: 10,
child: Container(
padding: EdgeInsets.only(left: 16.sp, right: 16.sp),
child: BlocBuilder<CreateDiaryCubit, CreateDiaryState>(
builder: (context, state) {
if (state is DiarySubmitting) {
return const CircularProgressIndicator(
color: AppColors.primaryColor,
);
} else {
return Scrollbar(
controller: _scrollController,
isAlwaysShown: true,
child: QuillEditor(
scrollController: _scrollController,
paintCursorAboveText: true,
embedBuilder: _customEmbedBuilder,
scrollPhysics:
const ClampingScrollPhysics(),
customStyleBuilder: (dynamic) {
return GoogleFonts.poppins(
fontSize: 14.sp,
color: const Color(0xff969798),
);
},
controller: quillController,
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
readOnly: false,
placeholder:
'Your diary description here...',
expands: false,
padding:
EdgeInsets.symmetric(horizontal: 8.sp),
customStyles: DefaultStyles(
paragraph: DefaultTextBlockStyle(
GoogleFonts.poppins(
fontSize: 14.sp,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
const Tuple2(16, 0),
const Tuple2(0, 0),
null),
h1: DefaultTextBlockStyle(
GoogleFonts.poppins(
fontSize: 32.sp,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
const Tuple2(16, 0),
const Tuple2(0, 0),
null),
sizeSmall: const TextStyle(fontSize: 9),
)),
);
}
},
),
),
),
],
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
width: 1.sw,
padding:
EdgeInsets.symmetric(horizontal: 4.sp, vertical: 5.sp),
decoration: BoxDecoration(
color: AppColors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
offset: Offset(0, 3.0),
blurRadius: 16.0,
),
],
),
child: QuillToolbar.basic(
multiRowsDisplay: false,
toolbarIconSize: 24.sp,
controller: quillController,
onImagePickCallback: _onImagePickCallback,
iconTheme: const QuillIconTheme(
iconUnselectedFillColor: Colors.white,
iconUnselectedColor: AppColors.grey,
iconSelectedFillColor: AppColors.primaryColor),
showVideoButton: false,
mediaPickSettingSelector: _selectMediaPickSetting,
showAlignmentButtons: true,
showHistory: false,
showItalicButton: false,
showStrikeThrough: false,
showCodeBlock: false,
showInlineCode: false,
showIndent: false,
showUnderLineButton: false,
showCameraButton: true,
showJustifyAlignment: false,
showListNumbers: false,
showDividers: false,
showClearFormat: false,
showHeaderStyle: false,
showLeftAlignment: false,
showCenterAlignment: false,
showRightAlignment: false,
showQuote: true,
showLink: false,
showListCheck: false,
showSmallButton: true,
),
),
),
),
],
),
),
),
);
}
Future<MediaPickSetting?> _selectMediaPickSetting(BuildContext context) =>
showDialog<MediaPickSetting>(
context: context,
builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero,
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton.icon(
icon: Icon(
DiaryIcons.image_gallery,
color: AppColors.primaryColor,
size: 18.sp,
),
label: Text(
'Gallery',
style: GoogleFonts.poppins(color: AppColors.primaryColor),
),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Gallery),
),
TextButton.icon(
icon: const Icon(Icons.link, color: AppColors.primaryColor),
label: Text(
' Link ',
style: GoogleFonts.poppins(color: AppColors.primaryColor),
),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Link),
)
],
),
),
);
}

I am not sure if you still need an answer to this but I found a solution to your Problem.
I went for the Q2 you had and upload all the images once it is saved.
This means we need a way to change the controller values.
They way to do is is to go over the operation that are done so far and find the position of the image you want to change.
I have to Maps one containing all the local Urls and one with the Urls i get from the bucket. The following function deals with the entire problem:
void replacesLinkFromController(QuillController controller, String imageName) {
var json = jsonEncode(controller.document.toDelta());
List<dynamic> delta = jsonDecode(json);
// Loop over the delta list and search for insert
for (Map<String, dynamic> line in delta) {
// Each line represents a Map -> line to insert
for (var key in line.keys) {
var value = line[key];
// Check if the key is insert
if (key == "insert") {
// Check if the value is a map
if (value is Map<String, dynamic>) {
// If it is a map then check if there is a image -> link pair
for (var newKey in value.keys) {
if (newKey == "image") {
// value == local storage
// Replaces the value
// Get the new value from the url map
String storageUrl = getUrlFromImageName(imageName);
String localUrl = getLocalUrlFromImageName(imageName);
if (storageUrl != "") {
value[newKey] = storageUrl;
var delta = controller.document.toDelta();
int position = 0;
int length = 0;
for (var operation in delta.toList()) {
position = position + operation.length!;
if (operation.key == 'insert') {
if (operation.data is Map) {
Map compare = operation.data as Map;
if (compare.containsKey('image')) {
if (compare['image'] == localUrl) {
length = operation.length!;
break;
}
}
}
}
}
Embeddable newImageWithUrl = Embeddable('image', storageUrl);
controller.document
.replace(position - 1, length, newImageWithUrl);
}
}
}
}
}
}
}
I hope you can take the part how I replace the images in the controller.

Related

Firebase onChildAdded adds duplicate entries to the list this makes the length of the list differ

My app is for the driver, who delivers orders through this application. In this, I am listening to all the orders assigned to the driver from the firebase realtime database. The onChildAdded.listen adds the order to a list named ongoingList of class Ongoing which means assigned to him. I am then displaying the length of the list as count in the dashboard. Now sometimes I am getting unexpected counts in the dashboard and also the list contains duplicate entries.
I am adding the code below
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard>
with SingleTickerProviderStateMixin {
bool isLoading = false;
bool isOnline = true;
var OngoingDetails;
List<Ongoing> ongoingList = [];
List<OpenRequest> openRequestList = [];
DatabaseReference databaseReference = FirebaseDatabase.instance.reference();
late StreamSubscription<Event> ongoingStream;
late StreamSubscription<Event> openRequestStream;
late StreamSubscription<Event> removeOpenNodes;
late LocationData currentLoc;
double currentLat = 0;
double currentLng = 0;
DriverDetail _driverData = DriverDetail();
// late Timer timer;
#override
void initState() {
super.initState();
// timer =
// Timer.periodic(Duration(seconds: 300), (Timer t) => reloadPageAuto());
getDriverData();
countOpenRequest = 0;
ongoingCount = 0;
}
reloadPageAuto() {
// Navigator.pop(context); // pop current page
Navigator.pushReplacementNamed(context, '/dashboard'); // push it back in
}
getDriverData() async {
setState(() {
isLoading = true;
});
_driverData = await getDriverDetails();
print("${_driverData.driverName}");
setState(() {
isLoading = false;
});
//getCurrentLocation();
}
getCurrentLocation() async {
currentLoc = await getLoc();
setState(() {
currentLat = currentLoc.latitude!;
currentLng = currentLoc.longitude!;
isLoading = false;
});
// checkOpenRequest();
}
#override
void dispose() {
// timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
DateTime timeBackPressed = DateTime.now();
return isLoading == false
? DefaultTabController(
child: WillPopScope(
onWillPop: () async {
final difference = DateTime.now().difference(timeBackPressed);
timeBackPressed = DateTime.now();
if (difference >= Duration(seconds: 1)) {
final String msg = 'Press the back button again to exit';
showToast(msg);
return false;
} else {
Fluttertoast.cancel();
SystemNavigator.pop();
return true;
}
},
child: Scaffold(
appBar: AppBar(
leading: Switch(
materialTapTargetSize: MaterialTapTargetSize.padded,
activeTrackColor: Colors.grey,
value: isOnline,
activeColor: kWagonColour,
onChanged: (value) async {
setState(() {
isOnline = value;
//print(isOnline);
Fluttertoast.showToast(
msg: isOnline == true
? "You are now Online"
: "You are now Offline",
gravity: ToastGravity.BOTTOM,
textColor: Colors.white,
backgroundColor: kWagonColour,
);
});
if (isOnline == true) {
await OneSignal.shared.disablePush(false);
OneSignal.shared
.setAppId("abd31b6b-5fc4-4769-8684-224a439e422e");
final status = await OneSignal.shared.getDeviceState();
var playerId = status!.userId;
OSCreateNotification notification =
OSCreateNotification(
androidChannelId:
"1ff1eb80-0316-419f-bd37-0992145ee37d",
heading: "Go Wagon Driver",
playerIds: [playerId.toString()],
content: "Background Service Active",
androidSound: "onesignal_default_sound.wav",
iosSound: "onesignal_default_sound.wav",
);
OneSignal.shared.postNotification(notification);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
));
} else {
//print('else');
await OneSignal.shared.disablePush(true);
}
},
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 15.0),
child: GestureDetector(
child: CircleAvatar(
backgroundImage: NetworkImage(_driverData.image),
),
onTap: () {
Navigator.pushNamed(context, '/profile');
},
),
),
],
centerTitle: true,
title: Image.asset(
'images/app_icon.png',
height: 50.0,
width: 50.0,
),
bottom: TabBar(
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.home,
color: kWagonColour,
),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text('Home'),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.price_change_outlined,
color: kWagonColour,
),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text('Earnings'),
),
],
),
),
],
labelColor: kWagonColour,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 2.0, color: kWagonColour),
insets: EdgeInsets.symmetric(horizontal: 25.0),
),
indicatorColor: kWagonColour,
unselectedLabelColor: Colors.grey,
indicatorSize: TabBarIndicatorSize.tab,
labelStyle: TextStyle(
fontSize: 16.0,
),
),
),
backgroundColor: Color(0xFFF5F5F5),
body: isLoading
? ColorLoader()
: GestureDetector(
child: TabBarView(
children: [
Status(
details: ongoingList,
),
Earnings(),
],
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.startFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => QRCodeScanner(),
));
},
child: Icon(
Icons.qr_code_scanner,
color: kWagonColour,
),
backgroundColor: Colors.white,
),
),
),
length: 2,
initialIndex: 0,
)
: Scaffold(
body: ColorLoader(),
);
}
}
class Status extends StatefulWidget {
const Status({Key? key, required this.details}) : super(key: key);
final List details;
#override
_StatusState createState() => _StatusState();
}
class _StatusState extends State<Status>
with AutomaticKeepAliveClientMixin<Status> {
#override
bool get wantKeepAlive => true;
var top = 10.0;
var left = 10.0;
DriverDetail _driverData = DriverDetail();
bool isLoading = false;
bool isLoadingCount = true;
List<Ongoing> ongoingList = [];
List<OpenRequest> openRequestList = [];
DatabaseReference databaseReference = FirebaseDatabase.instance.reference();
late StreamSubscription<Event> ongoingStream;
late StreamSubscription<Event> openRequestStream;
late StreamSubscription<Event> removeOpenNodes;
late LocationData currentLoc;
double currentLat = 0;
double currentLng = 0;
bool _enabled = true;
#override
void initState() {
super.initState();
getDriverData();
}
getCurrentLocation() async {
currentLoc = await getLoc();
setState(() {
currentLat = currentLoc.latitude!;
currentLng = currentLoc.longitude!;
});
setState(() {
isLoading = false;
});
checkOpenRequest();
}
checkOpenRequest() async {
print("checking open request");
if (countOpenRequest == 0) {
setState(() {
isOpenRequest = false;
});
}
print("open count $countOpenRequest");
countOpenRequest = 0;
openRequestStream = FirebaseDatabase.instance
.reference()
.child('shipments')
.child('open_requests')
.orderByChild('model_id')
.equalTo(double.parse(_driverData.modelId))
.onChildAdded.listen(_onEntryAddedOpen);
// .listen((event) {
// double total_distance = distance(
// currentLat,
// currentLng,
// double.parse(event.snapshot.value['pickup_latitude']),
// double.parse(event.snapshot.value['pickup_longitude']),
// 'K');
// print("distance for ${event.snapshot.key} $total_distance");
//
// if (total_distance <= 50) {
// HapticFeedback.heavyImpact();
// setState(() {
// // print("New request received: ${event.snapshot.value}");
// ++countOpenRequest;
// isOpenRequest = true;
// });
// }
// });
checkOngoing();
}
checkOngoing() async {
print("checking ongoing");
ongoingStream = FirebaseDatabase.instance
.reference()
.child('shipments')
.child('driver_shipments')
.child(_driverData.vehicleId)
.onChildAdded
.listen(_onEntryAddedOngoing);
setState(() {
isLoadingCount = false;
});
}
getDriverData() async {
setState(() {
isLoading = true;
});
_driverData = await getDriverDetails();
getCurrentLocation();
}
#override
void dispose() {
// timer.cancel();
openRequestStream.cancel();
ongoingStream.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
var deviceWidth = MediaQuery.of(context).size.width;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 100.0,
padding: EdgeInsets.symmetric(vertical: 8.0),
width: double.infinity,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
'Hi, ${_driverData.driverName}',
minFontSize: 16,
maxFontSize: 20,
overflow: TextOverflow.visible,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 10.0,
),
Text(
'What do you want to do today?',
style: TextStyle(
fontSize: 16.0,
),
),
],
),
SizedBox(
width: 50.0,
),
GestureDetector(
child: Icon(
Icons.replay_circle_filled,
color: kWagonColour,
size: 40.0,
semanticLabel: 'Refresh',
),
onTap: _enabled ? refreshCounts : null,
),
],
),
),
),
Container(
height: 100,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Positioned(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
OngoingShipments(ongoingDetails: ongoingList),
),
);
},
child: Row(
children: [
Container(
width: deviceWidth / 7,
height: 40,
child: Center(
child: isLoadingCount == false
? Text(
ongoingList.length.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)
: CircularProgressIndicator(
color: Colors.white,
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: kWagonColour,
),
),
SizedBox(
width: 5,
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OngoingShipments(
ongoingDetails: ongoingList),
),
);
},
child: Text(
'ONGOING',
style: TextStyle(
color: kWagonColour,
),
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
),
)
],
),
),
),
Positioned(
top: 20,
left: deviceWidth / 1.8,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TestFirebase()));
},
child: Row(
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TestFirebase()));
},
child: Text(
'OPEN',
style: TextStyle(
color: kWagonColour,
),
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
),
),
SizedBox(
width: 5,
),
Container(
width: 60,
height: 40,
child: Center(
child: isLoadingCount == false
? Text(
openRequestList.length.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)
: CircularProgressIndicator(
color: Colors.white,
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: kWagonColour,
),
),
],
),
),
),
],
),
),
],
),
);
}
refreshCounts() {
setState(() {
isLoadingCount = true;
_enabled = false;
});
isLoadingCount = true;
ongoingList = [];
openRequestList = [];
ongoingCount = 0;
countOpenRequest = 0;
Timer(Duration(seconds: 1), () {
setState(() {
_enabled = true;
});
checkOpenRequest();
});
}
_onEntryAddedOngoing(Event event) async {
setState(() {
ongoingList.add(Ongoing.fromSnapshot(event.snapshot));
});
}
_onEntryAddedOpen(Event event) async {
double totalDistance = distance(
currentLat,
currentLng,
double.parse(event.snapshot.value['pickup_latitude']),
double.parse(event.snapshot.value['pickup_longitude']),
'K');
print("distance for ${event.snapshot.key} $totalDistance");
if (totalDistance <= 50) {
HapticFeedback.heavyImpact();
setState(() {
openRequestList.add(OpenRequest.fromFirebase(event.snapshot));
});
}
}
}
Below is the code for my class Ongoing
Ongoing(
{this.baseFare,
this.cancelationFee,
this.customerId,
this.customerName,
this.distanceTotal,
this.driverId,
this.driverImage,
this.driverName,
this.driverNumber,
this.driverNumberCode,
this.dropAddress,
this.dropLatitude,
this.dropLongitude,
this.estimatedTime,
this.helpCount,
this.helpTotal,
this.isPayable,
this.itemCount,
this.masterFlag,
this.minHr,
this.minKm,
this.modelId,
this.payableFlag,
this.paymentMethod,
this.paymentStatus,
this.perHr,
this.perKm,
this.pickPayPaymentStatus,
this.pickupAddress,
this.pickupCode,
this.pickupLatitude,
this.pickupLongitude,
this.pickupTime,
this.price,
this.pricePerHr,
this.pricePerKm,
this.receiverName,
this.receiverPhone,
this.receiverPhoneCode,
this.senderName,
this.senderPhone,
this.senderPhoneCode,
this.shipmentId,
this.shipmentLabel,
this.shipmentLabelAr,
this.shipmentOtp,
this.shipmentPackageInstruction,
this.shipmentPackageName,
this.shipmentPackagePropertyId,
this.shipmentPackageTypeId,
this.shipmentPolyline,
this.status,
this.total,
this.type,
this.vehicleId,
this.vehicleName,
this.wagoncut,
this.isCashShipment,
this.invoiceNumber,
this.invoiceDate,
this.invoiceAmount,
this.operationMobile, this.packageName});
Ongoing.fromSnapshot(DataSnapshot snapshot) {
baseFare = snapshot.value['base_fare'];
cancelationFee = snapshot.value['cancelation_fee'];
customerId = snapshot.value['customer_id'];
customerName = snapshot.value['customer_name'];
distanceTotal = snapshot.value['distance_total'];
driverId = int.parse(snapshot.value['driver_id'].toString());
driverImage = snapshot.value['driver_image'];
driverName = snapshot.value['driver_name'];
driverNumber = snapshot.value['driver_number'];
driverNumberCode = snapshot.value['driver_number_code'];
dropAddress = snapshot.value['drop_address'];
dropLatitude = snapshot.value['drop_latitude'];
dropLongitude = snapshot.value['drop_longitude'];
estimatedTime = snapshot.value['estimated_time'];
helpCount = snapshot.value['help_count'];
helpTotal = snapshot.value['help_total'];
isPayable = snapshot.value['is_payable'];
itemCount = snapshot.value['item_count'];
masterFlag = snapshot.value['master_flag'];
minHr = snapshot.value['min_hr'];
minKm = snapshot.value['min_km'];
modelId = snapshot.value['model_id'];
payableFlag = snapshot.value['payable_flag'];
paymentMethod = snapshot.value['payment_method'];
paymentStatus = snapshot.value['payment_status'];
perHr = snapshot.value['per_hr'];
perKm = snapshot.value['per_km'];
pickPayPaymentStatus = snapshot.value['pick_pay_payment_status'];
pickupAddress = snapshot.value['pickup_address'];
pickupCode = snapshot.value['pickup_code'];
pickupLatitude = snapshot.value['pickup_latitude'];
pickupLongitude = snapshot.value['pickup_longitude'];
pickupTime = snapshot.value['pickup_time'];
price = snapshot.value['price'];
pricePerHr = snapshot.value['price_per_hr'];
pricePerKm = snapshot.value['price_per_km'];
receiverName = snapshot.value['receiver_name'];
receiverPhone = snapshot.value['receiver_phone'];
receiverPhoneCode = snapshot.value['receiver_phone_code'].toString();
senderName = snapshot.value['sender_name'];
senderPhone = snapshot.value['sender_phone'];
senderPhoneCode = snapshot.value['sender_phone_code'].toString();
shipmentId = snapshot.value['shipment_id'];
shipmentLabel = snapshot.value['shipment_label'];
shipmentLabelAr = snapshot.value['shipment_label_ar'];
shipmentOtp = snapshot.value['shipment_otp'];
shipmentPackageInstruction = snapshot.value['shipment_package_instruction'];
shipmentPackageName = snapshot.value['shipment_package_name'];
shipmentPackagePropertyId = snapshot.value['shipment_package_property_id'];
shipmentPackageTypeId = snapshot.value['shipment_package_type_id'];
shipmentPolyline = snapshot.value['shipment_polyline'];
status = snapshot.value['status'];
total = snapshot.value['total'];
type = snapshot.value['type'];
vehicleId = snapshot.value['vehicle_id'];
vehicleName = snapshot.value['vehicle_name'];
wagoncut = snapshot.value['wagoncut'];
isCashShipment = snapshot.value['is_cash_shipment'].toString();
invoiceAmount = snapshot.value['invoice_amount'].toString();
invoiceNumber = snapshot.value['invoice_no'].toString();
invoiceDate = snapshot.value['invoice_date'].toString();
operationMobile = snapshot.value['operation_mobile'].toString();
}
}
If anyone has relevant suggestion regarding it, please help me through it. Thank you in advance

Uploading a List of Assets to Firebase Storage and get a List of Strings Flutter

I'm having a question.
So I'm trying to upload multiple pictures into Firebase Storage, these are getting into Phone Memory through a MultiImage picker which only Saves the data into a List < Asset > , here the code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:multi_image_picker2/multi_image_picker2.dart';
import 'package:provider/provider.dart';
import '../widgets/bottom_navigation_bar.dart';
import '../providers/selling.dart';
//Property Features Screens
import '../screens/land_lot_size_features.dart';
import '../screens/condo_property_details_features.dart';
import '../screens/townhose_property_detais_features.dart';
import '../screens/home_property_details_features.dart';
import '../screens/selling_bathrooms.dart';
//Next Page Pricing
import '../screens/pricing_property.dart';
class MultipleImageDemo extends StatefulWidget {
static const routeName = '/multi-picker';
#override
_MultipleImageDemoState createState() => _MultipleImageDemoState();
}
class _MultipleImageDemoState extends State<MultipleImageDemo> {
List<Asset> images = <Asset>[];
double screenWidth = 0;
bool isLoading = false;
#override
void initState() {
setState(() {
isLoading = true;
});
images = Provider.of<Selling>(context, listen: false).images;
setState(() {
isLoading = true;
});
super.initState();
}
Future<void> loadAssets() async {
List<Asset> resultList = <Asset>[];
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 12,
enableCamera: true,
selectedAssets: images,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Roof Deck",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
} on Exception catch (e) {
//String error = e.toString();
print(e);
}
if (!mounted) return;
setState(() {
images = resultList;
});
}
deleteImage(index) {
images.removeAt(index);
setState(() {});
}
#override
Widget build(BuildContext context) {
final topData = Provider.of<Selling>(context);
final typeOfProperty = topData.menu;
screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: SafeArea(
child: Stack(
children: [
// SizedBox(
// height: 1,
// ),
// Positioned(
// top: 0,
// left: 0,
// right: 0,
// child: Container(
// width: screenWidth,
// padding: const EdgeInsets.all(15),
// margin: const EdgeInsets.all(15),
// child: LinearPercentIndicator(
// width: screenWidth / 1.2,
// lineHeight: 8.0,
// percent: 0.50,
// progressColor: Colors.deepPurple[900],
// ),
// ),
// ),
Container(
// width: screenWidth,
// padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
// margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
// padding: const EdgeInsets.only(
// top: 5,
// left: 15,
// right: 15,
// ),
// margin: const EdgeInsets.only(
// top: 5,
// left: 15,
// right: 15,
// ),
child: Column(
children: [
Center(
child: Text(
'Photos',
style: TextStyle(fontSize: 25),
),
),
Divider(
color: Colors.grey,
thickness: 2.00,
),
SizedBox(
height: 5,
),
],
),
),
),
// SliverToBoxAdapter(
// child: Padding(
// padding: const EdgeInsets.only(
// left: 15.0, right: 15.0, bottom: 5.0),
// child: Text(
// 'Homes are 30% more likely to sell with high quality photos.',
// style: TextStyle(
// color: Colors.grey,
// fontSize: 18.00,
// ),
// ),
// ),
// ),
// SliverToBoxAdapter(
// child: Padding(
// padding: const EdgeInsets.only(
// left: 15.0, right: 15.0, bottom: 20.0),
// child: Text(
// 'You can only upload 12 pictures, if you need to remove a picture just tap on it.',
// style: TextStyle(
// color: Colors.grey,
// fontSize: 18.00,
// ),
// ),
// ),
// ),
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return index == images.length
? Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.grey, width: 1.0),
),
child: Container(
margin: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(8.0),
height: 100,
width: 100,
child: IconButton(
icon: Icon(
Icons.add, //add_a_photo,
size: 50.00,
color: Colors.white,
),
onPressed: () {
if (images.length < 12) {
loadAssets();
} else {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(
'Max Number of Pictures'),
content: Text(
'You reached the maximum number of pictures that can be uploaded (12 pictures)'),
actions: [
TextButton(
child: Text('Okay'),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
),
);
}
},
),
),
),
)
: GestureDetector(
onTap: () {
deleteImage(index);
},
child: Container(
margin: const EdgeInsets.all(3),
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: AssetThumb(
asset: images[index],
width: 300,
height: 300,
),
),
),
);
},
childCount: images.length + 1,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
),
),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text(
'Previous',
style: TextStyle(fontSize: 22),
),
onPressed: () {
switch (typeOfProperty) {
case 0:
{
Navigator.pushNamed(
context, PropertyBathrooms.routeName);
}
break;
case 1:
{
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 0),
pageBuilder: (_, __, ___) =>
LandSizeFeatures()),
);
}
break;
case 2:
{
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 0),
pageBuilder: (_, __, ___) =>
TownhousePropertyDetails()),
);
}
break;
case 3:
{
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 0),
pageBuilder: (_, __, ___) =>
CondoPropertyDetails()),
);
}
break;
case 4:
{
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 0),
pageBuilder: (_, __, ___) =>
HomePropertyDetails()),
);
}
break;
}
// Navigator.pushNamed(
// context, PropertyBathrooms.routeName);
},
),
TextButton(
child: Text(
'Next',
style: TextStyle(fontSize: 22),
),
onPressed: () {
Provider.of<Selling>(context, listen: false)
.getImages(images);
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 0),
pageBuilder: (_, __, ___) => PricingProperty()),
);
//Navigator.pushNamed(context, PropertySize.routeName);
},
),
],
),
),
),
],
),
),
bottomNavigationBar: BottomNavBar(2),
);
}
}
Once we move to a different screen I save that list to a Provider to have it available in any place:
//***Property Images***/
List<Asset> _images = <Asset>[];
//Images Getter
List<Asset> get images {
return _images;
}
//Filling the Data Function
void getImages(List<Asset> images) {
_images = images;
notifyListeners();
}
//*** END OF IMAGES ***//
The code above is how I fill the images in the Images provider.
Now all of these works, however the questions is that now I wish to upload all the images in that list to Firebase Storage and get back a List of Strings with the Picture Viewing URL (DownloadURL).
I'm able to do this by doing it in a 1 by 1 case and sending a File not a List of Assets like this code:
final ref = FirebaseStorage.instance
.ref()
.child('user_images')
.child(authResult.user.uid + '.jpg');
await ref.putFile(image);
final url = await ref.getDownloadURL();
But is there a way to do this all at once instead of 1 by 1 and retrieve the List of Strings of the download URL?
My End Goal is to be able to have those images store in Firebase Storage and save to Firestore the Array of the urls for those images.
In my resume, my problem is that I'm not uploading a File or a List of Files, instead, I'm uploading a List of Assets, and that it is a List and not a single one.
Thank you and Kind Regards.
If anybody comes here for an Answer I found it:
First I added the List < File > to my Multi Image Picker and added this code (resultList is the Asset Array).
I add the package flutter_absolute_path: ^1.0.6 and with that got the following code:
resultList.forEach((imageAsset) async {
final filePath =
await FlutterAbsolutePath.getAbsolutePath(imageAsset.identifier);
File tempFile = File(filePath);
if (tempFile.existsSync()) {
fileImageArray.add(tempFile);
}
});
With that I save to my Provider the File Array. Then I created a Function to do the Upload to Firebase Storage:
Future<void> uploadImages(String id) async {
int i = 0;
for (File imageFile in imageFiles) {
try {
var ref = FirebaseStorage.instance
.ref()
.child('ListingImages')
.child(id)
.child(i.toString() + '.jpg');
await ref.putFile(imageFile);
var url = await ref.getDownloadURL();
_imagesUrl.add(url);
i++;
} catch (err) {
print(err);
}
}
notifyListeners();
print(_imagesUrl);
}
Allowing me to loop over each file uploaded and saved the Download URL in a String Array.
Hope this helps you, Guys.

Flutter Firebase get recordID

In the code below, I am trying to get the id of snap. This info is supposed to be recorded, later if the user tap on save.
But, I am not able to get the right id associated with the project selected. If you can put me to the right direction, it would be appreciated. Many thanks.
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 3.0, 0.0, 0.0),
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('projects')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
const Text("Loading.....");
else {
List<DropdownMenuItem <Project>> goalItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
snap = snapshot.data.docs[i];
// print (gael.id);
// print(snap['project_Name']);
goalItems.add(
DropdownMenuItem(
value: Project(snap['project_Name'], snap.id),//(snap['project_Name'], snap.id),
child: SizedBox(
width:MediaQuery.of(context).size.width*0.89,
child: Text(
(snap['project_Name']),
style: TextStyle(color: Colors.black),
),
),
// style: TextStyle(color: Color(0xff11b719)),
// ),
),
);
}
return Row(
children:<Widget> [
DropdownButton <Project>(
items: goalItems,
onChanged: (Project pro) {
setState(() {
selectedProject = pro.name;
taskNewValue ['project_Name'] = pro.name ;
taskNewValue['project_ID'] = pro.id;
print(pro.id);
});
},
value: selectedProject,
isExpanded: false,
hint: Text(projectName,
style: TextStyle(color: Color (0xff29B6F6)), //Color(0xff0d53dc)),
),
)]);
}
return Container(
height: 0,width: 0,
);
}),
),
class Project {
// var User(this.name, this.id);
final String id;
final String name;
Project(this.id,this.name);
}
//Last modification done. When I remove value, it works, but, when I select a new item, it is not displayed
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 3.0, 0.0, 0.0),
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('projects')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
const Text("Loading.....");
else {
List<DropdownMenuItem <Project>> goalItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
snap = snapshot.data.docs[i];
print(snap.id);
print(snap['project_Name']);
goalItems.add(
DropdownMenuItem(
value: Project(snap.id, snap['project_Name']),
//(snap['project_Name'], snap.id),
child: SizedBox(
width: MediaQuery
.of(context)
.size
.width * 0.89,
child: Text(
(snap['project_Name']),
style: TextStyle(color: Colors.black),
),
),
// style: TextStyle(color: Color(0xff11b719)),
// ),
),
);
}
return Row(
children: <Widget>[
DropdownButton <Project>(
items: goalItems,
onChanged: (Project pro) {
setState(() {
selectedProject = pro.name;
project_ID=pro.id;
myTest = pro.name;
final test4 = Project(project_ID, myTest);
// print('ID');
// print(pro.id);
// print('name');
// print(pro.name);
// print('test4');
// print(test4.id);
// print(test4.name);
// print(project_ID);
});
},
// value: Project(project_ID,myTest),//selectedProject,
isExpanded: false,
hint: Text(projectName,
style: TextStyle(color: Color(
0xff29B6F6)), //Color(0xff0d53dc)),
),
)
]);
}
return Container(
height: 0, width: 0,
);
}),
),
I have created a new page with the following code. I am getting the following error.
The following _TypeError was thrown building StreamBuilder<QuerySnapshot<Object?>>(dirty, dependencies: [MediaQuery], state: _StreamBuilderBaseState<QuerySnapshot<Object?>, AsyncSnapshot<QuerySnapshot<Object?>>>#50df7):
type 'String' is not a subtype of type 'Project?'
I am not sure how to fix that.
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
var selectedProject;
var idProject;
DocumentSnapshot snap;
class MyTest extends StatefulWidget {
const MyTest({Key key}) : super(key: key);
#override
_MyTestState createState() => _MyTestState();
}
class _MyTestState extends State<MyTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text('test'),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.add_circle_outline,
color: Colors.white,
),
onPressed: () {
},
),
],
),
body:
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 3.0, 0.0, 0.0),
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('projects')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
const Text("Loading.....");
else {
List<DropdownMenuItem <Project>> goalItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
snap = snapshot.data.docs[i];
print(snap.id);
print(snap['project_Name']);
goalItems.add(
DropdownMenuItem(
value: Project(snap.id, snap['project_Name']),
//(snap['project_Name'], snap.id),
child: SizedBox(
width: MediaQuery
.of(context)
.size
.width * 0.89,
child: Text(
(snap['project_Name']),
style: TextStyle(color: Colors.black),
),
),
// style: TextStyle(color: Color(0xff11b719)),
// ),
),
);
}
return Row(
children: <Widget>[
DropdownButton <Project>(
items: goalItems,
onChanged: (Project pro) {
setState(() {
selectedProject = pro.name;
idProject = pro.id;
final test4 = Project(idProject, selectedProject);
});
},
value: selectedProject,
isExpanded: false,
hint: Text('test',//projectName,
style: TextStyle(color: Color(
0xff29B6F6)), //Color(0xff0d53dc)),
),
)
]);
}
return Container(
height: 0, width: 0,
);
}),
),
);
}
}
class Project {
// var User(this.name, this.id);
final String id;
final String name;
Project(this.id,this.name);
}
You need to use a custom model like Project:
class Project {
const Project(this.name, this.id);
final String name;
final String id;
}
And instead of adding just the String names to the DropdownButton items add those models:
List<DropdownMenuItem> goalItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
DocumentSnapshot snap = snapshot.data.docs[i];
test = snapshot.data.docs[i];
goalItems.add(
DropdownMenuItem(
value: Project(snap['project_Name'], snap.id),
child: SizedBox(
width:MediaQuery.of(context).size.width*0.89,
child: Text(
(snap['project_Name']),
style: TextStyle(color: Colors.black),
),
),
// style: TextStyle(color: Color(0xff11b719)),
// ),
value: (snap['project_Name']),
),
);
}
Then you can get the id from the Project on the onClick listener:
return Row(
children:<Widget> [
DropdownButton(
items: goalItems,
onChanged: (Project pro) {
setState(() {
selectedProject = pro;
taskNewValue ['project_Name'] = pro.name ;
taskNewValue['project_ID'] = pro.id;
//here I am trying to get the id of the record, so I can record the id when the user select save
});
},
value: selectedProject,
isExpanded: false,
hint: Text(projectName,
style: TextStyle(color: Color (0xff29B6F6)), //Color(0xff0d53dc)),
),
)]);
}

Firebase List view is not displayed in real-time while adding data

I am storing Contact information on Firebase real-time DB after picking a contact from the phone.
When I add the data which is displayed using ListView and DataSnapshot the data is not refreshed in real-time until I restart the Activity. The ListView loads and displays all the data without any problem. How do I resolve this issue?
My screen looks like this:
Code for Screen:
import 'package:epicare/CaregiverClass.dart';
import 'package:epicare/Homepage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_switch/flutter_switch.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:contact_picker/contact_picker.dart';
//Check contacts permission
Future<PermissionStatus> _getPermission() async {
final PermissionStatus permission = await Permission.contacts.status;
if (permission != PermissionStatus.granted &&
permission != PermissionStatus.denied) {
final Map<Permission, PermissionStatus> permissionStatus =
await [Permission.contacts].request();
return permissionStatus[Permission.contacts] ??
PermissionStatus.undetermined;
} else {
return permission;
}
}
class CaregiverScreen extends StatefulWidget {
#override
_CaregiverScreenState createState() => _CaregiverScreenState();
}
class _CaregiverScreenState extends State<CaregiverScreen> {
//Firebase
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final ref = FirebaseDatabase.instance.reference();
User cuser = FirebaseAuth.instance.currentUser;
final fb = FirebaseDatabase.instance.reference().child("User_data");
List <CaregiverList> list = List();
// Adding data into Firebase
void addData(String name, String number) {
print("Saving data to firebase");
ref.child('User_data').child(cuser.uid).child("caregivers").push().set({'Caregiver_Name': name, 'Caregiver_Number': number});
}
// Get location
Position _currentPosition;
String _currentAddress;
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;
#override
void initState() {
// Firebase
fb.child(cuser.uid).child("caregivers").once().then((DataSnapshot snapshot)
{
var data = snapshot.value;
list.clear();
data.forEach((key,value){
print(value['Caregiver_Name']);
CaregiverList contact_list = new CaregiverList(
name: value['Caregiver_Name'],
phone_number: value['Caregiver_Number'],
isActive: true,
key: key,
);
list.add(contact_list);
});
setState(() {
});
}
);
super.initState();
}
final ContactPicker _contactPicker = new ContactPicker();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
//Size size = MediaQuery.of(context).size;
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: const Color(0xffE5E0A1),
elevation: 0,
centerTitle: true,
title: Text(
"Add Caregiver",
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
fontFamily: 'Montserrat',
fontWeight: FontWeight.normal,
),
),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return Homepage();
},
),
);
},
),
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
MaterialButton(
onPressed: () async {
final PermissionStatus permissionStatus =
await _getPermission();
if (permissionStatus == PermissionStatus.granted) {
//We can now access our contacts here
Contact contact = await _contactPicker.selectContact();
print("Contact name: ${contact.fullName}");
print("Contact number: ${contact.phoneNumber}");
List<String> num =
contact.phoneNumber.toString().split('(Work)');
print(num);
print(num[0]);
final pattern = RegExp('\\s+');
num[0].replaceAll(pattern, '');
num[0].replaceAll(new RegExp('-'),'');
addData(contact.fullName.toString(),num[0]);
} else {
//If permissions have been denied show standard cupertino alert dialog
showDialog(
context: context,
builder: (BuildContext context) =>
CupertinoAlertDialog(
title: Text('Permissions error'),
content: Text('Please enable contacts access '
'permission in system settings'),
actions: <Widget>[
CupertinoDialogAction(
child: Text('OK'),
onPressed: () =>
Navigator.of(context).pop(),
)
],
));
}
},
color: const Color(0xffd4d411),
textColor: Colors.white,
child: Icon(
Icons.add,
size: 32,
),
padding: EdgeInsets.all(3),
shape: CircleBorder(),
),
Text(
'Add a Caregiver',
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 13,
color: const Color(0xff000000),
height: 1.5384615384615385,
fontWeight: FontWeight.w600),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
)
],
),
list.length == 0 ? Center(child: Text("Please add Caregivers"))
: ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: false,
itemBuilder: (context, index) {
return _caregiverWidget(list[index].name,list[index].phone_number,list[index].isActive,list[index].key
);
},
separatorBuilder: (_, __) => Container(),
itemCount: list.length),
// _contact == null ? Container() : CaregiversList(_contact.fullName),
],
),
),
),
);
}
Widget _caregiverWidget( String name, String phone_number, bool isActive, String key
) {
print(name);
var c = name.split(' ');
print(c[0]);
var caregiver = c[0];
var output = getInitials(string: caregiver, limitTo: 1);
print(output);
return GestureDetector(
onLongPress: (){},
onTap: (){},
child: Card(
elevation: 0,
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 27),
child: Row(
//crossAxisAlignment: CrossAxisAlignment.center,
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CircleAvatar(
radius: 24,
backgroundColor: const Color(0xffd4d411),
child: CircleAvatar(
radius: 22,
backgroundColor: Colors.white,
child: Text(
output,
style: TextStyle(
fontFamily: 'Segoe UI',
fontSize: 20,
color: const Color(0xff000000),
),
),
),
),
SizedBox(width: 19),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
caregiver,
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 13,
color: const Color(0xff000000),
height: 1.5384615384615385,
fontWeight: FontWeight.w600),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
isActive
? Text(
"Activated",
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 10,
color: const Color(0x80232425),
fontWeight: FontWeight.w500),
textAlign: TextAlign.left,
)
: Text(
"Disabled",
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 10,
color: const Color(0x80232425),
fontWeight: FontWeight.w500),
textAlign: TextAlign.left,
),
],
),
SizedBox(width: 143),
FlutterSwitch(
width: 40.0,
height: 20.0,
value: isActive,
toggleSize: 15,
borderRadius: 40.0,
padding: 2.0,
showOnOff: false,
activeColor: const Color(0xffd4d411),
activeToggleColor: Colors.white,
inactiveColor: const Color(0xffDDDBAF),
inactiveToggleColor: Colors.white,
onToggle: (value) {
print(value);
setState(() {
isActive = value;
// _careList.removeAt(index);
// _careList.insert(index, _caregiver);
});
},
),
],
),
),
),
);
}
String getInitials({String string, int limitTo}) {
var buffer = StringBuffer();
var split = string.split(' ');
for (var i = 0; i < (limitTo ?? split.length); i++) {
buffer.write(split[i][0]);
}
return buffer.toString();
}
}
The issue is that this part:
fb.child(cuser.uid).child("caregivers").once()..
is only getting the value once.
To se realtime updates you should either use a listener like here:
CollectionReference reference = Firestore.instance.collection('caregivers');
reference.snapshots().listen((querySnapshot) {
querySnapshot.documentChanges.forEach((change) {
// Do something with change
});
});
or a StreamBuilder like here:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('caregivers').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['name']),
);
}).toList(),
);
},
);
for the RTDB the StreamBuilder would look like this:
StreamBuilder(
stream: _database.reference().child('caregivers').onValue,
builder: (context, event) {
if (event.hasData &&
!event.hasError &&
event.data.snapshot.value != null) {
DataSnapshot snapshot = event.data.snapshot;
}
})
the listener would be like:
_database.reference().child('caregivers').onValue.listen((event){
})
onValue will give you everytime the whole list so you need to empty it before you add again every item:
var data = event.snapshot.value;
list= List(); //Clear the list here
data.forEach((key, value) {
print(value['Caregiver_Name']);
CaregiverList contact_list = new CaregiverList(
name: value['Caregiver_Name'],
phone_number: value['Caregiver_Number'],
isActive: true,
key: key,
);
list.add(contact_list);
});

how to get videos from Gallery and upload it firebase firestore using Image Picker and Video Player in Flutter?

I am trying to get video from gallery and want to display them in grid view and inside container and then on save button want to upload it to firebase firestore. Firstly, I am not able get the videos from gallery and display it in UI. I have seen many examples and followed them but it is not working for me as I am new into all this and not able to put to it together, where it needs to be..Pls help me out. Thanks in advance.Though the complete code may or may not be related to my query but it may help somebody who is trying to achieve something same. This is the complete code which have play button, video length with indicator.
I think I am missing few things here:-
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
This is where I want display the video:-
Container(
height: MediaQuery.of(context).size.height,
padding: EdgeInsets.all(20),
child:GridView.builder(
itemCount: _video.length,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (context,index){
return Container(
child: _controller.value.isInitialized ?
Stack(
children: [
VideoPlayer(_controller),
_ControlsOverlay(_controller),
],
)
:Container(),
);
}
),
),
This is where I open gallery and pick video;-
chooseVideo() async {
final pickedVideo = await picker.getVideo(
source: ImageSource.gallery,
maxDuration: const Duration(seconds: 60)
);
setState(() {
_video.add(File(pickedVideo?.path));
_controller = VideoPlayerController.file(File(_video.toString()));
});
if (pickedVideo.path == null) retrieveLostData();
}
Future<void> retrieveLostData() async {
final LostData response = await picker.getLostData();
if (response.isEmpty) {
return;
}
if (response.file != null) {
setState(() {
_video.add(File(response.file.path));
});
} else {
print(response.file);
}
}
This is complete code:-
class AddVideo extends StatefulWidget {
#override
_AddVideo createState() => _AddVideo();
}
class _AddVideo extends State<AddVideo> {
bool uploading = false;
double val = 0;
CollectionReference imgRef;
firebase_storage.Reference ref;
List<File> _video = [];
File videoFile;
List<String> urls = [];
final picker = ImagePicker();
final auth = FirebaseAuth.instance;
final _stoarge = FirebaseStorage.instance;
VideoPlayerController _controller;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
ElevatedButton(
child: Text("SAVE", style: TextStyle(fontSize: 15)),
onPressed: () {
setState(() {
uploading = true;
});
_uploadVideo().whenComplete(() => Navigator.of(context).pop());
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.fromLTRB(25.0, 15.0, 25.0, 10.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
),
],
),
body: Container(
child: SafeArea(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
// padding: EdgeInsets.all(20),
child: ElevatedButton(
child: Text("Open Gallery", style: TextStyle(fontSize: 20)),
onPressed: () => !uploading ? chooseVideo() : null,
style: ElevatedButton.styleFrom(
padding: EdgeInsets.fromLTRB(25.0, 15.0, 25.0, 10.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
),
),
Container(
height: MediaQuery.of(context).size.height,
padding: EdgeInsets.all(20),
child:GridView.builder(
itemCount: _video.length,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (context,index){
return Container(
child: _controller.value.isInitialized ?
Stack(
children: [
VideoPlayer(_controller),
_ControlsOverlay(_controller),
],
)
:Container(),
);
}
),
),
uploading ?
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
child: Text(
'uploading...',
style: TextStyle(fontSize: 20),
),
),
SizedBox(
height: 10,
),
CircularProgressIndicator(
value: val,
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
)
],
)
)
: Container(),
],
),
),
),
),
);
}
chooseVideo() async {
final pickedVideo = await picker.getVideo(
source: ImageSource.gallery,
maxDuration: const Duration(seconds: 60)
);
setState(() {
_video.add(File(pickedVideo?.path));
_controller = VideoPlayerController.file(File(_video.toString()));
});
if (pickedVideo.path == null) retrieveLostData();
}
Future<void> retrieveLostData() async {
final LostData response = await picker.getLostData();
if (response.isEmpty) {
return;
}
if (response.file != null) {
setState(() {
_video.add(File(response.file.path));
});
} else {
print(response.file);
}
}
_uploadVideo() async {
for (var video in _video) {
var _ref = _stoarge.ref().child("videos/" + Path.basename(video.path));
await _ref.putFile(video);
String url = await _ref.getDownloadURL();
print(url);
urls.add(url);
print(url);
}
print("uploading:" + urls.asMap().toString());
await FirebaseFirestore.instance
.collection("users")
.doc(auth.currentUser.uid)
.update({"images": urls});
// .doc(auth.currentUser.uid).update({"images": FieldValue.arrayUnion(urls) });
}
}
class _ControlsOverlay extends StatelessWidget {
String getPosition() {
final duration = Duration(
milliseconds: controller.value.position.inMilliseconds.round());
return [duration.inMinutes, duration.inSeconds]
.map((seg) => seg.remainder(60).toString().padLeft(2, '0'))
.join(':');
}
const _ControlsOverlay(this.controller);
final VideoPlayerController controller;
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
AnimatedSwitcher(
duration: Duration(milliseconds: 50),
reverseDuration: Duration(milliseconds: 200),
child: controller.value.isPlaying
? SizedBox.shrink()
: Container(
color: Colors.black26,
child: Center(
child: Icon(
Icons.play_arrow,
color: Colors.white,
size: 100.0,
),
),
),
),
GestureDetector(
onTap: () {
controller.value.isPlaying ? controller.pause() : controller.play();
},
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Row(
children: [
const SizedBox(width: 8),
Expanded(child: Text(getPosition()),),
const SizedBox(width: 8),
Expanded(child: buildIndicator()),
const SizedBox(width: 8),
],
)
),
],
);
}
Widget buildIndicator() => Container(
margin: EdgeInsets.all(8).copyWith(right: 0),
height: 10,
child: VideoProgressIndicator(
controller, allowScrubbing: true,
),
);
}

Resources