Flutter FirebaseFirestore: retrieving fields from an array of IDs - firebase

I'm trying to retrieve fields from multiple IDs that are stored in an array.
I have a collection of users, and in every user's document, there is an array of restaurant IDs of that user's owned restaurant as follows:
And each restaurant's document is one of those IDs in the array.
I successfully retrieved the array of ID's from the User but now I'm trying to get the names of the restaurant depending on the ID
I'm retrieving my ID's using this code:
var restaurantIDs = [];
getRestaurantIDs() async {
await FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.get()
.then((value) {
restaurantIDs = value.data()['rid'];
});
return restaurantIDs;
}
Then I'm trying to retrieve the names and save them in an array using this code:
var restaurantNames = [];
getRestaurantNames() async {
for (int i = 0; i < restaurantIDs.length; i++) {
await FirebaseFirestore.instance
.collection('restaurant')
.doc(restaurantIDs[i])
.get()
.then((value) {
restaurantNames = value.data()['name'];
});
}
return restaurantNames;
}
What am I doing wrong?
and thanks for the help.
EDIT:
1- I tried placing print statements inside the for loop, but it's like as if it's not going inside the loop at all, because the print statements don't get executed.
2- I think it's because the list of IDs won't be already stored inside the array when the function is called inside the initState, because I tried to print the length of the restaurantIDs array before the for loop and in initState and it showed 0, but when I print it inside the Scaffold, it would show 2.
EDIT 2:
I tried merging both functions into one to make sure that the restaurantIDs isn't empty, and now it goes inside the for loop because it prints out the statement, but now I receive another error
new function:
var restaurantIDs = [];
var restaurantNames = [];
getRestaurantIDs() async {
await FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.get()
.then((value) {
restaurantIDs = value.data()['rid'];
});
for (var i = 0; i < restaurantIDs.length; i++) {
print('added one');
await FirebaseFirestore.instance
.collection('restaurant')
.doc(restaurantIDs[i])
.get()
.then((value) {
restaurantNames.add(value.data()['name']);
});
}
return restaurantNames;
}
Error:
E/flutter (22920): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: The method '[]' was called on null.
E/flutter (22920): Receiver: null
E/flutter (22920): Tried calling: []("name")
E/flutter (22920): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:68:5)
E/flutter (22920): #1 _AddMealScreenState.getRestaurantIDs.<anonymous closure>
package:bits_n_bobs/screens/add_meal_item_screen.dart:178
E/flutter (22920): #2 _rootRunUnary (dart:async/zone.dart:1436:47)
E/flutter (22920): #3 _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (22920): <asynchronous suspension>
E/flutter (22920): #4 _AddMealScreenState.getRestaurantIDs
E/flutter (22920): <asynchronous suspension>
E/flutter (22920):

There is something a bit off about the way you have organized your database. For each restaurant document, you should create a key with the user id as value, and then simply use the where query.
Example :
final restaurants = await FirebaseFirestore.instance.
.collection("restaurant")
.where("userId", isEqualTo: user.uid);
.get()
You can then map the retrieved list and filter the restaurant's name from it.
final restaurantNames = data.docs
.map((json) =>
json.data()["name"])
.toList();
Hope's that is hopefull.

restaurantNames.add( value.data()['name']);
Check the document id is matching.

Related

ERROR:flutter/lib/ui/ui_dart_state.cc(186) Unhandled Exception: type 'String' is not a subtype of type 'int' of 'index'

hope all are well.
As a heads up, I am still learning and am still very new to flutter and Dart so, if my code looks really stupid, that's why.
I am trying to get data from a specific doc from Firestore. Then display as a List tile. I am trying to save the data into a local variable(List), which I them use to make the list.
but I get an error, the error doesn't tell where the it occurs which makes it hard to fix.
Here is the function and the variables.
final uid = FirebaseAuth.instance.currentUser.uid;
List<PhoneModel> _phones = [];
Future<void> _getPhones() async {
var phones = await FirebaseFirestore.instance
.collection('phones')
.doc('5AfeF8xkdbM2xgXheDaK5OcDeT63')
.get();
List<PhoneModel> loadedPhones = [];
phones.data().forEach((key, value) {
loadedPhones.add(PhoneModel(
descriptions: value['description'],
price: value['Price'],
title: value['Title'],
id: key));
_phones = loadedPhones;
return _phones;
});
}
here is the error
[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: type 'String' is not a subtype of type 'int' of 'index'
E/flutter (32763): #0 _UserPhoneAddsState._getPhones.<anonymous closure>
package:secondhandphones/…/CreateEditPhone/UpdatePhone.dart:32
E/flutter (32763): #1 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:397:8)
E/flutter (32763): #2 _UserPhoneAddsState._getPhones
package:secondhandphones/…/CreateEditPhone/UpdatePhone.dart:30
E/flutter (32763): <asynchronous suspension>
EDIT: Here is the Phone Model class as per the comment
class PhoneModel {
final String title;
final String id;
final String descriptions;
final num price;
PhoneModel({
this.descriptions,
this.id,
this.price,
this.title,
});
}
Please tell me if I need to post anything else
Thanks for any help

NoSuchMethodError: The method 'ref' was called on null

there I want help, I was creating my app with flutter and firebase,
there, I want to upload images to firebase storage.
so my file
import 'package:firebase_storage/firebase_storage.dart';
uploadTOFirebase() async {
final ref = FirebaseStorage.instance.ref('posts/post_$postId.jpg');
await ref.putFile(image).whenComplete(() async {
final url = await ref.getDownloadURL();
setState(() {
postlink = url;
});
// print(postlink);
});
}
but that saw me that error
E/flutter (19469): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: NoSuchMethodError: The method 'ref' was called on null.
E/flutter (19469): Receiver: null
E/flutter (19469): Tried calling: ref("posts/post_27cfad88-62bb-4211-9e5b-0c6d1e9029be.jpg")
E/flutter (19469): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
E/flutter (19469): #1 _UploadState.uploadTOFirebase (package:instaclone/pages/upload.dart:230:48)
E/flutter (19469): #2 _UploadState.HandleSubmit (package:instaclone/pages/upload.dart:226:11)
E/flutter (19469): <asynchronous suspension>
E/flutter (19469):
and I'm using flutter in andoird
Try using it like this.
final ref = FirebaseStorage.instance.ref().child("posts/post_$postId.jpg");
Did you call Firebase.initializeApp() anywhere? Without that, the Firebase SDKs won't be able to find your project on Google's servers.
See initializing FlutterFire in the FlutterFire docs for an example of how to do this.
uploadTOFirebase() async {
UploadTask storageUploadTask = FirebaseStorage.instance.putFile('posts/post_$postId.jpg');
TaskSnapshot storageTaskSnapshot =
await storageUploadTask.whenComplete(() => null);
String url = await storageTaskSnapshot.ref.getDownloadURL();
setState((){postlink = url;});
}
Try this code. Did some small changes.

Error database_closed when using flutter's sqflite

I try to use sqflite to save some data like my class Movie but when i try to insert or query on the database, see this message:
[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: DatabaseException(error database_closed)
my MovieBloc class:
List<Movie> _movies = new List<Movie> ();
class MoviesBloc implements BaseBloc {
final _moviesController = StreamController<List<Movie>>();
Database moviesDB;
String _moviesPath;
Stream<List<Movie>> get moviesStream => _moviesController.stream;
List<Movie> get movies => _movies;
int getLengthMovieList() {
return _movies.length;
}
clearMovieList() {
_movies.clear();
}
Future<void> openMovieDB({String dbName:'movies.db'}) async {
var databasesPath = await getDatabasesPath();
_moviesPath = join(databasesPath, dbName);
moviesDB = await openDatabase(_moviesPath, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE movies (Id INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, imdbRating TEXT, Poster TEXT, Plot TEXT, Saved INTEGER DEFAULT 0)');
});
}
Future<void> closeMovieDB() async => moviesDB.close();
void dispose() {
_moviesController.close();
}
}
my MovieProvider class:
class MovieProvider extends MoviesBloc {
String _tableName = 'movies';
Future<Movie> insert(Movie movie, {conflictAlgorithm: ConflictAlgorithm.ignore}) async {
await moviesDB.insert(_tableName, movie.toMap(), conflictAlgorithm: conflictAlgorithm);
return movie;
}
Future<bool> insertAll(List<Movie> movies) async {
await Future.wait(movies.map((movie) async {
await this.insert(movie);
}
));
return true;
}
Future<List<Movie>> paginate(int page, {int limit: 15}) async {
print('in Paginate to SQLite');
List<Map> maps = await moviesDB.query(_tableName,
columns: ['Id', 'Title', 'imdbRating', 'Poster', 'Plot', 'Saved'],
limit: limit,
offset: page == 1 ? 0 : ((page -1) * limit)
);
List<Movie> movies = new List<Movie> ();
if (maps.length > 0) {
maps.map((movie) {
movies.add(Movie.fromJson(movie));
}
);
}
return movies;
}
}
my save method to sqflite:
static Future<bool> saveAllMoviesIntoSqlite(List<Movie> movies) async {
var db = new MovieProvider();
await db.openMovieDB();
await db.insertAll(movies);
await db.closeMovieDB();
return true;
}
my load method from sqflite:
static Future<Map> getAllMoviesFromSqlite(int page) async {
var db = new MovieProvider();
await db.openMovieDB();
List<Movie> movies = new List<Movie> ();
movies = await db.paginate(page);
await db.closeMovieDB();
return {
"currentPage": page,
"movies": movies
};
}
I observe that in insertAll method from MovieProvider class for insert any movie from the movies list to sq with await and async:
Future<bool> insertAll(List<Movie> movies) async {
await Future.wait(movies.map((movie) async {
await this.insert(movie);
}
));
return true;
}
I try to close and open database sometimes sequential and other plays with close and open database ...
my log:
I/flutter ( 1617): in Paginate to SQLite
E/flutter ( 1617): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: DatabaseException(error database_closed)
E/flutter ( 1617): #0 SqfliteDatabaseMixin.checkNotClosed (package:sqflite_common/src/database_mixin.dart:282:7)
E/flutter ( 1617): #1 SqfliteDatabaseExecutorMixin._rawQuery (package:sqflite_common/src/database_mixin.dart:125:8)
E/flutter ( 1617): #2 SqfliteDatabaseExecutorMixin.query (package:sqflite_common/src/database_mixin.dart:110:12)
E/flutter ( 1617): #3 MovieProvider.paginate (package:umdb/bloc/movie_provider.dart:24:38)
E/flutter ( 1617): #4 MovieService.getAllMoviesFromSqlite (package:umdb/services/movie_service.dart:54:23)
E/flutter ( 1617): <asynchronous suspension>
E/flutter ( 1617): #5 MovieService.getSavedFromSQ (package:umdb/services/movie_service.dart:95:26)
E/flutter ( 1617): #6 MovieService.fetchSavedMovies (package:umdb/services/movie_service.dart:105:5)
E/flutter ( 1617): <asynchronous suspension>
E/flutter ( 1617): #7 MovieDetailState.build.<anonymous closure>.<anonymous closure> (package:umdb/ui_widgets/movie_detail.dart:168:42)
E/flutter ( 1617): #8 State.setState (package:flutter/src/widgets/framework.dart:1240:30)
E/flutter ( 1617): #9 MovieDetailState.build.<anonymous closure> (package:umdb/ui_widgets/movie_detail.dart:142:27)
E/flutter ( 1617): #10 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:184:24)
E/flutter ( 1617): #11 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:524:11)
E/flutter ( 1617): #12 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:284:5)
E/flutter ( 1617): #13 BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:256:7)
E/flutter ( 1617): #14 GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:158:27)
E/flutter ( 1617): #15 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:224:20)
E/flutter ( 1617): #16 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:200:22)
E/flutter ( 1617): #17 GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:158:7)
E/flutter ( 1617): #18 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:104:7)
E/flutter ( 1617): #19 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:88:7)
E/flutter ( 1617): #20 _rootRunUnary (dart:async/zone.dart:1206:13)
E/flutter ( 1617): #21 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter ( 1617): #22 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter ( 1617): #23 _invoke1 (dart:ui/hooks.dart:267:10)
E/flutter ( 1617): #24 _dispatchPointerDataPacket (dart:ui/hooks.dart:176:5)
please help me to fix it
It is very hard to deal with opening/closing the database. There are indeed many cases where you might access a closed database and even worse, if you open twice the same database, you get the same instance so the first close will close the database.
Personally I recommend keeping the database open.
If you need to handle multiple databases and open/close them for short actions, make sure you have the proper mutex/lock mechanism so that you control how your database is opened/accessed/closed without any other method trying to access the same database at the same time (ok that part is not clear sorry).
Something like that (although I really don't recommend opening/closing each time):
import 'package:synchronized/synchronized.dart';
final static _lock = Lock();
static Future<bool> saveAllMoviesIntoSqlite(List<Movie> movies) async {
return _lock.synchronized(() {
var db = new MovieProvider();
await db.openMovieDB();
await db.insertAll(movies);
await db.closeMovieDB();
return true;
});
}
static Future<Map> getAllMoviesFromSqlite(int page) async {
return _lock.synchronized(() {
var db = new MovieProvider();
await db.openMovieDB();
List<Movie> movies = new List<Movie> ();
movies = await db.paginate(page);
await db.closeMovieDB();
return {
"currentPage": page,
"movies": movies
};
});
}
You need to set null for your database instance when you close your database. Otherwise it will always return the old instance.
Future close() async {
final db = await instance.database;
_database = null;
return db.close();
}
Keeping database without close, and using Singleton pattern is a good choice to solve this problem.
1- Create new file sql_lite_service.dart, this will contains SqlLiteService class with this full code:
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class SqlLiteService {
String dBName = 'my_db_name';
int dBVersion = 1;
// Singleton pattern
static final SqlLiteService _databaseService = SqlLiteService._internal();
factory SqlLiteService() => _databaseService;
SqlLiteService._internal();
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
Database db = await _getDB();
return db;
}
Future<Database> _getDB() async{
final path = await _getPath(); // Get a location using getDatabasesPath
return await openDatabase(
path,
onCreate: _onCreate,
version: dBVersion,
onConfigure: (db) async => await db.execute('PRAGMA foreign_keys = ON'),
);
}
// create tables
Future<void> _onCreate(Database db, int version) async {
// Run the CREATE {users} TABLE statement on the database.
await db.execute(
'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at datetime default current_timestamp, updated_at datetime default current_timestamp)'
);
// todo: Add your code here ...
}
Future<String> _getPath() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, dBName);
return path;
}
}
2- Now you can get Database instance from any other class by:
final Database database = await SqlLiteService().database;
// usage example
await database.rawDelete(
'DELETE FROM users WHERE id = ?',
[1]
);
Using this way will solve database_closed error...

Firebase cloud function returning unauthenticated right after login

I have the following problem. I have implemented a Firebase cloud function that returns a user profile from the Firestore. My cloud functions require you to be logged in to Firebase in order to be able to query them.
Now, in my app I implemented that on FirebaseAuth.instance.onAuthStateChanged a cloud function for getting a user profile is being called. However, when I log in to the app, the triggered call returns PlatformException with reason UNAUTHENTICATED, although in a line before that I check for current user with FirebaseAuth.instance.currentUser() and I get a correct and valid uid. There are no logs in the Firebase involving this function, only for other functions which all succeed and are also called on FirebaseAuth.instance.onAuthStateChanged.
Here is the cloud function, which is basically really simple:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
export default functions.https.onCall(async (data, context) => {
console.info(`[api-userProfile] called with userUid=${data.userUid}`);
const user: FirebaseFirestore.QuerySnapshot = await admin
.firestore()
.collection("userProfiles")
.where("userUid", "==", data.userUid)
.get();
if (user.docs.length === 1) {
console.info(
`[api-userProfile] returning user for userUid=${data.userUid}`
);
return user.docs[0].data();
}
console.info(`[api-userProfile] could not find userUid=${data.userUid}`);
return null;
});
And this is where I call it:
final _disposeBag = CompositeSubscription();
final userProfile = BehaviorSubject<UserProfile>();
final _userProfilesFn =
CloudFunctions.instance.getHttpsCallable(functionName: "api-userProfile");
UserProfileStore() {
_disposeBag.add(FirebaseAuth.instance.onAuthStateChanged
.map((v) => v != null ? v : throw "")
.handleError((v) => userProfile.add(null))
.asyncMap((v) {
print(v.uid); // here the correct uid is printed
return _userProfilesFn.call({"userUid": v.uid});
})
.map((v) => v as Map)
.map((v) => UserProfile.fromJson(v))
.addTo(userProfile)
.listen((_) {}));
}
And the error thrown is (first line is the uid of the logged in user):
I/flutter ( 9370): DXrcqtU34Dh8UfZYnsNdxn31hqx2
E/flutter ( 9370): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(functionsError, Cloud function failed with exception., {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED})
E/flutter ( 9370): #0 StandardMethodCodec.decodeEnvelope
package:flutter/…/services/message_codecs.dart:569
E/flutter ( 9370): #1 MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:321
E/flutter ( 9370): <asynchronous suspension>
E/flutter ( 9370): #2 MethodChannelCloudFunctions.callCloudFunction
package:cloud_functions_platform_interface/src/method_channel_cloud_functions.dart:43
E/flutter ( 9370): #3 HttpsCallable.call
package:cloud_functions/src/https_callable.dart:33
E/flutter ( 9370): #4 new UserProfileStore.<anonymous closure>

Cannot query a document from a sub collection from Firestore collection

I am trying to query a specific document nested under collection'users/document'useruid'/collection'items'/'document'documentid'. I keep getting a 'null' value and it errors out:
try {
final user = await _auth.currentUser();
if (user != null) {
_loggedInUser = user;
_userUid = _loggedInUser.uid;
setState(() {
// showSpinner = false;
print(_userUid);
});
}
} catch (e) {
print(e);
}
}
void getItemDetail() async {
await _firestore
.collection('users')
.document(_userUid)
.collection('items')
.document(documentID)
.get()
.then((DocumentSnapshot snapshot) {
itemDetails = snapshot.data;
itemName = snapshot['item_name'];
itemNum = snapshot['item_num'.toString()];
itemDesc = snapshot['item_desc'];
itemLoc = snapshot['item_location'];
itemQty = snapshot['item_qty'.toString()];
itemUom = snapshot['item_uom'];
itemMfr = snapshot['item_mfr'];
itemStock = snapshot['out_of_stock'];
lastEditDate = snapshot['edit_date'];
createDate = snapshot['create_date'];
imageURL = snapshot['image_url'];
setState(() {
dateCreated =
new DateFormat.yMMMMd("en_US").format(createDate.toDate());
itemDetails = snapshot.data;
showSpinner = false;
stockCheck();
editDateCheck();
});
});
}
#override
void initState() {
testApp();
getCurrentUser();
getItemDetail();
// editDateCheck();
super.initState();
}
flutter error output:
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling: []("item_name")
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 DocumentSnapshot.[] (package:cloud_firestore/src/document_snapshot.dart:29:42)
#2 _ItemDetailScreenState.getItemDetail.<anonymous closure> (package:simmanager/screens/item_detail_screen.dart:77:26)
#3 _rootRunUnary (dart:async/zone.dart:1132:38)
#4 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#5 _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#6 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#7 Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#8 Future._completeWithValue (dart:async/future_impl.dart:522:5)
#9 _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
#10 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
#<…>
The documentid is passed from the navigator on previous page. I have printed (userid, documentid" all the values correctly print.
You should always check whether you're snapshot has any data or null like,
if(snapshot!=null)
Then you always should check the data inside snapshot is null or not like,
if(snapshot.data!=null)
Also, in your case you are calling you're data on null object.
I think you need to replace your snapshot['item_name'] with snapshot.data['item_name'] for all the item.
You are mistakenly calling snapshot["your_field"] instead of snapshot.data["your_field"] which is causing the issue.
Also, checking null safety while parsing the data from the network is recommended always.

Resources