Cloud Firestore Keeps re-downloading documents - Flutter - firebase

This happens in my main application
and
I replicate it with the given codelabs:
https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html?index=..%2F..index#10
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Baby Names',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Baby Name Votes')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('baby').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot){
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.votes.toString()),
onTap: () => print(record),
),
),
);
}
}
class Record {
final String name;
final int votes;
final DocumentReference reference;
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
assert(map['votes'] != null),
name = map['name'],
votes = map['votes'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:$votes>";
}
Only plugin i use is the cloud_firestore 0.9.5+2.You need to be patient with this testing process please. You will not see the issue right away. Run the app first, set this project up. You can follow the directions in the given codelabs. Once everything is set up on front end and backend(create documents on firstore). Go have a lunch break, dinner, play video games or hang out with friends. Come back after 1 hour. Run the app, you will be incurred charges for those reads as new. Do it again, come back 1 hour and it will happen again
How to replicate it in real life:
Start this app by given code from codelabs. Run it, it should incur 4 document reads if you stored 4 documents into the firestore.
Start it up again. No reads are charged. Great it works! but no, it really doesnt.
I wake up next day and open up the app, im charged 4 reads. Okay maybe some magic happened. I restart it right away and no charges incur(great!). Later in 1 hour, i start up the app and I get charged 4 reads to display the very same 4 documents that have not been changed at all.
The problem is, on app start up. It seems to be downloading documents from the query snapshot. No changes have been made to the documents. This stream-builder has been previously run many times.
Offline mode(airplane mode), the cached data is displayed with no issues.
in my main application for example, I have a photoUrl and on fresh App start, you can see it being loaded from the firestore(meaning downloaded as a fresh document thus incurring a READ charge). I restart my main application, no charges are made and photo does not refresh(great!). 1 hour later i start up the app and charges are made for every document I retrieve(none of changed).
Is this how cloud firestore is supposed to behave?
From what I have read, its not supposed to behave like this :(

You should not do actual work in build() except building the widget tree
Instead of code like this in build
stream: Firestore.instance.collection('baby').snapshots(),
you should use
Stream<Snapshot> babyStream;
#override
void initState() {
super.initState();
babyStream = Firestore.instance.collection('baby').snapshots();
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: babyStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
The FutureBuilder docs don't mention it that explicitly but its the same
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.

Related

Using CachedNetworkImage inside of Futurebuilder Still shows progress indicator after image is cached

I would like to retrieve the image from the cache after looking it up the first time but since my URL is getting stored in a future it seems that I'm awaiting the future every time which in turn is causing the ProgressIndecator to show even when the image is cached. Which kinda defeats the purpose of caching.
Flutter: CachedNetworkImage not caching image -
What TesteurManiak stated in this post seems to make sense to whats happening in my situation
Here is my code
class ImageLayerWidget extends StatelessWidget {
ImageLayerWidget({Key key, this.layer}) : super(key: key);
final AsyncMemoizer<models.Image> _memorizer = AsyncMemoizer();
final models.ImageLayer layer;
Future<models.Image> _fetchData() async {
return this._memorizer.runOnce(() async {
return await StorageFile<models.Image>(path: layer.path).getDatatoRam();
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: FutureBuilder(
future: _fetchData(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
return Image(
image: CachedNetworkImageProvider(snap.data.path),
);
}
return LinearProgressIndicator();
},
),
),
);
}
}
I am using an AsyncMemoizer to cache my image model class which holds the URL retrieved from ref.getDownloadURL();. This didn't really seem to help. I want it to only show the progress indicator on the first fetch then cache the image so the user doesn't see the indicator again.
I've tried so many things. Can anyone help?
Update:
As GrahamD suggested I also tried this
class ImageLayerWidget extends StatefulWidget {
ImageLayerWidget({Key key, this.layer}) : super(key: key);
final models.ImageLayer layer;
#override
_ImageLayerWidget createState() => _ImageLayerWidget();
}
class _ImageLayerWidget extends State<ImageLayerWidget> {
Future<models.Image> _future;
Future<models.Image> _fetchData() async {
return await StorageFile<models.Image>(path: widget.layer.path)
.getDatatoRam();
}
#override
void initState() {
_future = _fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
return Image(
image: CachedNetworkImageProvider(snap.data.path),
);
}
return LinearProgressIndicator();
},
),
),
);
}
}
Unfortunately, it does not solve the problem. The LinearProgressIndicator always shows when returning to the same image. I also tried putting _future inside the stateful part of the widget but also didn't help. It's as if the snapshot is resolving the future every time. I even propagated this style of state up the widget tree. Still no fix. I'll keep trying things but I think it's the way I'm using ChachedNetworkImage with futures.

Struggling to load images from Firebase Firestore into ListItem in Flutter app

I am having a bit of issue loading images from my Firebase Firestore database into my Flutter app. I am sort of stuck on this since I don't know what to do currently. I have seen a way to use NetworkImage to download an image on a list tile but it only does one image. I am trying to figure out a way to use a separate image for each item in the Firestore database.
I have hooked up the image URL from the Firebase Storage to Firestore under the imageURL key which is a String. But I am still struggling to figure out how to do this. I have also downloaded all the dependencies such as Firebase and Firestore. If anyone does offer to help or shares a tip on how they have worked with something similar to this, it'd be greatly appreciated! :)
class HomeTab extends StatefulWidget {
#override
_HomeTabState createState() => _HomeTabState();
}
class _HomeTabState extends State<HomeTab> {
#override
Widget build(BuildContext context) {
// This will reference our posts collection in Firebase Firestore NoSQL database.
CollectionReference posts = FirebaseFirestore.instance.collection('posts');
return Center(
child: Container(
width: 250,
child: StreamBuilder<QuerySnapshot>(
// Renders every post from the Firebase Firestore Database.
stream: posts.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return Card(
margin: EdgeInsets.all(20.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
// Renders the title on every single listview.
title: new Text(document.data()['title']),
// leading: new NetworkImage(''),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ReaderPage(),
),
);
},
),
],
// Added Column
// In case if things don't work, go back to this.
// child: new ListTile(
// title: new Text(document.data()['text']),
// ),
),
);
}).toList(),
);
},
),
),
);
}
}
Here is my Firebase Storage
My Firestore database
The NetworkImage class is not a flutter widget class. It is responsible for downloading the image from the URL provided. It inherits from the ImageProvider class.
From the flutter docs:
Fetches the given URL from the network, associating it with the given scale.
Use Image.network for a shorthand of an Image widget backed by NetworkImage.
You should be using the Image.network named constructor of the Image widget to create a flutter widget used to display image with help of NetworkImage.
You should replace the NetworkImage in your widget tree with:
new Image.network("https://i.stack.imgur.com/W98kA.png")

How to play list(array) of videos from firebase :In my case I tried with lot of changes but I get this error

Here there are my two Dart file from that I retrieve my Video from firestore but in that, I made video array and try to retrieve it but it gives me an error , if I am doing it with string instead of array It looks fine but in my project, I am requiring array so please help me to finger out.
My cloud firestore looks like is: collection(Course)=>autoid=>doucuments(video array)=>([0]url [1]url2)
Here is my 2 Dart file please correct my code what is wrong in this case when I am running this I got error Like :
Error: list is not a subtype of type 'string'.
code:main.dart and chewie_list_item.dart
main.dart:It is for retrieve data
import 'chewie_list_item.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';`enter code here`
import 'package:video_player/video_player.dart';
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.dumpErrorToConsole(details);
if (kReleaseMode) exit(1);
};
runApp(MaterialApp(
home: CourseApp(),
));
}
class CourseApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
body: StreamBuilder(
stream: Firestore.instance.collection("courses").snapshots(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot courses = snapshot.data.documents[index];
return (ChewieListItem(
videoPlayerController: VideoPlayerController.network(
courses['video'] ?? 'default'),
));
},
);
},
));
}
}
[code 2: chewie_list_item.dart]: It for chewie video player.
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class ChewieListItem extends StatefulWidget {
// This will contain the URL/asset path which we want to play
final VideoPlayerController videoPlayerController;
final bool looping;
ChewieListItem({
#required this.videoPlayerController,
this.looping,
Key key,
}) : super(key: key);
#override
_ChewieListItemState createState() => _ChewieListItemState();
}
class _ChewieListItemState extends State<ChewieListItem> {
ChewieController _chewieController;
#override
void initState() {
super.initState();
// Wrapper on top of the videoPlayerController
_chewieController = ChewieController(
videoPlayerController: widget.videoPlayerController,
aspectRatio: 16 / 9,
// Prepare the video to be played and display the first frame
autoInitialize: true,
looping: widget.looping,
// Errors can occur for example when trying to play a video
// from a non-existent URL
errorBuilder: (context, errorMessage) {
return Center(
child: Text(
errorMessage,
style: TextStyle(color: Colors.white),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Chewie(
controller: _chewieController,
),
);
}
#override
void dispose() {
super.dispose();
// IMPORTANT to dispose of all the used resources
widget.videoPlayerController.dispose();
_chewieController.dispose();
}
}
So mainly I have not any idea hoe to retrieve this video array from my firebase please give the correct code.

Flutter How to pass in documents.length from Firestore in another method?

I have a method which takes an int value, which is supposed to be the value of the length of an array field in a document in a collection in Cloud Firestore.
I'm very new to both Flutter and especially Firestore, and I figure it has something to do with how futures, queries and streams works, but I can't seem to understand it correctly.
This is what I'm trying to achieve:
class UserBookmarksSection extends StatefulWidget {
#override
_UserBookmarksSectionState createState() => _UserBookmarksSectionState();
}
class _UserBookmarksSectionState extends State<UserBookmarksSection> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
HeadlineItem(title: "Your bookmarks"),
StreamBuilder(
stream: Firestore.instance.collection("brites").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loading...");
return Column(
children: populateBriteList(snapshot, x, true), //Here I want to pass in the length of the array in firestore into x
);
}
),
],
);
}
}
Basically I've tried to create a method which returns a Future<int> and tried to pass that, but that didn't work because a Future<int> isn't the same type as int. That I understand, but the solution is not clear to me.
I'm not sure the relationship between brites and bookmarks- that is, which you expect to change more often. I'm going to assume that the bookmarks isn't changing once the user opens the bookmarks section.
If that is the case you can retrieve the bookmarks length value in initState. You will override the state class's initState() method, and then call an async method which retrieves the value from the database. (init state can't be async itself). Once the value is retrieved, you can then call setState() to update the widget with the value of bookmarks set as an int.
Might look something like this:
class UserBookmarksSection extends StatefulWidget {
#override
_UserBookmarksSectionState createState() => _UserBookmarksSectionState();
}
class _UserBookmarksSectionState extends State<UserBookmarksSection> {
int bookmarksLength;
#override
void initState(){
super.initState();
getBookmarksLength();
}
Future<void> getBookmarksLength() async {
bookmarksLength = await getArrayLength(id);
setState((){});
}
#override
Widget build(BuildContext context) {
if(bookmarksLength == null) return CircularProgressIndicator();
else return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
HeadlineItem(title: "Your bookmarks"),
StreamBuilder(
stream: Firestore.instance.collection("brites").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loading...");
return Column(
children: populateBriteList(snapshot, bookmarksLength, true),
);
}
),
],
);
}
}
Firestore firestore = Firestore.instance;
Future<int> getArrayLength(String documentId) async {
DocumentSnapshot snapshot = await firestore.collection('collection').document(documentId).get();
return snapshot.data['array'].length;
}
A more robust solution might be to have a separate class that handles both listening to the stream and retrieving the length of the array, and then combining all of the information you're going to display into some abstract data type which you put into a separate stream you specify that is specifically for the UI to listen to.

StreamController with Firestore

I want to user a StreamController to control a StreamBuilder that gets data from a collection in Firestore. This will enable me to use a RefereshIndicator so that when I pull down on the list, it refreshes/ fetches more data if there is any.
I used most of the information in this article. My current code is below
class _Lists extends State<List> {
StreamController _controller;
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
#override
void initState() {
_controller = new StreamController();
loadPosts();
super.initState();
}
Future fetchPost() async {
return await .
Firestore.instance.collection(_locationState).snapshots();
}
Future<Null> _handleRefresh() async {
count++;
print(count);
fetchPost().then((res) async {
_controller.add(res);
showSnack();
return null;
});
}
showSnack() {
return scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text('New content loaded'),
),
);
}
loadPosts() async {
fetchPost().then((res) async {
print(res.document);
_controller.add(res);
return res;
});
}
#override
Widget build(BuildContext context) {
final topBar = AppBar(Title("List"));
bottom: TabBar(
indicatorColor: Colors.blueAccent,
indicatorWeight: 3.0,
//indicatorSize: 2.0,
indicatorPadding:
const EdgeInsets.only(bottom: 10.0, left: 47.0, right:
47.0),
tabs: [
Tab(
child: Image(
image: AssetImage("MyImage1"),
width: 65.0,
height: 65.0,
),
),
Tab(
child: Image(
image: AssetImage("Image2"),
width: 90.0,
height: 90.0,
),
),
],
),
return DefaultTabController(
length: 2,
child: Scaffold(
key: scaffoldKey,
appBar: topBar,
body: StreamBuilder(
stream: _controller.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.connectionState == ConnectionState.active) {
List aList = new List();
aList.clear();
for (DocumentSnapshot _doc in snapshot.data.documents) {
Model _add = new Model.from(_doc);
aList.add(_add);
}
return TabBarView(
children: <Widget>[
RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
itemCount: aList.length,
itemBuilder: (context, index) {
return Card(aList[index]);
},
),
),
Icon(Icons.directions_transit),
],
);
} else {
return Container(
child: Center(child: CircularProgressIndicator()));
}
})));
}
}
}
The Problem I have with this is I keep getting the error
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════
flutter: The following NoSuchMethodError was thrown building
StreamBuilder<dynamic>(dirty, state:
flutter: _StreamBuilderBaseState<dynamic,
AsyncSnapshot<dynamic>>#53c04):
flutter: Class '_BroadcastStream<QuerySnapshot>' has no instance getter 'documents'.
flutter: Receiver: Instance of '_BroadcastStream<QuerySnapshot>'
flutter: Tried calling: documents
Any ideas on how to go about using the StreamController with data from Firestore ?
Keeping a close eye on return types in your IDE will likely help avoid a lot of confusing issues like this. Unfortunately, that blog does not indicate any types for the API call, StreamController, or 'res' in the then statement. Having those types declared will help show what you are working with (at least it does for me in Android Studio). For example in my StreamBuilder with a stream from Firestore, I use AsyncSnapshot<QuerySnapshot> snapshot instead of just AsyncSnapshot. This allows the tools in Android Studio to tell me that snapshot.data.documents is the map from the QuerySnapshot class. If I don't add the extra type, I can't see that.
Here is an example of listening to the stream from the Firestore Dart package.
//Performing a query:
Firestore.instance
.collection('talks')
.where("topic", isEqualTo: "flutter")
.snapshots()
.listen((data: QuerySnapshot) =>
// do stuff here
);
Since you're using the async/await style (also perfectly fine), you'll have the same result that will be inside of .listen((data) =>. We can follow the documentation/classes to see what types are returned.
Firestore.instance.collection(<whatever>).snapshots()
will return
Stream<QuerySnapshot>,
so we know that
await Firestore.instance.collection(<whatever>).snapshots()
will return
QuerySnapshot.
Digging further into the class, we see it has a property called documents.
/// Gets a list of all the documents included in this snapshot
final List<DocumentSnapshot> documents;
This finally gives us those DocumentSnapshots, which you'll have to pull the data property from.
So in your case, I believe the res type being QuerySnapshot will help show you what data to put in your stream, which can be done multiple ways at this point. List<DocumentSnapshot> seems to look like what you're going for, but you could go farther to List<YourClass> built from the DocumentSnapshot data property. With that, you can say what data type your StreamController will return, making the builder's AsyncSnapshot<your stream type> much more clear to work with.
I'm not sure what development tools you are using, but in case you aren't familiar most will allow you to do something like: press/hold (command or ctrl), hover over the type/class/function/var you want to see, left click, and you should be taken to the source files/declarations (I find this super handy).

Resources