Charles Jr
Charles Jr

Reputation: 9139

How to get Flutter Firebase Storage in a separate method?

I've successfully saved an image to my Firebase Storage reference. Now I need to download it. The examples I've seen are uploading and downloading in the same method, using the same StorageUploadTask with this line of code...

final Uri downloadUrl = (await uploadTask.future).downloadUrl;

My question is how can I get the downloadUrl from a separate method that doesn't require an uploadTask.future since I'm only uploading an image when a FirebaseUser updates their profile image?

Upvotes: 0

Views: 2332

Answers (3)

jeroen-meijer
jeroen-meijer

Reputation: 2974

VizGhar provided a nice solution. I've cleaned up the class, added some features and documentation.

It's available on this gist as well.

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

enum ImageDownloadState { Idle, GettingURL, Downloading, Done, Error }

class FirebaseStorageImage extends StatefulWidget {
  /// The reference of the image that has to be loaded.
  final StorageReference reference;
  /// The widget that will be displayed when loading if no [placeholderImage] is set.
  final Widget fallbackWidget;
  /// The widget that will be displayed if an error occurs.
  final Widget errorWidget;
  /// The image that will be displayed when loading if no [fallbackWidget] is set.
  final ImageProvider placeholderImage;

  FirebaseStorageImage(
      {Key key,
      @required this.reference,
      @required this.errorWidget,
      this.fallbackWidget,
      this.placeholderImage}) {
    assert(
        (this.fallbackWidget == null && this.placeholderImage != null) ||
            (this.fallbackWidget != null && this.placeholderImage == null),
        "Either [fallbackWidget] or [placeholderImage] must not be null.");
  }

  @override
  _FirebaseStorageImageState createState() => _FirebaseStorageImageState(
      reference, fallbackWidget, errorWidget, placeholderImage);
}

class _FirebaseStorageImageState extends State<FirebaseStorageImage>
    with SingleTickerProviderStateMixin {
  _FirebaseStorageImageState(StorageReference reference, this.fallbackWidget,
      this.errorWidget, this.placeholderImage) {
    var url = reference.getDownloadURL();
    this._imageDownloadState = ImageDownloadState.GettingURL;
    url.then(this._setImageData).catchError((err) {
      this._setError();
    });
  }

  /// The widget that will be displayed when loading if no [placeholderImage] is set.
  final Widget fallbackWidget;
  /// The widget that will be displayed if an error occurs.
  final Widget errorWidget;
  /// The image that will be displayed when loading if no [fallbackWidget] is set.
  final ImageProvider placeholderImage;

  /// The image that will be/has been downloaded from the [reference].
  Image _networkImage;
  /// The state of the [_networkImage].
  ImageDownloadState _imageDownloadState = ImageDownloadState.Idle;

  /// Sets the [_networkImage] to the image downloaded from [url].
  void _setImageData(dynamic url) {
    this._networkImage = Image.network(url);
    this
        ._networkImage
        .image
        .resolve(ImageConfiguration())
        .addListener((_, __) {
      if (mounted)
        setState(() => this._imageDownloadState = ImageDownloadState.Done);
    });
    if (this._imageDownloadState != ImageDownloadState.Done)
      this._imageDownloadState = ImageDownloadState.Downloading;
  }

  /// Sets the [_imageDownloadState] to [ImageDownloadState.Error] and redraws the UI.
  void _setError() {
    if (mounted)
      setState(() => this._imageDownloadState = ImageDownloadState.Error);
  }

  @override
  Widget build(BuildContext context) {
    switch (this._imageDownloadState) {
      case ImageDownloadState.Idle:
      case ImageDownloadState.GettingURL:
      case ImageDownloadState.Downloading:
        return Image(image: this.placeholderImage) ?? this.fallbackWidget;
      case ImageDownloadState.Error:
        return this.errorWidget;
      case ImageDownloadState.Done:
        return this._networkImage;
        break;
      default:
        return this.errorWidget;
    }
  }
}

Upvotes: 2

VizGhar
VizGhar

Reputation: 3138

StorageReference now has Future<dynamic> getDownloadURL() method. Retype result to String and use it with your NetworkImage widget:

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class FirestoreImage extends StatefulWidget {
  final StorageReference reference;
  final Widget fallback;
  final ImageProvider placeholder;

  FirestoreImage(
      {Key key,
      @required this.reference,
      @required this.fallback,
      @required this.placeholder});

  @override
  FirestoreImageState createState() =>
      FirestoreImageState(reference, fallback, placeholder);
}

class FirestoreImageState extends State<FirestoreImage> {
  final Widget fallback;
  final ImageProvider placeholder;

  String _imageUrl;
  bool _loaded = false;

  _setImageData(dynamic url) {
    setState(() {
      _loaded = true;
      _imageUrl = url;
    });
  }

  _setError() {
    setState(() {
      _loaded = false;
    });
  }

  FirestoreImageState(
      StorageReference reference, this.fallback, this.placeholder) {
    reference.getDownloadURL().then(_setImageData).catchError((err) {
      _setError();
    });
  }

  @override
  Widget build(BuildContext context) => _loaded
      ? FadeInImage(
          image: NetworkImage(_imageUrl),
          placeholder: placeholder,
        )
      : fallback;
}

Old Answer:

I've just started developing in Flutter (Dart) so my answer will definitely not be perfect (maybe even bad) but here is how I did it:

import 'dart:typed_data';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';

class FirestoreImage extends StatefulWidget {

  final StorageReference _reference;

  FirestoreImage(this._reference);

  @override
  FirestoreImageState createState() => FirestoreImageState(_reference);
}

class FirestoreImageState extends State<FirestoreImage> {

  Uint8List _imageData;

  _setImageData(Uint8List data) {
    setState(() {
      _imageData = data;
    });
  }

  FirestoreImageState(StorageReference reference) {
    reference
        .getData(0x3FFFFFFF)
        .then(_setImageData)
        .catchError((err) {});
  }

  @override
  Widget build(BuildContext context) =>
      _imageData == null ? Container() : Image.memory(_imageData);
}

Now you can display FirestoreImage by calling new FirestoreImage(imageStorageReference). Maybe there is better way by extending Image

Upvotes: 2

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277457

Not possible (yet). You need to store that uri yourself inside a database.

But you may and should use getData instead of using a download url within a firebase app.

Upvotes: 1

Related Questions