EditProductScreen is used for both to update the existing product or to add new product, just difference is while updating product screen data is filled with initial values of existing product to update it. App is working properly to update the product but, throws error mentioned in title when I click button to add new product.
I used didChangeDependencies for getting id of existing product and also to set it’s initial values.
As new product doesn’t have id yet, if condition is applied in it, but still programm throws the error.
EditProductScreen
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-screen';
// As form filling will going to take place here we need to show changes on page
@override
State<EditProductScreen> createState() => _EditProductScreenState();
}
class _EditProductScreenState extends State<EditProductScreen> {
final _priceFocusNode = FocusNode();
final _descriptionFocusNode = FocusNode();
final _imageUrlController = TextEditingController();
final _imageUrlFocusNode = FocusNode();
// FocusNode stick in memory and use much storage (here we are using stateful widget not Provider), so we need to dispose them
final _form = GlobalKey<FormState>();
var _editedProduct = Product(
id: '',
title: '',
description: '',
price: 0,
imageUrl: '',
);
var _isInit = true;
var _initValues = {
'title': '',
'description': '',
'price': '',
'imageUrl': '',
};
@override
void dispose() {
_imageUrlFocusNode.removeListener(
_updateImageUrl); // dispose before diaposing _imageUrlFocusNode
_priceFocusNode.dispose();
_descriptionFocusNode.dispose();
_imageUrlController.dispose();
_imageUrlFocusNode.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
if (_isInit) {
final productId = ModalRoute.of(context)!.settings.arguments as String;
// print('Siddhant her is your Id $productId');
if (productId != null) {
// if (!productId.isEmpty)
_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 initState() {
_imageUrlFocusNode.addListener(
_updateImageUrl); // execute updateImageUrl whenever imageUrlFocusNode chandeg
super.initState();
}
void _updateImageUrl() {
if (!_imageUrlFocusNode.hasFocus) {
if ((_imageUrlController.text.isEmpty) ||
(!_imageUrlController.text.startsWith('http') &&
!_imageUrlController.text.startsWith('https')) ||
(!_imageUrlController.text.endsWith('jpeg') &&
!_imageUrlController.text.endsWith('png') &&
!_imageUrlController.text.endsWith('jpg'))) {
return;
}
setState(() {});
}
}
void _saveForm() {
final _isValid = _form.currentState!.validate(); // trigger all validators
if (!_isValid) {
return;
}
_form.currentState!.save();
if (_editedProduct.id != null) {
Provider.of<Products>(context, listen: false)
.updateProduct(_editedProduct.id, _editedProduct);
} else {
Provider.of<Products>(context, listen: false).addProduct(_editedProduct);
}
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Products'),
actions: [
// TextButton(
// onPressed: () {
// Navigator.of(context).pop();
// },
// child: Text(
// 'Cancle',
// style: TextStyle(
// color: Colors.red,
// fontSize: 17,
// ),
// ),
// ),
IconButton(
onPressed: _saveForm,
icon: Icon(Icons.save),
),
],
),
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _form,
child: ListView(
children: [
TextFormField(
initialValue: _initValues['title'],
decoration: InputDecoration(labelText: 'Title'),
textInputAction: TextInputAction.next,
validator: (value) {
if (value!.isEmpty) {
return 'Please provide a title';
}
return null;
},
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_priceFocusNode);
},
onSaved: (newValue) {
_editedProduct = Product(
id: _editedProduct.id,
title: newValue.toString(),
description: _editedProduct.description,
price: _editedProduct.price,
imageUrl: _editedProduct.imageUrl,
isFavourite: _editedProduct.isFavourite,
);
},
),
TextFormField(
initialValue: _initValues['price'],
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
focusNode: _priceFocusNode,
validator: (value) {
if (value!.isEmpty) {
return 'Please Enter a Price.';
}
if (double.tryParse(value) == null) {
// tryParse returns a null when it fails
return 'Please Enter a Valid Price.';
}
if (double.parse(value) < 0) {
return 'Price can not be Negative.';
}
return null;
},
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_descriptionFocusNode);
},
onSaved: (newValue) {
_editedProduct = Product(
id: _editedProduct.id,
title: _editedProduct.title,
description: _editedProduct.description,
price: double.parse(newValue.toString()),
imageUrl: _editedProduct.imageUrl,
isFavourite: _editedProduct.isFavourite,
);
},
),
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: (newValue) {
_editedProduct = Product(
id: _editedProduct.id,
title: _editedProduct.title,
description: newValue.toString(),
price: _editedProduct.price,
imageUrl: _editedProduct.imageUrl,
isFavourite: _editedProduct.isFavourite,
);
},
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
height: 100,
width: 100,
margin: EdgeInsets.only(top: 8, right: 10),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.black87),
),
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, // TextFormField already has a controller but here in case of imageUrl we need our own controller to preview the image
focusNode: _imageUrlFocusNode,
validator: (value) {
if (value!.isEmpty) {
return 'Please enter an URL';
}
if (!value.startsWith('http') &&
!value.startsWith('https')) {
return 'Enter a valid URL';
}
if (!value.endsWith('jpeg') &&
!value.endsWith('png') &&
!value.endsWith('jpg')) {
return 'Enter a valid Image URL';
}
return null;
},
onSaved: (newValue) {
_editedProduct = Product(
id: _editedProduct.id,
title: _editedProduct.title,
description: _editedProduct.description,
price: _editedProduct.price,
imageUrl: newValue.toString(),
isFavourite: _editedProduct.isFavourite,
);
},
// onFieldSubmitted: (_) {
// _saveForm();
// },
),
),
],
),
],
),
),
),
);
}
}
UserProductItem
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../screens/edit_product_screen.dart';
import '../providers/products.dart';
class UserProductItem extends StatelessWidget {
final String id;
final String title;
final String imageUrl;
UserProductItem(this.id, this.title, this.imageUrl);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
trailing: Container(
width: 100,
child: Row(children: [
IconButton(
onPressed: () {
Navigator.of(context)
.pushNamed(EditProductScreen.routeName, arguments: id);
// print(id);
},
icon: Icon(Icons.edit),
color: Theme.of(context).primaryColor,
),
IconButton(
onPressed: () {
Provider.of<Products>(context, listen: false).deleteProduct(id);
},
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
),
]),
),
);
}
}
2
Answers
You might consider using this while creating product, change the ” to 0 , if the parameter is of type int
The error can occur from
Instead of using
as
it would be better to accept null value.now while using it, do a null check like