I am trying to capture the error thrown by firstore plugin in flutter when listening to document snapshots. The error is thrown in the debug logs but I cannot access it on catch error or handle error. Is this an enhancement needed for the plugin or is there a way?
Error in debug
I/System.out(16041): com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
Here is my code, I tried a number of ways but it didn't work
_getUserCollection.document(uid).snapshots();
_getUserCollection.document(uid).snapshots().handleError((onError) {
print(onError.toString());
});
try {
_getUserCollection.document(uid).snapshots();
} catch (e) {
print(e);
}
try {
_getUserCollection.document(uid).snapshots();
} on PlatformException catch (e) {
print(e.toString());
}
_getUserCollection.document(uid).snapshots().listen((event) {
print('here on listen');
}, onError: (e) {
print('on error $e');
});
"Missing or insufficient permissions" means that your query violated one of your security rules. You will need to examine those rules, and make sure they allow the query you intend to perform.
There is plenty of documentation for security rules, and it's necessary to understand how they work in order to work with Firestore effectively from web and mobile clients.
It's not true that you can't catch an error from a Firestore query. You can't use try/catch - you will have to pass an error handler to listen().
I was having the same issue. PERMISSION_DENIED was coming out in the logs but I wanted to catch the error myself so that I could display it to the user. I found this issue on GitHub:
Firebase - native error messages not provided issue
It states that a lot of work has been done to improve the error handling in Firebase. So I spent yesterday upgrading my app to the latest version of firebase_auth (0.18.0 at the time of writing) and I can now catch and handle the PERMISSION_DENIED error like this:
return StreamBuilder<List<DistanceDocSnapshot>>(
stream: _eventStream,
builder: (BuildContext context,
AsyncSnapshot<List<DistanceDocSnapshot>> snapshot) {
if (snapshot.hasError) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Error retrieving events: ${snapshot.error.toString()}',
style: TextStyle(fontSize: 20.0),
textAlign: TextAlign.center,
),
);
}
if (snapshot.hasData) {
// Handle data as desired
}
}
);
This can be seen working in the following screenshot Screenshot of error on my app (I had to provide a link to the screenshot because I don't have enough rep to embed images yet)
My code is laid out differently to yours but I think yours will start working as desired if you just upgrade your firebase_auth version.
Related
The following code throws this error "The method '[]' can't be unconditionally invoked because the receiver can be 'null'"
if (snapshot.hasData == true) {
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
return ListTile(
title: Text(document.data()['title']),
);
}).toList(),
);
}
Is it related to null-safety? how to fix it?
This problem is related to a Flutter update.
In the newest Flutter update, there is no need in adding the .data().
Removing the.data() from the code in the description solves the issue.
Try
doc.get('title')
instead of
document.data()['title']
I'm new to both Flutter and Firebase, so please bear with me.
I have a realtime database which stores (as of now) a list of products.
The addProduct, and updateProduct methods in my code are working fine. But for some reason, deleteProduct isn't. It's giving me a 404 error when I try to access the database through URL, even though the exact same URL is working for the updateProduct function.
Here is the code for updateProduct:
final _productIndex =
_items.indexWhere((element) => element.id == productId);
if (_productIndex >= 0) {
final url = Uri.parse(
"https://flutter-shop-app-f1b23-default-rtdb.firebaseio.com/products/$productId.json");
return http
.patch(url,
body: json.encode({
"title": newProduct.title,
"description": newProduct.description,
"imageUrl": newProduct.imageUrl,
"price": newProduct.price,
}))
.then((_) {
_items[_productIndex] = newProduct;
notifyListeners();
});
} else {
print("ERROR");
}
return Future(null);
}
And here is the code for deleteProduct:
Future<void> deleteProduct(String productId) {
print(productId);
final url = Uri.parse(
"https://flutter-sdhop-app-f1b23-default-rtdb.firebaseio.com/products/$productId.json");
print(url.toString());
return http.delete(url).then((response) {
print(response.statusCode);
print(response.body);
if (response.statusCode >= 400) {
throw HttpException("Could not delete!");
}
_items.removeWhere((element) => element.id == productId);
notifyListeners();
});
}
And this is the (printed) error message :
I/flutter ( 5104): 404
I/flutter ( 5104): {
I/flutter ( 5104): "error" : "404 Not Found"
I/flutter ( 5104): }
I tried opening the URL in my browser window, and got the same 404 error, which was puzzling since Flutter seems to be able to access it for updating without any issues. The firebase project was created in test mode, so I don't think authentication will be the issue here.
Please help on how to fix this.
Here are the database rules:
{
"rules": {
".read": "now < 1624300200000", // 2021-6-22
".write": "now < 1624300200000", // 2021-6-22
}
}
To be honest I would not recommend that solution for you. The REST API is made for use cases where there is no native SDK. Flutter has definitely a very good one you can use. If you continue with that approach you would have the double work because you can't reause anything you made with the REST API. And also the BEST stuff in Firebase like realtime listeners is not in the REST API. Authentication is a hustle with the REST API. It's only a Plan B solution when there is no native SDK.
In the link for deletion you have a typo:
https://flutter-sdhop-app-f1b23-default-rtdb.firebaseio.com/products/
It should be shop and not sdhp.
I'm quite new to flutter and dart. I have started to build an app to help understand the languages better. So far I can authenticate users, log them in, log them out and perform actions based on the status of the user etc.... but I have been struggling for hours on something that I imagine is quite a basic concept. I have tried countless code examples from SO and Flutter Dev but nothing seems to work.
I want to query my Cloud Firestore, searching for any documents that have the document name of 'uid' that matches the current logged in users user ID. This code brings me no errors in Android Studio, and the app runs fine and displays the 'page' but does not redirect or print anything.
As this is really just saying if it's empty do x but if it's not empty do y - even if the syntax was wrong (even though AndroidStuid says it's valid) I would still expect it to change the path to either 'Home' or 'Hometwo' and print one of the two statements, but it does nothing.
If I simply print 'user' it does this ok, and displays the current users ID - but seems to 'break' when querying the collection?
What am I doing wrong here?
void checkCurrentUser() async {
// final FirebaseFirestore database = FirebaseFirestore.instance;
final user = await _auth.currentUser.uid;
if (user == null) {
navigateToSubPageMyAccount(context);
} else {
FirebaseFirestore.instance
.collection('samplecollection')
.doc(user)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
navigateToSubPageHome(context);
print('Document exists on the database: ${documentSnapshot.data()}');
} else {
navigateToSubPageHomeTwo(context);
print('Document does not exist');
}
});
}
}
Update:
I found the answer as per my comment below. If anyone else stumbles upon this thread, the reason none of this code was working is because I had commented it out in error from within initState during some other testing:
void initState() {
super.initState();
// checkCurrentUser();
}
...and simply removed the commenting so it was included when the app ran:
void initState() {
super.initState();
checkCurrentUser();
}
I have to integrate firebase remote config to my flutter app. From the searches in various sites, I couldn't find the complete solution.
class RemoteConfigurartion{
Future<RemoteConfig> setupRemoteConfig() async {
String value =null;
final RemoteConfig remoteConfig = await RemoteConfig.instance;
remoteConfig.setConfigSettings(RemoteConfigSettings(debugMode: false));
remoteConfig.setDefaults(<String, dynamic>{
'riddle': "off",
});
try {
// Using default duration to force fetching from remote server.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
await remoteConfig.activateFetched();
} on FetchThrottledException catch (exception) {
// Fetch throttled.
print(exception);
} catch (exception) {
print(
'Unable to fetch remote config. Cached or default values will be '
'used');
}
return remoteConfig;
}
}
This is what I found already. This the result I'm getting:
No implementation found for method RemoteConfig#instance on channel plugins.flutter.io/firebase_remote_config
But I have added all the plugins in the pubspec.yaml and in android gradle folder
Can anyone help me to find out a complete solution to integrate remote config to a flutter app?
Make sure that firebase_core is initialized, and for Android, google-services.json should be in the app folder. Aside from that, there doesn't seem to be any issues on your code. You might've missed some steps during set up.
If you've just added the plugins, it's best to run the app using restart instead of hot reload to ensure that all recently added plugins is included in the build.
I have a drawer in my App and I need to make it display some info about the current user. I did it, but the data I need only show up after a few seconds because the functions to get it are inside the widget.
Here's some code:
class MainDrawer extends StatefulWidget {
#override
_MainDrawerState createState() => _MainDrawerState();
}
class _MainDrawerState extends State<MainDrawer> {
UserCore user = UserCore();
Future getUser() async {
var uid = await FirebaseAuth.instance.currentUser().then((val) {
return val.uid;
});
Firestore.instance
.collection("users")
.where("uid", isEqualTo: uid)
.getDocuments()
.then((val) {
user = UserCore.fromMap(val.documents[0]);
setState(() {});
});
}
#override
void initState() {
getUser();
super.initState();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Container(
alignment: Alignment.centerLeft,
color: Color.fromRGBO(59, 46, 46, 1),
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
//padding: EdgeInsets.zero,
children: <Widget>[
Column(
children: <Widget>[
DrawerHeader(
child: Image.asset(
"img/logo.png",
width: 100,
),
),
drawerInfo("User: ", user.info1 == "" ? user.info2: user.info1),
drawerInfo("Email: ", user.email),
],
),
This is the core part of the code and my final question is, how can I make the data comming from the Firebase database stop of suddenly popping in the drawer and be already there when the user open it?
This drawer are present in most of the app, so passing the info as a parameter are impracticable.
You're loading data from Firebase, which means it may have to come from the network, and that will inevitably take time. There are many ways to make this (seem) faster to the user though:
Use a faster network connection, or move closer to the server. While I know these are often not realistic, it is important to set your expectation right: the majority of the time your user is waiting is of three factors:
Their distance to the servers that contain the data, typically translated into latency: the time it takes to send a data packet to the server and get a response back.
The available bandwidth of their connection, as that determines how fast data can be sent from/to their device.
The amount of data that your code is requesting, since sending/receiving more data will take longer.
As the first two options are unlikely under your control, the only one of these that you can control is the amount of data that you request. Be sure to only request data that you absolutely need to display. You seem to be doing so already, but it is usually something to consider.
Cache the data locally, and reload it from there. Firestore automatically stores any data it receives in a disk cache on the local device, and can load it from there when the app requests it again. But the way you are currently loading the user document bypasses that cache, and forces the request to be answered from the server.
There are two ways to fix this:
Force the getDocuments() call to come from the cache, by passing Source: Source.serverAndCache into it. If you get a result there, show it to the user. If you don't get a result, you'll still have to request it from the server.
Use a realtime listener, which first returns the document(s) from the cache, then checks with the server for updates, and calls your code again with any updates. This means the user sees the locally cached version right away, and then may see an update after a few moments.