Flutter Image.memory() loading animation with slow large files - flutter

I am fetching an image from the gallery and displaying it in my cropper with Image.memory(). But sometimes (with large files) it takes a few seconds for the image to show. I want to show a loading animation but how do I detect when it is loading or finished? There is no loadingBuilder like for Image.network(). Any ideas?
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
Image.memory(_imageToCrop!)

Like Ankit Kumar Maurya said I studied the frameBuilder documentation and this solved my issue:
Image.memory(
_imageToCrop!,
frameBuilder:
((context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) return child;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: frame != null
? child
: SizedBox(
height: 60,
width: 60,
child: CircularProgressIndicator(
strokeWidth: 6),
),
);
}),
),

you can maintain an enum for this,
enum State {idle, loading, done ,error}
now,
State fetchState = State.idle
void fetch()async{
setState((){
fetchState = State.loading;
});
try{
final XFile? image = await _picker.pickImage(source:
ImageSource.gallery);
Image.memory(_imageToCrop!);
setState((){
fetchState = State.done;
});
}catch(e){
setState((){
fetchState = State.error;
});
}
}
Now use this states in your widget tree to show loader when state is loading.
you can use a single boolean variable as said in other answer but with this method you can catch errors at run time. You can use this method for api calls also.

You should once check this documentation provided by flutter frame_builder. You could also use frameBuilder for your requirement.
May be my code will help you:-
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(const MaterialApp(
title: 'Temp',
home: const MyApp(),
));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool loading = false;
late Uint8List image;
XFile? file = null;
ImagePicker imagePicker = ImagePicker();
Future<void> pickImage() async {
print('starting');
setState(() {
loading = true;
});
file = await imagePicker.pickImage(source: ImageSource.gallery);
print('file');
if(file != null){
print(file.toString());
image = await file!.readAsBytes();
}
setState(() {
loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Picker'),
),
body: loading
? Container(
child: const Center(child: CircularProgressIndicator()),
)
: file != null?Container(child: Center(child: Image.memory(image),)) : Container(
child: const Center(child: Text('Pick an Image')),
),
floatingActionButton: FloatingActionButton(
child: const Icon(
Icons.image,
),
onPressed: (){
pickImage();
}),
);
}
}
Short Description - my code will fetch an image from the system and then show … I have used a loading widget while the image is fetching and being converted to bytes.
It might be helpful if you share your logic here to figure out the main problem(if missing any thing).

Related

Adding images with persistent storage

I am building a home page where the user can replace default image. Whenever the user opens the application second time, I want that image user picked previously to show up and not the default image. How do I do this in dart/flutter?
I tried doing research, but was not able to find helpful articles.
You can save the image as files of specific path(getting the directory from path_provider, and then look for file at the same path the next time.
Also you can use image_picker to select the image on iOS and Android.
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
late Directory dir;
late File file;
final imagePicker = ImagePicker();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
dir = await getApplicationDocumentsDirectory();
file = File("${dir.path}/userImage1");
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
height: 300,
width: 300,
child: MyWidget(),
),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
if (!file.existsSync()) {
return Column(
children: [
Container(
color: Colors.purple,
),
ElevatedButton(
onPressed: () async {
final imgfile = await imagePicker.pickImage(source: ImageSource.gallery);
if (imgfile != null) {
await imgfile.saveTo(file.path);
//clear the cache image of that path, or else the image won't change
FileImage(file).evict();
}
setState(() {});
},
child: const Text("Pick Image"),
),
],
);
} else {
return Column(
children: [
Image.file(key: UniqueKey(), file),
ElevatedButton(
onPressed: () async {
final imgfile = await imagePicker.pickImage(source: ImageSource.gallery);
if (imgfile != null) {
await imgfile.saveTo(file.path);
//clear the cache image of that path, or else the image won't change
FileImage(file).evict();
}
setState(() {});
},
child: const Text("Pick Image")),
],
);
}
}
}
You will need to persist between runs which image the user has selected. To do so, you can use the shared_preferences package.
When the user selects an image, set the image and also set a key using shared_preferences indicating which image was selected. The value of the key will likely be a string, and it could be -- for example -- a URL, a path on the local filesystem, or a path in the assets bundle. How exactly you choose to represent the user's selection is up to you and depends on your specific use case.
And, when your app loads, retrieve that key from shared_preferences and use the value to load the correct image for display.
For this, it is necessary to save the last remaining image using the internal memory.
For example
Getx Storage
Hive
Remember the image sequence number every time the image is changed. This will help you show the images from the index immediately when the program is closed and reopened.
I gave a brief explanation here as general information.

Flutter with Getx ImagePicker does not work properly

Why does the image update only when I save file?
image_picker version
image_picker: ^0.8.4+3
My Code related to ImagePicker
// image_picker_controller.dart
// controller
import 'dart:io';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
class ImagePickerController extends GetxController {
File? pickedImageFile;
var seletedImagePath = ''.obs;
void _pickImage() async {
final picker = ImagePicker();
// final pickedImage = await picker.pickImage(source: ImageSource.gallery);
final pickedImage = await picker.pickImage(source: ImageSource.camera);
if (pickImage != null) {
pickedImageFile = File(pickedImage!.path);
}
update();
}
void Function() get pickImage => _pickImage;
}
view page
// user_image_picker.dart
// page screen
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_chatting_app/src/controllers/image_picker_controller.dart';
import 'package:get/get.dart';
class UserImagePicker extends GetView<ImagePickerController> {
UserImagePicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
CircleAvatar(
radius: 40,
backgroundColor: Colors.grey,
backgroundImage: controller.pickedImageFile != null
? FileImage(controller.pickedImageFile as File)
: null,
),
TextButton.icon(
onPressed: controller.pickImage,
icon: Icon(Icons.image),
label: Text('Add Image'),
),
],
);
}
}
You can have a look at what happens in this gif link
When I add image with ImagePicker, DEBUG CONSOLE shows below, and image isn't updated.
// DEBUG CONSOLE
D/MediaScannerConnection( 7062): Scanned /data/user/0/com.example.flutter_chatting_app/cache/bc149d80-91bb-487d-b2b7-3597357c4d182105624148556557573.jpg to null
but, after I save codes, the image appear. I think the state doens't update, but I have no idea why it happens.
I've googled about this problem, but I couldn't figure it out.
Please, somebody help me.
I used the default Stateless and GetBuilder combination and it worked:
class ImagePickerController extends GetxController {
File? pickedImageFile;
void _pickImage() async {
final picker = ImagePicker();
final pickedImage = await picker.pickImage(source: ImageSource.camera);
pickedImageFile = File(pickedImage!.path);
update();
}
void Function() get pickImage => _pickImage;
}
and the view:
class UserImagePicker extends StatelessWidget {
const UserImagePicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder<ImagePickerController>(builder: (logic) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: 40,
backgroundColor: Colors.grey,
backgroundImage: logic.pickedImageFile != null
? FileImage(logic.pickedImageFile as File)
: null,
),
TextButton.icon(
onPressed: logic.pickImage,
icon: const Icon(Icons.image),
label: const Text('Add Image'),
),
],
);
});
}
}

Share local image with Flutter

I want to share an image that I took from the CameraController.
I location of the file is as example /data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
How it is possible to share this local image?
I added these two line for read/write to local storage:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
I use the share component from https://pub.dev/packages/esys_flutter_share which works great.
void _sharePicture() async {
print('Share picture');
print(this.imagePath);
final ByteData bytes = await rootBundle.load(this.imagePath);
await Share.file('esys image', 'esys.png', bytes.buffer.asUint8List(), 'image/png', text: 'My optional text.');
}
this.imagePath is the local location of the file: :/data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
Do you first have to save the image? And the use it for sharing? How is it possible to share this local image?
The idea is share Uint8List
This demo use camera_camera package's example. https://github.com/gabuldev/camera_camera/tree/master/example
camera_camera package https://pub.dev/packages/camera_camera is an greate package have well made features and use camera plugin inside
code snippet
after click take picture, the system return a file (val in this example), read bytes and transfer to Uint8List
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
full code
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera_camera/camera_camera.dart';
import 'dart:typed_data';
import 'package:esys_flutter_share/esys_flutter_share.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
File val;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Rully")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
onPressed: () async {
val = await showDialog(
context: context,
builder: (context) => Camera(
mode: CameraMode.fullscreen,
orientationEnablePhoto: CameraOrientation.landscape,
/*
imageMask: CameraFocus.square(
color: Colors.black.withOpacity(0.5),
),
*/
));
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
setState(() {});
}),
body: Center(
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
width: MediaQuery.of(context).size.width * 0.8,
child: val != null
? Image.file(
val,
fit: BoxFit.contain,
)
: Text("Tire a foto"))));
}
}
demo screen
In camera_camera example take picture button will show in landscape mdoe
file path display in bottom
For camera plugin official example, I only change the following
code snippet
void onTakePictureButtonPressed() {
takePicture().then((String filePath) async{
if (mounted) {
setState(() {
imagePath = filePath;
videoController?.dispose();
videoController = null;
});
if (filePath != null) {
showInSnackBar('Picture saved to $filePath');
File val = File(filePath);
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
}
}
});
}

Flutter Error: Type 'File' not found. What's going on here?

I'm trying to use firebase_ml_vision with image_picker.
Here's the code:
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
void main() => runApp(MyHomePage());
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
FirebaseVisionImage visionImage;
final BarcodeDetector barcodeDetector = FirebaseVision.instance.barcodeDetector();
final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
final ImageLabeler labeler = FirebaseVision.instance.imageLabeler(
ImageLabelerOptions(confidenceThreshold: 0.75),
);
final FaceDetector faceDetector = FirebaseVision.instance.faceDetector();
void detections() async {
final VisionText visionText = await textRecognizer.processImage(visionImage);
}
File _image;
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
var something = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
this.visionImage = FirebaseVisionImage.fromFile(something);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Picker Example'),
),
body: Center(
child: _image == null
? Text('No image selected.')
: Image.file(_image),
),
floatingActionButton: FloatingActionButton(
onPressed: getImage,
tooltip: 'Pick Image',
child: Icon(Icons.add_a_photo),
),
);
}
}
But I am unable to use the File datatype and this is the error I get:
Error
Here's what I get when I run flutter doctor:
Doctor
And here's what I get when I run flutter --version:
Version
I'm very new to Flutter so please let me know if you need anything else.
Thank you!
File type is in dart:io, which you are missing. Add import 'dart:io'; to the top of the file.

Flutter default image not loading

New to flutter. Working on a personal project. Stuck with a small issue related to show images. Here is my widget code which I'm using for showing images.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Map userDetails = {};
String profileImgPath;
#override
void initState() {
super.initState();
getUserDetails();
}
Future<Null> getUserDetails() async {
try {
final SharedPreferences prefs = await _prefs;
this.userDetails = json.decode(prefs.getString('user'));
if (prefs.getString('user') != null) {
if (this.userDetails['isLoggedIn']) {
setState(() {
this.profileImgPath = this.userDetails['profileImg'];
print('Shared preference userDetailsss : ${this.userDetails}');
});
}
} else {
print('Shared preference has no data');
}
} catch (e) {
print('Exception caught at getUserDetails method');
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
Widget profileImage = new Container(
margin: const EdgeInsets.only(top: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
children: <Widget>[
new CircleAvatar(
backgroundImage: (this.profileImgPath == null) ? new AssetImage('images/user-avatar.png') : new CachedNetworkImageProvider(this.profileImgPath),
radius:50.0,
)
],
)
)
],
)
);
return new Scaffold(
appBar: new AppBar(title: new Text("Profile"), backgroundColor: const Color(0xFF009688)),
body: new ListView(
children: <Widget>[
profileImage,
],
),
);
}
}
What I'm trying to do is, show the default user-avatar.png image as long as CachedNetworkImageProvider don't get original image. But, it's bit behaving differently.
Whenever I'm opening the page - I'm getting a blank blue box then suddenly the original image from CachedNetworkImageProvider comes up.
Can't able to understand what's happening.
#Jonah Williams for your reference -
CachedNetworkImage can't be used for backgroundImage property because it does not extends ImageProvider. You can create a custom CircleAvatar like described below to use the CachedNetworkImage package:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CustomCircleAvatar extends StatelessWidget {
final int animationDuration;
final double radius;
final String imagePath;
const CustomCircleAvatar({
Key key,
this.animationDuration,
this.radius,
this.imagePath
}) : super(key: key);
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(
milliseconds: animationDuration,
),
constraints: new BoxConstraints(
minHeight: radius,
maxHeight: radius,
minWidth: radius,
maxWidth: radius,
),
child: new ClipOval(
child: new CachedNetworkImage(
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
imageUrl: imagePath,
placeholder: (context, url) => CircularProgressIndicator(),
),
),
);
}
}
And how to use:
body: new Center(
child: new CustomCircleAvatar(
animationDuration: 300,
radius: 100.0,
imagePath: 'https://avatars-01.gitter.im/g/u/mi6friend4all_twitter?s=128',
),
),
Maybe it is not the better way. But, it works!
(I'm assuming that CachedNetworkImageProvider is actually CachedNetworkImage from this package).
This line of code will always display the second image.
(this.profileImgPath == null)
? new AssetImage('images/user-avatar.png')
: new CachedNetworkImageProvider(this.profileImgPath)
Since profileImagePath is not null, the AssetImage is never created. Even if it was, as soon as it's not the cached network image will replace it before it has loaded. Instead, use the placeholder parameter of the network image. This will display your asset image until the network image loads.
new CachedNetworkImage(
placeholder: new AssetImage('images/user-avatar.png'),
imageUrl: profileImgPath,
)