setState(() {}); Required in Android Flutter - flutter

After selecting image using image_picker: ^0.6.0+15 package, we received a file in Android and iOS, but the issue is with the display/render of the image in the UI.
//Display Image to user
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: _selectedImage == null
? Center(
child: Container(
child: Text('No Image selected'),
),
)
: Image.memory(
_selectedImage.readAsBytesSync(),
fit: BoxFit.contain,
),
),
)
setState(() {}); is required in Android but on iOS image display with out calling setState(() {}); glitches the UI on iOS if we call setState().
We have an option:
Future getImage(int sourceType) async {
_selectedImage = await ImagePicker.pickImage(
source: sourceType == 1 ? ImageSource.gallery : ImageSource.camera,
maxHeight: 500,
maxWidth: 500,
);
// to show Images in Images View
if (Platform.isAndroid) {
setState(() {});
}
}
But is there any explanation on setState() is not needed on iOS?

Any interface should be ready for multiple build() calls per second. You should be free to call setState() with no reservations anytime. When this is a problem, you are doing something wrong.
I think this is what's wrong in your case: you call _selectedImage.readAsBytesSync() in build(). You should do that elsewhere and keep info about it in fields of your class, which the build() function uses to simply convert that information to UI.

Related

how to show circular progress indicator while uploading multiple images to firebase using two screens

i am making a house management app i have to upload images of the property alongside other data related to the property so i am using two screens one for the general info about the house and the second one specifically to upload images
Form screen
Image Upload Screen
from the upload screen i am returning back a list of images to the form screen
// i am waiting for the list in the form screen
images = await Navigator.push(context, MaterialPageRoute(builder: (context) => AddPictures()));
// i am returning the list back from the upload screen
Navigator.pop(context,imageStrings);
I am failing to show circular progress indicator for some reason beyond my capacity to know itried all ways i know
this is the rest of the code
//outiside the widdget build i have two lists
List<XFile> imagesXFiles = []; //for raw image files from the gallery or camera
List<String> imageStrings = []; //for image links from the firebase storage
body: isLoading == true ? CircularProgressIndicator() : Column(
children: [
Expanded(
//the first grid is a button to let the user access camera or gallery
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0
),
itemCount: imagesXFiles.length + 1,
itemBuilder: (BuildContext context, int index) {
return index == 0 ? GestureDetector(
onTap: (){
// a function to pick images and add store them to the list "imagesXFiles"
_showPicker(context);
},
child: Container(
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(5.0),
),
child: Icon(
Icons.add,
color: Colors.black,
size: 30.0,
),
),
): Container(
child: Image(
image: FileImage(File(imagesXFiles[index-1].path)),
fit: BoxFit.fill
),
);
},
),
),
TextButton(
onPressed: ()async{
// for some reason the circular progress doesn't work i dont understand why
setState(() {
isLoading = true;
});
imageStrings = await uploadImages(imagesXFiles).whenComplete(() {
setState(() {
isLoading = false;
Navigator.pop(context,imageStrings);
});
});
},
child: Text("Upload",style: TextStyle(color: Colors.black,fontSize: 25),)),
],
),
here is the upload function that uploads the images to firebase
Future<List<String>> uploadImages(List<XFile> imagesXFiles) async {
imagesXFiles.forEach((image) async {
final storageRef = storage.ref().child(Random().nextInt(100).toString());
await storageRef.putFile(File(image.path));
String imageURL = await storageRef.getDownloadURL();
imageStrings.add(imageURL);
firebaseFirestore
.collection("housePictures")
.add({
"imageURL" : imageURL,
});
});
return imageStrings;
}
You can use forEach with Future as below.
await Future.forEach(imagesXFiles, (image) async {
final storageRef = storage.ref().child(Random().nextInt(100).toString());
await storageRef.putFile(File(image.path));
String imageURL = await storageRef.getDownloadURL();
imageStrings.add(imageURL);
FirebaseFirestore.instance
.collection("housePictures")
.add({
"imageURL" : imageURL,
});
});
You can’t use forEach statement in an async operation. It is not going to wait. Use a normal for statement. Example: for(var item in items) etc. That should fix your issue. If you really want to use a for each you need to use Future. foreach see this thread -> How to Async/await in List.forEach() in Dart

How to render image from Firebase without rebuilding?

I'm trying to implement profile image picking in my flutter app using Firebase Storage. I use image_picker to get the image and upload it to Firebase, get the download link and add the download link to the imgsrc field in the cloud firestore, from where I can render the NetworkImage.
Center(
child: Stack(
children: [
buildImage(),
Positioned(
bottom: 5,
right: 5,
child: GestureDetector(
onTap: showPhotoAlertDialog,
child: buildEditIcon(Color(0xff407bff))),
),
],
),
),
How can I get the default Icons.person kind image for when the user has no profile image, and get the image from the database otherwise?
The code I'm using right now is as follows:
Widget buildImage() {
return CircleAvatar(
backgroundImage: NetworkImage(loggedInUser.imgsrc ??
'https://th.bing.com/th/id/R.945f33b643f2ceffcdae90fb57c61854?rik=XcI0SYBgSefoCA&riu=http%3a%2f%2fgetdrawings.com%2ffree-icon-bw%2fanonymous-avatar-icon-19.png&ehk=5n%2buJG66CeLQZsmhaMt8gag5rXuM3TdebAL6W35K1E4%3d&risl=&pid=ImgRaw&r=0'),
backgroundColor: Colors.grey[350],
radius: 100,
);
}
I created an Alert Dialog widget to choose whether to choose the image from camera or from the gallery.
showPhotoAlertDialog() {
AlertDialog alert = AlertDialog(
title: Text("Upload from"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
imageFromCamera()
.then((value) => uploadFile())
.whenComplete(() => postSource());
setState(() {}); ----->
},
child: Text("Upload from camera"),
),
TextButton(
onPressed: () {
imageFromGallery().then((value) => uploadFile());
postSource();
setState(() {});
},
child: Text("Upload from gallery"),
)
],
),
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
To upload the image to storage and post the source to cloud firestore, I use the following methods:
Future uploadFile() async {
if (file == null) return;
final fileName = path.basename(file!.path);
final destination = 'files/$fileName';
task = FirebaseApi.uploadFile(destination, file!);
setState(() {});
if (task == null) return;
final snapshot = await task!.whenComplete(() {});
urlDownload = await snapshot.ref.getDownloadURL();
print('Download-Link: $urlDownload');
}
postSource() async {
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
await firebaseFirestore
.collection("users")
.doc(user?.uid)
.update({'imgsrc': urlDownload});
}
The link gets uploaded properly and I'm able to get the link in my NetworkImage, but it doesn't get rendered immediately. I have to close the parent drawer and open it again to get it. I call setState(){} as well after posting the source, but it doesn't make any difference. How can I get the image without having to close and open the drawer?
Any help would be appreciated!
Thanks
You also have to update image in model class or in this imgsrc also just add this line above setState in onPressed of TextButton.
loggedInUser.imgsrc = urlDownload;

Show picture from assets or from files Flutter

I want to show a picture inside a CircleAvatar, if the user never inserted a picture before then the default picture "empty_profile.png" will appear, otherwise if the user already inserted a picture from gallery or camera then the last one will be shown.
This is the code :
File _file = null;
File variable declared at the beginning.
Future<void> changeImage() async { //This will change the picture
File tmp = await imgFromGallery();
setState(() {
_file = tmp;
});
return;
}
The function above will change _file value to the file picked from gallery.
Widget myAvatar() {
return GestureDetector(
onTap: null,
child: CircleAvatar(
radius: 55,
backgroundColor: Color(0xffFDCF09),
child: CircleAvatar(
radius: 50,
child: Container(
child: _file == null
? AssetImage("empty_profile.png")
: FileImage(_file),
),
),
),
);
}
Finally if file is still null then the asset image is loaded, otherwise if a new picture is choosen then FileImage(_file) will show the picked image.
I have a lots of error because I don't know very well how to handle files, their paths and show images...Can you explain me how I should do?
To include static images in your Flutter project, place them inside the "assets/images" folder. Then, make sure to add this folder to your pubspec.yml:
flutter:
assets:
- assets/images/
Next, you may have errors related to trying to render an AssetImage inside a CircleAvatar. To render the file as an Image widget instead, use Image.asset(<path>). Your example could be written as:
Widget myAvatar() {
return GestureDetector(
onTap: null,
child: CircleAvatar(
radius: 55,
backgroundColor: Color(0xffFDCF09),
child: _file == null
? Image.asset("assets/images/empty_profile.png")
: _file
),
);
}
Finally, a great resource for user-selected images is the image_picker library: https://pub.dev/packages/image_picker. For example, a "Select from Gallery" button could invoke the following code, which allows the user to crop the image and saves it as an Image widget:
PickedFile image = await picker.getImage(
source: ImageSource.gallery, // to select from camera, use ImageSource.camera
maxHeight: 1024,
maxWidth: 1024,
imageQuality: 50
);
try {
File croppedImage = await ImageCropper.cropImage( // use platform-native image cropping
sourcePath: image.path,
cropStyle: CropStyle.circle,
maxWidth: 512,
maxHeight: 512
);
setState(() { // refresh state to render new profile image
_file = Image.file(croppedImage)
})
} catch (err) {
print(err)
}
You can use CachedNetworkImage PlugIn - Update other details as per your need. This code show Network Images, if not available will show Assets Image.
new Container(
width: 140.0,
height: 140.0,
child: ClipOval(
child: CachedNetworkImage(
imageUrl: _profile == null ? "" : Api.baseURL + _profile,
placeholder: (context, url) =>
Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => Image.asset(
"img/user.png",
fit: BoxFit.cover,
height: 140.0,
width: 140.0,
),
fit: BoxFit.cover,
height: 140.0,
width: 140.0,
),
),
),

flutter, trying to update a picture from Storage

I'm trying to upload, and update a picture from firebase storage. But for some reason this is not working properly.
This would be the scenario; A user take a picture from his camera, so this picture is uploaded to my storage, so after that I get the url picture, so I can use this like NetworkImage(url) to update the picture.
This is what's happening currently:
Let's say I don't have any picture.
I update my profile with a new picture. (This works perfectly).
Now let's say I want to update my profile picture, so I take another picture, let's call it A so I upload this one.
Nothing happens, picture doesn't change.
But If I try again with a B picture, for some reason the picture is updated with the A picture.
This would be my code and how I'm facing this feature.
I have the next method which is invoked when a user click over his picture. I wait for the result (value) so when I got the result I upload the picture. Then, when the picture is uploaded, I just call to setState and save the url into the _imageUrl variable.
After that, I change the "profilePic" attribute from my data base to true.
String _imageUrl = 'assets/profileDefault.jpg';
bool profilePicture = false;
io.File profilePic;
Future getFromCamara() async {
await ImagePicker().getImage(source: ImageSource.camera).then((value) {
profilePic = io.File(value.path);
FirebaseStorage.instance.ref().child('picture').putFile(profilePic);
}).then((result) {
var ref = FirebaseStorage.instance.ref().child('picture');
ref.getDownloadURL().then((loc) => setState(() => _imageUrl = loc));
});
try {
FirebaseFirestore.instance
.collection('Usuarios')
.doc(uid)
.update({'profilePic': true});
} catch (e) {
print(e.toString());
}
So now, using a StreamBuilder, I get the result of profilePic from my DataBase, if is True, I download the URL, and if don't, I just use the Asset default's pic.
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('Usuarios').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot1) {
if (!snapshot1.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
List<DocumentSnapshot> users = snapshot1.data.docs;
for (DocumentSnapshot user in users) {
if (user.id == userID) {
profilePicture = user['profilePic'];
}
}
if (profilePicture) {
FirebaseStorage.instance
.ref()
.child('picture')
.getDownloadURL()
.then((loc) => _imageUrl = loc);
} else {
_imageUrl = 'assets/profileDefault.jpg';
}
return Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 0,
child: Container(
color: Color.fromRGBO(255, 0, 0, 70),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * .55,
),
),
Positioned(
top: MediaQuery.of(context).size.height * .015,
left: 15,
right: 15,
child: Container(
width: MediaQuery.of(context).size.height * .90,
height: 300,
padding: EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () => _setPic(context),
child: CircleAvatar(
radius: 79,
backgroundColor: Color(0xFFFFFFFFF),
child: CircleAvatar(
radius: 75,
backgroundImage: profilePicture
? NetworkImage(_imageUrl)
: AssetImage(_imageUrl),
),
),
),
// More code...
What I'm doing wrong? Why my picture isn't getting updated?
Updated:
I tried this, so I can save in pictureTemp the picture's bytes, calling to setState to rebuild the widget and put the picture with Image.memory(pictureTemp). But it doesn't seem to work.
Uint8List pictureTemp;
io.File profilePic;
Future getFromCamara() async {
var pickedFile = await ImagePicker().getImage(source: ImageSource.camera);
var image = await pickedFile.readAsBytes();
FirebaseStorage.instance
.ref()
.child('picture')
.putFile(io.File(pickedFile.path));
setState(() {
pictureTemp = image;
});
child: CircleAvatar(
radius: 75,
backgroundImage: profilePicture
? pictureTemp == null
? AssetImage(_imageUrl)
: Image.memory(pictureTemp)
: AssetImage(_imageUrl),
),
Here when the image is picked you should directly set it or you can set it after the firebase upload is finished successfully.
But rather than loading an image from Firebase URL after the upload, you can directly load from the picked file as follows;
image = Image.memory(await pickedFile.readAsBytes())
This will instantly set the image and will save you a read call to Firebase. You should always try to minimize the Firebase Reads whenever possible.

Failed assertion: line 854 pos 14: 'file != null': is not true

I'm trying to display an image using FileImage() but it's throwing the "'file != null': is not true" error. The image is coming from an Image Picker function. The wierd thing is that, I know that the file exists because when I try to print it, it shows me the path for the file.
EDIT: Here's the link to the entire code if you need to check it:
https://github.com/MBanawa/ecommerce_app/blob/master/lib/Admin/uploadItems.dart
Below is the function for the ImagePicker with a print method:
File imageFile;
pickImage(ImageSource imageSource) async {
Navigator.pop(context);
final pickedFile = await ImagePicker().getImage(source: imageSource);
setState(() {
imageFile = File(pickedFile.path);
print('Path $imageFile');
});
}
I trigger the function in an onPressed() by either camera or gallery :
camera:
onPressed: () => pickImage(ImageSource.camera),
gallery
onPressed: () => pickImage(ImageSource.gallery),
Below is how I call the imageFile:
Container(
height: 230.0,
width: MediaQuery.of(context).size.width * 0.8,
child: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(imageFile),
fit: BoxFit.cover,
),
),
),
),
),
),
The container above is inside a function called displayAdminUploadFormScreen() that gets called if imageFile is not null. I place that after the build method of my statefulwidget state:
#override
Widget build(BuildContext context) {
return imageFile == null
? displayAdminHomeScreen()
: displayAdminUploadFormScreen();
}
What I'm confused about is that, print() returns an actual link after taking a picture. here is a sample result when taking a picture using the camera:
I/flutter (16227): Path File: '/storage/emulated/0/Android/data/com.example.ecommerceapp/files/Pictures/068e58d6-88af-4b13-9453-c8c8d836083c5388058709499582194.jpg'
the exact error massage was:
The following assertion was thrown building UploadPage(dirty, dependencies:
[MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#71a32]], state: _UploadPageState#0a649):
'package:flutter/src/painting/image_provider.dart': Failed assertion: line 854 pos 14: 'file !=
null': is not true.
When checking the links that that error gave, it points to FileImage(imageFile). If I open up FileImage, line 854 pos 14 is an assertion:
const FileImage(this.file, { this.scale = 1.0 })
: assert(file != null),
assert(scale != null);
I also tried the traditional way of creating an ImagePicker function. (the one in pub.dev) but it's still showing the same error. Any guidance will be much appreciated. Thank you!
it's because you are making a call of clearFormInfo() function inside IconButton widget. I believe that you wanted to call this function inside callback onPressed property.
So instead of:
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: clearFormInfo(),
)
Please use:
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
clearFormInfo();
},
),
You need to check if the imageFile is null. If it is, then you can use a simple Container until imageFile is not null. Try something like this:
Container(
height: 230.0,
width: MediaQuery.of(context).size.width * 0.8,
child: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: imageFile != null // Here is important!
? Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(imageFile),
fit: BoxFit.cover,
),
),
)
: Container(),
),
),
),
displayAdminUploadFormScreen() is a function and not a widget right?
So does this function can get the value of imageFile correctly?
Try to print the imageFile at the start of this function, to check if you get the image path correctly.
displayAdminUploadFormScreen(){
print(imagePath);
.
// rest of code
.
}
If the result is null, then then you might need to pass the imageFile
displayAdminUploadFormScreen(File imageFile){
print(imagePath);
.
// rest of cocde
.
}
and
#override
Widget build(BuildContext context) {
return imageFile == null
? displayAdminHomeScreen()
: displayAdminUploadFormScreen(imageFile);
}
Have you set the permissions inside Info.plist and AndroidManifest.xml files?
Inside **/path/to/project/android/app/src/[debug/main/profile] you need to put something like
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Inside **/path/to/project/ios/Runnmer/Info.plist you need to put something like
<key>NSCameraUsageDescription</key>
<string>camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photos for this</string>
I'm not 100% sure on the Android one as I haven't developed for Android yet, but i'm sure that's true of the iOS version.