i am a flutter beginner and i am getting the "Flutter & Dart - The Complete Guide [2022 Edition]" course at Udemy (if you know the course i am at Section 10, Lesson 242 "Sending POST Requests". This is my first time using Firebase and a database and i stuck at this problem for quite some time now. We built a basic shop app at previous sections at course and now we are supposed to send a Post request to our Firebase realtime database when we add a new product to our app. We supposed to add some basic information to our database about product. When i try to add a new product, nothing happens, no data at my database and no new product at my app (adding product to app happens in same function right after sending Post request). There is no error at my terminal only one line below.
I/flutter (11699): ...
I was able to add products to app before adding http package and writing the post request codes from last lesson. These are my debug, database rules, and the function that sends post request.
void addProduct(Product product) {
final url = Uri.parse(
'https://flutter-update-ca55a-default-rtdb.firebaseio.com/products.json');
http.post(
url,
body: json.encode({
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
'isFavorite': product.isFavorite,
}),
);
final newProduct = Product(
id: DateTime.now().toString(),
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl);
_items.add(newProduct);
notifyListeners();
}
I tried course constructors example codes and it didn't work. Then i tried disabling my firewall, changing my database rules to as seen in my question, still same problem. I don't even know if my code is communicating with firebase or not.
Edit:
Sometimes i get the latest text at my Terminal as well, adding a screenshot below:
Click to see Terminal SS
Edit 2 : So i created an empty project to see if my computer is communicating with firebase at all and it worked, i added information to my database from a single screen app. I afraid i have a logical error in my Shop App (most likely at the screen i use addProduct function) so i am posting the code if you see the mistake please warn me. This is edit-product-screen, where i use addProduct function via provider.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/product.dart';
import '../providers/product_provider.dart';
class EditProductScreen extends StatefulWidget {
static const String routeName = '/edit-product-screen';
#override
State<EditProductScreen> createState() => _EditProductScreenState();
}
class _EditProductScreenState extends State<EditProductScreen> {
//final _priceFocusNode = FocusNode();
//final _descriptionFocusNode = FocusNode();
final _imageUrlController = TextEditingController();
//final _imageUrlFocusNode = FocusNode();
final _form = GlobalKey<FormState>();
var _editedProduct =
Product(id: '', title: '', description: '', price: 0, imageUrl: '');
var _isInit = true;
var _isloading = false;
var _initValues = {
'title': '',
'description': '',
'price': '',
'imageUrl': '',
};
#override
void initState() {
//_imageUrlFocusNode.addListener(_updateImageUrl);
super.initState();
}
#override
void didChangeDependencies() {
if (_isInit) {
final String? productId =
ModalRoute.of(context)!.settings.arguments as String?;
if (productId != null) {
_editedProduct = Provider.of<ProductProvider>(context, listen: false)
.findbyID(productId);
_initValues = {
'title': _editedProduct.title,
'description': _editedProduct.description,
'price': _editedProduct.price.toString(),
// 'imageUrl': _editedProduct.imageUrl,
'imageUrl': '',
};
_imageUrlController.text = _editedProduct.imageUrl;
}
}
_isInit = false;
super.didChangeDependencies();
}
// #override
// void dispose() {
// _imageUrlFocusNode.removeListener(_updateImageUrl);
// _priceFocusNode.dispose();
// _descriptionFocusNode.dispose();
// _imageUrlController.dispose();
// _imageUrlFocusNode.dispose();
// super.dispose();
// }
void _updateImageUrl() {
// if (!_imageUrlFocusNode.hasFocus) {
// if ((!_imageUrlController.text.startsWith('http') &&
// !_imageUrlController.text.startsWith('https')) ||
// (!_imageUrlController.text.endsWith('.png') &&
// !_imageUrlController.text.endsWith('.jpg') &&
// !_imageUrlController.text.endsWith('.jpeg'))) {
// return;
// }
setState(() {});
//}
}
void _saveForm() {
final isValid = _form.currentState!.validate();
if (!isValid) {
return;
}
_form.currentState!.save();
setState(() {
_isloading = true;
});
if (_editedProduct.id != null) {
//var olan bir product editleniyor
Provider.of<ProductProvider>(context, listen: false)
.updateProduct(_editedProduct.id, _editedProduct);
setState(() {
_isloading = false;
});
Navigator.of(context).pop();
} else {
//yeni bir product oluşturuluyor.
Provider.of<ProductProvider>(context, listen: false)
.addProduct(_editedProduct)
.catchError((error) {
return showDialog<Null>(
context: context,
builder: (ctx) => AlertDialog(
title: Text('An Error Occured'),
content: Text(error.toString()),
actions: [
FlatButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text('Okay'))
],
),
);
}).then((_) {
setState(() {
_isloading = false;
});
Navigator.of(context).pop();
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Products'),
actions: [
IconButton(
onPressed: _saveForm,
icon: Icon(Icons.save),
),
],
),
body: _isloading
? Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _form,
child: ListView(
children: [
TextFormField(
initialValue: _initValues['title'],
decoration: InputDecoration(labelText: 'Title'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter a title';
}
return null;
},
textInputAction: TextInputAction.next,
// onFieldSubmitted: (_) {
// FocusScope.of(context).requestFocus(_priceFocusNode);
// },
onSaved: (value) {
_editedProduct = Product(
title: value as String,
price: _editedProduct.price,
description: _editedProduct.description,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
TextFormField(
initialValue: _initValues['price'],
decoration: InputDecoration(labelText: 'Price'),
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
//focusNode: _priceFocusNode,
// onFieldSubmitted: (_) {
// FocusScope.of(context).requestFocus(_descriptionFocusNode);
// },
validator: (value) {
if (value!.isEmpty) {
return 'Please enter a price';
}
if (double.tryParse(value) == null) {
return 'Please enter a valid number';
}
if (double.parse(value) <= 0) {
return 'Plase enter a number greater than zero';
}
return null;
},
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: double.parse(value as String),
description: _editedProduct.description,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
TextFormField(
initialValue: _initValues['description'],
decoration: InputDecoration(labelText: 'Description'),
maxLines: 3,
validator: (value) {
if (value!.isEmpty) {
return 'Please enter a description';
}
if (value.length < 10) {
return 'Your description must be longer then 10 characters';
}
return null;
},
textInputAction: TextInputAction.next,
//focusNode: _descriptionFocusNode,
keyboardType: TextInputType.multiline,
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: _editedProduct.price,
description: value as String,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
width: 100,
height: 100,
margin: EdgeInsets.only(top: 8, right: 10),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
),
child: _imageUrlController.text.isEmpty
? Text('Enter a URL')
: FittedBox(
child: Image.network(
_imageUrlController.text,
fit: BoxFit.cover,
),
),
),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Image URL'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter a image url';
}
if (!value.startsWith('http') &&
!value.startsWith('https')) {
return 'Please enter a valid image url';
}
return null;
},
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
controller: _imageUrlController,
//focusNode: _imageUrlFocusNode,
onFieldSubmitted: (_) {
_saveForm();
},
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: _editedProduct.price,
description: _editedProduct.description,
imageUrl: value as String,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
),
],
)
],
),
),
),
);
}
}
And this is the latest version of addProduct function
Future<void> addProduct(Product product) async {
final url = Uri.parse(
'https://flutter-update-ca55a-default-rtdb.firebaseio.com/products2.json');
try {
final response = await http.post(
url,
body: json.encode({
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
'isFavorite': product.isFavorite,
}),
);
print(json.decode(response.body));
final newProduct = Product(
id: DateTime.now().toString(),
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl);
_items.add(newProduct);
notifyListeners();
} catch (error) {
print(error);
throw error;
}
}
I'm thinking you're missing to implement await. Could you try to add await in the beginning http.post after trying to change your method Future then it going to work.
Future<Product> addProduct(Product product) async{
final url = Uri.parse(
'https://flutter-update-ca55a-default-rtdb.firebaseio.com/products.json');
await http.post(
url,
body: json.encode({
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
'isFavorite': product.isFavorite,
}),
);
And lastly, you should be use json to dart side for parsing and try to implement json encode to your model with factory methods. ( I've added my first post to your backend, please don't forget to change write rules for everyone)
If you want to learn more detail, please look at dart.pad
samples:
https://dartpad.dev/?id=c04e9842d98ac871988973df025c097e
Maybe this is late answer, but this problem has happened to me also,
if (_editedProduct.id != null) - in this line you are making a mistake, this should be: if (_editedProduct.id != '')
I hope it helps.
Actually i had the same problem, it is a not logical error as it might sound, I only created a new project with a new realtime data base with a different location than my vpn for instance my vpn was set to be US so i set realtime location on Belgium. I hope it works out for u as well.
Related
I have made a online store app with flutter and backend on firebase , in sign up page whenever click on create account button it says same error, no matter if I put data on Text Fields or not, I am really confused about this!
the error is
given String is empty or null
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../widgets/custom_button.dart';
import '../widgets/custom_input.dart';
import 'const.dart';
import 'login.dart';
class SingUP extends StatefulWidget {
SingUP({Key? key}) : super(key: key);
#override
State<SingUP> createState() => _SingUPState();
}
class _SingUPState extends State<SingUP> {
Future<void> advanceAlertDialog(String message) {
return Get.defaultDialog(
title: 'Invalid Input',
content: Text(message),
actions: [
ElevatedButton(
onPressed: () => Get.back(),
child: Text('OK !'),
)
],
contentPadding: EdgeInsets.all(15.0),
);
}
Future<String?> signInMethod() async {
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: takeEmail, password: takePassword);
return 'ok';
} on FirebaseAuthException catch (error) {
if (error.code == 'weak-password') {
return 'the password is too week';
} else if (error.code == 'email-already-in-use') {
return 'The account with this email is already exist !';
}
return error.message;
} catch (error) {
return error.toString();
}
}
void signIn() async {
setState(() {
isLoading = true;
});
String? result = await signInMethod();
if (result != 'ok') {
advanceAlertDialog(result!);
setState(() {
isLoading = false;
});
} else {
setState(() {
isLoading = false;
});
Get.to(LoginPage());
Get.snackbar('Successful', 'Account Created !',
backgroundColor: Colors.greenAccent, icon: Icon(Icons.check_circle));
}
}
bool isLoading = false;
String takeEmail = '';
String takePassword = '';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.only(top: 50),
child: Text(
'Create a New Account !',
style: Constans.headingTextStyle,
textAlign: TextAlign.center,
),
),
Column(
children: [
CustomInput(
hintText: 'Enter Your Email ...',
onChanged: (value) {
takeEmail = value;
},
checkPassword: false,
),
CustomInput(
hintText: 'Enter Your Password ...',
onChanged: (value) {
takeEmail = value;
},
checkPassword: true,
),
CustomButton(
text: 'Create Account',
loading: isLoading,
onTap: () {
signIn();
},
mode: false,
)
],
),
CustomButton(
text: 'Back to Login',
onTap: () => Get.back(),
mode: true,
loading: isLoading,
)
],
),
),
);
}
}
Emain and password widgets are the same takeEmail = value;, change the password onChange value to takePassword = value;, also where is exactly the error thrown?. I advice to add breakpoints to signInMethod() to see the input values of email: takeEmail, password: takePassword (both of this strings) and after the on FirebaseAuthException catch (error) { to see if the error is thrown from firebase or is in your code.
I am trying to make a form so that whenever you enter information and press a button, it will delete an item from the firebase database. I got the addUser code working, but when I got to the deleteUser, it compiles fine but then whenever I submit a form it doesn't show that I deleted it. I know I am putting in the same data exactly, so I think the problem is either the async in my function title or the await in my itemsSnapshot, but I have no idea since deleting a document this way is super weird. What am I doing wrong? (I am not including the imports because I'm not sure if that is sensitive data)
class AdminScreen extends StatefulWidget {
const AdminScreen({Key? key}) : super(key: key);
#override
State<AdminScreen> createState() => _AdminScreenState();
}
class _AdminScreenState extends State<AdminScreen> {
CollectionReference items = FirebaseFirestore.instance.collection('items');
reloadUser() {
setState(() {});
}
//this section is for adding stuff to database
String name = "";
String type = "";
String location="";
String imageURL = "";
String claimed = "false";
bool addDataVisible = true;
//end of this section
TextEditingController nameController = TextEditingController();
TextEditingController typeController = TextEditingController();
TextEditingController locationController = TextEditingController();
TextEditingController imageURLController = TextEditingController();
String deleteName = "";
String deleteType = "";
String deleteLocation = "";
TextEditingController deleteNameController = TextEditingController();
TextEditingController deleteTypeController = TextEditingController();
TextEditingController deleteLocationController = TextEditingController();
Object addUser() {
// Call the user's CollectionReference to add a new user
if(name!="" && type!="" && location!="") {
return items
.add({
'name': name,
'type': type,
'location': location,
'image': imageURL,
'claimed': claimed
});
// .then((value) => print("User Added"))
// .catchError((error) => print("Failed to add user: $error"));
}else{
return "There was a null error";
}
}
String idDelete = "";
Future<Object> deleteUser() async{
var itemsSnapshot = await items.get();
// Call the user's CollectionReference to add a new user
if (name != "" && type != "" && location != "") {
for (var document in itemsSnapshot.docs) {
if (document[name] == deleteName &&
document[type] == deleteType &&
document[location] == deleteLocation) {
idDelete = document.id;
}
}
print(idDelete);
items.doc(idDelete).delete();
print("user deleted success");
return items;
} else {
return "There was a null error";
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Admin'),
),
body: SingleChildScrollView(
child: Column(children: [
FirebaseAuth.instance.currentUser == null ? LoginButton(
updateState: reloadUser,) : //Text('logged in'),
//render the new widgets
Visibility(
visible: addDataVisible,
child: Column(
children: [
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Name of Item to Add',
),
controller: nameController,
onChanged: (text) {
setState(() {
name = text;
});
},
//for description
),
TextField(
controller: typeController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Type of Item to Add',
),
onChanged: (text) {
setState(() {
type = text;
});
},
//for location found
),
TextField(
controller: locationController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Location of Item to Add',
),
onChanged: (text) {
setState(() {
location = text;
});
},
//for location found
),
TextField(
controller: imageURLController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Image URL of Item (put it in the folder)',
),
onChanged: (text) {
setState(() {
imageURL = text;
});
},
//for location found
),
TextButton(
child: const Text('Click Here to Submit New Item to Database'),
onPressed: addUser,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return Colors.blue.withOpacity(0.04);
}
if (states.contains(MaterialState.focused) ||
states.contains(MaterialState.pressed)) {
return Colors.blue.withOpacity(0.12);
}
return null; // Defer to the widget's default.
},
),
),
),
//THIS SECTION IS FOR DELETING ITEMS
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Name of Item to Delete',
),
controller: deleteNameController,
onChanged: (text) {
setState(() {
deleteName = text;
});
},
//for description
),
TextField(
controller: deleteTypeController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Type of Item to Delete',
),
onChanged: (text) {
setState(() {
deleteType = text;
});
},
//for location found
),
TextField(
controller: deleteLocationController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Location of Item to Delete',
),
onChanged: (text) {
setState(() {
deleteLocation = text;
});
},
//for location found
),
TextButton(
child: const Text('Click Here to Delete Item to Database'),
onPressed: deleteUser,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return Colors.blue.withOpacity(0.04);
}
if (states.contains(MaterialState.focused) ||
states.contains(MaterialState.pressed)) {
return Colors.blue.withOpacity(0.12);
}
return null; // Defer to the widget's default.
},
),
),
),
],
),
),
]),
),
);
}}
I'm very new to flutter and trying to learn it. I have been trying to work with firebase and trying to upload data to it for the storage purpose. I have been using Realtime Database in test mode just to learn it but unfortunately due to some issues it's not working for me now.
The code that I wrote is : -
This is my provider file for interacting with my firebase
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import './product.dart';
class Products with ChangeNotifier {
List<Product> _items = [
Product(
id: 'p1',
title: 'Red Shirt',
description: 'A red shirt - it is pretty red!',
price: 29.99,
imageUrl:
'https://cdn.pixabay.com/photo/2016/10/02/22/17/red-t-shirt-1710578_1280.jpg',
),
Product(
id: 'p2',
title: 'Trousers',
description: 'A nice pair of trousers.',
price: 59.99,
imageUrl:
'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Trousers%2C_dress_%28AM_1960.022-8%29.jpg/512px-Trousers%2C_dress_%28AM_1960.022-8%29.jpg',
),
Product(
id: 'p3',
title: 'Yellow Scarf',
description: 'Warm and cozy - exactly what you need for the winter.',
price: 19.99,
imageUrl:
'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
),
Product(
id: 'p4',
title: 'A Pan',
description: 'Prepare any meal you want.',
price: 49.99,
imageUrl:
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
),
];
// var _showFavoritesOnly = false;
List<Product> get items {
// if (_showFavoritesOnly) {
// return _items.where((prodItem) => prodItem.isFavorite).toList();
// }
return [..._items];
}
List<Product> get favoriteItems {
return _items.where((prodItem) => prodItem.isFavorite).toList();
}
Product findById(String id) {
return _items.firstWhere((prod) => prod.id == id);
}
// void showFavoritesOnly() {
// _showFavoritesOnly = true;
// notifyListeners();
// }
// void showAll() {
// _showFavoritesOnly = false;
// notifyListeners();
// }
Future<void> addProduct(Product product) {
const url = 'https://random-app-effc2-default-rtdb.firebaseio.com/products.json';
return http
.post(
url,
body: json.encode({
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
'isFavorite': product.isFavorite,
}),
)
.then((response) {
final newProduct = Product(
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl,
id: json.decode(response.body)['name'],
);
_items.add(newProduct);
// _items.insert(0, newProduct); // at the start of the list
notifyListeners();
});
}
void updateProduct(String id, Product newProduct) {
final prodIndex = _items.indexWhere((prod) => prod.id == id);
if (prodIndex >= 0) {
_items[prodIndex] = newProduct;
notifyListeners();
} else {
print('...');
}
}
void deleteProduct(String id) {
_items.removeWhere((prod) => prod.id == id);
notifyListeners();
}
}
This is my widget code which is directly interacting with my firebase : -
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/product.dart';
import '../providers/products.dart';
class EditProductScreen extends StatefulWidget {
static const routeName = '/edit-product';
#override
_EditProductScreenState createState() => _EditProductScreenState();
}
class _EditProductScreenState extends State<EditProductScreen> {
final _priceFocusNode = FocusNode();
final _descriptionFocusNode = FocusNode();
final _imageUrlController = TextEditingController();
final _imageUrlFocusNode = FocusNode();
final _form = GlobalKey<FormState>();
var _editedProduct = Product(
id: null,
title: '',
price: 0,
description: '',
imageUrl: '',
);
var _initValues = {
'title': '',
'description': '',
'price': '',
'imageUrl': '',
};
var _isInit = true;
var _isLoading = false;
#override
void initState() {
_imageUrlFocusNode.addListener(_updateImageUrl);
super.initState();
}
#override
void didChangeDependencies() {
if (_isInit) {
final productId = ModalRoute.of(context).settings.arguments as String;
if (productId != null) {
_editedProduct =
Provider.of<Products>(context, listen: false).findById(productId);
_initValues = {
'title': _editedProduct.title,
'description': _editedProduct.description,
'price': _editedProduct.price.toString(),
// 'imageUrl': _editedProduct.imageUrl,
'imageUrl': '',
};
_imageUrlController.text = _editedProduct.imageUrl;
}
}
_isInit = false;
super.didChangeDependencies();
}
#override
void dispose() {
_imageUrlFocusNode.removeListener(_updateImageUrl);
_priceFocusNode.dispose();
_descriptionFocusNode.dispose();
_imageUrlController.dispose();
_imageUrlFocusNode.dispose();
super.dispose();
}
void _updateImageUrl() {
if (!_imageUrlFocusNode.hasFocus) {
if ((!_imageUrlController.text.startsWith('http') &&
!_imageUrlController.text.startsWith('https')) ||
(!_imageUrlController.text.endsWith('.png') &&
!_imageUrlController.text.endsWith('.jpg') &&
!_imageUrlController.text.endsWith('.jpeg'))) {
return;
}
setState(() {});
}
}
void _saveForm() {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
_form.currentState.save();
setState(() {
_isLoading = true;
});
if (_editedProduct.id != null) {
Provider.of<Products>(context, listen: false)
.updateProduct(_editedProduct.id, _editedProduct);
setState(() {
_isLoading = false;
});
Navigator.of(context).pop();
} else {
Provider.of<Products>(context, listen: false)
.addProduct(_editedProduct)
.then((_) {
setState(() {
_isLoading = false;
});
Navigator.of(context).pop();
});
}
// Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Product'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: _saveForm,
),
],
),
body: _isLoading
? Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _form,
child: ListView(
children: <Widget>[
TextFormField(
initialValue: _initValues['title'],
decoration: InputDecoration(labelText: 'Title'),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_priceFocusNode);
},
validator: (value) {
if (value.isEmpty) {
return 'Please provide a value.';
}
return null;
},
onSaved: (value) {
_editedProduct = Product(
title: value,
price: _editedProduct.price,
description: _editedProduct.description,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite);
},
),
TextFormField(
initialValue: _initValues['price'],
decoration: InputDecoration(labelText: 'Price'),
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
focusNode: _priceFocusNode,
onFieldSubmitted: (_) {
FocusScope.of(context)
.requestFocus(_descriptionFocusNode);
},
validator: (value) {
if (value.isEmpty) {
return 'Please enter a price.';
}
if (double.tryParse(value) == null) {
return 'Please enter a valid number.';
}
if (double.parse(value) <= 0) {
return 'Please enter a number greater than zero.';
}
return null;
},
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: double.parse(value),
description: _editedProduct.description,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite);
},
),
TextFormField(
initialValue: _initValues['description'],
decoration: InputDecoration(labelText: 'Description'),
maxLines: 3,
keyboardType: TextInputType.multiline,
focusNode: _descriptionFocusNode,
validator: (value) {
if (value.isEmpty) {
return 'Please enter a description.';
}
if (value.length < 10) {
return 'Should be at least 10 characters long.';
}
return null;
},
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: _editedProduct.price,
description: value,
imageUrl: _editedProduct.imageUrl,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
width: 100,
height: 100,
margin: EdgeInsets.only(
top: 8,
right: 10,
),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child: _imageUrlController.text.isEmpty
? Text('Enter a URL')
: FittedBox(
child: Image.network(
_imageUrlController.text,
fit: BoxFit.cover,
),
),
),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Image URL'),
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
controller: _imageUrlController,
focusNode: _imageUrlFocusNode,
onFieldSubmitted: (_) {
_saveForm();
},
validator: (value) {
if (value.isEmpty) {
return 'Please enter an image URL.';
}
if (!value.startsWith('http') &&
!value.startsWith('https')) {
return 'Please enter a valid URL.';
}
if (!value.endsWith('.png') &&
!value.endsWith('.jpg') &&
!value.endsWith('.jpeg')) {
return 'Please enter a valid image URL.';
}
return null;
},
onSaved: (value) {
_editedProduct = Product(
title: _editedProduct.title,
price: _editedProduct.price,
description: _editedProduct.description,
imageUrl: value,
id: _editedProduct.id,
isFavorite: _editedProduct.isFavorite,
);
},
),
),
],
),
],
),
),
),
);
}
}
final url = Uri.parse('https://random-app-effc2-default-rtdb.firebaseio.com/products.json');
use this code instead of
const url = 'https://random-app-effc2-default-rtdb.firebaseio.com/products.json';
I try to implement Firebase authentication in my mobile app. (I am very new to this..)
I have the following code which attempts to create the user for the first time:
class WpAuthService {
FirebaseAuth _auth = FirebaseAuth.instance;
Stream<WpUser> get wpUser {
return _auth.authStateChanges().map((User firebaseUser) =>
(firebaseUser != null) ? WpUser(uid: firebaseUser.uid) : null);
}
Future<String> createUserWithEmail(email, password) async {
UserCredential userCredential;
try {
userCredential = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} on FirebaseAuthException catch (e) {
print(e.code + " - " + e.message);
return e.message;
}
return 'SUCCESS';
}
}
And in another file, I am trying to call the createUserWithEmail function as following:
class SignupForm extends StatefulWidget {
#override
_SignupFormState createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final _formKey = GlobalKey<FormState>();
var _userEmail = "";
var _userPassword = "";
String _opResult;
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter a valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email address'),
onSaved: (value) {
_userEmail = value;
},
),
TextFormField(
key: ValueKey('password'),
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 characters long.';
}
return null;
},
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onSaved: (value) {
_userPassword = value;
},
),
SizedBox(height: 12),
RaisedButton(
child: Text('Sign up',
style: Theme.of(context).textTheme.headline6),
onPressed: _trySubmit,
),
],
),
),
),
),
),
);
}
}
When I run the above piece of code, it prints out the following:
I/flutter ( 4032): MESSAGE:
I/flutter ( 4032): null
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 370 pos 10: 'data != null'
Looking at some other examples, I expect my implementation to work but something is seemingly wrong. How can I use the return value as String from the createUserWithEmail function?
by default your _opResult variable is null and your passing it to the Text widget of the Snackbar which throws that assertion.
You need either to first wait for the response to return or change your code to be inside the then method.
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
});
}
}
Your _opResult is null. And it is not equal to 'SUCCESS'. And you are trying to set it into Text() widget. Text widget requires a string parameter, not null.
You can set a default string when initializing the _opResult. Like this:
String _opResult = "";
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
Solved.
In the createUserWithEmail function, I was returning String values as the following:
return e.message;
return 'SUCCESS';
I updated it so that the function now returns as stated below:
return Future.value(e.message);
return Future.value('SUCCESS');
Everything else is the same and it worked.
But I saw some other examples where people were just returning String values from their functions. My problem is solved but is this behavior really expected? You are more than welcome to educate me.
I'm having troubles with creating users in my app, I get an error that says I don't have sufficient permissions even though I should have permission. My security rules are allowing users to be created so I don't really understand why I am getting this error. What's even more strange is that it works on my mates computer, he can create users (with the exact same userdetails). He is on a pc and I on a mac not sure if that matters?
The error I get is the following:
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: PlatformException(Error 7, FIRFirestoreErrorDomain, Missing or insufficient permissions.)
Our security rules are:
service cloud.firestore {
//match /databases/{database}/documents {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth != null;
}
match /Users/{userId} {
allow update: if request.auth.uid == userId;
}
match /Users/{document=**} {
allow create;
}
match /Recipes/{document=**} {
allow create: if request.auth != null;
allow update, delete: if request.resource.data.userId == request.auth.uid;
}
match /Recipes/{document=**}/newRatings {
allow create, update: if request.auth != null;
}
}
} ```
Our code for registering users is:
´´´ import 'package:cibus/pages/loginScreens/username_screen.dart';
import 'package:cibus/pages/loginScreens/verify_screen.dart';
import 'package:cibus/services/colors.dart';
import 'package:flutter/material.dart';
import 'package:cibus/services/login/auth.dart';
import 'package:cibus/services/constants.dart';
import 'package:cibus/pages/loginScreens/e-sign_in_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_user_stream/firebase_user_stream.dart';
import 'package:cibus/services/my_page_view.dart';
import 'package:provider/provider.dart';
import 'package:cibus/services/login/user.dart';
import 'package:cibus/services/database.dart';
import 'package:cibus/pages/loading_screen.dart';
const registerButtonColor = kTurquoise;
const formSizedBox = SizedBox(height: 20.0);
const EdgeInsets formPadding =
EdgeInsets.symmetric(vertical: 10.0, horizontal: 50.0);
const TextStyle textStyleErrorMessage =
TextStyle(color: Colors.red, fontSize: 14.0);
const TextStyle textStyleRegisterButton = TextStyle(color: Colors.white);
OutlineInputBorder textInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
);
class RegisterScreen extends StatefulWidget {
final Function toggleView;
RegisterScreen({this.toggleView});
#override
_RegisterScreenState createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final AuthService _auth = AuthService();
final _formKey = GlobalKey<FormState>();
bool loading = false;
bool isVerified = false;
//final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
//text field state
String email = '';
String password = '';
String error = '';
String name = '';
String description = '';
String _currentUsername;
int age = 0;
int dropdownValue = null;
final TextEditingController _pass = TextEditingController();
final TextEditingController _confirmPass = TextEditingController();
#override
Widget build(BuildContext context) {
return loading
? LoadingScreen()
: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
appBar: AppBar(
backgroundColor: Theme.of(context).backgroundColor,
elevation: 0.0,
title: Text('Sign up to Cibus', style: TextStyle(color: kCoral)),
actions: <Widget>[
FlatButton.icon(
icon: Icon(Icons.person, color: kCoral),
label: Text('Sign in', style: TextStyle(color: kCoral)),
onPressed: () {
//widget.toggleView();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return EmailSignIn();
},
),
);
},
),
],
),
body: Padding(
padding: formPadding,
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(children: <Widget>[
formSizedBox,
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Email',
),
validator: (val) =>
val.isEmpty ? 'Enter an email' : null,
onChanged: (val) {
setState(() => email = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Password',
),
obscureText: true,
controller: _pass,
validator: (String val) {
Pattern pattern =
r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!##\$&*~]).{8,}$';
RegExp regex = new RegExp(pattern);
print(val);
if (val.isEmpty) {
return 'Please enter password';
} else if (val.length < 8) {
return 'Minimum 8 characters required';
} else if (!val.contains(RegExp(r'[A-Z]'))) {
return 'One upper case letter required.';
} else if (!val.contains(RegExp(r'[a-z]'))) {
return 'One lower case letter required.';
} else if (!val.contains(RegExp(r'[0-9]'))) {
return 'One digit required.';
} else if (!val
.contains(RegExp(r'[!##$%^&*(),.?":{}|<>]'))) {
return 'One special character required.';
} /*
else {
if (!regex.hasMatch(val))
return 'Enter valid password: \n'
'Password must contain at least one upper case letter. \n'
'Password must contain at least one lower case letter. \n'
'Password must contain at least one digit. \n'
'Password must contain at least one special character.'; */
else
return null;
//}
},
onChanged: (val) {
setState(() => password = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: ' Re-enter Password',
),
obscureText: true,
controller: _confirmPass,
validator: (String val) {
if (val.isEmpty)
return 'Re-enter password field is empty';
if (val != _pass.text)
return 'passwords do not match';
return null;
},
onChanged: (val) {
setState(() => password = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Name',
),
validator: (val) =>
val.isEmpty ? 'Enter your name' : null,
onChanged: (val) {
setState(() => name = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Description',
),
minLines: 5,
maxLines: 10,
validator: (val) =>
val.isEmpty ? 'Enter your description' : null,
onChanged: (val) {
setState(() => description = val);
},
),
formSizedBox,
TextFormField(
maxLength: 20,
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Username',
),
validator: (val) {
if (val.length < 3)
return 'Username must be more than 2 characters';
/*else if (checkUsername == false)
return 'Username is allready taken';*/
return null;
},
onChanged: (val) {
setState(() {
_currentUsername = val;
print(_currentUsername);
});
}),
formSizedBox,
RaisedButton(
color: kCoral,
child: Text('Register', style: textStyleRegisterButton),
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() => loading = true);
bool isUsernameFree = await DatabaseService()
.isUsernameTaken(username: _currentUsername);
print(' checkUsername: $isUsernameFree');
if (!isUsernameFree) {
dynamic result =
await _auth.registerWithEmailAndPassword(
email, password, name, description, age);
if (result == null) {
setState(() {
error = 'Email is already registered';
_verificationEmailDialog();
});
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return VerifyScreen();
},
),
);
}
} else {
setState(() {
error = 'Username is already taken';
});
_usernameDialog();
}
}
},
),
Text(
error,
style: textStyleErrorMessage,
),
])),
),
));
}
Future<void> _usernameDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Username is already taken'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'Unfortunately it seems like your username is allready taken. Please try another one'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Aight bruh'),
onPressed: () {
setState(() {
loading = false;
});
Navigator.of(context)
.pop(); //TODO: When popping try to keep text in forms
},
),
],
);
},
);
}
Future<void> _verificationEmailDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Email is already in use'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'Unfortunately it seems like the email is already in use. Please try another one'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Aight bruh'),
onPressed: () {
setState(() {
loading = false;
});
Navigator.of(context)
.pop(); //TODO: When popping try to keep text in forms
},
),
],
);
},
);
}
}
and here is the registerWithEmailAndPassword function:
Future registerWithEmailAndPassword(String email, String password,
String name, String description, int age) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
user.sendEmailVerification();
user.isEmailVerified;
print('Email verification sent?');
//create a new document for the user with the uid
await DatabaseService(uid: user.uid)
.updateUserData(name: name, description: description, age: age);
await DatabaseService(uid: user.uid).updateUserPicture(
pictureURL:
'https://firebasestorage.googleapis.com/v0/b/independent-project-7edde.appspot.com/o/blank_profile_picture.png?alt=media&token=49efb712-d543-40ca-8e33-8c0fdb029ea5');
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
It was a mistake on my part with the Security rules. I had that users could create users but they could only read if they were authenticated, so when I checked if username was taken the error occurred since I did not have the read permissions. I just added the read part on users and it works
match /Users/{document=**} {
allow create: if true;
allow read: if true;
}
´´´