In Flutter how to save an image from network to the local directory.
I am new to encoding and decoding images. Can anyone point me in the right direction?
If all you want is to save an image (example: a .png) to the device, you can easily achieve this with a simple get (http/http.dart) and a File (dart:io).
To do so, you can base yourself in the example below:
var response = await http.get(imgUrl);
Directory documentDirectory = await getApplicationDocumentsDirectory();
File file = new File(join(documentDirectory.path, 'imagetest.png'));
file.writeAsBytesSync(response.bodyBytes); // This is a sync operation on a real
// app you'd probably prefer to use writeAsByte and handle its Future
Note that in the case above I’ve used the ‘path_provider’ package from the dart pub. In overall you would have imported at least these items:
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
There's a simple answer and a more complicated answer to this. I'll describe the complicated one and then give you the simple one.
The complicated answer is that you could manually cache images by using a NetworkImage, resolving it, and getting the image stream. Once you have the image stream, you could save it to the filesystem using flutter's file reading & writing capabilities (this is a good resource to learn more about that) - you also need to use a plugin called PathProvider which gets the right path for both iOS and Android which is described in that link. You'd also want to keep track of all the images you'd downloaded and probably delete them after certain amount of time. You'd also have to read the files back before using them to create Image widgets.
That gives you lots of control, but is a bit of work (although not a crazy amount, but if you're new to flutter maybe not something you want to do just now depending on why you want to save the images).
The simple answer is Packages to the rescue! Someone else has already come across this problem and written a plugin that solves it for you, so you don't have to think about it!
See cached_network_image package for information about the plugin.
You need to add to your dependencies in pubspec.yaml
dependencies:
cached_network_image: "^0.3.0"
Import it:
import 'package:cached_network_image/cached_network_image.dart';
And use it!
new CachedNetworkImage(
imageUrl: "http://imageurl.png",
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
),
Note that this downloads & shows the image - if you want to do those seperately you can use a new CachedNetworkImageProvider(url) and show it using new Image.
After wandering around the codes and flutter docs. I have found the methods and classes which would work for both iOS and Android and here it goes.
Helper Classs
import 'dart:async';
import 'dart:io' as Io;
import 'package:image/image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:path_provider/path_provider.dart';
class SaveFile {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<Io.File> getImageFromNetwork(String url) async {
var cacheManager = await CacheManager.getInstance();
Io.File file = await cacheManager.getFile(url);
return file;
}
Future<Io.File> saveImage(String url) async {
final file = await getImageFromNetwork(url);
//retrieve local path for device
var path = await _localPath;
Image image = decodeImage(file.readAsBytesSync());
Image thumbnail = copyResize(image, 120);
// Save the thumbnail as a PNG.
return new Io.File('$path/${DateTime.now().toUtc().toIso8601String()}.png')
..writeAsBytesSync(encodePng(thumbnail));
}
}
Usage of class
class HomePageState extends State<HomePage>{
Future<Null> _launched ;
Widget _showResult(BuildContext context, AsyncSnapshot<Null> snapshot){
if(!snapshot.hasError){
return Text('Image is saved');
}
else{
return const Text('Unable to save image');
}
}
Future<Null> _saveNetworkImage(String url) async{
try{
await SaveFile().saveImage(url);
}
on Error catch(e){
throw 'Error has occured while saving';
}
}
#override
Widget Build(BuildContext context){
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('Image'),
),
body: Column(
children: <Widget>[
IconButton(icon: Icon(Icons.save), onPressed: (){
setState(() {
_launched =_saveNetworkImage(url);
});
}),
new FutureBuilder<Null>(future: _launched ,builder: _showResult),
],
),
);
}
}
To save the network image in local system you need to use ImagePickerSave dart plugin. Add the dart plugin in pub.yaml file: image_picker_saver: ^0.1.0 and call below code to save the image. URL is the image URL of network image
void _onImageSaveButtonPressed(String url) async {
print("_onImageSaveButtonPressed");
var response = await http
.get(url);
debugPrint(response.statusCode.toString());
var filePath = await ImagePickerSaver.saveFile(
fileData: response.bodyBytes);
var savedFile= File.fromUri(Uri.file(filePath));
}
Follow this url flutter save network image.
Code Snippet
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
class NetworkToLocalImage extends StatefulWidget{
String url;
NetworkToLocalImage(this.url);
#override
_LoadImages createState() => new _LoadImages(url);
}
class _LoadImages extends State<NetworkToLocalImage>{
String url;
String filename;
var dataBytes;
_LoadImages(this.url){
filename = Uri.parse(url).pathSegments.last;
downloadImage().then((bytes){
setState(() {
dataBytes = bytes;
});
});
}
Future<dynamic> downloadImage() async {
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
if (file.existsSync()) {
print('file already exist');
var image = await file.readAsBytes();
return image;
} else {
print('file not found downloading from server');
var request = await http.get(url,);
var bytes = await request.bodyBytes;//close();
await file.writeAsBytes(bytes);
print(file.path);
return bytes;
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
if(dataBytes!=null)
return new Image.memory(dataBytes);
else return new CircularProgressIndicator();
}
}
I had a lot of trouble doing this, so I wanted to expound a little on the above answers with a simple example that downloads a file on startup, saves it to a local directory. I marked a couple of lines with the comment //%%% to show lines that can be commented out the second time that the app runs. (because it doesn't need to be downloaded to be displayed... it's already on the device itself:
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Image',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Test Image'),
);
}
}
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
initState() {
_downloadAndSavePhoto();
super.initState();
}
_downloadAndSavePhoto() async {
// Get file from internet
var url = "https://www.tottus.cl/static/img/productos/20104355_2.jpg"; //%%%
var response = await get(url); //%%%
// documentDirectory is the unique device path to the area you'll be saving in
var documentDirectory = await getApplicationDocumentsDirectory();
var firstPath = documentDirectory.path + "/images"; //%%%
//You'll have to manually create subdirectories
await Directory(firstPath).create(recursive: true); //%%%
// Name the file, create the file, and save in byte form.
var filePathAndName = documentDirectory.path + '/images/pic.jpg';
File file2 = new File(filePathAndName); //%%%
file2.writeAsBytesSync(response.bodyBytes); //%%%
setState(() {
// When the data is available, display it
imageData = filePathAndName;
dataLoaded = true;
});
}
String imageData;
bool dataLoaded = false;
#override
Widget build(BuildContext context) {
if (dataLoaded) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// imageData holds the path AND the name of the picture.
Image.file(File(imageData), width: 600.0, height: 290.0)
],
),
),
);
} else {
return CircularProgressIndicator(
backgroundColor: Colors.cyan,
strokeWidth: 5,
);
}
}
}
And heres my pubspec.yaml file:
http: ^0.12.0+2
path_provider: 1.5.0
you can use this function to upload and get Url(download) Image from the camera using
firebase-storage
Future _upIm()async {
final StorageReference firebaseStorageRef =
FirebaseStorage.instance.ref().child(DateTime.now().toString());
final StorageUploadTask task =
firebaseStorageRef.putFile(_storedImage);
var downUrl=await (await task.onComplete).ref.getDownloadURL();
var url =downUrl.toString();
print(url);
setState(() {
uploadImage=url;
});
}
inspired by Paras,CacheManager.getInstance() is deprecated
final DefaultCacheManager defaultCacheManager = DefaultCacheManager();
final fileInfo = await defaultCacheManager.getFileFromCache(imageUrl);
ImageGallerySaver.saveImage(image);
below is saveImage function
// save raw data to gallery with permission
static Future<void> saveImage(Uint8List image) async {
try {
if (Platform.isIOS) {
await PermissionHandler().requestPermissions([PermissionGroup.photos]);
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.photos);
if (permission == PermissionStatus.granted) {
} else {
throw 'denied';
}
} else if (Platform.isAndroid) {
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
if (permission == PermissionStatus.granted) {
} else {
throw 'denied';
}
}
await ImageGallerySaver.saveImage(image);
} catch (e) {
throw e;
}
}
You can use like that
var response = await http.get(Uri.parse(imageUrl));
Directory documentDirectory = await getApplicationDocumentsDirectory();
File file = File(join(documentDirectory.path, 'imagetest.png'));
await GallerySaver.saveImage(file.path);
You can use image_downloader.
For ios, image is saved in Photo Library.
For Android, image is saved in Environment.DIRECTORY_DOWNLOADS or specified location. By calling inExternalFilesDir(), specification of permission becomes unnecessary.
By callback(), you can get progress status.
The following is the simplest example. It will be saved.
await ImageDownloader.downloadImage(url);
Related
I am trying to make an app that captures image (and doesnt display the camera stream) using dart:ffi (.c file is not included in code below). (I used this page) I use allocate() and free() methods, but I get an error: "The method 'free' ('allocate') isn't defined for the type '_MyHomePageState'. How do i fix this?
calloc.allocate(), calloc.free(), malloc.allocate() and malloc.free() work and then ffi library is used. Without this, errors appear again.
I have ffi: ^2.0.1 in my dependencies and i ran flutter pub get, flutter clean, flutter outdated, flutter upgrade and restarted my computer.
import 'dart:io';
import 'dart:typed_data';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:image/image.dart' as imglib;
typedef convert_func = Pointer<Uint32> Function(Pointer<Uint8>, Pointer<Uint8>, Pointer<Uint8>, Int32, Int32, Int32, Int32);
typedef Convert = Pointer<Uint32> Function(Pointer<Uint8>, Pointer<Uint8>, Pointer<Uint8>, int, int, int, int);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Camera App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const MyHomePage(title: 'Camera App'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late CameraController _camera;
bool _cameraInitialized = false;
late CameraImage _savedImage;
final DynamicLibrary convertImageLib = Platform.isAndroid
? DynamicLibrary.open("lib-convertImage.so")
: DynamicLibrary.process();
late Convert conv;
#override
void initState() {
super.initState();
_initializeCamera();
// Load the convertImage() function from the library
conv = convertImageLib.lookup<NativeFunction<convert_func>>('convertImage').asFunction<Convert>();
}
void _initializeCamera() async {
// Get list of cameras of the device
List<CameraDescription> cameras = await availableCameras();
// Create the CameraController
_camera = CameraController(cameras[0], ResolutionPreset.veryHigh);
_camera.initialize().then((_) async{
// Start ImageStream
await _camera.startImageStream((CameraImage image) => _processCameraImage(image));
setState(() {
_cameraInitialized = true;
});
});
}
void _processCameraImage(CameraImage image) async {
setState(() {
_savedImage = image;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const Center(),
floatingActionButton: FloatingActionButton(
onPressed: (){
// Allocate memory for the 3 planes of the image
Pointer<Uint8> p = allocate(count: _savedImage.planes[0].bytes.length);
Pointer<Uint8> p1 = allocate(count: _savedImage.planes[1].bytes.length);
Pointer<Uint8> p2 = allocate(count: _savedImage.planes[2].bytes.length);
// Assign the planes data to the pointers of the image
Uint8List pointerList = p.asTypedList(_savedImage.planes[0].bytes.length);
Uint8List pointerList1 = p1.asTypedList(_savedImage.planes[1].bytes.length);
Uint8List pointerList2 = p2.asTypedList(_savedImage.planes[2].bytes.length);
pointerList.setRange(0, _savedImage.planes[0].bytes.length, _savedImage.planes[0].bytes);
pointerList1.setRange(0, _savedImage.planes[1].bytes.length, _savedImage.planes[1].bytes);
pointerList2.setRange(0, _savedImage.planes[2].bytes.length, _savedImage.planes[2].bytes);
// Call the convertImage function and convert the YUV to RGB
Pointer<Uint32> imgP = conv(p, p1, p2, _savedImage.planes[1].bytesPerRow,
_savedImage.planes[1].bytesPerPixel, _savedImage.width, _savedImage.height);
// Get the pointer of the data returned from the function to a List
List imgData = imgP.asTypedList((_savedImage.width * _savedImage.height));
// Generate image from the converted data
imglib.Image img = imglib.Image.fromBytes(_savedImage.height, _savedImage.width, imgData);
// Free the memory space allocated
// from the planes and the converted data
free(p);
free(p1);
free(p2);
free(imgP);
},
tooltip: 'Increment',
child: const Icon(Icons.camera_alt),
), // This trailing comma makes auto-formatting nicer for build methods.
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
I am trying to figure out how can i watch a StateNotifierProvider and trigger some methods (defined in the class subclassing StateNotifier) on this provider after having done some async computations in another Provider watching the StateNotifierProvider.
Loking at the example below
i need to perform a reset from the RandomAdderNotifierobject provided by the randomAdderProvider if the doneProvider return true.
I try to reset from the doReset Provider. However the provider has nothing to provide.
The point is that both the doneProvider and the doreset provider are not rebuild on state changes of AdderProvider.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:equatable/equatable.dart';
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
final randomProvider = Provider<Random>((ref) {
return Random(1234);
});
//immutable state
class RandomAdder extends Equatable {
final int sum;
const RandomAdder(this.sum);
#override
List<Object> get props => [sum];
}
//State notifier extension
class RandomAdderNotifier extends StateNotifier<RandomAdder> {
RandomAdderNotifier(this.ref) : super(const RandomAdder(0));
final Ref ref;
void randomIncrement() {
state = RandomAdder(state.sum + ref.read(randomProvider).nextInt(5));
}
void reset() {
state = RandomAdder(0);
}
}
/// Providers are declared globally and specify how to create a state
final randomAdderProvider =
StateNotifierProvider<RandomAdderNotifier, RandomAdder>(
(ref) {
return RandomAdderNotifier(ref);
},
);
Future<bool> delayedRandomDecision(ref) async {
int delay = ref.read(randomProvider).nextInt(5);
await Future.delayed(Duration(seconds: delay));
print("You waited $delay seconds for a decision.");
return delay > 4;
}
final doneProvider = FutureProvider<bool>(
(ref) async {
ref.watch(randomAdderProvider);
bool decision = await delayedRandomDecision(ref);
print("the decision is $decision");
return decision;
},
);
final doreset = Provider((ref) {
if (ref.watch(doneProvider).value!) {
ref.read(randomAdderProvider.notifier).reset();
}
});
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
// Consumer is a widget that allows you reading providers.
child: Consumer(builder: (context, ref, _) {
final count = ref.watch(randomAdderProvider);
return Text('$count');
}),
),
floatingActionButton: FloatingActionButton(
// The read method is a utility to read a provider without listening to it
onPressed: () =>
ref.read(randomAdderProvider.notifier).randomIncrement(),
child: const Icon(Icons.add),
),
);
}
}
I think ref.listen is more appropriate for usage within the doreset function than ref.watch.
Similarly to ref.watch, it is possible to use ref.listen to observe a provider.
The main difference between them is that, rather than rebuilding the widget/provider if the listened to provider changes, using ref.listen will instead call a custom function.
As per the Riverpod documentation
For ref.listen we need an additional argument - the callback function that we wish to execute on state changes - Source
The ref.listen method needs 2 positional arguments, the first one is the Provider and the second one is the callback function that we want to execute when the state changes.
The callback function when called will be passed 2 values, the value of the previous State and the value of the new State.
&
We will need to handle an AsyncValue - Source
As you can see, listening to a FutureProvider inside a widget returns an AsyncValue – which allows handling the error/loading states.
In Practice
doreset function
I chose to handle the AsyncValue by only handling the data case with state.whenData()
final doReset = Provider<void>(
(ref) {
final done = ref.listen<AsyncValue<bool>>(doneProvider, (previousState, state) {
state.whenData((value) {
if (value) {ref.read(randomAdderProvider.notifier).reset();}
});
});
},
);
Don't forget to watch either doReset/doneProvider in your Home Widget's build method. Without that neither will kick off (Don't have an explanation for this behaviour)
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(doReset);
...
Lastly, your random function will never meet the condition for true that you have setup as delay>4, as the max possible delay is 4. Try instead using delay>3 or delay=4.
Also perhaps disable the button to prevent clicks while awaiting updates
and in a case where you are using ChangeNotifier You can pass ref in you provider and use the ref same as we can use in ConsumerWidget.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class YourProvider extends ChangeNotifier {
Ref ref;
YourProvider(this.ref) : super();
callOtherProviderFromThisProvider() {
ref.read(otherProvider).someMethodINeedToTrigger();
}
}
final yourProvider = ChangeNotifierProvider<YourProvider>(
(ref) => YourProvider(ref));
I am using flutter plugin audioplayers: ^0.7.8, the below code is working in Android but not working in IOS. I run the code in real ios device and click the button. It suppose the play the mp3 file, but no sound at all. Please help to solve this issue.
I already set up the info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Here with the print out from the console:
flutter: finished loading, uri=file:///var/mobile/Containers/Data/Application/E3A576E2-0F21-44CF-AF99-319D539767D0/Library/Caches/demo.mp3
Syncing files to device iPhone...
flutter: _platformCallHandler call audio.onCurrentPosition {playerId: 273e1d27-b6e8-4516-bb3f-967a41dff308, value: 0}
flutter: _platformCallHandler call audio.onError {playerId: 273e1d27-b6e8-4516-bb3f-967a41dff308, value: AVPlayerItemStatus.failed}
Here with my code:
class _MyHomePageState extends State<MyHomePage> {
AudioPlayer audioPlugin = AudioPlayer();
String mp3Uri;
#override
void initState() {
AudioPlayer.logEnabled = true;
_load();
}
Future<Null> _load() async {
final ByteData data = await rootBundle.load('assets/demo.mp3');
Directory tempDir = await getTemporaryDirectory();
File tempFile = File('${tempDir.path}/demo.mp3');
await tempFile.writeAsBytes(data.buffer.asUint8List(), flush: true);
mp3Uri = tempFile.uri.toString();
print('finished loading, uri=$mp3Uri');
}
void _playSound() {
if (mp3Uri != null) {
audioPlugin.play(mp3Uri, isLocal: true,
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Audio Player Demo Home Page'),
),
body: Center(),
floatingActionButton: FloatingActionButton(
onPressed: _playSound,
tooltip: 'Play',
child: const Icon(Icons.play_arrow),
),
);
}
}
If you want to use a local file, you have to use AudioCache.
Looking at the documentation, it says at the bottom:
AudioCache
In order to play Local Assets, you must use the AudioCache class.
Flutter does not provide an easy way to play audio on your assets, but this class helps a lot. It actually copies the asset to a temporary folder in the device, where it is then played as a Local File.It works as a cache because it keep track of the copied files so that you can replay then without delay.
To get audio to play, this is what I figured out what we need to do:
import 'package:flutter/material.dart';
import 'package:audioplayers/audio_cache.dart';
AudioCache audioPlayer = AudioCache();
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override initState(){
super.initState();
audioPlayer.play("Jingle_Bells.mp3");
}
#override Widget build(BuildContext context) {
//This we do not care about
}
}
Important:
It automatically places 'assets/' in front of your path. This means you if you wanted to load assets/Jingle_Bells.mp3, you would simply put audioPlayer.play("Jingle_Bells.mp3");. If you entered audioPlayer.play("assets/Jingle_Bells.mp3"); instead, AudioPlayers would actually load assets/assets/Jingle_Bells.mp3 instead.
``
For iOS you must add file:// in front of your local url.
For Android this approach will also work but it's not required.
I see there are a lot of examples on how to upload an image using flutter to firebase storage but nothing on actually downloading/reading/displaying one that's already been uploaded.
In Android, I simply used Glide to display the images, how do I do so in Flutter? Do I use the NetworkImage class and if so, how do I first get the url of the image stored in Storage?
To view the images inside your storage, what you need is the name of the file in the storage. Once you've the file name for the specific image you need.
In my case if i want the testimage to be loaded,
final ref = FirebaseStorage.instance.ref().child('testimage');
// no need of the file extension, the name will do fine.
var url = await ref.getDownloadURL();
print(url);
Once you've the url,
Image.network(url);
That's all :)
New alternative answer based on one of the comments.
I don't see anywhere google is charging extra money for downloadURL.
So if you're posting some comments please attach a link to it.
Once you upload a file to storage, make that filename unique and save that name somewhere in firestore, or realtime database.
getAvatarUrlForProfile(String imageFileName) async {
final FirebaseStorage firebaseStorage = FirebaseStorage(
app: Firestore.instance.app,
storageBucket: 'gs://your-firebase-app-url.com');
Uint8List imageBytes;
await firebaseStorage
.ref()
.child(imageFileName)
.getData(100000000)
.then((value) => {imageBytes = value})
.catchError((error) => {});
return imageBytes;
}
Uint8List avatarBytes =
await FirebaseServices().getAvatarUrlForProfile(userId);
and use it like,
MemoryImage(avatarBytes)
update
In newer versions use
await ref.getDownloadURL();
See How to get full downloadUrl from UploadTaskSnapshot in Flutter?
original
someMethod() async {
var data = await FirebaseStorage.instance.ref().child("foo$rand.txt").getData();
var text = new String.fromCharCodes(data);
print(data);
}
see Download an image from Firebase to Flutter
or
final uploadTask = imageStore.putFile(imageFile);
final url = (await uploadTask.future).downloadUrl;
In the later case you'd need to store the downloadUrl somewhere and then use NetworkImage or similar to get it rendered.
Here's an example of a stateful widget that loads an image from Firebase Storage object and builds an Image object:
class _MyHomePageState extends State<MyHomePage> {
final FirebaseStorage storage = FirebaseStorage(
app: Firestore.instance.app,
storageBucket: 'gs://my-project.appspot.com');
Uint8List imageBytes;
String errorMsg;
_MyHomePageState() {
storage.ref().child('selfies/me2.jpg').getData(10000000).then((data) =>
setState(() {
imageBytes = data;
})
).catchError((e) =>
setState(() {
errorMsg = e.error;
})
);
}
#override
Widget build(BuildContext context) {
var img = imageBytes != null ? Image.memory(
imageBytes,
fit: BoxFit.cover,
) : Text(errorMsg != null ? errorMsg : "Loading...");
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView(
children: <Widget>[
img,
],
));
}
}
Note that FirebaseApp initialization is handled by the Firestore class, so no further initialization code is necessary.
The way I did it to respect the Storage rules and keep the image in cache is downloading the image as a File and store in the device. Next time I want to display the image I just check if the file already exists or not.
This is my widget:
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class FirebaseImage extends StatefulWidget {
final String storagePath;
FirebaseImage({
required this.storagePath,
}) : super(key: Key(storagePath));
#override
State<FirebaseImage> createState() => _FirebaseImageState();
}
class _FirebaseImageState extends State<FirebaseImage> {
File? _file;
#override
void initState() {
init();
super.initState();
}
Future<void> init() async {
final imageFile = await getImageFile();
if (mounted) {
setState(() {
_file = imageFile;
});
}
}
Future<File?> getImageFile() async {
final storagePath = widget.storagePath;
final tempDir = await getTemporaryDirectory();
final fileName = widget.storagePath.split('/').last;
final file = File('${tempDir.path}/$fileName');
// If the file do not exists try to download
if (!file.existsSync()) {
try {
file.create(recursive: true);
await FirebaseStorage.instance.ref(storagePath).writeToFile(file);
} catch (e) {
// If there is an error delete the created file
await file.delete(recursive: true);
return null;
}
}
return file;
}
#override
Widget build(BuildContext context) {
if (_file == null) {
return const Icon(Icons.error);
}
return Image.file(
_file!,
width: 100,
height: 100,
);
}
}
Note: The code can be improved to show a loading widget, error widget, etc.
Assume I have declared my image in my pubspec.yaml like this:
assets:
- assets/kitten.jpg
And my Flutter code is this:
void main() {
runApp(
new Center(
child: new Image.asset('assets/kitten.jpg'),
),
);
}
Now that I have a new Image.asset(), how do I determine the width and height of that image? For example, I just want to print out the image's width and height.
(It looks like dart:ui's Image class has width and height, but not sure how to go from widget's Image to dart:ui's Image.)
Thanks!
The other answers seem overly complicated if you just want the width and height of an image in an async function. You can get the image resolution using flutter lib directly like this:
import 'dart:io';
File image = new File('image.png'); // Or any other way to get a File instance.
var decodedImage = await decodeImageFromList(image.readAsBytesSync());
print(decodedImage.width);
print(decodedImage.height);
UPDATED SOLUTION:
With the new version of flutter old solution is obsolete. Now the addListener needs an ImageStreamListener.
Widget build(BuildContext context) {
Image image = new Image.network('https://i.stack.imgur.com/lkd0a.png');
Completer<ui.Image> completer = new Completer<ui.Image>();
image.image
.resolve(new ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(info.image));
})
...
...
ORIGINAL VERSION:
If you already have an Image widget, you can read the ImageStream out of it by calling resolve on its ImageProvider.
import 'dart:ui' as ui;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(new MaterialApp(
home: new MyHomePage(),
));
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
Image image = new Image.network('https://i.stack.imgur.com/lkd0a.png');
Completer<ui.Image> completer = new Completer<ui.Image>();
image.image
.resolve(new ImageConfiguration())
.addListener((ImageInfo info, bool _) => completer.complete(info.image));
return new Scaffold(
appBar: new AppBar(
title: new Text("Image Dimensions Example"),
),
body: new ListView(
children: [
new FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData) {
return new Text(
'${snapshot.data.width}x${snapshot.data.height}',
style: Theme.of(context).textTheme.display3,
);
} else {
return new Text('Loading...');
}
},
),
image,
],
),
);
}
}
Create a method, like:
Future<Size> _calculateImageDimension() {
Completer<Size> completer = Completer();
Image image = Image.network("https://i.stack.imgur.com/lkd0a.png");
image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
completer.complete(size);
},
),
);
return completer.future;
}
And use it like:
_calculateImageDimension().then((size) => print("size = ${size}")); // 487.0,696.0
You can resolve the ImageProvider to get an ImageStream, then use addListener to be notified when the image is ready.
import 'dart:ui' as ui;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(new MaterialApp(
home: new MyHomePage(),
));
}
class MyHomePage extends StatelessWidget {
Future<ui.Image> _getImage() {
Completer<ui.Image> completer = new Completer<ui.Image>();
new NetworkImage('https://i.stack.imgur.com/lkd0a.png')
.resolve(new ImageConfiguration())
.addListener((ImageInfo info, bool _) => completer.complete(info.image));
return completer.future;
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Image Dimensions Example"),
),
body: new Center(
child: new FutureBuilder<ui.Image>(
future: _getImage(),
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData) {
ui.Image image = snapshot.data;
return new Text(
'${image.width}x${image.height}',
style: Theme.of(context).textTheme.display4);
} else {
return new Text('Loading...');
}
},
),
),
);
}
}
With new version of flutter old solution not working example:
image.image
.resolve(new ImageConfiguration())
.addListener((ImageInfo info, bool _) => completer.complete(info.image));
Below the Working version:
_image.image
.resolve(new ImageConfiguration())
.addListener(new ImageStreamListener((ImageInfo image, bool _) {
completer.complete(image.image);
}));
If you don't want to use FutureBuilder or then, you can also use await like this:
Don't forget to import the Image. But as there are two Image classes, import it like this and use with ui.Image
import 'dart:ui' as ui
Then you can fetch the dimensions of the image as follows.
final Image image = Image(image: AssetImage('assets/images/someimage.png'));
Completer<ui.Image> completer = new Completer<ui.Image>();
image.image
.resolve(new ImageConfiguration())
.addListener(new ImageStreamListener((ImageInfo image, bool _) {
completer.complete(image.image);
}));
ui.Image info = await completer.future;
int width = info.width;
int height = info.height;
A way for who only want decode image bounds:
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
final descriptor = await ui.ImageDescriptor.encoded(buffer);
final imageWidth = descriptor.width;
final imageHeight = descriptor.height;
print("imageWidth: $imageWidth, imageHeight: $imageHeight");
descriptor.dipose();
buffer.dipose();
A simple way how to check image dimensions that is loaded from assets.
var img = await rootBundle.load("Your image path");
var decodedImage = await decodeImageFromList(img.buffer.asUint8List());
int imgWidth = decodedImage.width;
int imgHeight = decodedImage.height;
Here's a handy helper function, based on other solutions
helper function
Future<ImageInfo> getImageInfo(Image img) async {
final c = new Completer<ImageInfo>();
img.image
.resolve(new ImageConfiguration())
.addListener(new ImageStreamListener((ImageInfo i, bool _) {
c.complete(i);
}));
return c.future;
}
usage
Image image = Image.network("https://example.com/pic.png");
ImageInfo info = await getImageInfo(image);
Easy way to get asset images size.
Future<Size> _loadAssetImageSize(String asset) async{
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await
ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec.getNextFrame();
return Size(fi.image.width.toDouble(), fi.image.height.toDouble());
}
Does any of the solution works for Flutter web? I am unable to find any solution, if i try to use image_size_getter package its throws "Error: Unsupported operation: _Namespace".
All the answers on this page currently only get the raw image size: that is the pixel height and width of the original image. Not the size of the image on the Flutter app. I needed that to resize bounding boxes as the page was resized. I need the current image size, and also know when it changes so I can resize the bounding box.
My solution involves:
GlobalKey and addPostFrameCallback to get the image size
Some logic to render when the image size changes
At the top of my build method:
final _imageKey = GlobalKey();
Size imageSize = Size.zero;
#override
Widget build(BuildContext context) {
MediaQuery.of(context); // Trigger rebuild when window is resized. This updates the bounding box sizes.
final image = Image.asset(exampleImagePath, key: _imageKey);
// Yes, we call this every time the widget rebuilds, so we update our understanding of the image size.
WidgetsBinding.instance.addPostFrameCallback(_updateImageSize);
My _updateImageSize
void _updateImageSize(Duration _) {
final size = _imageKey.currentContext?.size;
if (size == null) return;
if (imageSize != size) {
imageSize = size;
// When the window is resized using keyboard shortcuts (e.g. Rectangle.app),
// The widget won't rebuild AFTER this callback. Therefore, the new
// image size is not used to update the bounding box drawing.
// So we call setState
setState(() {});
}
}