How to retrieve an image from Firestore database - Flutter - flutter

i was wondering how to retrieve an image from Firestore, i was trying and reading and watching tutorials so i have come with this solution:
1- Upload images to Firebase storage.
2- Go to Firestore create a collection and a document with field name and value of image path. ( collection "majorChallenges", doc "challenge-1", field "source", value "image path url")
3- Initiate a Firestore Instance: var _fireStoreInstance = FirebaseFirestore.instance;
4- Access the Database collection using QuerySnapshot: QuerySnapshot qn = await _fireStoreInstance.collection("majorChallenges").get();
5- now after that am not sure how to setState, initState, return and display the image inside Scaffold or Container.
some tutorials are using the image link or name which is not efficient for me what if i have to change the image in the future so the best solution i believe must not use the image name it should access the collection and the doc and retrieve the image path.
i already set up the read and write permissions and i have accessed the database before to retrieve text so no authorisation problems, I would be very thankful if someone could complete the steps for us

I can now answer my own question, first of all it is wrong to specify this operation in steps because there are a lot of approaches to achieve this goal, and the approach i am using is pretty nice and simple using QuerySnapshot. so to retrieve picture we need simply do these steps (for my approach):
1- we need to declare a string variable then create asynchronous method that will fetch the image URL from Firestore and update the variable state using set state method, inside this method we can initiate a Firestore instance and a QuerySnapshot:
String img = "";
fetchMajorChallengeImage() async {
var _fireStoreInstance = FirebaseFirestore.instance;
QuerySnapshot qn =
await _fireStoreInstance.collection("majorChallenges").get();
setState(() {
img = qn.docs[0]["source"];
});
return qn.docs;
}
2- we will initiate the state using initState method and call our function inside:
void initState() {
fetchMajorChallengeImage();
super.initState();
}
3- now the image is retrieved and the path stored to "img" variable we declared, so we need to display the image, here its your choice where and how to display the image but for me this is how i displayed it:
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.network(img, height: 150, fit: BoxFit.fill),
],
),
);
}
note that it must be displayed inside Image.network() widget because the variable holds a URL value fetched from the firestore collection > doc > value and now the image is retrieved and displayed successfully.

Related

Caching images (thumbnails) in Listview

I made a simple application where in listview I display a list of items by ListView.builder. Each item is a widget where by FutureBuilder I build a CircleAvatar with a picture taken in initState () via Api. I'm using the AppWrite API. The method returns the photo in the form of Uint8list. It's working fine.
#override
void initState() {
super.initState();
myFuture = AppWriteService.getImagePreview()
}
FutureBuilder(
future: myFuture ,
builder: (context, snapshot) {
print("build photo for:" +widget.doc.place!);
// print(snapshot.data);
Uint8List? bytes = snapshot.data as Uint8List?;
// print(bytes);
return snapshot.hasData && snapshot.data != null
? CircleAvatar(
radius: 40,
backgroundImage: MemoryImage(bytes!),
)
: CircularProgressIndicator();
},
)
However, I wanted the whole list to not refresh after removing one item, I mean, it can build, but I would not like the fetch method to download photos for previously displayed items to be performed again. Now, when you delete an item, the whole list refreshes and the photos are downloaded again by fetch method for all elements.
I have already made the another solution, but I have doubts if it is good.
After downloading the items, before I build the list, I download a photo for each item and save it as bytes in my object. so each item already "holds" a photo and there is no need to use FutureBuilder.
So first I get all elements by first request fetchAll() and then in loop for every element I run getImagePreview() and then I build a ListView
I would be grateful for your tips which solution is better.
If you really want to use cached_network_image, you can. You'll just have to manually build the URL yourself:
CachedNetworkImage(
imageUrl: '${client.endPoint}/storage/buckets/${bucketId}/files/${fileId}/view',
httpHeaders: {
'X-Appwrite-Project': widget.client.config['project']!,
},
)
If your file isn't public, you'll also need to generate a JWT token via account.getJWT() and then pass in the headers:
CachedNetworkImage(
imageUrl: '${client.endPoint}/storage/buckets/${bucketId}/files/${fileId}/view',
httpHeaders: {
'X-Appwrite-Project': widget.client.config['project']!,
'X-Appwrite-JWT': jwt,
},
)

How do I access variable from a function and use it in my widget - Flutter storage variable

I have this project, so I already stored the data I need in flutter storage but I'm unable to pick that data and use it in my text widget
checkname() async {
String? name = await storage.read(key: "name");
setState(() {
splitname = name;
});
print(splitname);
}
checkname();
I created this function to read the value in the storage but but it kept on printing the value about a 1000 times a minutes and I feel something might go wrong, is there a way I could go around it.
Printing the value about a 1000 times indicates that you are calling the function inside build method.
Here's on overall of what you should do:
create a statuful widget
call checkname inside initState function of the widget

How to combine features of FutureProvider/FutureBuilder (waiting for async data) and ChangeNotifierProvider (change data and provide on request)

What I wanted to achieve, was to retrieve a document from firestore, and provide it down the widget tree, and be able to make changes on the data of the document itself.
show something like "No data available/selected" when there's nothing to display,
show loading-screen/widget if the document is loading,
show data in the UI when document is ready
be able to make changes to the data of the document itself (in firestore) AND reflect those changes in the UI, WITHOUT reading the document again from firestore
be able to reload the document/load another document from firestore, show a loading screen while waiting for document, then show data again
The whole purpose of this, is to avoid too many firestore read operations. I update the document data on the server (firestore), then make the same updates in the frontend (an ugly alternative would be to retrieve the document from firestore each time I make a change).
If the changes are too complex though, or if it is an operation that is rarely executed, it might just be a better idea to read the whole document again.
"Answer your own question – share your knowledge, Q&A-style" -> see my solution to this problem in my answer below
The above described functionalities are NOT POSSIBLE with:
FutureProvider: you can use this to retrieve a document from firestore and show a loading-screen while waiting, but can't make changes to it afterwards, that will also show in the UI
FutureBuilder: same as above, even worse, this can't provide data down the widget-tree
It was however possible with ChangeNotifierProvider (& ChangeNotifier), and this is how I did it:
enum DocumentDataStatus { noData, loadingData, dataAvailable }
...
class QuestionModel extends ChangeNotifier {
DocumentSnapshot _questionDoc; //the document object, as retrieved from firestore
dynamic _data; //the data of the document, think of it as Map<String, dynamic>
DocumentDataStatus _documentDataStatus;
DocumentDataStatus get status => _documentDataStatus;
//constructors
QuestionModel.example1NoInitialData() {
_documentDataStatus = DocumentDataStatus.noData; //no question selected at first
}
QuestionModel.example2WithInitialData() {
_documentDataStatus = DocumentDataStatus.loadingData; //waiting for default document or something...
//can't use async/await syntax in a dart constructor
FirebaseFirestore.instance.collection('quiz').doc('defaultQuestion').get().then((doc) {
_questionDoc = doc;
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
});
}
//all kinds of getters for specific data of the firestore document
dynamic get questionText => _data['question'];
dynamic get answerText => _data['answer'];
// dynamic get ...
///if operation too complex to update in frontend (or if lazy dev), just reload question-document
Future<void> loadQuestionFromFirestore(String questionID) async {
_data = null;
_documentDataStatus = DocumentDataStatus.loadingData;
notifyListeners(); //now UI will show loading-screen while waiting for document
_questionDoc = await FirebaseFirestore.instance.collection('quiz').doc(questionID).get();
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
}
///instantly update data in the UI
void updateQuestionTextInUI(String newText) {
_data['question'] = newText; //to show new data in UI (this line does nothing on the backend)
notifyListeners(); //UI will instantly update with new data
}
}
...
Widget build(BuildContext context) {
QuestionModel questionModel = context.watch<QuestionModel>();
return [
Text('No question to show'),
Loading(),
Text(questionModel.questionText),
][questionModel.status.index]; //display ONE widget of the list depending on the value of the enum
}
The code in my example is simplified for explanation. I have implemented it in a larger scale, and everything works just as expected.

Flutter: save a generated PDF from preview page with a custom function

I'm using the pdf package (link to pub.dev page) to create documents inside my app.
How can I save the document from the preview page?
I know about the built-in print and share functions, but I need something else.
I need a function that does the following:
save the document to the device
upload it to Firebase Storage
delete the local file
I know how to do all of these steps, but I'm unable to access the Uint8List bytes of the pdf document that is being previewed... I've tried to add a PdfPreviewAction, but I still can't access the bytes of the document from it
all you have to do is call the save method in the Document() object
import "package:pdf/widgets.dart" as w;
final pdf = w.Document();
Future<Uint8List> getPDFData() async {
return await pdf.save();
}
you can do all the operations using the getPDFData() method
for example:
return PdfPreview(
build: (format) => getPdfData(),
actions: [
PdfPreviewAction(
icon: Icon(Icons.print),
onPressed: (context, fn, format) {
//if you call fn(format), you'll get Future<UInt8List>
}),
],
);

Flutter Function returns empty list using Firebase

I'm using Flutter web and firebase realtime database. In my databse I have some questions, structured as maps, and I'm trying to display them in my app.
For each question, I create a widget, I add it inside a list and then I append the list to a Column() widget.
The Column() widget is the following (view_question.dart file):
Column(
children: Question().view(),
),
As you can see, I use the method view() from a class I created named Question().
The view() method returns a list of widgets.
Before I show you the .view() method, I need to mention that outside of the Question class I have initialized the following:
List<Widget> widgets = List();
Below you can see the .view() method from the Question class (Question.dart file):
List<Widget> view() {
Database db = database();
DatabaseReference ref = db.ref('questions/');
ref.orderByKey().once("value").then((e) {
DataSnapshot datasnapshot = e.snapshot;
datasnapshot.val().forEach((key, values) {
widgets.add(QuestionWidget(values));
print(widgets);
});
});
print(widgets);
return widgets;
I'm getting my questions' data from my database, then for each question I'm creating a QuestionWidget() widget, where I pass inside it the respective data and I add it to the widgets list. Afterwards, I print() the widgets list, so I can monitor the whole process.
As you also can see, I've added a print(widgets) just before the return statement.
This is the prints' output in my console
From that, what I understand is that the function returns an empty list and then I retrieve my data from the database and add them to the list.
So I'm asking, how can I add my data to the list, and when this process has finished, return the list and append it to the Column() widget? Should I add a delay to the return statement? What am I missing?
Thank you for your time
Data is loaded from Firebase asynchronously. By the time your return widgets runs, the widgets.add(QuestionWidget(values)) hasn't been called yet. If you check the debug output of your app, the print statements you have should show that.
For this reason you should set the widgets in the state once they're loaded. That will then trigger a repaint of the UI, which can pick them up from the state. Alternatively you can use a FutureBuilder to handle this process.
See:
How can I put retrieved data from firebase to a widget in Flutter?
Slow data loading from firebase causing "RangeError (index)"
// return type is like this
Future<List<Widget>> view() {
Database db = database();
DatabaseReference ref = db.ref('questions/');
// return this
return ref.orderByKey().once("value").then((e) {
DataSnapshot datasnapshot = e.snapshot;
datasnapshot.val().forEach((key, values) {
widgets.add(QuestionWidget(values));
});
// return widgets after added all values
return widgets;
});