46

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?

spongyboss
  • 8,016
  • 15
  • 48
  • 65

4 Answers4

41

Firebase Console

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)
Manoj Perumarath
  • 9,337
  • 8
  • 56
  • 77
  • 4
    This is not a good solution, because it creates a long lived url and is therefore a security vulnerability. From the docs: "Asynchronously retrieves a long lived download URL with a revokable token. This can be used to share the file with others, but can be revoked by a developer in the Firebase Console if desired." So it's basically like creating a public link in Google Drive. –  Mar 26 '20 at 11:00
  • 2
    @janosch I agree with you, I would like to see a solution without creating a shareable download link – ykonda Jul 12 '20 at 18:17
  • 1
    So for a list of 100 thumbnails you have to run 100 x await functions just to show them? Doesn't seem right. – Wesley Barnes May 08 '21 at 12:00
  • 1
    @WesleyBarnes why cant you write async? – Manoj Perumarath Jun 01 '21 at 04:43
23

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.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 8
    This is not a good solution, because it creates a long lived url and is therefore a security vulnerability. From the docs: "Asynchronously retrieves a long lived download URL with a revokable token. This can be used to share the file with others, but can be revoked by a developer in the Firebase Console if desired." So it's basically like creating a public link in Google Drive. –  Mar 26 '20 at 11:03
  • 1
    So you downvoted because you want something different? Perhaps you could provide a reference to the Google API that you want to use from Flutter? In case it does not exist, it is not my responsibility. – Günter Zöchbauer Mar 26 '20 at 14:01
  • 4
    I downvoted it, because you didn't point out that public urls to the images are being created by calling getDownloadURL() . Many people who use this solution are probably not aware of this, so it is a very important detail. getDownloadURL is apparently not intended to be used for this purpose, but for the purpose of intentionally sharing a link to a file with other users. –  Mar 26 '20 at 21:36
  • 5
    Thanks for your response. If one can download the image the image can be shared or the link can be shared. I don't really see the difference. If you don't want the image to get out, you probably should not allow to access it at the server at all. – Günter Zöchbauer Mar 27 '20 at 08:35
  • @GünterZöchbauer Is there any way to get the url without the Future/await approach? In most of the cases, I need to provide the String url without the Future wrapper – Mike.R Dec 19 '20 at 08:47
  • @Mike.R no, because it's fetched from the Firebase server. You can use `await` to get the value out of the future. I know that's sometimes annoying, but that's how it is. – Günter Zöchbauer Dec 19 '20 at 17:10
  • You can also use FutureBuilder to satisfy the needs of an async call. [Here](https://flutterigniter.com/build-widget-with-async-method-call/) is a great article explaining how to do it. – Curt Eckhart Jan 01 '21 at 16:36
17

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.

Alexander Ryzhov
  • 2,705
  • 3
  • 19
  • 19
  • 5
    this should be the accepted answer, very good. Thank you! getDownloadURL creates long lived url and google also charges you extra money for each urls created – cs guy Sep 21 '20 at 18:02
  • @Alexander, if we fetch the image this way, can we then store it in cachednetworkimage? if so, how? – mcfred Mar 04 '21 at 13:51
  • @csguy can you share me the link where firebase says that it charges extra money? – Manoj Perumarath Jun 01 '21 at 04:15
0

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.

Yayo Arellano
  • 3,575
  • 23
  • 28