Cannot find cached network image in flutter test - flutter

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?

Related

Flutter: Can we save a Canvas/CustomPainter to a gif or continuous pictures or event a video?

The bounty expires in 4 days. Answers to this question are eligible for a +50 reputation bounty.
Danny is looking for a canonical answer:
Is it possible to save a flutter canvas to a gif or a video? Is it possible to directly convert the canvas to a video with ffmpeg?
Thanks
Can we export custom painter used animation controller to a gif image or continuous images or event a video such as mp4 file?
Yes I did it one time (2 years ago) and I converted a Flutter Animation to a mp4 file. unfortunately I couldn't find the code. please follow the steps to make what you want.
capture your widget with RenderRepaintBoundary
https://api.flutter.dev/flutter/rendering/RenderRepaintBoundary/toImage.html
class PngHome extends StatefulWidget {
const PngHome({super.key});
#override
State<PngHome> createState() => _PngHomeState();
}
class _PngHomeState extends State<PngHome> {
GlobalKey globalKey = GlobalKey();
Future<void> _capturePng() async {
final RenderRepaintBoundary boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
print(pngBytes);
}
#override
Widget build(BuildContext context) {
return RepaintBoundary(
key: globalKey,
child: Center(
child: TextButton(
onPressed: _capturePng,
child: const Text('Hello World', textDirection: TextDirection.ltr),
),
),
);
}
}
you need to capture each frame of your Animation and save it to a directory. with special naming for example (1.png,2.png .... 1000.png)
import 'package:path_provider/path_provider.dart';
import 'dart:io';
Uint8List imageInUnit8List = // store unit8List image here ;
final tempDir = await getTemporaryDirectory();
File file = await File('${tempDir.path}/image.png').create();
file.writeAsBytesSync(imageInUnit8List);
install ffmpeg https://pub.dev/packages/ffmpeg_kit_flutter and use it to execute FFMPEG command
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
FFmpegKit.execute('your command').then((session) async {
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
// SUCCESS
} else if (ReturnCode.isCancel(returnCode)) {
// CANCEL
} else {
// ERROR
}
});
search for a command to convert your images with ffmpeg to Gif Or Mp4 (some thing like these Example1 or Example2)
you can use screenshot library. by wrapping the parent container with Screenshot library you can convert widget to multiple images and those images can be converted to a gif but I think it is tricky, not efficient, and difficult to implement. you can give it a try.

How to upload pic from assets?

I am using below code to upload the pic from gallery in flutter, if in case the pic is not picked up from the gallery I want the pic from the assets to be uploaded to the firebase storage, for that avatarImageFile should be equivalent to the image file from the assets.
How Can I achieve that?
Future getImage() async {
print("get image");
PickedFile image = await _picker.getImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
final File file = File(image.path);
avatarImageFile = file;
isLoading = true;
});
}
else{
//if image is null then the image from the assets should be made picked into `avatarImageFile `
}
}
In Flutter you can load your assets in two ways:
Using rootBundle.loadString("assets/my_file.json") to load text files
Using rootBundle.load("assets/something.png") to load any kind of file (images, pdf or any other kind of binary).
Note that load also works with .json files but in general loadString is a better choice when it comes to retrieving text. For more info, read the doc.
avatarImageFile = await rootBundle.load("assets/a/b/c.png");
Do not use rootBundle when you're inside widgets: instead, prefer using DefaultAssetBundle:
class MyWidget extends StatelessWidget {
const MyWidget();
Future<String> loadConfig(BuildContext context) async =>
await DefaultAssetBundle
.of(context)
.loadString("myassets/some_cfg.json");
#override
Widget build(BuildContext context) {...}
}
Again, do the above when you're inside widgets. In any other case, go for rootBundle.

ImagePicker, how to set image again?

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

flutter for web: Get width/height form NetworkImage before displaying it

I want to get attribute values, such as width and height, from NetworkImage or Image.network before displaying the image.
I found the following good posts, but it doesn't work. It got the size values, but the image is not loaded in FutureBuilder.
How do I determine the width and height of an image in Flutter?
How do I tell when a NetworkImage has finished loading?
My code is as below;
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder(
future: _getImage(),
builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
print(snapshot.hasData);
if (snapshot.hasData) {
return snapshot.data;
} else {
return Text('Loading...');
}
},
),
),
);
}
Future<Image> _getImage() async {
final Completer completer = Completer();
final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
final image = NetworkImage(url);
image.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool isSync) {
print(info.image.width);
completer.complete(info.image);
}));
return completer.future;
}
}
The result is;
- The screen only shows "Loading..." and the image is not loaded.
- print output is as below. This should means, FutureBuilder is called twice before loading the image, and we can get the width but FutureBuilder is not called after that.
false
false
112
Environment:
Flutter 1.13.0 • channel dev (due to flutter web)
Chrome Version 79.0.3945.79
Couple of observations based on the reference post you provided.
You have mixed up ui.Image with Image widget.
If you separate the info logic from widget building then you will not have access to the Image widget meaning you will have to recreate it. Instead you can create a widget and return it.
In your http based answer response.body.length might not exactly represent the image dimension. You have to see if the response headers has any information about the image.
Also note FutureBuilder's build method will be called more than once with different ConnectionState depending on the state of the future like waiting or done. Check here
Option 1:
If you don't care about the Image widget then your current code can work with some slight modification. This is exactly identical to the original post but modified to match to the way you defined it in your post.
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class ImageSizeTestWidget extends StatefulWidget {
ImageSizeTestWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_ImageSizeTestWidgetState createState() => _ImageSizeTestWidgetState();
}
class _ImageSizeTestWidgetState extends State<ImageSizeTestWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder<ui.Image>(
future: _getImage(),
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
print(snapshot.hasData);
if (snapshot.hasData) {
return Text('${snapshot.data.width} X ${snapshot.data.height}');
} else {
return Text('Loading...');
}
},
),
),
);
}
Future<ui.Image> _getImage() async {
final Completer<ui.Image> completer = Completer<ui.Image>();
final String url =
'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
Image image = Image.network(url);
image.image
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool isSync) {
print(info.image.width);
completer.complete(info.image);
}));
return completer.future;
}
}
Option 2:
Just use the code as is in the original post bringing the Image widget creation and information extraction into the build method.
Based on the advice from Abion47, I successfully get the image with http package. But I still cannot get width and/ or height values even after getting the image.
Alternatively, I use response.body.length to check whether the downloaded image is valid or not.
Future<Image> _getImage() async {
Image _image;
final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
var response = await http.get(url);
print("Response status: ${response.statusCode}"); // 200
_image = Image.memory(response.bodyBytes);
print(_image.width); // still null
print(response.body.length); // this shows numbers. I'll use this.
return _image;
}
You are halfway there with your self-answer code. From there, you can convert the bytes to a ui.Image object with instantiateImageCodec.
Future<Image> _getImage() async {
Image _image;
final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
var response = await http.get(url);
print("Response status: ${response.statusCode}"); // 200, ideally
final bytes = response.bodyBytes);
final codec = await instantiateImageCodec(bytes);
final frame = await codec.getNextFrame();
final uiImage = frame.image; // a ui.Image object, not to be confused with the Image widget
print(uiImage.width); // The width of the image in pixels
print(uiImage.height); // The heightof the image in pixels
_image = Image.memory(bytes);
return _image;
}
It kind of sucks that you have to do it this way as the image will get decoded twice, but short of creating your own custom Image widget that can take the ui.Image object directly, I don't think there's much that can be done about that.

Flutter Camera Plugin taking dark image bug

I am getting dark images from flutter Camera Plugin.
Camera Preview is showing correctly but after taking the picture it becomes too dark.
I searched and what i found that it's about the FPS and exposure of the camera.
How can I solve this problem?
I need to show camera preview and take pictures in my app.
Please don't tell me to use image_picker package.
Device : Redmi note 4
Android OS : 7.0
Here is the Image
dark image
Here is the code
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({
Key key,
#required this.camera,
}) : super(key: key);
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
CameraController _controller;
Future<void> _initializeControllerFuture;
#override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Take a picture')),
// Wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner
// until the controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
// Construct the path where the image should be saved using the
// pattern package.
final path = join(
// Store the picture in the temp directory.
// Find the temp directory using the `path_provider` plugin.
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
// Attempt to take a picture and log where it's been saved.
await _controller.takePicture(path);
// If the picture was taken, display it on a new screen.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(imagePath: path),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}
Just put delay before take picture.
Future.delayed(const Duration(milliseconds: 500), () {
_controller.takePicture(path);
});
I think it's not about a delay, images are dark if exposure is not handled.
Also exposure requires focus pre captures to work and are not handled in official plugin now.
You can use this plugin : CamerAwesome
Official plugin has been quite abandonned. This plugin includes flash, zoom, auto focus, exposure... and no initialisation required.
It uses value notifier to change data directly in preview like this :
// init Notifiers
ValueNotifier<CameraFlashes> _switchFlash = ValueNotifier(CameraFlashes.NONE);
ValueNotifier<Sensors> _sensor = ValueNotifier(Sensors.BACK);
ValueNotifier<Size> _photoSize = ValueNotifier(null);
#override
Widget build(BuildContext context) {
return CameraAwesome(
onPermissionsResult: (bool result) { }
selectDefaultSize: (List<Size> availableSizes) => Size(1920, 1080),
onCameraStarted: () { },
onOrientationChanged: (CameraOrientations newOrientation) { },
zoom: 0.64,
sensor: _sensor,
photoSize: _photoSize,
switchFlashMode: _switchFlash,
orientation: DeviceOrientation.portraitUp,
fitted: true,
);
};
A hack that works for me, with camera plugin: take the picture twice. The first one buys time for the second one to have the proper exposure and focus.
final image = await controller.takePicture(); // is not used
final image2 = await controller.takePicture();