I hava simple app with list of products. Products are stored on Firebase Firestore. I want to download list of products and give user possibility to update some data.
So I prepare list with products:
Widget _buildProductsList() {
return new StreamBuilder(
stream: Firestore.instance
.collection('products')
.snapshots,
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Loading...");
return new ListView(
children: snapshot.data.documents.map((document) {
Product product = new Product(document.data);
_ProductCell cell = new _ProductCell();
cell.product = product;
return cell;
}).toList(),
);
},
);
}
I serialize Document Snapshot to my Product object:
class Product {
static const NAME_KEY = 'name';
static const DESC_KEY = 'desc';
static const PHOTO_URL_KEY = 'photoUrl';
static const AUTHOR_ID_KEY = 'authorId';
static const CREATED_AT_KEY = 'createdAt';
String name;
String description;
String photoUrl;
String authorId;
int createdAt;
Product(Map<String, dynamic> json) {
name = json[NAME_KEY];
description = json[DESC_KEY];
photoUrl = json[PHOTO_URL_KEY];
authorId = json[AUTHOR_ID_KEY];
createdAt = json[createdAt];
}
}
Now, when user make some interact with ProductCell I want to update Product Document in Firestore which is linked with but I don't have an ID so it is impossible to create proper Firestore reference.
How to achieve this?
Since the issue #12471 is resolved, you can get the document Id from the document object.
print(document.documentID)
the syntax for this has since been updated you can now get it from
print(document.id);
https://pub.dev/documentation/firebase/latest/firebase_firestore/DocumentSnapshot/id.html
I have made an issue about this yesterday as I encountered the same bug. You can track it here: https://github.com/flutter/flutter/issues/12471
Currently the FireStore plugin has some more issues.
You can see all issues with Firebase plugins by filtering on the plugin:
https://github.com/flutter/flutter/labels/plugin%3A%20firebase
Related
I have the following problem. I'm trying to receive data from my Firestore collection called tournaments. I'm querying the database from within my DatabaseService class. That looks like the following:
class Collection<T> {
final Firestore _db = Firestore.instance;
final String path;
CollectionReference ref;
Collection({this.path}) {
ref = _db.collection(path);
}
Future<List<Tournament>> getData() async {
var snapshots = await ref.getDocuments();
return snapshots.documents
.map((doc) => Global.models[Tournament](doc.data))
.toList();
}
}
The widget implements a FutureBuilder
Widget build(BuildContext context) {
return FutureBuilder(
future: Global.tournamentRef.getData(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.connectionState == ConnectionState.done) {
List<Tournament> tournaments = snap.data;
...
I want to deserialize the firestore data into a Tournament object. I defined the Tournament class as this:
class Tournament {
String id;
String name;
String mode;
String owner;
int size;
Tournament({this.id, this.name, this.mode, this.owner, this.size});
factory Tournament.fromMap(Map data) {
return Tournament(
id: data["id"] ?? '',
mode: data["mode"] ?? '',
name: data["name"] ?? "group",
owner: data["owner"] ?? "",
size: data["size"] ?? 6);
}
}
The last class that is important is the globals.dart
class Global {
static final Map models = {Tournament: (data) => Tournament.fromMap(data)};
static final Collection<Tournament> tournamentRef =
Collection<Tournament>(path: "tournaments");
}
It simply specifies the collection path. I want the data to be deserialized but I don't have a clue why it isn't returning anything. I've tried querying the database in a simple old-fashioned way like
Future<dynamic> retrieveData() async {
var querySnap = await ref.getDocuments();
var list = querySnap.documents.map((snap) => snap.data);
return list;
}
That worked perfectly fine, however it doesn't help with deserialization at all. I think i missed something at some point and as you may notice I'm still a Flutter/dart beginner and some of these discussed topics are a bit too complicated to me.
I appreciate any help.
Thank you
I'm having a problem with my flutter application that leverages firebase/firestore. The following code is attempting to copy data from a document in one collection and take that data from that document and copy it into another document in a different collection. However, when I run the code with some breakpoints I noticed that whenever the function is called it skips over the Streambuilder line. If you could help me out that would be appreciated. I have put the error in one function.
Code:
copyDocument(String copyId) async {
bool beingCalled;
var createdAt;
String chattingWith, id2, nickname, photoUrl;
DocumentReference collectionRef = Firestore().collection('messages').document(id).collection('pastchats').document(copyId);
return StreamBuilder<DocumentSnapshot> (
stream: Firestore.instance.collection('messages').document(copyId).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
print('waiting...');
return CircularProgressIndicator(
strokeWidth: 2,
);
} else {
beingCalled = snapshot.data['beingCalled'];
chattingWith = snapshot.data['chattingWith'];
createdAt = snapshot.data['createdAt'];
id2 = snapshot.data['id'];
nickname = snapshot.data['nickname'];
photoUrl = snapshot.data['photoUrl'];
Firestore.instance.runTransaction((transaction) async {
await transaction.set(
collectionRef,
{
'beingCalled': beingCalled,
'chattingWith': chattingWith,
'createdAt': createdAt,
"id" : id2,
'nickname': nickname,
'photoUrl': photoUrl,
},
);
}
);
}
},
);
}
I have a Stream building a list from a Firebase collection QuerySnapShot. The query and the stream builder work great, if I don't pass variable data to the query (the 'where' statement). However, what I am trying to do is pass the FirebaseAuth.currentUser as a filter in the where clause of my Stream.
I am sure there is something I am not understanding about making these 2 separate async calls.
Basically I need to get the uid of the currently authenticated user and pass that into the query in my stream.
I am super new to Flutter and am on a rapid fast track to get my chops. Been fully immersed for about a week.
class Booking {
final DateTime startTime;
final DateTime endTime;
final String name;
final String bookingId;
final String truckID;
Booking({ this.bookingId, this.truckID, this.startTime, this.endTime, this.name });
}
// build the booking list from the QuerySnapShot
List<Booking> _bookingListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Booking(
bookingId: doc.documentID ?? '',
name: doc.data['name'] ?? '',
startTime: doc.data['startTime'].toDate() ?? '',
endTime: doc.data['endTime'].toDate() ?? '',
truckID: doc.data['truckID']
);
}).toList();
}
//asynchronously get the uid of the currentuser from FirebaseAuth
Future<String> inputData() async {
final FirebaseUser _aUser = await FirebaseAuth.instance.currentUser();
final String _uid = _aUser.uid.toString();
return _uid;
}
Here is where I am trying to pass the current user into the Stream
//get user specific booking stream
Stream<List<Booking>> get bookings {
final _myUserId = inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
// the widget consuming the list
class _BookingListState extends State<BookingList> {
#override
Widget build(BuildContext context) {
final bookings = Provider.of<List<Booking>>(context) ?? [];
return ListView.builder(
itemCount: bookings.length,
itemBuilder: (context,index){
return BookingTile(booking: bookings[index]);
},
);
}
}
EDIT to include the Stream usage for feedback (after wrapping the Stream in a Future as suggested)
In my home.dart file I listen for the Stream<List<Booking>>> so I can build the list of bookings that are displayed on that page. In this next block I now get an error that I cannot assign the parameter type Stream<List<Booking>> to the argument type Future<Stream<List<Booking>>>. The compiler suggests changing the parameter type or casting the argument to <Stream<list<Booking>>
The full compile message
lib/screens/home/home.dart:38:32: Error: The argument type 'Future<Stream<List<Booking>>>' can't be assigned to the parameter type 'Stream<List<Booking>>'.
- 'Future' is from 'dart:async'.
- 'Stream' is from 'dart:async'.
- 'List' is from 'dart:core'.
- 'Booking' is from 'package:models/booking.dart' ('lib/models/booking.dart').
Try changing the type of the parameter, or casting the argument to 'Stream<List<Booking>>'.
value: DatabaseService().bookings,
home.dart
return StreamProvider<List<Booking>>.value(
value: DatabaseService().bookings,
child: Scaffold( ... ) //Scaffold
); //StreamProvider.value
I have tried changing either the parameter value or the argument DatabaseService().value to the suggested types...and I have failed :)
modified bookings getter after I changed it based on feedback
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings async {
final _myUserId = await inputData();
print(_myUserId);
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //here is where I want to pass the currentUser
.snapshots()
.map(_bookingListFromSnapshot);
}
Yes, you can use and await futures only inside async function. So first change your bookings getter as follows.
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings {
final _myUserId = await inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
So, where you are providing this stream, you need to provide Future then only you can get stream from Future.
How do you query data from SQLite database in Flutter using the SQFlite plugin?
I have been working on learning this recently, so I am adding my answer below as a means to help me learn and also as a quick reference for others in the future.
Add the dependencies
Open pubspec.yaml and in the dependencies section add the following lines:
sqflite: ^1.0.0
path_provider: ^0.4.1
The sqflite is the SQFlite plugin of course and the path_provider will help us get the user directory on Android and iPhone. You can check the most up-to-date version numbers here: sqflite and path_provider.
Make a database helper class
I'm keeping a global reference to the database in a singleton class. This will prevent concurrency issues and data leaks. You can also add helper methods (like query) in here for accessing the database.
Create a new file called database_helper.dart and paste in the following code:
import 'dart:io' show Directory;
import 'package:path/path.dart' show join;
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart' show getApplicationDocumentsDirectory;
class DatabaseHelper {
static final _databaseName = "MyDatabase.db";
static final _databaseVersion = 1;
static final table = 'my_table';
static final columnId = '_id';
static final columnName = 'name';
static final columnAge = 'age';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY,
$columnName TEXT NOT NULL,
$columnAge INTEGER NOT NULL
)
''');
// prepopulate a few rows (consider using a transaction)
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Bob", 23)');
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Mary", 32)');
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Susan", 12)');
}
}
Note that when the database is created I pre-populated a few rows. This is so that we have something to work with in the query examples below.
Query data
We'll use an async method to do our query because database operations can be expensive.
Get all rows
To do a SELECT * and return everything in the table you just pass in the table name.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get all rows
List<Map> result = await db.query(DatabaseHelper.table);
// print the results
result.forEach((row) => print(row));
// {_id: 1, name: Bob, age: 23}
// {_id: 2, name: Mary, age: 32}
// {_id: 3, name: Susan, age: 12}
}
Get a single row
We can pass an argument in for the where parameter to select specific rows that meet our criteria. In this example we will query the row with an ID of 1.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get single row
List<String> columnsToSelect = [
DatabaseHelper.columnId,
DatabaseHelper.columnName,
DatabaseHelper.columnAge,
];
String whereString = '${DatabaseHelper.columnId} = ?';
int rowId = 1;
List<dynamic> whereArguments = [rowId];
List<Map> result = await db.query(
DatabaseHelper.table,
columns: columnsToSelect,
where: whereString,
whereArgs: whereArguments);
// print the results
result.forEach((row) => print(row));
// {_id: 1, name: Bob, age: 23}
}
The items in the whereArguments list get substituted in place of the ?s in the whereString. In this case there was only one ? so the whereArguments only had one item. If there were two ?s (for example an integer and a string), then you would have two items in the list.
Raw query
If you prefer the familiarity or flexibility of SQL code itself, you can do a raw query. In this example we will select any row whose name column is 'Mary'.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// raw query
List<Map> result = await db.rawQuery('SELECT * FROM my_table WHERE name=?', ['Mary']);
// print the results
result.forEach((row) => print(row));
// {_id: 2, name: Mary, age: 32}
}
Be sure to use data binding using ? string replacements. This will guard against SQL injection attacks.
Notes
You will have to import the DatabaseHelper class and sqflite if you are in another file (like main.dart).
The SQFlite plugin uses a Map<String, dynamic> to map the column names to the data in each row.
Supplemental code
For your copy-and-paste convenience, here is the layout code for main.dart:
import 'package:flutter/material.dart';
// I called my project 'flutter_database_operations'. You can update for yours.
import 'package:flutter_database_operations/database_helper.dart';
import 'package:sqflite/sqflite.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQFlite Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqflite'),
),
body: RaisedButton(
child: Text('query', style: TextStyle(fontSize: 20),),
onPressed: () {_query();},
),
);
}
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get all rows
List<Map> result = await db.query(DatabaseHelper.table);
// get single row
//List<Map> result = await db.query(DatabaseHelper.table,
// columns: [DatabaseHelper.columnId, DatabaseHelper.columnName, DatabaseHelper.columnAge],
// where: '${DatabaseHelper.columnId} = ?',
// whereArgs: [1]);
// raw query
//List<Map> result = await db.rawQuery('SELECT * FROM my_table WHERE name=?', ['Mary']);
// get each row in the result list and print it
result.forEach((row) => print(row));
}
}
Going on
This post is a development from my previous post: Simple SQFlite database example in Flutter. See that post for other SQL operations and advice.
I am creating a Flutter app that allows a list of products of a store. To practice, I have base myself in flutter firebase example:
I have managed to show a list with products stored in firebase, but my problem is that when a user adds a product, the list automatically refreshes, since it is realdatatime. The example code:
new Flexible(
child: new FirebaseAnimatedList(
key: new ValueKey<bool>(_anchorToBottom),
query: _messagesRef,
reverse: _anchorToBottom,
sort: _anchorToBottom
? (DataSnapshot a, DataSnapshot b) => b.key.compareTo(a.key)
: null,
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation, int index) {
return new SizeTransition(
sizeFactor: animation,
child: new Text("$index: ${snapshot.value.toString()}"),
);
},
),
),
In the code i use FirebaseAnimatedList() to load a query from firebase, it works perfectly, but when many products are added in a short time, for the user it would be very annoying.
I would like the user to refresh this manually.
I've seen that the firebase_list library exists, but I can not find any example of how to use it and I do not know if it would be the solution to the problem.
Thanks for any help or suggestions.
It looks like you can override didChangeDependencies and instead of calling setState when a child is added, note the change with a needsRefresh variable for example.
#override
void didChangeDependencies() {
if (widget.sort != null) {
_model = new FirebaseSortedList(
query: widget.query,
comparator: widget.sort,
onChildAdded: (_) => _needsRefresh = true, //_onChildAdded,
onChildRemoved: _onChildRemoved,
onChildChanged: _onChildChanged,
onValue: _onValue,
);
} else {
_model = new FirebaseList(
query: widget.query,
onChildAdded: _onChildAdded,
onChildRemoved: _onChildRemoved,
onChildChanged: _onChildChanged,
onChildMoved: _onChildMoved,
onValue: _onValue,
);
}
super.didChangeDependencies();
}