Xata + Flutter: A getting started guide.

Demola Malomo - Feb 5 - - Dev Community

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

Create database
Add table
Add field

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.

Created column

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.

click the Get code snippet button
Copy URL
Add a new 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:

git clone https://github.com/Mr-Malomz/flutter_xata.git
Enter fullscreen mode Exit fullscreen mode

Running the project

We need to install the project dependencies by running the command below:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Then, run the project using the following command:

flutter run
Enter fullscreen mode Exit fullscreen mode

The command above will run the application on the selected device.

Home screen
Create screen

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.

XATA_DATABASE_URL= <REPLACE WITH THE COPIED DATABASE URL>
XATA_API_KEY=<REPLACE WITH THE COPIED API KEY>
Enter fullscreen mode Exit fullscreen mode

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

class Project {
  String? id;
  String name;
  String description;
  String status;

  Project({
    this.id,
    required this.name,
    required this.description,
    required this.status,
  });

  Map<dynamic, dynamic> toJson() {
    return {
      "name": name,
      "description": description,
      "status": status,
    };
  }

  factory Project.fromJson(Map<dynamic, dynamic> json) {
    return Project(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      status: json['status'],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_xata/utils.dart';

class XataService {
  final _dio = Dio();
  static String _apiKey = dotenv.get("XATA_API_KEY");
  static String _baseURL = dotenv.get("XATA_DATABASE_URL");

  final _headers = {
    "content-type": "application/json",
    "AUTHORIZATION": "Bearer $_apiKey",
  };

  Future<List<Project>> getProjects() async {
    var response = await _dio.post(
      "$_baseURL:main/tables/Project/query",
      options: Options(headers: _headers),
    );

    if (response.statusCode == 200) {
      var respList = response.data['records'] as List;
      var projectList = respList.map((json) => Project.fromJson(json)).toList();
      return projectList;
    } else {
      throw Exception('Error getting projects');
    }
  }

  Future<Project> getSingleProject(String id) async {
    var response = await _dio.get(
      "$_baseURL:main/tables/Project/data/$id",
      options: Options(headers: _headers),
    );

    if (response.statusCode == 200) {
      var resp = response.data;
      var project = Project.fromJson(resp);
      return project;
    } else {
      throw Exception('Error getting project');
    }
  }

  Future createProject(Project newProject) async {
    var response = await _dio.post(
      "$_baseURL:main/tables/Project/data",
      options: Options(headers: _headers),
      data: newProject.toJson(),
    );

    if (response.statusCode == 201) {
      return response.data;
    } else {
      throw Exception('Error creating project');
    }
  }

  Future updateProject(String id, Project updatedProject) async {
    var response = await _dio.put(
      "$_baseURL:main/tables/Project/data/$id",
      options: Options(headers: _headers),
      data: updatedProject.toJson(),
    );

    if (response.statusCode == 200) {
      return response.data;
    } else {
      throw Exception('Error updating project');
    }
  }

  Future deleteProject(String id) async {
    var response = await _dio.delete(
      "$_baseURL:main/tables/Project/data/$id",
      options: Options(headers: _headers),
    );

    if (response.statusCode == 204) {
      return response.data;
    } else {
      throw Exception('Error deleting project');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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, and deleteProject 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:

//Other imports goes here
import 'package:flutter_xata/utils.dart';
import 'package:flutter_xata/xata_service.dart';

class Home extends StatefulWidget {
  const Home({super.key});
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late List<Project> projects;
  bool _isLoading = false;
  bool _isError = false;

  @override
  void initState() {
    getProjects();
    super.initState();
  }

  getProjects() {
    setState(() {
      _isLoading = true;
    });
    XataService().getProjects().then((value) {
      setState(() {
        projects = value;
        _isLoading = false;
      });
    }).catchError((onError) {
      setState(() {
        _isLoading = false;
        _isError = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    //UI CODE GOES HERE
  }
}
Enter fullscreen mode Exit fullscreen mode

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 the XataService().getProjects and set states accordingly

Lastly, we need to modify the UI to use the states and method created to get the projects list.

//imports goes here

class Home extends StatefulWidget {
  //code goes here
}

class _HomeState extends State<Home> {
  //states goes here

  @override
  void initState() {
    //code goes here
  }

  getProjects() {
    //code goes here
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const Center(
            child: CircularProgressIndicator(
            color: Colors.blue,
          ))
        : _isError
            ? const Center(
                child: Text(
                  'Error getting projects',
                  style: TextStyle(
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              )
            : Scaffold(
                appBar: AppBar(
                  title: const Text('Projects',
                      style: TextStyle(color: Colors.white)),
                  backgroundColor: Colors.black,
                ),
                body: ListView.builder(
                  itemCount: projects.length,
                  itemBuilder: (context, index) {
                    return InkWell(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) =>
                                Detail(id: projects[index].id as String),
                          ),
                        );
                      },
                      child: Container(
                        decoration: const BoxDecoration(
                          border: Border(
                            bottom: BorderSide(width: .5, color: Colors.grey),
                          ),
                        ),
                        padding: EdgeInsets.fromLTRB(10, 20, 10, 20),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            Expanded(
                              flex: 7,
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    projects[index].name,
                                    style: TextStyle(
                                        color: Colors.black,
                                        fontWeight: FontWeight.w800),
                                  ),
                                  const SizedBox(height: 10.0),
                                  Row(
                                    children: [
                                      Icon(projects[index].status == "Started"
                                          ? Icons.start
                                          : Icons.stop_circle_outlined),
                                      const SizedBox(width: 5.0),
                                      Text(projects[index].status)
                                    ],
                                  ),
                                  const SizedBox(height: 10.0),
                                  Text(projects[index].description)
                                ],
                              ),
                            ),
                            const Column(
                              crossAxisAlignment: CrossAxisAlignment.end,
                              children: [
                                SizedBox(height: 10.0),
                                Icon(Icons.arrow_forward_ios_rounded)
                              ],
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
                floatingActionButton: FloatingActionButton(
                  onPressed: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => const Create(),
                      ),
                    );
                  },
                  backgroundColor: Colors.black,
                  tooltip: 'Create project',
                  child: const Icon(
                    Icons.add,
                    color: Colors.white,
                  ),
                ),
              );
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

//other import goes here
import 'package:flutter_xata/screens/home.dart';
import 'package:flutter_xata/utils.dart';
import 'package:flutter_xata/xata_service.dart';

class Create extends StatefulWidget {
  const Create({
    Key? key,
  }) : super(key: key);
  @override
  State<Create> createState() => _CreateState();
}

class _CreateState extends State<Create> {
  final _formKey = GlobalKey<FormState>();
  var _selected = '';
  var _dropdownItems = ["Started", "Not_Started"];
  final TextEditingController _name = TextEditingController();
  final TextEditingController _description = TextEditingController();
  bool _isLoading = false;

  createProject() {
    setState(() {
      _isLoading = true;
    });

    Project newProject = Project(
      name: _name.text,
      description: _description.text,
      status: _selected,
    );

    XataService().createProject(newProject).then((value) {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Project created successfully!')),
      );
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => const Home()),
      );
    }).catchError((onError) {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Error creating project!')),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    //UI CODE GOES HERE
  }
}
Enter fullscreen mode Exit fullscreen mode

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 the XataService().createProject service, set states accordingly

Lastly, we need to modify the UI to use the method and states created to process the form.

//import goes here

class Create extends StatefulWidget {
  //code goes here
}

class _CreateState extends State<Create> {
  //states goes here

  createProject() {
    //code goes here
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:
            const Text("Create project", style: TextStyle(color: Colors.white)),
        backgroundColor: Colors.black,
        iconTheme: const IconThemeData(color: Colors.white),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Name',
                        style: TextStyle(
                          color: Colors.grey,
                          fontSize: 14.0,
                        ),
                      ),
                      const SizedBox(height: 5.0),
                      TextFormField(
                        controller: _name,
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Please input name';
                          }
                          return null;
                        },
                        decoration: InputDecoration(
                          contentPadding: const EdgeInsets.symmetric(
                              vertical: 10, horizontal: 20),
                          hintText: "input name",
                          fillColor: Colors.white,
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          errorBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.red),
                          ),
                        ),
                        keyboardType: TextInputType.text,
                        maxLines: null,
                      ),
                      const SizedBox(height: 30.0),
                      const Text(
                        'Status',
                        style: TextStyle(
                          color: Colors.grey,
                          fontSize: 14.0,
                        ),
                      ),
                      const SizedBox(height: 5.0),
                      DropdownButtonFormField(
                        items: _dropdownItems.map((String item) {
                          return DropdownMenuItem(
                            value: item,
                            child: Text(item),
                          );
                        }).toList(),
                        onChanged: (value) {
                          setState(() => _selected = value!);
                        },
                        decoration: InputDecoration(
                          contentPadding: const EdgeInsets.symmetric(
                              vertical: 10, horizontal: 20),
                          hintText: "select status",
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          errorBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.red),
                          ),
                        ),
                      ),
                      const SizedBox(height: 30.0),
                      const Text(
                        'Description',
                        style: TextStyle(
                          color: Colors.grey,
                          fontSize: 14.0,
                        ),
                      ),
                      const SizedBox(height: 5.0),
                      TextFormField(
                        controller: _description,
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Please input ydescription';
                          }
                          return null;
                        },
                        maxLines: 5,
                        decoration: InputDecoration(
                          contentPadding: const EdgeInsets.symmetric(
                              vertical: 10, horizontal: 20),
                          hintText: "input description",
                          fillColor: Colors.white,
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          errorBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.red),
                          ),
                        ),
                        keyboardType: TextInputType.multiline,
                      ),
                    ],
                  ),
                ],
              ),
              const SizedBox(height: 30.0),
              SizedBox(
                height: 45,
                width: double.infinity,
                child: TextButton(
                  onPressed: _isLoading
                      ? null
                      : () {
                          if (_formKey.currentState!.validate()) {
                            createProject();
                          }
                        },
                  style: ButtonStyle(
                    backgroundColor:
                        MaterialStateProperty.all<Color>(Colors.black),
                  ),
                  child: const Text(
                    'Create project',
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 14.0,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

//import goee here
import 'package:flutter_xata/screens/home.dart';
import 'package:flutter_xata/utils.dart';
import 'package:flutter_xata/xata_service.dart';

class Detail extends StatefulWidget {
  const Detail({Key? key, required this.id}) : super(key: key);
  final String id;
  @override
  State<Detail> createState() => _DetailState();
}

class _DetailState extends State<Detail> {
  final _formKey = GlobalKey<FormState>();
  var _selected = '';
  var _dropdownItems = ["Started", "Not_Started"];
  final TextEditingController _name = TextEditingController();
  final TextEditingController _description = TextEditingController();
  bool _isLoading = false;
  bool _isSubmitting = false;
  bool _isError = false;

  @override
  void initState() {
    getSingleProject();
    super.initState();
  }

  getSingleProject() {
    setState(() {
      _isLoading = true;
    });
    XataService().getSingleProject(widget.id).then((value) {
      setState(() {
        _isLoading = false;
      });
      _name.text = value.name;
      _description.text = value.description;
      _selected = value.status;
    }).catchError((onError) {
      setState(() {
        _isLoading = false;
        _isError = true;
      });
    });
  }

  updateProject() {
    setState(() {
      _isSubmitting = true;
    });
    Project updatedProject = Project(
      name: _name.text,
      description: _description.text,
      status: _selected,
    );
    XataService().updateProject(widget.id, updatedProject).then((value) {
      setState(() {
        _isSubmitting = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Project updated successfully!')),
      );
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => const Home()),
      );
    }).catchError((onError) {
      setState(() {
        _isSubmitting = false;
        _isError = true;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Error updating project!')),
      );
    });
  }

  deleteProject() {
    setState(() {
      _isSubmitting = true;
    });
    XataService().deleteProject(widget.id).then((value) {
      setState(() {
        _isSubmitting = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Project deleted successfully!')),
      );
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => const Home()),
      );
    }).catchError((onError) {
      setState(() {
        _isSubmitting = false;
        _isError = true;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Error deleting project!')),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    //UI CODE GOES HERE
  }
}
Enter fullscreen mode Exit fullscreen mode

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, and deleteProject methods to retrieve details of the selected project, update it, and delete it using the XataService().getSingleProject, XataService().updateProject, and XataService().deleteProject services, respectively. Set states accordingly.

Lastly, we need to modify the UI to use the methods and states created to process the operations.

//imports goes here

class Detail extends StatefulWidget {
  //code goes here
}

class _DetailState extends State<Detail> {
  //state goes here

  @override
  void initState() {
    //code goes here
  }

  getSingleProject() {
    //code goes here
  }

  updateProject() {
    //code goes here
  }

  deleteProject() {
    //code goes here
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const Center(
            child: CircularProgressIndicator(
            color: Colors.blue,
          ))
        : _isError
            ? const Center(
                child: Text(
                  'Error getting project,
                  style: TextStyle(
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              )
            : Scaffold(
                appBar: AppBar(
                  title: const Text("Details",
                      style: TextStyle(color: Colors.white)),
                  backgroundColor: Colors.black,
                  iconTheme: const IconThemeData(color: Colors.white),
                ),
                body: Padding(
                  padding: const EdgeInsets.symmetric(
                      horizontal: 16.0, vertical: 30.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      children: [
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const Text(
                                  'Name',
                                  style: TextStyle(
                                    color: Colors.grey,
                                    fontSize: 14.0,
                                  ),
                                ),
                                const SizedBox(height: 5.0),
                                TextFormField(
                                  controller: _name,
                                  validator: (value) {
                                    if (value == null || value.isEmpty) {
                                      return 'Please input name';
                                    }
                                    return null;
                                  },
                                  decoration: InputDecoration(
                                    contentPadding: const EdgeInsets.symmetric(
                                        vertical: 10, horizontal: 20),
                                    hintText: "input name",
                                    fillColor: Colors.white,
                                    focusedBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    enabledBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    errorBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.red),
                                    ),
                                  ),
                                  keyboardType: TextInputType.text,
                                  maxLines: null,
                                ),
                                const SizedBox(height: 30.0),
                                const Text(
                                  'Status',
                                  style: TextStyle(
                                    color: Colors.grey,
                                    fontSize: 14.0,
                                  ),
                                ),
                                const SizedBox(height: 5.0),
                                DropdownButtonFormField(
                                  value: _selected,
                                  items: _dropdownItems.map((String item) {
                                    return DropdownMenuItem(
                                      value: item,
                                      child: Text(item),
                                    );
                                  }).toList(),
                                  onChanged: (value) {
                                    setState(() => _selected = value!);
                                  },
                                  decoration: InputDecoration(
                                    contentPadding: const EdgeInsets.symmetric(
                                        vertical: 10, horizontal: 20),
                                    hintText: "select status",
                                    focusedBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    enabledBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    errorBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.red),
                                    ),
                                  ),
                                ),
                                const SizedBox(height: 30.0),
                                const Text(
                                  'Description',
                                  style: TextStyle(
                                    color: Colors.grey,
                                    fontSize: 14.0,
                                  ),
                                ),
                                const SizedBox(height: 5.0),
                                TextFormField(
                                  controller: _description,
                                  validator: (value) {
                                    if (value == null || value.isEmpty) {
                                      return 'Please input ydescription';
                                    }
                                    return null;
                                  },
                                  maxLines: 5,
                                  decoration: InputDecoration(
                                    contentPadding: const EdgeInsets.symmetric(
                                        vertical: 10, horizontal: 20),
                                    hintText: "input description",
                                    fillColor: Colors.white,
                                    focusedBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    enabledBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.grey),
                                    ),
                                    errorBorder: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                      borderSide:
                                          const BorderSide(color: Colors.red),
                                    ),
                                  ),
                                  keyboardType: TextInputType.multiline,
                                ),
                              ],
                            ),
                          ],
                        ),
                        const SizedBox(height: 30.0),
                        SizedBox(
                          height: 45,
                          width: double.infinity,
                          child: TextButton(
                            onPressed: _isSubmitting
                                ? null
                                : () {
                                    if (_formKey.currentState!.validate()) {
                                      updateProject();
                                    }
                                  },
                            style: ButtonStyle(
                              backgroundColor: MaterialStateProperty.all<Color>(
                                  Colors.black),
                            ),
                            child: const Text(
                              'Update project',
                              style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 14.0,
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                floatingActionButton: FloatingActionButton(
                  onPressed: _isSubmitting
                      ? null
                      : () {
                          deleteProject();
                        },
                  backgroundColor: Colors.red,
                  tooltip: 'Delete',
                  child: const Icon(
                    Icons.delete,
                    color: Colors.white,
                  ),
                ),
              );
  }
}
Enter fullscreen mode Exit fullscreen mode

With that done, we restart the application using the code editor or run the command below:

flutter run
Enter fullscreen mode Exit fullscreen mode

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:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .