Flutter Load Image from Firebase Storage - dart

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.

Related

flutter riverpod question: watch a provider from another provider and trigger action on the first provider

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));

How to rebuild widget in Flutter when a change occurs

Edit: I've edited the code below to feature the method that fetches the data along with the widgets that build the train estimates (replacing any API information along the way with "API_URL" and "API_STOP_ID"). I hope this even better helps us figure out the problem! I really appreciate any information anyone can give -- I've been working very hard on this project! Thank you all again!
Original post:
I have a ListView of ListTiles that each have a trailing widget which builds train arrival estimates in a new Text widget. These trailing widgets are updated every five seconds (proven by print statements). As a filler for when the app is fetching data from the train's API, it displays a "no data" Text widget which is built by _buildEstimatesNull().
However, the problem is that "no data" is still being shown even when the app has finished fetching data and _isLoading = false (proven by print statements). Still, even if that was solved, the train estimates would become quickly outdated, as the trailing widgets are updating every five seconds on their own but this would not be reflected in the actual app as the widgets were built on page load. Thus, I need a way to rebuild those trailing widgets whenever they fetch new information.
Is there a way to have Flutter automatically rebuild the ListTile's trailing widget every five seconds as well (or whenever _buildEstimatesS1 is updated / the internals of the trailing widget is updated)?
class ShuttleApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new ShuttleState();
}
}
class ShuttleState extends State<ShuttleApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new HomeState();
}
}
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
arrivalsList.clear();
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
_buildEstimateNull();
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
class ArrivalEstimates {
final String routeId;
final String arrivalAt;
final String stopId;
ArrivalEstimates(this.routeId, this.arrivalAt, this.stopId);
}
Thank you so much in advance for any help you can give! I really super appreciate it! :)
There are a few ways you could tackle this. It is slightly difficult however to tell what's going on without seeing a bit more of your code - specifically how you're getting the data and what you're doing with it. But I think I can give you a sufficient answer anyways.
The simple way of doing this is to either:
Have a StatefulWidget which keeps track of the build estimates for all of the items in the list. It should request data from your API, get the results, and then call setState(() => this.listData = data);. The call to setState is what tells the widget that it needs to rebuild.
Have a StatefulWidget for each item in the list. They would all each perform an API request every 5 seconds, get the results, and then each would call setState(() => this.itemData = data);. This means multiple calls to the API etc.
The advantage of #1 is that you can batch API calls, whereas the advantage to #2 is that your build would change less overall (although the way flutter works, this would be pretty minimal)... so I would probably go with #1 if possible.
However, there is a better way of doing this!
The better way of doing this is to have some sort of API Manager (or whatever you want to call it) which handles the communication with your API. It probably would live higher up in your widget tree and would be started/stopped with whatever logic you want. Depending on how far up the widget tree is, you could either pass it into each child or more likely hold it in an InheritedWidget which could then be used to retrieve it from each list element or from the overall list.
The API manager would provide various streams - either with a bunch of named fields/methods or with a getStream(id) sort of structure depending on your API.
Then, within your various list elements, you would use StreamBuilder widgets to build each of the elements based on the data - by using a StreamBuilder you get a ConnectionState object that lets you know whether the stream has received any data yet so you can choose to show an isLoading type widget instead of the one that shows data.
By using this more advanced method, you get:
Maintainability
If your API changes, you only have to change the API manager
You can write better testing as the API interactions and the UI interactions are separated
Extensibility
If you, later on, use push notifications for updates rather than pinging a server every 5 seconds, that can be incorporated into the API manager so that it can simply update the stream without touching the UI
EDIT: as per OP's comments, they have already implemented more or less the first suggestion. However, there are a few problems with the code. I'll list them below and I've posted the code with a couple of changes.
The arrivalsList should be replaced each time a new build is done rather than simply being changed. This is because dart compares the lists and if it finds the same list, it doesn't necessarily compare all of the elements. Also, while changing it in the middle of a function isn't necessarily going to cause problems, it's generally better to use a local variable and then change the value at the end. Note that the member is actually set within setState.
If serviceActive == false, the return was missed from return _buildEstimateNull();.
Here's the code:
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
var arrivalsList = new List<ArrivalEstimates>(); // *********** #1
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
print("no service id");
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
this.arrivalsList = arrivalsList; // *********** #1
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
return _buildEstimateNull(); // ************ #2
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
print("arrivalsList length: ${arrivalsList.length}");
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
print("Estimate match found: ${arrival.stopId}");
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
Instead of clearing and re-using the arrivalsList, create a new list every time the data is fetched. Otherwise Flutter is unable to detect if the list has changed.
Also, the code would clearer if you called setState whenever you change the list.
_fetchData() async {
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
globals.serviceActive = false;
setState(() {
_isLoading = false;
});
} else {
final newArrivalsList = new List<ArrivalEstimates>();
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
newArrivalsList.add(arrivalEstimate);
});
}
});
setState(() {
arrivalsList = newArrivalsList;
_isLoading = false;
});
}
}
A few side notes:
I'm not sure if you actually want to clear the list before you fetch the data. If the state was updated properly, that would cause a flicker every 5 seconds.
I'm not sure if you simplified the code, but calling the _fetchData method every five seconds may become a problem if the network is slow.
If you are certain that you want a child widget to rebuild every time you call setState() and it is stubbornly refusing, you can give it a UniqueKey(). This will ensure that when setState() triggers a rebuild the child widget keys will not match, the old widget will be popped and disposed of, and, the new widget will replace it in the widget tree.
Note that this is using keys in sort of the opposite way for which they were intended (to reduce rebuilding) but if something beyond your control is hindering necessary rebuilds then this is a simple, built-in way to achieve the desired goal.
Here is a very helpful Medium article on keys from one the Flutter team members, Emily Fortuna:
https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d
I am not sure if this is what your looking for but and im probably late on this but i believe you can use a change notifier efficiently to achieve this. Basically a change notifier is hooked to your backed logic() for instance an api data fetch. A widget is then registered with a change notifier of the same type as the change notifier provider. In event of data change, the widgets registered with the change notifier will be rebuild.
For instance
// extend the change notifier class
class DataClass extends ChangeNotifier {
....
getData(){
Response res = get('https://data/endpoint')
notifyListeners()
}
void onChange() {
notifyListeners();
}
....
}
Every time there is change in data you call the notifyListeners() that will trigger rebuild of consuming widgets.
Register you widget with a changenotifier
class View extends StatefulWidget {
Widget create(BuildContext context) {
return ChangeNotifierProvider<ModelClass>(
builder: (context) => DataClass(auth: auth),
child: Consumer<ModelClass>(
builder: (context, model, _) => View(model: model),
),
);
}
}
You can also user a Consumer for the same. Get more on this from the Documentation

Flutter Future returns null

I've seen this already: https://stackoverflow.com/a/49146503/1757321
Followed the solution, but it is not working in my case.
Some enlightenment would do for me this afternoon
Future<String> loadInterest() async {
print('Going to load interests');
final whenDone = new Completer();
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = await prefs.getString('token');
print('The token ${token}');
await this.api.interests(token).then((res) {
// print('The response: ${res['interests']}'); <-- this prints response alright. Data is coming.
whenDone.complete(res['interests']);
});
return whenDone.future;
}
Then I'm trying to use the above Future in a future builder like so:
new FutureBuilder(
future: loadInterest(),
builder: (BuildContext context, snapshot) {
return snapshot.connectionState == ConnectionState.done
? new Wrap(
children: InterestChips(snapshot.data),
)
: Center(child: CircularProgressIndicator());
},
),
Where InterestChips(...) is this:
InterestChips(items) {
print('Interest Chips ${items}');
List chipList;
for (Object item in items) {
chipList.add(Text('${item}'));
}
return chipList;
}
But, I always get null as the snapshot, which means the loadInterest() for the Future is not returning anything.
If I understand this answer correctly, then I'm doing mine along the lines of that: https://stackoverflow.com/a/49146503/1757321
You don't need to use a Completer for this. Since your method is already async, you should just do this for your first code block:
Future<String> loadInterest() async {
print('Going to load interests');
final whenDone = new Completer();
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = await prefs.getString('token');
print('The token ${token}');
final res = await this.api.interests(token).then((res) {
// print('The response: ${res['interests']}'); <-- this prints response alright. Data is coming.
return res['interests']);
}
You might also want to check for snapshot.hasError to make sure you're not getting any exceptions in there.

Flutter save a network image to local directory

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);

How do I determine the width and height of an image in Flutter?

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(() {});
}
}

Resources