Xata + Flutter: A getting started guide.
This post discusses what Xata is and provides a detailed step-by-step guide to using it to build a basic project management application with Flutter
Demola Malomo
Feb 05 2024
10 min read
Xata is a serverless data platform for building modern and robust applications. Built on top of PostgreSQL, Xata provides a unified REST API for efficient data management. Setting itself apart from other data platforms, Xata introduces unique functionalities that significantly streamline the developer workflow. Here are some key benefits of integrating Xata into any application:
- Robust file management: Xata provides APIs and SDKs to manage and securely upload images, documents, and more, directly to a database record.
- Multiple environments support and workflow: With Xata, creating isolated production environments for testing, staging, or feature releases is seamless.
- Fast search support: Xata automatically indexes uploaded data, facilitating fast and efficient data searches across tables and branches.
- AI support: Xata offers vector embedding and AI solutions that empower the development of intelligent applications.
To experience the capabilities of Xata, we will build a project management application with Xata and Flutter. The project repository can be found here.
Prerequisites
To follow along with this tutorial, the following are needed:
- Basic understanding of Dart and Flutter
- Xata account. Signup is free
Setup the database on Xata
To get started, log into the Xata workspace and create a project
database. Inside the project
database, create a Project
table and add columns as shown below:
Column type | Column name |
---|---|
String | name |
Text | description |
String | status |
Inside a table, Xata automatically adds an id
, xata.createdAt
, xata.updatedAt
, and xata.version
columns that we can also leverage to perform advanced data operations.
Get the Database URL and set up the API Key
To securely connect to the database, Xata provides a unique and secure URL for accessing it. To get the database URL, click the Get code snippet button and copy the URL. Then click the API Key link, add a new key, save and copy the API key.
We must keep the copied URL and API key as they will come in handy when building our application.
Building the project management application with Xata and Flutter
To get started, we need to clone the project by navigating to the desired directory and running the command below:
1git clone https://github.com/Mr-Malomz/flutter_xata.git
Running the project
We need to install the project dependencies by running the command below:
1flutter pub get
Then, run the project using the following command:
flutter run
The command above will run the application on the selected device.
Setup environment variable
Next, we must add our database URL and API key as an environment variable. To do this, create .env
file in the root directory and add the copied URL and API key.
1XATA_DATABASE_URL= <REPLACE WITH THE COPIED DATABASE URL> 2XATA_API_KEY=<REPLACE WITH THE COPIED API KEY>
Create the API models
To represent the application data, we need to create a utils.dart
file in the lib
folder and add the snippet below. The model will cater to converting the response sent from the JSON response to a Dart object and JSON serialization
1class Project { 2 String? id; 3 String name; 4 String description; 5 String status; 6 7 Project({ 8 this.id, 9 required this.name, 10 required this.description, 11 required this.status, 12 }); 13 14 Map<dynamic, dynamic> toJson() { 15 return { 16 "name": name, 17 "description": description, 18 "status": status, 19 }; 20 } 21 22 factory Project.fromJson(Map<dynamic, dynamic> json) { 23 return Project( 24 id: json['id'], 25 name: json['name'], 26 description: json['description'], 27 status: json['status'], 28 ); 29 } 30}
Create a service
With that done, we need to create a service file to separate the application core logic from the UI. To do this, create a xata_service.dart
file inside the lib
directory. Then, add the snippet below:
1import 'package:dio/dio.dart'; 2import 'package:flutter_dotenv/flutter_dotenv.dart'; 3import 'package:flutter_xata/utils.dart'; 4 5class XataService { 6 final _dio = Dio(); 7 static String _apiKey = dotenv.get("XATA_API_KEY"); 8 static String _baseURL = dotenv.get("XATA_DATABASE_URL"); 9 10 final _headers = { 11 "content-type": "application/json", 12 "AUTHORIZATION": "Bearer $_apiKey", 13 }; 14 15 Future<List<Project>> getProjects() async { 16 var response = await _dio.post( 17 "$_baseURL:main/tables/Project/query", 18 options: Options(headers: _headers), 19 ); 20 21 if (response.statusCode == 200) { 22 var respList = response.data['records'] as List; 23 var projectList = respList.map((json) => Project.fromJson(json)).toList(); 24 return projectList; 25 } else { 26 throw Exception('Error getting projects'); 27 } 28 } 29 30 Future<Project> getSingleProject(String id) async { 31 var response = await _dio.get( 32 "$_baseURL:main/tables/Project/data/$id", 33 options: Options(headers: _headers), 34 ); 35 36 if (response.statusCode == 200) { 37 var resp = response.data; 38 var project = Project.fromJson(resp); 39 return project; 40 } else { 41 throw Exception('Error getting project'); 42 } 43 } 44 45 Future createProject(Project newProject) async { 46 var response = await _dio.post( 47 "$_baseURL:main/tables/Project/data", 48 options: Options(headers: _headers), 49 data: newProject.toJson(), 50 ); 51 52 if (response.statusCode == 201) { 53 return response.data; 54 } else { 55 throw Exception('Error creating project'); 56 } 57 } 58 59 Future updateProject(String id, Project updatedProject) async { 60 var response = await _dio.put( 61 "$_baseURL:main/tables/Project/data/$id", 62 options: Options(headers: _headers), 63 data: updatedProject.toJson(), 64 ); 65 66 if (response.statusCode == 200) { 67 return response.data; 68 } else { 69 throw Exception('Error updating project'); 70 } 71 } 72 73 Future deleteProject(String id) async { 74 var response = await _dio.delete( 75 "$_baseURL:main/tables/Project/data/$id", 76 options: Options(headers: _headers), 77 ); 78 79 if (response.statusCode == 204) { 80 return response.data; 81 } else { 82 throw Exception('Error deleting project'); 83 } 84 } 85}
The snippet above does the following:
- Imports the required dependencies
- Creates an
XataService
class with_apiKey
,_baseURL
, and_headers
properties to connect to the Xata instance - Creates a
getProjects
,getSingleProject
,createProject
,updateProject
, anddeleteProject
method that uses the_storage
property to get, save, and preview images
Consuming the service
With that done, we can use the service to perform the required operation.
Get all projects
To get started, we need to modify the home.dart
file in the screens
directory and update it by doing the following:
First, we need to import the required dependencies and create a method to get the list of projects saved in the database:
1//Other imports goes here 2import 'package:flutter_xata/utils.dart'; 3import 'package:flutter_xata/xata_service.dart'; 4 5class Home extends StatefulWidget { 6 const Home({super.key}); 7 8 State<Home> createState() => _HomeState(); 9} 10 11class _HomeState extends State<Home> { 12 late List<Project> projects; 13 bool _isLoading = false; 14 bool _isError = false; 15 16 17 void initState() { 18 getProjects(); 19 super.initState(); 20 } 21 22 getProjects() { 23 setState(() { 24 _isLoading = true; 25 }); 26 XataService().getProjects().then((value) { 27 setState(() { 28 projects = value; 29 _isLoading = false; 30 }); 31 }).catchError((onError) { 32 setState(() { 33 _isLoading = false; 34 _isError = true; 35 }); 36 }); 37 } 38 39 40 Widget build(BuildContext context) { 41 //UI CODE GOES HERE 42 } 43}
The snippet above does the following:
- Imports the required dependencies
- Lines 12-14: Creates the
projects
,_isLoading
, and_isError
properties to manage the application state - Lines 16-37: Creates a
getProjects
method to get the list of available projects on the database using theXataService().getProjects
and set states accordingly
Lastly, we need to modify the UI to use the states and method created to get the projects list.
1//imports goes here 2 3class Home extends StatefulWidget { 4 //code goes here 5} 6 7class _HomeState extends State<Home> { 8 //states goes here 9 10 11 void initState() { 12 //code goes here 13 } 14 15 getProjects() { 16 //code goes here 17 } 18 19 20 Widget build(BuildContext context) { 21 return _isLoading 22 ? const Center( 23 child: CircularProgressIndicator( 24 color: Colors.blue, 25 )) 26 : _isError 27 ? const Center( 28 child: Text( 29 'Error getting projects', 30 style: TextStyle( 31 color: Colors.red, 32 fontWeight: FontWeight.bold, 33 ), 34 ), 35 ) 36 : Scaffold( 37 appBar: AppBar( 38 title: const Text('Projects', 39 style: TextStyle(color: Colors.white)), 40 backgroundColor: Colors.black, 41 ), 42 body: ListView.builder( 43 itemCount: projects.length, 44 itemBuilder: (context, index) { 45 return InkWell( 46 onTap: () { 47 Navigator.push( 48 context, 49 MaterialPageRoute( 50 builder: (context) => 51 Detail(id: projects[index].id as String), 52 ), 53 ); 54 }, 55 child: Container( 56 decoration: const BoxDecoration( 57 border: Border( 58 bottom: BorderSide(width: .5, color: Colors.grey), 59 ), 60 ), 61 padding: EdgeInsets.fromLTRB(10, 20, 10, 20), 62 child: Row( 63 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 64 children: [ 65 Expanded( 66 flex: 7, 67 child: Column( 68 crossAxisAlignment: CrossAxisAlignment.start, 69 children: [ 70 Text( 71 projects[index].name, 72 style: TextStyle( 73 color: Colors.black, 74 fontWeight: FontWeight.w800), 75 ), 76 const SizedBox(height: 10.0), 77 Row( 78 children: [ 79 Icon(projects[index].status == "Started" 80 ? Icons.start 81 : Icons.stop_circle_outlined), 82 const SizedBox(width: 5.0), 83 Text(projects[index].status) 84 ], 85 ), 86 const SizedBox(height: 10.0), 87 Text(projects[index].description) 88 ], 89 ), 90 ), 91 const Column( 92 crossAxisAlignment: CrossAxisAlignment.end, 93 children: [ 94 SizedBox(height: 10.0), 95 Icon(Icons.arrow_forward_ios_rounded) 96 ], 97 ), 98 ], 99 ), 100 ), 101 ); 102 }, 103 ), 104 floatingActionButton: FloatingActionButton( 105 onPressed: () { 106 Navigator.push( 107 context, 108 MaterialPageRoute( 109 builder: (context) => const Create(), 110 ), 111 ); 112 }, 113 backgroundColor: Colors.black, 114 tooltip: 'Create project', 115 child: const Icon( 116 Icons.add, 117 color: Colors.white, 118 ), 119 ), 120 ); 121 } 122}
Create project
To create a project, we need to modify the create.dart
file in the screen
directory and update it by doing the following:
First, we need to import the required dependency and create a method to save the project to the database:
1//other import goes here 2import 'package:flutter_xata/screens/home.dart'; 3import 'package:flutter_xata/utils.dart'; 4import 'package:flutter_xata/xata_service.dart'; 5 6class Create extends StatefulWidget { 7 const Create({ 8 Key? key, 9 }) : super(key: key); 10 11 State<Create> createState() => _CreateState(); 12} 13 14class _CreateState extends State<Create> { 15 final _formKey = GlobalKey<FormState>(); 16 var _selected = ''; 17 var _dropdownItems = ["Started", "Not_Started"]; 18 final TextEditingController _name = TextEditingController(); 19 final TextEditingController _description = TextEditingController(); 20 bool _isLoading = false; 21 22 createProject() { 23 setState(() { 24 _isLoading = true; 25 }); 26 27 Project newProject = Project( 28 name: _name.text, 29 description: _description.text, 30 status: _selected, 31 ); 32 33 XataService().createProject(newProject).then((value) { 34 setState(() { 35 _isLoading = false; 36 }); 37 ScaffoldMessenger.of(context).showSnackBar( 38 const SnackBar(content: Text('Project created successfully!')), 39 ); 40 Navigator.push( 41 context, 42 MaterialPageRoute(builder: (context) => const Home()), 43 ); 44 }).catchError((onError) { 45 setState(() { 46 _isLoading = false; 47 }); 48 ScaffoldMessenger.of(context).showSnackBar( 49 const SnackBar(content: Text('Error creating project!')), 50 ); 51 }); 52 } 53 54 55 Widget build(BuildContext context) { 56 //UI CODE GOES HERE 57 } 58}
The snippet above does the following:
- Import the required dependencies
- Lines 18-20: Creates the
_name
,_description
, and_isLoading
properties to manage the application state - Lines 22-52: Creates a
createProject
method to save the project using theXataService().createProject
service, set states accordingly
Lastly, we need to modify the UI to use the method and states created to process the form.
1//import goes here 2 3class Create extends StatefulWidget { 4 //code goes here 5} 6 7class _CreateState extends State<Create> { 8 //states goes here 9 10 createProject() { 11 //code goes here 12 } 13 14 15 Widget build(BuildContext context) { 16 return Scaffold( 17 appBar: AppBar( 18 title: 19 const Text("Create project", style: TextStyle(color: Colors.white)), 20 backgroundColor: Colors.black, 21 iconTheme: const IconThemeData(color: Colors.white), 22 ), 23 body: Padding( 24 padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0), 25 child: Form( 26 key: _formKey, 27 child: Column( 28 children: [ 29 Column( 30 crossAxisAlignment: CrossAxisAlignment.start, 31 children: [ 32 Column( 33 crossAxisAlignment: CrossAxisAlignment.start, 34 children: [ 35 const Text( 36 'Name', 37 style: TextStyle( 38 color: Colors.grey, 39 fontSize: 14.0, 40 ), 41 ), 42 const SizedBox(height: 5.0), 43 TextFormField( 44 controller: _name, 45 validator: (value) { 46 if (value == null || value.isEmpty) { 47 return 'Please input name'; 48 } 49 return null; 50 }, 51 decoration: InputDecoration( 52 contentPadding: const EdgeInsets.symmetric( 53 vertical: 10, horizontal: 20), 54 hintText: "input name", 55 fillColor: Colors.white, 56 focusedBorder: OutlineInputBorder( 57 borderRadius: BorderRadius.circular(10), 58 borderSide: const BorderSide(color: Colors.grey), 59 ), 60 enabledBorder: OutlineInputBorder( 61 borderRadius: BorderRadius.circular(10), 62 borderSide: const BorderSide(color: Colors.grey), 63 ), 64 errorBorder: OutlineInputBorder( 65 borderRadius: BorderRadius.circular(10), 66 borderSide: const BorderSide(color: Colors.red), 67 ), 68 ), 69 keyboardType: TextInputType.text, 70 maxLines: null, 71 ), 72 const SizedBox(height: 30.0), 73 const Text( 74 'Status', 75 style: TextStyle( 76 color: Colors.grey, 77 fontSize: 14.0, 78 ), 79 ), 80 const SizedBox(height: 5.0), 81 DropdownButtonFormField( 82 items: _dropdownItems.map((String item) { 83 return DropdownMenuItem( 84 value: item, 85 child: Text(item), 86 ); 87 }).toList(), 88 onChanged: (value) { 89 setState(() => _selected = value!); 90 }, 91 decoration: InputDecoration( 92 contentPadding: const EdgeInsets.symmetric( 93 vertical: 10, horizontal: 20), 94 hintText: "select status", 95 focusedBorder: OutlineInputBorder( 96 borderRadius: BorderRadius.circular(10), 97 borderSide: const BorderSide(color: Colors.grey), 98 ), 99 enabledBorder: OutlineInputBorder( 100 borderRadius: BorderRadius.circular(10), 101 borderSide: const BorderSide(color: Colors.grey), 102 ), 103 errorBorder: OutlineInputBorder( 104 borderRadius: BorderRadius.circular(10), 105 borderSide: const BorderSide(color: Colors.red), 106 ), 107 ), 108 ), 109 const SizedBox(height: 30.0), 110 const Text( 111 'Description', 112 style: TextStyle( 113 color: Colors.grey, 114 fontSize: 14.0, 115 ), 116 ), 117 const SizedBox(height: 5.0), 118 TextFormField( 119 controller: _description, 120 validator: (value) { 121 if (value == null || value.isEmpty) { 122 return 'Please input ydescription'; 123 } 124 return null; 125 }, 126 maxLines: 5, 127 decoration: InputDecoration( 128 contentPadding: const EdgeInsets.symmetric( 129 vertical: 10, horizontal: 20), 130 hintText: "input description", 131 fillColor: Colors.white, 132 focusedBorder: OutlineInputBorder( 133 borderRadius: BorderRadius.circular(10), 134 borderSide: const BorderSide(color: Colors.grey), 135 ), 136 enabledBorder: OutlineInputBorder( 137 borderRadius: BorderRadius.circular(10), 138 borderSide: const BorderSide(color: Colors.grey), 139 ), 140 errorBorder: OutlineInputBorder( 141 borderRadius: BorderRadius.circular(10), 142 borderSide: const BorderSide(color: Colors.red), 143 ), 144 ), 145 keyboardType: TextInputType.multiline, 146 ), 147 ], 148 ), 149 ], 150 ), 151 const SizedBox(height: 30.0), 152 SizedBox( 153 height: 45, 154 width: double.infinity, 155 child: TextButton( 156 onPressed: _isLoading 157 ? null 158 : () { 159 if (_formKey.currentState!.validate()) { 160 createProject(); 161 } 162 }, 163 style: ButtonStyle( 164 backgroundColor: 165 MaterialStateProperty.all<Color>(Colors.black), 166 ), 167 child: const Text( 168 'Create project', 169 style: TextStyle( 170 color: Colors.white, 171 fontWeight: FontWeight.bold, 172 fontSize: 14.0, 173 ), 174 ), 175 ), 176 ), 177 ], 178 ), 179 ), 180 ), 181 ); 182 } 183}
Get a project, edit a project and delete project
To perform the stated operations in our application, we need to modify the detail.dart
file in the screens
directory and update it by doing the following:
First, we need to import the required dependencies and create methods to get, edit, and delete projects.
1//import goee here 2import 'package:flutter_xata/screens/home.dart'; 3import 'package:flutter_xata/utils.dart'; 4import 'package:flutter_xata/xata_service.dart'; 5 6class Detail extends StatefulWidget { 7 const Detail({Key? key, required this.id}) : super(key: key); 8 final String id; 9 10 State<Detail> createState() => _DetailState(); 11} 12 13class _DetailState extends State<Detail> { 14 final _formKey = GlobalKey<FormState>(); 15 var _selected = ''; 16 var _dropdownItems = ["Started", "Not_Started"]; 17 final TextEditingController _name = TextEditingController(); 18 final TextEditingController _description = TextEditingController(); 19 bool _isLoading = false; 20 bool _isSubmitting = false; 21 bool _isError = false; 22 23 24 void initState() { 25 getSingleProject(); 26 super.initState(); 27 } 28 29 getSingleProject() { 30 setState(() { 31 _isLoading = true; 32 }); 33 XataService().getSingleProject(widget.id).then((value) { 34 setState(() { 35 _isLoading = false; 36 }); 37 _name.text = value.name; 38 _description.text = value.description; 39 _selected = value.status; 40 }).catchError((onError) { 41 setState(() { 42 _isLoading = false; 43 _isError = true; 44 }); 45 }); 46 } 47 48 updateProject() { 49 setState(() { 50 _isSubmitting = true; 51 }); 52 Project updatedProject = Project( 53 name: _name.text, 54 description: _description.text, 55 status: _selected, 56 ); 57 XataService().updateProject(widget.id, updatedProject).then((value) { 58 setState(() { 59 _isSubmitting = false; 60 }); 61 ScaffoldMessenger.of(context).showSnackBar( 62 const SnackBar(content: Text('Project updated successfully!')), 63 ); 64 Navigator.push( 65 context, 66 MaterialPageRoute(builder: (context) => const Home()), 67 ); 68 }).catchError((onError) { 69 setState(() { 70 _isSubmitting = false; 71 _isError = true; 72 }); 73 ScaffoldMessenger.of(context).showSnackBar( 74 const SnackBar(content: Text('Error updating project!')), 75 ); 76 }); 77 } 78 79 deleteProject() { 80 setState(() { 81 _isSubmitting = true; 82 }); 83 XataService().deleteProject(widget.id).then((value) { 84 setState(() { 85 _isSubmitting = false; 86 }); 87 ScaffoldMessenger.of(context).showSnackBar( 88 const SnackBar(content: Text('Project deleted successfully!')), 89 ); 90 Navigator.push( 91 context, 92 MaterialPageRoute(builder: (context) => const Home()), 93 ); 94 }).catchError((onError) { 95 setState(() { 96 _isSubmitting = false; 97 _isError = true; 98 }); 99 ScaffoldMessenger.of(context).showSnackBar( 100 const SnackBar(content: Text('Error deleting project!')), 101 ); 102 }); 103 } 104 105 106 Widget build(BuildContext context) { 107 //UI CODE GOES HERE 108 } 109}
The snippet above does the following:
- Import the required dependencies
- Lines 17-21: Creates the
_name
,_description
,_isLoading
,_isSubmitting
, and_isError
properties to manage the application state - Lines 23-103: Creates the
getSingleProject
,updateProject
, anddeleteProject
methods to retrieve details of the selected project, update it, and delete it using theXataService().getSingleProject
,XataService().updateProject
, andXataService().deleteProject
services, respectively. Set states accordingly.
Lastly, we need to modify the UI to use the methods and states created to process the operations.
1//imports goes here 2 3class Detail extends StatefulWidget { 4 //code goes here 5} 6 7class _DetailState extends State<Detail> { 8 //state goes here 9 10 11 void initState() { 12 //code goes here 13 } 14 15 getSingleProject() { 16 //code goes here 17 } 18 19 updateProject() { 20 //code goes here 21 } 22 23 deleteProject() { 24 //code goes here 25 } 26 27 28 Widget build(BuildContext context) { 29 return _isLoading 30 ? const Center( 31 child: CircularProgressIndicator( 32 color: Colors.blue, 33 )) 34 : _isError 35 ? const Center( 36 child: Text( 37 'Error getting project, 38 style: TextStyle( 39 color: Colors.red, 40 fontWeight: FontWeight.bold, 41 ), 42 ), 43 ) 44 : Scaffold( 45 appBar: AppBar( 46 title: const Text("Details", 47 style: TextStyle(color: Colors.white)), 48 backgroundColor: Colors.black, 49 iconTheme: const IconThemeData(color: Colors.white), 50 ), 51 body: Padding( 52 padding: const EdgeInsets.symmetric( 53 horizontal: 16.0, vertical: 30.0), 54 child: Form( 55 key: _formKey, 56 child: Column( 57 children: [ 58 Column( 59 crossAxisAlignment: CrossAxisAlignment.start, 60 children: [ 61 Column( 62 crossAxisAlignment: CrossAxisAlignment.start, 63 children: [ 64 const Text( 65 'Name', 66 style: TextStyle( 67 color: Colors.grey, 68 fontSize: 14.0, 69 ), 70 ), 71 const SizedBox(height: 5.0), 72 TextFormField( 73 controller: _name, 74 validator: (value) { 75 if (value == null || value.isEmpty) { 76 return 'Please input name'; 77 } 78 return null; 79 }, 80 decoration: InputDecoration( 81 contentPadding: const EdgeInsets.symmetric( 82 vertical: 10, horizontal: 20), 83 hintText: "input name", 84 fillColor: Colors.white, 85 focusedBorder: OutlineInputBorder( 86 borderRadius: BorderRadius.circular(10), 87 borderSide: 88 const BorderSide(color: Colors.grey), 89 ), 90 enabledBorder: OutlineInputBorder( 91 borderRadius: BorderRadius.circular(10), 92 borderSide: 93 const BorderSide(color: Colors.grey), 94 ), 95 errorBorder: OutlineInputBorder( 96 borderRadius: BorderRadius.circular(10), 97 borderSide: 98 const BorderSide(color: Colors.red), 99 ), 100 ), 101 keyboardType: TextInputType.text, 102 maxLines: null, 103 ), 104 const SizedBox(height: 30.0), 105 const Text( 106 'Status', 107 style: TextStyle( 108 color: Colors.grey, 109 fontSize: 14.0, 110 ), 111 ), 112 const SizedBox(height: 5.0), 113 DropdownButtonFormField( 114 value: _selected, 115 items: _dropdownItems.map((String item) { 116 return DropdownMenuItem( 117 value: item, 118 child: Text(item), 119 ); 120 }).toList(), 121 onChanged: (value) { 122 setState(() => _selected = value!); 123 }, 124 decoration: InputDecoration( 125 contentPadding: const EdgeInsets.symmetric( 126 vertical: 10, horizontal: 20), 127 hintText: "select status", 128 focusedBorder: OutlineInputBorder( 129 borderRadius: BorderRadius.circular(10), 130 borderSide: 131 const BorderSide(color: Colors.grey), 132 ), 133 enabledBorder: OutlineInputBorder( 134 borderRadius: BorderRadius.circular(10), 135 borderSide: 136 const BorderSide(color: Colors.grey), 137 ), 138 errorBorder: OutlineInputBorder( 139 borderRadius: BorderRadius.circular(10), 140 borderSide: 141 const BorderSide(color: Colors.red), 142 ), 143 ), 144 ), 145 const SizedBox(height: 30.0), 146 const Text( 147 'Description', 148 style: TextStyle( 149 color: Colors.grey, 150 fontSize: 14.0, 151 ), 152 ), 153 const SizedBox(height: 5.0), 154 TextFormField( 155 controller: _description, 156 validator: (value) { 157 if (value == null || value.isEmpty) { 158 return 'Please input ydescription'; 159 } 160 return null; 161 }, 162 maxLines: 5, 163 decoration: InputDecoration( 164 contentPadding: const EdgeInsets.symmetric( 165 vertical: 10, horizontal: 20), 166 hintText: "input description", 167 fillColor: Colors.white, 168 focusedBorder: OutlineInputBorder( 169 borderRadius: BorderRadius.circular(10), 170 borderSide: 171 const BorderSide(color: Colors.grey), 172 ), 173 enabledBorder: OutlineInputBorder( 174 borderRadius: BorderRadius.circular(10), 175 borderSide: 176 const BorderSide(color: Colors.grey), 177 ), 178 errorBorder: OutlineInputBorder( 179 borderRadius: BorderRadius.circular(10), 180 borderSide: 181 const BorderSide(color: Colors.red), 182 ), 183 ), 184 keyboardType: TextInputType.multiline, 185 ), 186 ], 187 ), 188 ], 189 ), 190 const SizedBox(height: 30.0), 191 SizedBox( 192 height: 45, 193 width: double.infinity, 194 child: TextButton( 195 onPressed: _isSubmitting 196 ? null 197 : () { 198 if (_formKey.currentState!.validate()) { 199 updateProject(); 200 } 201 }, 202 style: ButtonStyle( 203 backgroundColor: MaterialStateProperty.all<Color>( 204 Colors.black), 205 ), 206 child: const Text( 207 'Update project', 208 style: TextStyle( 209 color: Colors.white, 210 fontWeight: FontWeight.bold, 211 fontSize: 14.0, 212 ), 213 ), 214 ), 215 ), 216 ], 217 ), 218 ), 219 ), 220 floatingActionButton: FloatingActionButton( 221 onPressed: _isSubmitting 222 ? null 223 : () { 224 deleteProject(); 225 }, 226 backgroundColor: Colors.red, 227 tooltip: 'Delete', 228 child: const Icon( 229 Icons.delete, 230 color: Colors.white, 231 ), 232 ), 233 ); 234 } 235}
With that done, we restart the application using the code editor or run the command below:
1flutter run
Conclusion
This post discussed how to build a basic project management application with Xata and Flutter. In addition to the functionalities explored earlier, Xata also includes well-tailored features that developers can harness to build applications ranging from small to large.
These resources may also be helpful: