Created
November 26, 2019 05:23
-
-
Save danielsmykowski1/8b82e013e303b59adf3a2e905f8f7f8f to your computer and use it in GitHub Desktop.
A Flutter component using Bloc pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:io'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:flutter_bloc/flutter_bloc.dart'; | |
import 'package:image_picker/image_picker.dart'; | |
import 'package:lumbungrempah_common/bloc/commodity/commodity.dart'; | |
import 'package:lumbungrempah_common/model/commodity.dart'; | |
import 'package:lumbungrempah_flutter/api/commodity_service_api_impl.dart'; | |
import 'package:lumbungrempah_flutter/api/storage_service_api_impl.dart'; | |
import 'package:lumbungrempah_flutter/screens/GetLocation.dart'; | |
import 'package:lumbungrempah_flutter/utils/widget_utils.dart'; | |
import 'package:lumbungrempah_flutter/widgets/loading_view.dart'; | |
class CommodityEdit extends StatefulWidget { | |
final String qrCode; | |
CommodityEdit({Key key, @required this.qrCode}) : super(key: key); | |
@override | |
_CommodityEditState createState() { | |
return _CommodityEditState(); | |
} | |
} | |
class _CommodityEditState extends State<CommodityEdit> { | |
CommodityBloc _screenBloc; | |
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); | |
TextEditingController _nameController = TextEditingController(); | |
TextEditingController _descController = TextEditingController(); | |
TextEditingController _priceController = TextEditingController(); | |
TextEditingController _unitController = TextEditingController(); | |
TextEditingController _quantityController = TextEditingController(); | |
String errorMessage; | |
bool isError = false; | |
File _image; | |
String _imageUrl; | |
num _longitude, _latitude; | |
String _address; | |
@override | |
void initState() { | |
_screenBloc = CommodityBloc(CommodityServiceApiImpl()); | |
_screenBloc.dispatch(LoadInitialData(qrCode: widget.qrCode)); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return BlocListener( | |
bloc: _screenBloc, | |
listener: (BuildContext context, CommodityState state) { | |
print('state: $state'); | |
_imageUrl = null; | |
if (state is CommodityFailure) { | |
showErrorSnackbar(_scaffoldKey, state.error); | |
} else if (state is CommoditySaveSuccess) { | |
Navigator.of(context).pop('Commodity saved successfully.'); | |
} else if (state.commodity != null) { | |
_nameController.text = state.commodity.name; | |
_descController.text = state.commodity.desc; | |
_priceController.text = state.commodity.price.toString(); | |
_unitController.text = state.commodity.unit; | |
_quantityController.text = state.commodity.quantity.toString(); | |
_imageUrl = state.commodity.imageUrl; | |
_longitude = null; | |
_latitude = state.commodity.latitude; | |
_address = state.commodity.address; | |
} | |
}, | |
child: BlocBuilder<CommodityBloc, CommodityState>( | |
bloc: _screenBloc, | |
builder: (BuildContext context, state) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(state.commodity == null | |
? 'Create Commodity' | |
: 'Update Commodity'), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text( | |
'Save', | |
style: TextStyle(color: Colors.white), | |
), | |
onPressed: state.isLoading | |
? null | |
: () async { | |
if (_isValidate()) { | |
_scaffoldKey.currentState.showSnackBar(SnackBar( | |
duration: Duration(minutes: 5), | |
content: Row( | |
mainAxisAlignment: | |
MainAxisAlignment.spaceBetween, | |
children: [ | |
Text('Loading...'), | |
CircularProgressIndicator(), | |
], | |
), | |
)); | |
await _onSaveButtonPressed(state); | |
} else { | |
setState(() { | |
isError = true; | |
}); | |
} | |
}, | |
) | |
], | |
), | |
key: _scaffoldKey, | |
body: _getBody(state), | |
); | |
}, | |
), | |
); | |
} | |
bool _isValidate() { | |
if (_nameController.text.trim().isEmpty || | |
_descController.text.trim().isEmpty || | |
_priceController.text.trim().isEmpty || | |
_unitController.text.trim().isEmpty || | |
_quantityController.text.trim().isEmpty) { | |
errorMessage = 'Please fill all required fields'; | |
return false; | |
} else { | |
num n = num.tryParse(_priceController.text.trim()); | |
if (n == null) { | |
errorMessage = 'Please enter a number for price'; | |
return false; | |
} | |
n = num.tryParse(_quantityController.text.trim()); | |
if (n == null) { | |
errorMessage = 'Please enter a number for quantity'; | |
return false; | |
} | |
return true; | |
} | |
} | |
Future _onSaveButtonPressed(CommodityState state) async { | |
if (_image != null) { | |
_imageUrl = | |
await StorageServiceApiImpl().uploadFile('commodityimage', _image); | |
print('uploaded image url: $_imageUrl'); | |
} | |
Commodity commodity = state.commodity; | |
if (commodity == null) { | |
commodity = Commodity( | |
name: _nameController.text.trim(), | |
desc: _descController.text.trim(), | |
price: num.parse(_priceController.text.trim()), | |
unit: _unitController.text.trim(), | |
quantity: num.parse(_quantityController.text.trim()), | |
kapuToken: widget.qrCode, | |
imageUrl: _imageUrl, | |
); | |
_screenBloc.dispatch(CreateCommodity(commodity: commodity)); | |
} else { | |
commodity.name = _nameController.text.trim(); | |
commodity.desc = _descController.text.trim(); | |
commodity.price = num.parse(_priceController.text.trim()); | |
commodity.unit = _unitController.text.trim(); | |
commodity.quantity = num.parse(_quantityController.text.trim()); | |
commodity.updatedAt = DateTime.now().toUtc(); | |
commodity.imageUrl = _imageUrl; | |
_screenBloc.dispatch(UpdateCommodity(commodity: commodity)); | |
} | |
} | |
Widget _getBody(CommodityState state) { | |
if (state.isLoading) { | |
print('Loading'); | |
return LoadingView(); | |
} else { | |
return ListView( | |
children: <Widget>[ | |
_error(), | |
_getTextField( | |
title: '*Name:', | |
controller: _nameController, | |
validator: (String value) { | |
if (value.trim().isEmpty) { | |
return 'Please enter commodity name'; | |
} else { | |
return null; | |
} | |
}), | |
_getImageField(state), | |
_getTextField( | |
title: '*Description:', | |
controller: _descController, | |
lines: 3, | |
validator: (String value) { | |
if (value.trim().isEmpty) { | |
return 'Please enter commodity description'; | |
} else { | |
return null; | |
} | |
}), | |
_getTextField( | |
title: '*Price:', | |
controller: _priceController, | |
isNumber: true, | |
validator: (String value) { | |
if (value.trim().isEmpty) { | |
return 'Please enter commodity price'; | |
} else { | |
final n = num.tryParse(value.trim()); | |
if (n == null) { | |
return 'Please enter a number for price'; | |
} | |
return null; | |
} | |
}), | |
_getTextField( | |
title: '*Unit:', | |
controller: _unitController, | |
validator: (String value) { | |
if (value.isEmpty) { | |
return 'Please enter commodity unit'; | |
} else { | |
return null; | |
} | |
}), | |
_getTextField( | |
title: '*Quantity:', | |
controller: _quantityController, | |
isNumber: true, | |
validator: (String value) { | |
if (value.isEmpty) { | |
return 'Please enter commodity quantity'; | |
} else { | |
final n = num.tryParse(value.trim()); | |
if (n == null) { | |
return 'Please enter a number for quantity'; | |
} | |
return null; | |
} | |
}), | |
_getLocationField(state), | |
], | |
); | |
} | |
} | |
Widget _error() { | |
return (isError) | |
? Padding( | |
padding: const EdgeInsets.all(10.0), | |
child: Center( | |
child: Text( | |
errorMessage, | |
style: TextStyle(color: Colors.red, fontSize: 16), | |
), | |
), | |
) | |
: Container(); | |
} | |
Widget _getTextField( | |
{String title, | |
TextEditingController controller, | |
int lines = 1, | |
bool isNumber = false, | |
String Function(String) validator}) { | |
return Padding( | |
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Padding( | |
padding: const EdgeInsets.only(bottom: 5), | |
child: Text(title), | |
), | |
TextFormField( | |
validator: validator, | |
autovalidate: isError, | |
decoration: InputDecoration( | |
border: OutlineInputBorder(), | |
contentPadding: EdgeInsets.all(10)), | |
controller: controller, | |
maxLines: lines, | |
keyboardType: isNumber ? TextInputType.number : TextInputType.text, | |
), | |
], | |
), | |
); | |
} | |
Widget _getImageField(CommodityState state) { | |
print('_getImageField, imageUrl: $_imageUrl'); | |
return Padding( | |
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Container( | |
width: 200, | |
height: 150, | |
decoration: BoxDecoration( | |
border: Border.all( | |
width: 2, | |
color: Colors.blue, | |
), | |
), | |
child: (_image != null) | |
? Image.file(_image, fit: BoxFit.cover) | |
: ((_imageUrl != null && _imageUrl != '') | |
? Image.network(_imageUrl, fit: BoxFit.cover) | |
: Image.asset( | |
'assets/placeholder.png', | |
fit: BoxFit.cover | |
)), | |
margin: EdgeInsets.only(right: 10), | |
), | |
Column( | |
children: <Widget>[ | |
IconButton( | |
icon: Icon(Icons.image), | |
onPressed: () { | |
getImage(false); | |
}, | |
iconSize: 40, | |
), | |
IconButton( | |
icon: Icon(Icons.camera_alt), | |
onPressed: () { | |
getImage(true); | |
}, | |
iconSize: 40, | |
), | |
], | |
) | |
], | |
), | |
); | |
} | |
Future getImage(bool isCamera) async { | |
var image = await ImagePicker.pickImage( | |
source: isCamera ? ImageSource.camera : ImageSource.gallery); | |
setState(() { | |
_image = image; | |
}); | |
} | |
Widget _getLocationField(CommodityState state) { | |
return Padding( | |
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Padding( | |
padding: const EdgeInsets.only(bottom: 10), | |
child: Text('Location'), | |
), | |
Padding( | |
padding: EdgeInsets.only(bottom: 10, left: 20), | |
child: Text( | |
'Longitude: ${_longitude == null ? 'unset' : _longitude}', | |
style: TextStyle( | |
fontSize: 17, | |
), | |
), | |
), | |
Padding( | |
padding: EdgeInsets.only(bottom: 10, left: 20), | |
child: Text( | |
'Latitude : ${_latitude == null ? 'unset' : _latitude}', | |
style: TextStyle( | |
fontSize: 17, | |
), | |
), | |
), | |
Padding( | |
padding: EdgeInsets.only(bottom: 10, left: 20), | |
child: Text( | |
'Address : ${_address == null ? 'unset' : _longitude}', | |
style: TextStyle( | |
fontSize: 17, | |
), | |
), | |
), | |
Container( | |
padding: EdgeInsets.only(left: 20), | |
child: RaisedButton( | |
onPressed: _onGetLocationPressed, | |
child: Text( | |
'Get Location', | |
style: TextStyle( | |
color: Colors.white, | |
fontSize: 15, | |
), | |
), | |
padding: EdgeInsets.only(left: 20, right: 20), | |
color: Colors.blue, | |
), | |
), | |
], | |
), | |
); | |
} | |
void _onGetLocationPressed() async { | |
String result = await Navigator.push( | |
context, | |
MaterialPageRoute( | |
builder: (BuildContext context) => GetLocation( | |
latitude: _latitude, | |
longitude: _longitude, | |
address: _address, | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment