Last active
July 30, 2024 18:01
-
-
Save solanoize/67cfc6be5d6caca1b4d8395aed914722 to your computer and use it in GitHub Desktop.
CRUD Flutter REST API
This file contains hidden or 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
| // lib/screens/product_create_screen.dart | |
| import 'package:crud_flutter_rest/models/product.dart'; | |
| import 'package:crud_flutter_rest/services/product_service.dart'; | |
| import 'package:flutter/cupertino.dart'; | |
| import 'package:flutter/material.dart'; | |
| class ProductCreateScreen extends StatefulWidget { | |
| const ProductCreateScreen({super.key}); | |
| @override | |
| State<ProductCreateScreen> createState() => _ProductCreateScreenState(); | |
| } | |
| class _ProductCreateScreenState extends State<ProductCreateScreen> { | |
| TextEditingController titleController = TextEditingController(); | |
| TextEditingController imageController = TextEditingController( | |
| text: "https://loremflickr.com" | |
| "/640/480/animals", | |
| ); | |
| TextEditingController descriptionController = TextEditingController( | |
| text: "Andy shoes are designed to keeping " | |
| "in mind durability as well as trends", | |
| ); | |
| TextEditingController priceController = TextEditingController(text: "34.90"); | |
| Future<Product>? futureProduct; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| centerTitle: true, | |
| title: Text('New Product'), | |
| ), | |
| body: (futureProduct == null) ? buildLayout() : buildFutureBuilder(), | |
| ); | |
| } | |
| SingleChildScrollView buildLayout() { | |
| return SingleChildScrollView( | |
| child: Column( | |
| children: <Widget>[ | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: _title(), | |
| ), | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: _image(), | |
| ), | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: _description(), | |
| ), | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: _price(), | |
| ), | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: _save(), | |
| ), | |
| SizedBox(height: 32), | |
| ], | |
| ), | |
| ); | |
| } | |
| TextField _title() { | |
| OutlineInputBorder outlineInputBorder = OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(10), | |
| ); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input your product title", | |
| labelText: "Title", | |
| border: outlineInputBorder, | |
| ); | |
| return TextField( | |
| controller: titleController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _image() { | |
| OutlineInputBorder outlineInputBorder = | |
| OutlineInputBorder(borderRadius: BorderRadius.circular(10)); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input image URL like https://...", | |
| labelText: "Image (url)", | |
| border: outlineInputBorder); | |
| return TextField( | |
| keyboardType: TextInputType.url, | |
| controller: imageController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _description() { | |
| OutlineInputBorder outlineInputBorder = | |
| OutlineInputBorder(borderRadius: BorderRadius.circular(10)); | |
| InputDecoration decoration = InputDecoration( | |
| alignLabelWithHint: true, | |
| labelText: "Description", | |
| helperText: "Please input your product description", | |
| border: outlineInputBorder); | |
| return TextField( | |
| maxLines: 4, | |
| controller: descriptionController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _price() { | |
| OutlineInputBorder outlineInputBorder = | |
| OutlineInputBorder(borderRadius: BorderRadius.circular(10)); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input your product price", | |
| labelText: "Price", | |
| border: outlineInputBorder); | |
| return TextField( | |
| keyboardType: TextInputType.number, | |
| controller: priceController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextButton _save() { | |
| return TextButton( | |
| onPressed: () async { | |
| setState(() { | |
| futureProduct = ProductService.create( | |
| titleController.text, | |
| imageController.text, | |
| priceController.text, | |
| descriptionController.text, | |
| ); | |
| futureProduct?.then((Product product) { | |
| Navigator.pop(context); | |
| ScaffoldMessenger.of(context).showSnackBar(SnackBar( | |
| content: Text("Success creating product"), | |
| )); | |
| }); | |
| }); | |
| }, | |
| style: TextButton.styleFrom( | |
| backgroundColor: Colors.blue, | |
| minimumSize: Size(double.infinity, 30), | |
| padding: EdgeInsets.symmetric(vertical: 16), | |
| shape: const RoundedRectangleBorder( | |
| borderRadius: BorderRadius.all(Radius.circular(8)), | |
| ), | |
| ), | |
| child: Text( | |
| "Save", | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.bold, | |
| color: Colors.white, | |
| ), | |
| ), | |
| ); | |
| } | |
| FutureBuilder<Product> buildFutureBuilder() { | |
| return FutureBuilder<Product>( | |
| future: futureProduct, | |
| builder: (BuildContext context, AsyncSnapshot<Product> snapshot) { | |
| if (snapshot.connectionState == ConnectionState.none) { | |
| if (snapshot.hasError) { | |
| return Center( | |
| child: Text(snapshot.error.toString()), | |
| ); | |
| } | |
| return buildLayout(); | |
| } | |
| return Center( | |
| child: CircularProgressIndicator(), | |
| ); | |
| }, | |
| ); | |
| } | |
| } |
This file contains hidden or 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
| // lib/screens/product_list_screen.dart | |
| import 'package:crud_flutter_rest/models/product.dart'; | |
| import 'package:crud_flutter_rest/screens/product_create_screen.dart'; | |
| import 'package:crud_flutter_rest/screens/product_update_screen.dart'; | |
| import 'package:crud_flutter_rest/services/product_service.dart'; | |
| import 'package:flutter/material.dart'; | |
| class ProductListScreen extends StatefulWidget { | |
| const ProductListScreen({super.key}); | |
| @override | |
| State<ProductListScreen> createState() => _ProductListScreenState(); | |
| } | |
| class _ProductListScreenState extends State<ProductListScreen> { | |
| late Future<List<Product>> futureProducts; | |
| TextEditingController searchController = TextEditingController(); | |
| @override | |
| void initState() { | |
| super.initState(); | |
| futureProducts = ProductService.fetchAll(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| centerTitle: true, | |
| title: Text('Products'), | |
| ), | |
| floatingActionButton: FloatingActionButton( | |
| backgroundColor: Colors.blue, | |
| tooltip: 'New', | |
| onPressed: () { | |
| Navigator.push(context, MaterialPageRoute(builder: (context) { | |
| return ProductCreateScreen(); | |
| })); | |
| }, | |
| child: const Icon(Icons.add, color: Colors.white, size: 28), | |
| ), | |
| body: buildFutureBuilder(), | |
| ); | |
| } | |
| FutureBuilder<List<Product>> buildFutureBuilder() { | |
| return FutureBuilder<List<Product>>( | |
| future: futureProducts, | |
| builder: (BuildContext context, AsyncSnapshot<List<Product>> snapshot) { | |
| if (snapshot.connectionState == ConnectionState.done) { | |
| if (snapshot.hasData) { | |
| List<Product> products = snapshot.data!; | |
| return buildLayout(products: products); | |
| } else if (snapshot.hasError) { | |
| return Center( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.center, | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Text(snapshot.error.toString()), | |
| ElevatedButton(onPressed: onRefresh, child: Text("Refresh")) | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| return Center(child: CircularProgressIndicator()); | |
| }, | |
| ); | |
| } | |
| RefreshIndicator buildLayout({required List<Product> products}) { | |
| return RefreshIndicator( | |
| onRefresh: onRefresh, | |
| child: SafeArea( | |
| child: Column( | |
| children: <Widget>[ | |
| SizedBox(height: 32), | |
| Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 16), | |
| child: textFieldSearch(), | |
| ), | |
| SizedBox(height: 32), | |
| Expanded(child: productList(products)) | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<void> onRefresh() async { | |
| await Future<void>.delayed(const Duration(seconds: 3)); | |
| setState(() { | |
| futureProducts = ProductService.fetchAll(); | |
| }); | |
| } | |
| TextField textFieldSearch() { | |
| OutlineInputBorder outlineInputBorder = OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(10), | |
| ); | |
| IconButton iconButton = IconButton( | |
| onPressed: () { | |
| setState(() { | |
| futureProducts = | |
| ProductService.search({"search": searchController.text}); | |
| }); | |
| }, | |
| icon: Icon(Icons.search), | |
| ); | |
| InputDecoration decoration = InputDecoration( | |
| labelText: "Search", | |
| border: outlineInputBorder, | |
| suffixIcon: iconButton, | |
| ); | |
| return TextField( | |
| controller: searchController, | |
| decoration: decoration, | |
| ); | |
| } | |
| ListView productList(List<Product> products) { | |
| return ListView.separated( | |
| itemBuilder: (BuildContext build, int index) { | |
| return productDetail(products[index]); | |
| }, | |
| separatorBuilder: (BuildContext context, int index) => const Divider(), | |
| itemCount: products.length, | |
| ); | |
| } | |
| ListTile productDetail(Product product) { | |
| return ListTile( | |
| title: Text(product.title), | |
| subtitle: Text(product.description), | |
| trailing: Text(product.price), | |
| onTap: () { | |
| Navigator.push(context, MaterialPageRoute(builder: (context) { | |
| return ProductUpdateScreen(id: product.id); | |
| })); | |
| }, | |
| ); | |
| } | |
| } |
This file contains hidden or 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
| // lib/screens/product_update_screen.dart | |
| import 'package:crud_flutter_rest/models/product.dart'; | |
| import 'package:crud_flutter_rest/services/product_service.dart'; | |
| import 'package:crud_flutter_rest/widgets/componentes.dart'; | |
| import 'package:crud_flutter_rest/widgets/values.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/widgets.dart'; | |
| class ProductUpdateScreen extends StatefulWidget { | |
| const ProductUpdateScreen({super.key, required this.id}); | |
| final String id; | |
| @override | |
| State<ProductUpdateScreen> createState() => _ProductUpdateScreenState(); | |
| } | |
| class _ProductUpdateScreenState extends State<ProductUpdateScreen> { | |
| TextEditingController titleController = TextEditingController(); | |
| TextEditingController imageController = TextEditingController( | |
| text: "https://loremflickr.com/640/480/animals", | |
| ); | |
| TextEditingController descriptionController = TextEditingController( | |
| text: "Andy shoes are designed to keeping " | |
| "in mind durability as well as trends", | |
| ); | |
| TextEditingController priceController = TextEditingController(text: "34.90"); | |
| Future<Product>? futureProduct; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| futureProduct = ProductService.fetchOne(widget.id); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: _header(), | |
| body: buildFutureBuilder(), | |
| ); | |
| } | |
| void _initController(Product product) { | |
| titleController.text = product.title; | |
| imageController.text = product.image; | |
| descriptionController.text = product.description; | |
| priceController.text = product.price; | |
| } | |
| AppBar _header() { | |
| return AppBar( | |
| centerTitle: true, | |
| title: Text('Update Product'), | |
| ); | |
| } | |
| SingleChildScrollView buildLayout() { | |
| return SingleChildScrollView( | |
| child: Column( | |
| children: <Widget>[ | |
| sh32(), | |
| ph16(_title()), | |
| sh32(), | |
| ph16(_image()), | |
| sh32(), | |
| ph16(_description()), | |
| sh32(), | |
| ph16(_price()), | |
| sh32(), | |
| ph16(_save()), | |
| sh32() | |
| ], | |
| ), | |
| ); | |
| } | |
| TextField _title() { | |
| OutlineInputBorder outlineInputBorder = | |
| OutlineInputBorder(borderRadius: BorderRadius.circular(10)); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input your product title", | |
| labelText: "Title", | |
| border: outlineInputBorder); | |
| return TextField( | |
| controller: titleController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _image() { | |
| OutlineInputBorder outlineInputBorder = OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(10), | |
| ); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input image URL like https://...", | |
| labelText: "Image (url)", | |
| border: outlineInputBorder, | |
| ); | |
| return TextField( | |
| keyboardType: TextInputType.url, | |
| controller: imageController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _description() { | |
| OutlineInputBorder outlineInputBorder = OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(10), | |
| ); | |
| InputDecoration decoration = InputDecoration( | |
| alignLabelWithHint: true, | |
| labelText: "Description", | |
| helperText: "Please input your product description", | |
| border: outlineInputBorder); | |
| return TextField( | |
| maxLines: 4, | |
| controller: descriptionController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextField _price() { | |
| OutlineInputBorder outlineInputBorder = OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(10), | |
| ); | |
| InputDecoration decoration = InputDecoration( | |
| helperText: "Please input your product price", | |
| labelText: "Price", | |
| border: outlineInputBorder, | |
| ); | |
| return TextField( | |
| keyboardType: TextInputType.number, | |
| controller: priceController, | |
| decoration: decoration, | |
| ); | |
| } | |
| TextButton _save() { | |
| return TextButton( | |
| onPressed: () { | |
| setState(() { | |
| futureProduct = ProductService.update( | |
| widget.id, | |
| titleController.text, | |
| imageController.text, | |
| priceController.text, | |
| descriptionController.text, | |
| ); | |
| futureProduct?.whenComplete(() { | |
| info(context, "Sucess updating product."); | |
| Navigator.pop(context); | |
| }); | |
| }); | |
| }, | |
| style: TextButton.styleFrom( | |
| backgroundColor: Colors.blue, | |
| minimumSize: Size(double.infinity, 30), | |
| padding: EdgeInsets.symmetric(vertical: 16), | |
| shape: const RoundedRectangleBorder( | |
| borderRadius: BorderRadius.all( | |
| Radius.circular(8), | |
| ), | |
| ), | |
| ), | |
| child: Text("Save", | |
| style: TextStyle( | |
| fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)), | |
| ); | |
| } | |
| FutureBuilder<Product> buildFutureBuilder() { | |
| return FutureBuilder<Product>( | |
| future: futureProduct, | |
| builder: (BuildContext context, AsyncSnapshot<Product> snapshot) { | |
| if (snapshot.connectionState == ConnectionState.done) { | |
| if (snapshot.hasData) { | |
| Product product = snapshot.data!; | |
| _initController(product); | |
| return buildLayout(); | |
| } else if (snapshot.hasError) { | |
| return Center(child: Text(snapshot.error.toString())); | |
| } | |
| } | |
| return Center(child: CircularProgressIndicator()); | |
| }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment