How to get Flutter Firebase Storage in a separate method? - url

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?

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

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

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.

Related

Fetch Data from a Wix Database through http request

I created a database with Wix that has several different types of content like Strings, images, addresses and more.I want to use the information from the WIX database for an app(made with Flutter and Dart); simply portraying the information in a ListView but it seems the data doesnt reach the app.
I created the necessary function on Wix to make the database accessible for third parties and tested it with Postman. When i make the request with this Url (https://daudadmin.editorx.io/acteeventpage/_functions/regions) it works fine and Postman returns the items with all the information as JSON.
Now when i use the Url in my app code; it just returns a blank page. That is the code i use currently:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final url = 'https://daudadmin.editorx.io/acteeventpage/_functions/regions';
var _postsJson = [];
void fetchData() async {
try {
final response = await get(Uri.parse(url));
final jsonData = jsonDecode(response.body) as List;
setState(() {
_postsJson = jsonData;
});
} catch (err) {
//handle error here with error message
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: _postsJson.length,
itemBuilder: (context, i) {
final post = _postsJson[i];
return Text("Title: ${post["title"]}");
}),
),
);
}
}
This is the line causing your error
final jsonData = jsonDecode(response.body) as List;
The data being returned from the endpoint is a Map and not a List.
{
"items": [
{},
...
],
}
To access the list, try accessing the items property of the map by changing the declaration to
final jsonData = jsonDecode(response.body)["items"] as List;

How to make a Sink<Locale> to format the result of a Stream<String>?

In google IO 18, the Flutter presenters have showed a feature but have not showed how to implement this.
The video (at exact time) is: https://youtu.be/RS36gBEp8OI?t=1776
How to implement such thing? How can I properly make the Stream to be correctly formatted based on a Sink?
(sorry but I am not too familiar with Rx)
Use the combineLatest function from the rxdart package. It takes the latest values of input streams, so any time either the locale or cart items change it will calculate and format the total cost.
import 'dart:async'; // Sink, Stream
import 'dart:ui'; // Locale
import 'package:rxdart/rxdart.dart'; // Observable, *Subject
class Bloc {
var _locale = BehaviorSubject<Locale>(seedValue: Locale('en', 'US'));
var _items = BehaviorSubject<List<CartItem>>(seedValue: []);
Stream<String> _totalCost;
Sink<Locale> get locale => _locale.sink;
Stream<List<CartItem>> get items => _items.stream;
Stream<String> get totalCost => _totalCost;
Bloc() {
_totalCost = Observable.combineLatest2<Locale, List<CartItem>, String>(
_locale, _items, (locale, items) {
// TODO calculate total price of items and format based on locale
return 'USD 10.00';
}).asBroadcastStream();
}
void dispose() {
_locale.close();
_items.close();
}
}
Disclaimer: I didn't try to run this code so there might be errors but the basic idea should be solid.
The best candidate for doing this cross-platform is NumberFormat from the intl package. However you still have to pass it a locale string ("en_US") and ISO 4217 currency code ("USD").
After a little digging I couldn't find this information in any Dart package. The NumberFormat class has a private map for looking up a currency symbol ("$") from a currency code, but keys of the map, the currency codes, are inaccessible. So I decided to make a package that makes locale strings and currency codes available.
currency_bloc.dart
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'package:intl/intl.dart';
import 'package:locales/locales.dart';
import 'package:locales/currency_codes.dart';
class LocalCurrency {
const LocalCurrency(this.locale, this.code);
final Locale locale;
final CurrencyCode code;
#override toString() => '$code ($locale)';
#override operator==(o) => o is LocalCurrency && o.locale == locale && o.code == code;
#override hashCode => toString().hashCode;
}
/// Emits currency strings according to a locale.
class CurrencyBloc {
// Inputs.
final _valueController = StreamController<double>();
final _currencyController = StreamController<LocalCurrency>();
// Outputs.
final _currency = BehaviorSubject<String>();
/// The last formatted currency value emitted from the output stream.
String lastCurrency;
// For synchronously receiving the latest inputs.
double _value;
NumberFormat _formatter;
CurrencyBloc({LocalCurrency initialCurrency, double initialValue}) {
_valueController.stream
.distinct()
.listen((value) => _updateCurrency(value: value));
_currencyController.stream
.distinct()
.listen((currency) => _updateCurrency(currency: currency));
// Initialize inputs.
locale.add(initialCurrency ??
LocalCurrency(Locale.en_US, CurrencyCode.usd));
value.add(initialValue ?? 0.0);
}
void dispose() {
_valueController.close();
_currencyController.close();
_currency.close();
}
_updateCurrency({double value, LocalCurrency currency}) {
if (currency != null) {
_formatter = NumberFormat.simpleCurrency(
locale: '${currency.locale}',
name: '${currency.code}',
decimalDigits: 2);
}
if (value != null) {
_value = value;
}
if (_value != null && _formatter != null) {
lastCurrency = _formatter.format(_value);
_currency.add(lastCurrency);
}
}
/// Change the current [Locale] and/or [CurrencyCode].
Sink<LocalCurrency> get locale => _currencyController.sink;
/// Change the the value to be formatted.
Sink<double> get value => _valueController.sink;
/// Formatted currency.
Stream<String> get currency => _currency.stream;
}
currency_provider.dart (conventional)
class CurrencyProvider extends InheritedWidget {
CurrencyProvider({Key key, #required this.bloc, #required Widget child})
: super(key: key, child: child);
final CurrencyBloc bloc;
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static CurrencyBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(CurrencyProvider) as CurrencyProvider)
.bloc;
}
Example usage
...
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CurrencyBloc bloc;
#override
Widget build(BuildContext context) =>
CurrencyProvider(bloc: bloc, child: CurrencyExample());
#override
void initState() {
super.initState();
bloc = CurrencyBloc();
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
void didUpdateWidget(StatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
bloc.dispose();
bloc = CurrencyBloc();
}
}
class CurrencyExample extends StatelessWidget {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final bloc = CurrencyProvider.of(context);
return ListView(
children: <Widget>[
TextField(controller: controller),
StreamBuilder(
stream: bloc.currency,
initialData: bloc.lastCurrency,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return new Text('${snapshot.error}');
}
return Center(child: CircularProgressIndicator());
}),
FlatButton(
child: Text('Format Currency'),
onPressed: () => bloc.value.add(double.tryParse(controller.text)),
)
],
);
}
}

Update the display on a regular basis in flutter

In flutter, we want to update the display on a regular basis.
We can update the display when the value changes.
However, the set value does not change in this case.
Therefore, we do not know what to do as a trigger to update the display.
We changed from Stateless Widget to Stateful Widget. And we started the timer in initState() and canceled the timer in dispose(). We regularly call notifyListeners () on ScopedModel and are updating the drawing.
It works as expected. Unfortunately, it is not a beautiful way. It is hard to understand, it is annoying. Do not you know a better way? How should we do?
We will clarify our implementation example below. It is a minimal code.
xxxxx_widget.dart
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:pregnancy/widgets/yyyyy_widget.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<XxxxxModel>(
model: XxxxxModel(),
child: YyyyyWidget(),
);
}
}
yyyyy_widget.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:scoped_model/scoped_model.dart';
class YyyyyWidget extends StatefulWidget {
#override
YyyyyWidgetState createState() {
return new YyyyyWidgetState();
}
}
class YyyyyWidgetState extends State<YyyyyWidget> {
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(
const Duration(milliseconds: 500),
(Timer t) {
XxxxxModel.of(context).notify();
},
);
super.initState();
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<XxxxxModel>(
builder: (context, child, model) {
var diff = model.datetime.difference(DateTime.now());
var hours = diff.inHours.remainder(Duration.hoursPerDay);
var minutes = diff.inMinutes.remainder(Duration.minutesPerHour);
var seconds = diff.inSeconds.remainder(Duration.secondsPerMinute);
return Text('${hours} hours ${minutes} minutes ${seconds} seconds');
},
);
}
}
xxxxx_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxModel extends Model {
static XxxxxModel of(BuildContext context) =>
ScopedModel.of<XxxxxModel>(context);
DateTime _datetime = DateTime.now().add(Duration(days: 1));
get datetime => _datetime;
set datetime(DateTime value) {
_datetime = value;
notifyListeners();
}
void notify() {
notifyListeners();
}
}
You could use a StreamBuilder that listens to an interval stream. It automatically manages the subscription.
Your model can provide the stream as a property.

Remote Config Device Language Changes in Flutter

I am encountering a problem, where localization works fine, but the applications needs to be restarted in order for the changes to propagate.
Orientation changes
I know about OrientationBuilder, which will call its builder whenever it detects a change in the device's orientation, which in e.g. Android would be considered as a configuration change, just like device language changes.
Language changes
Is there something like LanguageBuilder? I could not find anything on my own and not on flutter.io nor on pub. I have read this tutorial and know about Locale, but I do not see a Stream for Locale.
My problem is that changing the language in iOS and Android native is really smooth. It gets handled automatically and perfectly integrates with services like Firebase Remote Config.
I really wonder if there is some method that will allow me to refresh my localization.
Question
So I am asking how I can refresh my Remote Config when the device language changes.
No there's no Builder for Locale.
Instead, there's an InheritedWidget which you can subscribe to using Localizations.of.
Since it is an InheritedWidget, all widgets that call Localizations.of will automatically refresh on locale change.
EDIT :
A example on how to live reload text using Flutter Locale system :
Let's assume you have the following class that holds translations :
class MyData {
String title;
MyData({this.title});
}
You'd then have a LocalizationsDelegate that contains such data. A dumb implementation would be the following :
class MyLocale extends LocalizationsDelegate<MyData> {
MyData data;
MyLocale(this.data);
#override
bool isSupported(Locale locale) {
return true;
}
#override
Future<MyData> load(Locale locale) async {
return data;
}
#override
bool shouldReload(MyLocale old) {
return old.data != data;
}
}
To use it simply pass it to MaterialApp.localizationsDelegates (be sure to add flutter_localizations to your pubspec.yaml) :
LocalizationsDelegate myLocale = MyLocale(MyData(title: "Foo"));
...
MaterialApp(
localizationsDelegates: [
myLocale,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
You can then freely live reload your translations by replacing myLocale with a new MyLocale instance.
Here's a full example of a click counter app. But where the current count is instead stored inside Locale (because why not ?)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class MyCount {
String count;
MyCount({this.count});
}
class MyCountLocale extends LocalizationsDelegate<MyCount> {
MyCount data;
MyCountLocale(this.data);
#override
bool isSupported(Locale locale) {
return true;
}
#override
Future<MyCount> load(Locale locale) async {
return data;
}
#override
bool shouldReload(MyCountLocale old) {
return old.data != data;
}
}
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ValueNotifier<int> count = ValueNotifier<int>(0);
LocalizationsDelegate myLocale;
#override
void initState() {
count.addListener(() {
setState(() {
myLocale = MyCountLocale(MyCount(count: count.value.toString()));
});
});
myLocale = MyCountLocale(MyCount(count: count.value.toString()));
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
myLocale,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
home: MyHomePage(count: count),
);
}
}
class MyHomePage extends StatefulWidget {
final ValueNotifier<int> count;
MyHomePage({this.count});
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: AppBar(),
body: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => widget.count.value++,
child: Icon(Icons.plus_one),
),
ListTile(
title: Text(Localizations.of<MyCount>(context, MyCount).count),
),
],
),
);
}
}
Device language changes can be detected using a WidgetsBindingObserver.
It is the simplest to use it with a StatefulWidget in your State (with WidgetsBindingObserver):
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
#override
void didChangeLocales(List<Locale> locale) {
// The device language was changed when this is called.
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
...
}
This means that you can now reload your RemoteConfig in didChangeLocales:
#override
void didChangeLocales(List<Locale> locale) {
_updateRemoteConfig();
}
Future<void> _updateRemoteConfig() async {
final remoteConfig = await RemoteConfig.instance;
await remoteConfig.activateFetched(); // This will apply the new locale.
}

How to create subclass from abstract class ui,Image in Dart / Flutter?

I am trying to get an local asset image loaded into an ui.Image object. But the ui.Image is an abstract class. I basically have this :
import 'dart:ui' as ui;
class MyImage implements ui.Image{
int height;
int width;
MyImage(String file){
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
String toString() {
// TODO: implement toString
return super.toString();
}
}
Using your code with the function definition Below. The error is at
' ui.Image image = await loadImage(img); ' await is underlined red and tool tip is 'Undefined name 'await' in function body not marked with async.'
class TrialApp extends StatefulWidget {
#override
_TrialAppState createState() => new _TrialAppState();
}
class _TrialAppState extends State<TrialApp> {
NodeWithSize rootNode;
#override
void initState() {
// TODO: implement initState
super.initState();
rootNode = new NodeWithSize(new Size(400.0, 400.0));
}
#override
Widget build(BuildContext context) {
// define a function that converts the I.Image object into ui.Image
//object
Future<ui.Image> loadImage(I.Image img) async {
final Completer<ui.Image> imageCompleter = new Completer();
ui.decodeImageFromList(img.getBytes(), (ui.Image img) {
imageCompleter.complete(img);
});
return imageCompleter.future;
}
// Obtain a `I.Image` object from the image file
I.Image img = I.decodeImage(new io.File('images/tile.png').readAsBytesSync());
// Obtain the `ui.Image` from the `I.Image` object
ui.Image image = await loadImage(img);
Sprite myButton = new Sprite.fromImage(image);
rootNode.addChild(myButton);
return new SpriteWidget(rootNode);
}
}
First obtain the image from the assetbundle using rootBundle. The convert the obtained ByteData to List<int>. Now you can obtain a ui.Image using the decodeImageFromList method.
Example:
// import statements
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:spritewidget/spritewidget.dart';
class TrialApp extends StatefulWidget {
#override
_TrialAppState createState() => new _TrialAppState();
}
class _TrialAppState extends State<TrialApp> {
NodeWithSize rootNode = new NodeWithSize(new Size(400.0, 400.0));
#override
void initState() {
super.initState();
init();
}
Future<Null> init() async {
rootNode = new NodeWithSize(new Size(400.0, 400.0));
// Read file from assetbundle
final ByteData data = await rootBundle.load('images/tile.png');
// Convert the obtained ByteData into ui.Image
final ui.Image image = await loadImage(new Uint8List.view(data.buffer)); // Uint8List converts the ByteData into List<int>
Sprite myButton = new Sprite.fromImage(image);
rootNode.addChild(myButton);
// notify to redraw with child
setState(() {
rootNode = rootNode;
});
}
// define a function that converts the List<int> into ui.Image object
Future<ui.Image> loadImage(List<int> img) async {
final Completer<ui.Image> imageCompleter = new Completer();
ui.decodeImageFromList(img, (ui.Image img) {
imageCompleter.complete(img);
});
return imageCompleter.future;
}
#override
Widget build(BuildContext context) {
return new Container(
color: Colors.white,
child: new SpriteWidget(rootNode),
);
}
}
Hope that helps!
If you are using SpriteWidget you can load images using the ImageMap class to save some code. From the SpriteWidget docuementation:
ImageMap images = new ImageMap(rootBundle);
// Load a single image
ui.Image image = await images.loadImage('assets/my_image.png');
// Load multiple images
await images.load(<String>[
'assets/image_0.png',
'assets/image_1.png',
'assets/image_2.png',
]);
// Access a loaded image from the ImageMap
image = images['assets/image_0.png'];

Resources