ImagePicker, how to set image again? - flutter

Minimal Code:
File _file;
Future<void> _pickImage() async {
final image = await ImagePicker.pickImage(source: ImageSource.camera);
if (image != null) {
final file = File("${(await getApplicationDocumentsDirectory()).path}/image.png");
await file.writeAsBytes(await image.readAsBytes());
setState(() => _file = file); // `_file = image` works though
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(child: Icon(Icons.camera_alt), onPressed: _pickImage),
body: _file == null ? Container() : Image.file(_file),
);
}
Watch video
As you can see, once I pick the image, it works, but on picking it second time, it doesn't work and I also don't run into any error. Can anyone please help?

you need 3 things:
first you have to use ImageProvider and its evict() method:
var image = FileImage(File('someImage.jpg'));
then you need Image widget that uses above ImageProvider and also assigns a unique key in order to be "different" each time build() method is called:
child: Image(
image: image,
key: UniqueKey(),
),
and finally after you overwrite someImage.jpg you have to call evict() method:
// part of your _pickImage() method
// here someImage.jpg contains updated content
image.evict();
setState(() {});
UPDATE: actually you dont need var image = FileImage(File('someImage.jpg')); - you can use it directly inside Image widget as image: FileImage(File('someImage.jpg')) and call FileImage(File('someImage.jpg')).evict() after your image is ovewritten

Related

Expected a value of type 'ImageProvider<Object>', but got one of type 'Image'

I'm building a web app with flutter and I want an option to change the profile picture. To pick image from gallery I use the image_picker_web 2.1.1 package.
class ImagePickerService {
late Image? img = Image.asset('whatever'); //has to be initialised
Future<void> pickImage() async {
img = await ImagePickerWeb.getImageAsWidget();
}
}
I didn't set the type of pickImage() to Future<Image?> because then I'd have to convert from Future. This asset('whatever'), although not very elegant, doesn't cause any problems because before _fileLoaded is set to true while picking an image, I display username's initial letter as avatar. Without it I was getting an error of no initialisation.
Relevant snippets from settings page class:
late Image? avatar;
bool _fileLoaded = false;
final ImagePickerService _ips = ImagePickerService();
Center(
child: displayAvatar(),
),
TextButton.icon(
onPressed: () {
setState(() {
_ips.pickImage();
avatar = _ips.img;
_fileLoaded = true;
});
},
icon: const Icon(Icons.edit),
label: const Text('Change avatar'),
),
Widget displayAvatar() {
if (_fileLoaded) {
return CircleAvatar(
backgroundImage: avatar as ImageProvider, radius: 50.0);
} else...
I searched for similar problems, but didn't find any answer except for adding as ImageProvider, which in my case removes the error from IDE and lets me run the project, but the error appears later on the red screen when I press the button to change avatar, even though the type should be correct. Please, do you know any solution?
ImageProvider is an abstract class. Classes that implement ImageProvider are AssetImage(""), NetworkImage("URL"), FileImage() etc. I believe FileImage should be well suited for your case.
I'm posting the solution, in case someone has the same problem.
I switched back to the image_picker package, it supports web too, you just have to use it a bit differently:
class ImagePickerService {
final ImagePicker _picker = ImagePicker();
Uint8List picked = Uint8List(8);
Future<void> pickImage() async {
XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) picked = await image.readAsBytes();
}
}
ClipOval(
child: Image.memory(
avatar,
fit: BoxFit.cover,
width: 100.0,
height: 100.0,
),
)
avatar is assigned to _ips.picked, so it's of Uint8List type.

NoSuchMethodError when taking Screenshot

I am trying to take a Screanshot of a Stak with a list of iteams in it. It displays normaly and works, but when i try to take screenshot of the Widget I resive:
NoSuchMethodError (NoSuchMethodError: The getter 'stateWidget' was called on null.
Receiver: null
Tried calling: stateWidget)
(I use a Inhereted widget)
Her is the Widget I am trying to take a Screenshot of
class BlinkSkjerm extends StatelessWidget {
#override
Widget build(BuildContext context) {
final provider = InheritedDataProvider.of(context);
final data = provider.historikken[provider.index];
return SizedBox(
height: 400,
child: Stack(
children: data.inMoveableItemsList,
));
}
}
and her is the onPress funtion:
onPressed: () async {
final controler = ScreenshotController();
final bytes = await controler.captureFromWidget(BlinkSkjerm());
setState(() {
this.bytes = bytes;
});
}
you used InheritedDataProvider in wrong way. you did not provide data that needed in BlinkSkjerm.
you want to take screen shot from widget that not in the tree, but that widget need data that should provide before build it which you did not provide it.
this approach work this way:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InheritedDataProvider(
child: BlinkSkjerm(),
data:'some string',
)),
);
this way you can use
final provider = InheritedDataProvider.of(context);
and make sure it is not null.
for your situation I recommended to do something like this:
onPressed: () async {
final controler = ScreenshotController();
final bytes = await controler.captureFromWidget(InheritedDataProvider(
child: BlinkSkjerm(),
data:'some string',
));
setState(() {
this.bytes = bytes;
});
}
for more information see this page

Cannot find cached network image in flutter test

I have a widget AvatarImage which renders a CircleAvatar if an image is provided or a default image DefaultAvatar otherwise.
Widget implementation looks like:
class AvatarImage extends StatelessWidget {
final ImageProvider? image;
const AvatarImage({this.image}): super();
Widget build(BuildContext context) {#
if (image == null) return DefaultAvatar();
return CicrcleAvatar(
image: image,
)
}
}
Now I wanted to test that image is actually rendered using the following test case:
testWidgets("Renders image avatar when image is provided", (WidgetTester tester) async {
mockNetworkImagesFor(() async {
final testNetworkImage = CachedNetworkImageProvider("https://placebear.com/100/100");
await tester.pumpWidget(TestApp(
child: AvatarImage(image: testNetworkImage)
));
await tester.pumpAndSettle();
final image = find.image(testNetworkImage);
debugDumpApp();
expect(image, findsOneWidget);
expect((image as Image).image, equals(testNetworkImage));
});
});
But it is unable to find the image. I have tried using find.byType(CachedNetworkImage) but this also has no effect the test is always failing because the image cannot be found.
I have verified that the widget is present in tree using debugDumpApp.
Why doesnt find find this image?
How can I find it without using keys?

Flutter: Image.memory() fails with bytes != null': is not true -- when there IS data

I'm trying to display an image based on (base64) data coming from a backend, but I keep getting the error bytes != null': is not true.
Here's my code:
class _FuncState extends State<Func> {
Uint8List userIconData;
void initState() {
super.initState();
updateUI();
}
void updateUI() async {
await getUserIconData(1, 2, 3).then((value) => userIconData = value);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
Container(
child: CircleAvatar(
backgroundImage: Image.memory(userIconData).image, // <--- problem here
maxRadius: 20,
),
),
),
);
}
}
Helper code:
Future<Uint8List> getUserIconData(
role,
id,
session,
) async {
var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
var response = await http.post(url, body: {
'method': 'getUserProfilePic',
'a': id.toString(),
'b': role.toString(),
'c': session.toString(),
});
if (response.statusCode == 200) {
Map data = jsonDecode(response.body);
return base64Decode(data['img']);
}
return null;
}
I have stepped through the code with a debugger and I have confirmed the helper function is returning the correct series of bytes for the image.
I'd appreciate any pointers.
Further note. The error also says:
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub
This is quite simple; if you take a look at your code you should be able to follow through this sequence of operations.
The widget is created. No action. At this point userIconData is null.
initState is called. async http call is initiated. userIconData == null
build is called. build occurs, throws error. userIconData == null
http call returns. userIconData is set. userIconData == your image
Due to not calling setState, your build function won't run again. If you did, this would happen (but you'd still have had the exception earlier).
build is called. userIconData is set. userIconData == your image
The key here is understanding that asynchronous calls (anything that returns a future and optionally uses async and await) do not return immediately, but rather at some later point, and that you can't rely on them having set what you need in the meantime. If you had previously tried doing this with an image loaded from disk and it worked, that's only because flutter does some tricks that are only possible because loading from disk is synchronous.
Here are two options for how you can write your code instead.
class _FuncState extends State<Func> {
Uint8List? userIconData;
// if you're using any data from the `func` widget, use this instead
// of initState in case the widget changes.
// You could also check the old vs new and if there has been no change
// that would need a reload, not do the reload.
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
updateUI();
}
void updateUI() async {
await getUserIconData(widget.role, widget.id, widget.session).then((value){
// this ensures that a rebuild happens
setState(() => userIconData = value);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
// this only uses your circle avatar if the image is loaded, otherwise
// show a loading indicator.
child: userIconData != null ? CircleAvatar(
backgroundImage: Image.memory(userIconData!).image,
maxRadius: 20,
) : CircularProgressIndicator(),
),
),
);
}
}
Another way to do the same thing is to use a FutureBuilder.
class _FuncState extends State<Func> {
// using late isn't entirely safe, but we can trust
// flutter to always call didUpdateWidget before
// build so this will work.
late Future<Uint8List> userIconDataFuture;
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
userIconDataFuture =
getUserIconData(widget.role, widget.id, widget.session);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
child: FutureBuilder(
future: userIconDataFuture,
builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return CircleAvatar(
backgroundImage: Image.memory(snapshot.data!).image,
maxRadius: 20);
} else {
return CircularProgressIndicator();
}
},
),
),
),
);
}
}
Note that the loading indicator is just one option; I'd actually recommend having a hard-coded default for your avatar (i.e. a grey 'user' image) that gets switched out when the image is loaded.
Note that I've used null-safe code here as that will make this answer have better longevity, but to switch back to non-null-safe code you can just remove the extraneous ?, ! and late in the code.
The error message is pretty clear to me. userIconData is null when you pass it to the Image.memory constructor.
Either use FutureBuilder or a condition to check if userIconData is null before rendering image, and manually show a loading indicator if it is, or something along these lines. Also you'd need to actually set the state to trigger a re-render. I'd go with the former, though.

How to refresh widget?

I want to "refresh" my widget after i upload a file.
new Container(
child: _profileAvatar(),
width: 150.0,
height: 150.0,
),
Widget _profileAvatar(){
if(!hasProfilImage){
return CircleAvatar(
radius: 30.0,
backgroundImage:
new ExactAssetImage('assets/images/default_profile.png'),
backgroundColor: Colors.transparent,
);
}else {
return CircleAvatar(
radius: 30.0,
backgroundImage:
NetworkImage( "${Configuration.url}assets/profileImage/${userProfilImage}"),
backgroundColor: Colors.transparent,
);
}
}
and here is how i upload a file
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
if(image != null) {
_image = image;
hasProfilImage = true;
_upload();
}
});
}
for now after the upload process finished, i need to re open this "page" to see the new image.
How can i show the new image immediately after the upload process finish?
thanks in advance
Here is what i try so far , but still no help
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
if(image != null) {
_image = image;
_upload().then((isSuccess){
setState(() {
hasProfilImage = true;
});
});
}
});
}
here is my full script https://pastebin.com/59UfYa2P
As you are working with flutter you can use StatefulWidget which has a setState method. whenever you want to update the data put that data after processing. It will automatically update at its position.
setState(() {});
If you have the API call or function in another widget you can use callback to update the widget.
If you want to update the previous widget after the processing the on the next widget Here is the callback
_navigateAndDisplayDriverSelection(
BuildContext context, String userId, String driveId) async {
final driverModal = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Drivers(userId: userId, driveId: driveId)),
);
setState(() {});
}
Your Upload function probably returns a Future with some value whether the image is successfully uploaded or not.
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
if(image != null) {
_image = image;
_upload().then((isSuccess){
// once upload is completed then, call setState and it will rebuild the widget tree and update the view.
setState(() {
hasProfilImage = true;
});
});
}
}
If you don't want to pass down your setState method through your widget tree, you can also use ScopedModels.
This page has a brief example of how to implement a scopedmodel:
https://pub.dev/packages/scoped_model
Just know that every time you call notifylisteners(), everything underneath ScopedModelDescendant gets rebuild.
The issue you are facing is one of state-management, and there are also alternative solutions for this problem: https://flutter.dev/docs/development/data-and-backend/state-mgmt