FirebaseStorage with flutter - firebase

Im new to firebase and i wasnt able to figure out what kinda parameter i shud pass to the putfile in the
following code , and is there any tutorials or proper documentation for firebase with flutter?
Implemented code
import "dart:io";
import "package:flutter/material.dart";
import "package:firebase_storage/firebase_storage.dart";
import "package:flutter/widgets.dart";
class uploadApp extends StatefulWidget{
#override
State<StatefulWidget> createState() =>_uploadApp();
}
class _uploadApp extends State<uploadApp>{
var count=1;
final File file = File("images/ts2.jpg");
final FirebaseStorage _storage = FirebaseStorage(storageBucket:"gs://console.firebase.google.com/project/baby-name-4ef54/storage/baby-name-4ef54.appspot.com");
StorageUploadTask _uploadTask;
void firebasebackend(){
String filePath = "images/$count.jpg";
setState(() {
_uploadTask = _storage.ref().child(filePath).putFile(file);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"upload",
home:Material(
child:Container(child:Center(
child:
Column(
children: <Widget>[
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(300.0)),
image:DecorationImage(
image: AssetImage("images/ts2.jpg"),
fit: BoxFit.cover
)
),
),
Center(
child: RaisedButton(
child:Text("Upload"),
onPressed: (){
firebasebackend();
setState(){
count=count+1;
};
},
),
)
],
),
)
)
)
);
}
}
'package:firebase_storage/src/storage_reference.dart': Failed assertion: line 62 pos 12: 'file.existsSync()': is not true.
What should I do to just upload an image directly from a folder to the firebase storage?

According to the code of firebase_storage plugin:
/// Asynchronously uploads a file to the currently specified
/// [StorageReference], with an optional [metadata].
StorageUploadTask putFile(File file, [StorageMetadata metadata]) {
assert(file.existsSync());
final _StorageFileUploadTask task =
_StorageFileUploadTask._(file, _firebaseStorage, this, metadata);
task._start();
return task;
}
Takes a file as a parameter and a variable of type StorageMetadata as an optional parameter.
https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_storage/lib/src/storage_reference.dart#L61
https://api.dart.dev/stable/2.7.0/dart-io/File-class.html

From the error message you get, it looks like there is no file at File("images/ts2.jpg"); on your phone, which means the SDK can't find any data to upload to Cloud Storage.
In general I'd be suspicious of the relative path you're using here. The example that comes with the Firebase Storage SDK creates a file at an absolute path in the temp dir with final File file = await File('${systemTempDir.path}/foo$uuid.txt').create();, and I'd recommend using absolute paths on the file system too.
You will need to find the absolute path of the image you want to upload, either by browsing the file system of your device, or by taking the absolute path from the gallery or camera. For example, if you use a file picker, you can get the file with:
File file = await FilePicker.getFile();

This might seem very trivial, but Peter Haddad pointed out in the comments of his post
That's probably because there is no image with that path...
which is exactly the problem why I had this error.
My mistake was, that I assumed an error is thrown when the File object is created and a non-existent path was used to create this object.
However, this is not the case. Therefore, make sure to verify the File object refers to the correct path.

Related

Flutter update widgets using firebase remote config

I am working on a flutter app and I want to update the widgets of a page without pushing new updates on the play store. I have researched a bit on this topic and found that I can use firebase remote config. My idea is to use firebase remote config to fetch the widgets and show them on the app so that I don't need to publish frequent updates for small changes.
For Example, the below code is used to show a circular loading bar:
import 'package:flutter/material.dart';
class LoadingWidget extends StatelessWidget {
const LoadingWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: const CircularProgressIndicator(
color: Colors.black,
),
);
}
}
But now even for the smallest change, let it be changes in color, alignment, or size I need to push a new update to the play store and users need to install the new update in order to reflect the changes.
I want to store the flutter widget in firebase remote config (JSON format) then in the app, it will fetch the JSON data from remote config and show the new changes.
Once your RemoteConfig, you need do add key-value pair to match your needs:
key : value
color : black
Thenm fetch the value:
myRemoteColor = remoteConfig.getString('color')
Now, your myRemoteColor has a string with value black.
Since you already know that Remote Config only stores primitive values, the problem becomes how to encode and decode a Flutter widget into a string.
I doubt that is going to be simple though, as it'd mean that your application would need to compile the JSON/string into a binary. You'd essentially be implementing something akin to Flutter's Hot Reload for your remote clients, something that'd be pretty cool but is not a built-in feature.
More feasible is to control specific widgets that you already have in your code, as for example Rubens answered. In a recent sample app I used Remote Config to enable/disable a new feature (a process known as feature flagging) with:
class _MyHomePageState extends State<MyHomePage> {
final FirebaseRemoteConfig remoteConfig;
_MyHomePageState(this.remoteConfig);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: remoteConfig.getBool("chat_enabled") ? 3 : 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
const Tab(icon: Icon(Icons.games_outlined)),
const Tab(icon: Icon(Icons.swap_vert_circle_outlined)),
if (remoteConfig.getBool("chat_enabled")) const Tab(icon: Icon(Icons.chat_bubble_outlined)),
],
),
title: Text(widget.title),
),
body: TabBarView(
children: [
const MyGame(key: Key('game')),
const Leaderboard(key: Key('leaderboard')),
if (remoteConfig.getBool("chat_enabled")) const Messageboard(key: Key('messageboard')),
],
),
),
),
);
}

How can I mix a statenotifier, with streamproviders that fetch 2 collections from Firebase to get constant update on a chat list

This is my first question here and I hope I’m not making it too complex.
So, I’m a junior programmer and I’ve start learning flutter, firebase and riverpod a couple of months ago and I’m stuck in a specific project.
For part of the app, what I need to do is something very similar to WhatsApp:
A screen with all the user chats,
This screen (or part of it) should update every time a chat gets a new message, showing the last message snippet and turning into bold if not read,
The shown chats should change between unarchived and archived, depending on a button in the UI.
Couldn’t have picked up an easier starting project, right? ;) (now every time I look at WhatsApp I say wow!)
Regarding firebase/firestore I’m fetching 2 different collections for this:
the sub-collection ‘chats’ within the ‘user_chats’ collection: where I get all the chat Ids plus it’s status (if this chat is archived and if the last message was read), for the current user,
the main ‘chats’ collection, where I have the main info of each chat.
At this moment I’m doing this:
In the chats_screen (UI) I’m fetching my chat provider: userChatsProvider
(note: this is just an example of the UI implementation. I have another implementation for it, but as long I get the chatName and lastMsgContent updated for each chat, perfect.)
class ChatsScreen extends ConsumerWidget {
const ChatsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
(…)
return Scaffold(
appBar: (…) // not relevant for this question
body: Center(
child: Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
return ref.watch(userChatsProvider).when(
loading: () => const CircularProgressIndicator(),
error: (err, st) => Center(child: Text(err.toString())),
data: (chatData) {
return Column(
children: [
// button to get archived chats
child: TextButton(
child: (…)
onPressed: () {}),
),
Expanded(
child: ListView.builder(
itemCount: chatData.length,
itemBuilder: (ctx, index) => Row(
children: [
Text (chatData[index].chatName!),
Text (chatData[index].lastMsgContent!),
]
),
),
),
]
);
}
);
}
)
)
);
}
}
In the chats_provider (provider) I’m fetching the 2 repository providers and joining them into a specific model I’ve created for this screen:
final userChatsProvider = FutureProvider.autoDispose<List<ChatScreenModel>>((ref) async {
const String userId = ‘XXX’; // this will be substituted by a dynamic variable with the current user Id
try {
final List<UserChat> userChats =
await ref.watch(userChatsRepositoryProvider).get(userId: userId);
final List<String> chatIdList = userChats.map<String>((e) => e.id).toList();
final List<Chat> chats =
await ref.watch(chatsRepositoryProvider).get(chatIds: chatIdList);
// ref.maintainState = true;
return toChatScreenModel(chats, userChats);
} on Exception catch (e) {
throw const CustomException();
}
});
In the repositories I’m fetching the firestorm collections I mentioned above. Here’s an example:
final userChatsRepositoryProvider =
Provider<UserChatsRepository>((ref) => UserChatsRepository(ref.read));
class UserChatsRepository {
final Reader _read;
const UserChatsRepository(this._read);
Future<List<UserChat>> get({required String userId}) async {
try {
final snap = await _read(firebaseFirestoreProvider)
.collection('users_chats/$userId/chats')
.get();
// Maybe this step is not necessary, but I’ve decided to transform the data into temporary models, before sending to provider
List<UserChat> userChats =
snap.docs.map((doc) => UserChat.fromJson(doc.data(), doc.id)).toList();
return userChats;
} on FirebaseException catch (e) {
throw CustomException(message: e.message);
}
}
}
And by the way, this is the model I’m sending to the UI:
class ChatScreenModel {
final String? id;
final String? chatName;
final String? chatImage;
final String? lastMsgContent;
final String? lastMsgDate;
final ChatType? chatType;
final bool? archived;
final bool? msgRead;
ChatScreenModel({
this.id,
this.chatName,
this.chatImage,
this.lastMsgContent,
this.lastMsgDate,
this.chatType,
this.archived,
this.msgRead,
});
Problems with this implementation:
I’m getting the user chats in the screen, but they don’t update since I’m not using a stream. So I get a snapshot, but it will only update if I leave and enter that chats_screen again. And it would be important to have it updating with a stream.
I’m showing all the chats, and not a filtered list with only the unarchived chats.
Also, related with the previous point, I still don’t have the archived button working, to only show the archived chats.
I’ve lost many, many hours trying to understand how I could implement a stream provider and a state notifier provider in this workflow.
Tried many combinations, but without success.
Can anyone help me understand how to do this?
Priority: transform these providers into stream providers (so it updates the UI constantly).
Nice to have: also include the archived/unarchived dynamic to filter the chats that appear and be able to switch between them.
Thanks a lot. :)

How can I get a image from Firebase storage with the file name I stored in the database?

I stored a image in firebase storage and image path in Firestore, how can I get that specific image using the path saved in Firestore?
getImagePath() async {
var document = await firestoreInstance
.collection("products")
.document(widget.productId)
.get();
String path = document["filePath"];
setState(() {
filePath = path;
});
}
I got the filename from Firestore like this, how can I get the image by using this file name?
I could give you an idea.
Since you have commented that your filePath looks like this image_picker4011381564582645700.jpg, then you must be storing only the file names. Then, you can something like this with the help of firebase_storage
String getStorageUrl(String fileName) async {
String url = "";
StorageReference storageReference = FirebaseStorage.instance.ref().child("folder/$fileName");
url = await storageReference.getDownloadURL();
return url;
}
Call the function, where you want it. Or in your case,
getImagePath() async {
var document = await firestoreInstance
.collection("products")
.document(widget.productId)
.get();
String path = await getStorageUrl(document["filePath"]);
setState(() {
filePath = path;
});
}
You could then use that URL in an Image.network() to display it, or if you have other plans with it, you can go for flutter_downloader.
Make sure, you are changing the function with the correct folder name. Hope that suits your case.
Tip: It is better to store the full URL to the database, instead of just saving the file name.
You can also use FirebaseImage widget
CircleAvatar(
backgroundColor: Colors.black,
backgroundImage:
FirebaseImage('gs://yourapp=67791.appspot.com/images/cPW7zkbCpYQT2II7JqEOcUK5rOm2.png'),
)
NetworkImage() and Image.network() directly fetch image from given Firebase image storage
//To set image on background
CircleAvatar(
backgroundColor: Colors.transparent,foregroundColor:Colors.blueAccent,
radius:16,
backgroundImage:NetworkImage(filepath),
child: FlatButton(
onPressed: (){
print("button pressed");},
),
),
//within child
Container(
child:Image.network(filepath),
);

How to get list of document ids?

I am trying to retrieve a list of document Ids from firestore.
My firestore functions are stored as static methods in a FirebaseAPI class I created.
This is the function that should return the list of documentIds:
static List<String> avaiableDocuments() {
List<String> ids = [];
final Future<QuerySnapshot> snapshot = Firestore.instance.collection('admin').getDocuments();
snapshot.then((value) {
value.documents.forEach((element) {
ids.add(element.documentID);
});
});
return ids;
}
When I restart the app, the list of document ids do not appear.
However, they do appear when the is then hot reloaded.
My understanding is that when the app restarts, the list of strings is still empty.
This is because the page renders before the future returns.
Then when hot reload calls the build method again, the list is already populated so it displays it.
I tried adding "await" before the getDocuments call.
This turns the Future to just QuerySnapshot. Fair Enough.
Now "async" must be added to the function.
Then the function is now requiring a future return instead of a list.
Can't I just get a list from this function?
I tried to go with it and ended up with this:
static Future<List<String>> avaiableDocuments() async{
List<String> ids = [];
final QuerySnapshot snapshot = await Firestore.instance.collection('admin').getDocuments();
snapshot.documents.forEach((element) {
ids.add(element.documentID);
});
return ids;
}
How is it that I'm allowed to return a list from a function that says it's returning a future?
When I tried to consume the future in the screen class, same thing happens. List only appears on hot reload after restart.
Note: I feel it's a simple issue, but I pretty much a noob when it comes to asynchronous voodoo.
Class that should display the list of document Ids
import 'package:flutter/material.dart';
import 'package:hisab/database/firestore_api.dart';
class PreviousCarts extends StatefulWidget {
const PreviousCarts({Key key}) : super(key: key);
#override
_PreviousCartsState createState() => _PreviousCartsState();
}
class _PreviousCartsState extends State<PreviousCarts> {
List<String> documentIds = [];
#override
void initState() {
super.initState();
Future<List<String>> f = FirestoreAPI.avaiableDocuments();
f.then((value) {
value.forEach((element) {
documentIds.add(element);
});
});
}
#override
Widget build(BuildContext context) {
print(documentIds);
return Material(
child: Padding(
padding: const EdgeInsets.only(left: 8.0, top: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: documentIds.map((e) {
return Card(child: ListTile(title: Text(e)));
}).toList(),
),
),
);
}
}
Can't I just get a list from this function?
No. Since the database query is asynchronous, everything that involves it must also be async. It's a bad idea to try to turn an async method in to an immediate return method, as that would cause your app to freeze if a query is slow or the app is offline.
I suggest reading this for some context. https://medium.com/firebase-developers/why-are-the-firebase-apis-asynchronous-e037a6654a93
How is it that I'm allowed to return a list from a function that says it's returning a future?
Because the dart compiler is making sure that the caller receives a Future. It's handling the hard stuff behind the scenes with respect to async/await to make it easier for you to write code, so that you don't have to be explicit about futures whenever they appear.
This style of asynchronous programming is very common, and comes up in other languages such as JavaScript. If you want to be effective with dart, and async programming in general, then I suggest taking time to become familiar with these techniques. It takes practice.

Streambuilder help flutter firebase storage

Hello I’m trying to make a sequence of images with flutter but when I update the photo in firebase Storage the photo in my APP remains the same.
Maybe I can do this with streambuilder but I don’t know how to use it and I can’t learn how to use it.
Does anyone know how I can do that thanks so much for the help.
class _MyHomePageState extends State<MyHomePage> {
final FirebaseStorage storage = FirebaseStorage(
app: Firestore.instance.app,
storageBucket: 'gs:...com/');
Uint8List imageBytes;
String errorMsg;
_MyHomePageState() {
storage.ref().child('images/opencv.png').getData(100000000000000).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(
),
body: new ListView(
children: <Widget>[
img,
],
));
}
}
Firebase Storage does not automatically notify clients when there's a change to an image they loaded. When you call getData() it gets the data once, and that' the end of it.
If you want to auto-reload the image when it changes in Storage, I recommend using another service to handle that notification.
For example, you could use Firebase Realtime Database or Cloud Firestore to store the path of the image and when it was last modified, and then update that at the same time when you update the image data in Storage. Then your client code would use a realtime listener to the database, and from that load/reload the image.

Resources