Going fullstack with Flutter and MongoDB Atlas Data API
Tags: flutter, mongodb, fullstackmobile, mobiledevelopement
Published: 2023-03-29 23:03:02

Application Programming Interface (API) has revolutionized how applications are built. It enables software modules to communicate with each other using sets of standards. APIs have allowed companies to integrate new applications with existing software systems, provide an avenue for rapid innovation, and act as a gateway for enhancing customer experience across multiple platforms (mobile, web, etc.).

With the possibilities that APIs offer, it has also come with its share of difficulties. Developers are burdened with selecting the right database, integrating security, logging and monitoring, etc.

In this post, we will learn how to build a mobile phonebook using MongoDB Data API and Flutter. The project’s GitHub repository can be found [here](https://github.com/Mr-Malomz/flutter_mongo/tree/dev).

## What is MongoDB Data API

MongoDB Data API is a fully managed platform-agnostic service that provides a secure and fast standard HTTPS for managing data stored on MongoDB. Web browsers, web servers, CI/CD pipelines, serverless & edge computing environments, mobile applications, or any HTTPS-enabled platforms can connect to the Data API using standard HTTPS requests. The Data API also ships with the following:

-   Authorization and authentication mechanism
-   Data validation when writing and reading from the API
-   Support for [JSON or EJON](https://www.mongodb.com/docs/atlas/api/data-api-data-formats/#std-label-atlas-data-api-data-formats?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) API response

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

-   Basic understanding of Dart and Flutter
-   Flutter [SDK](https://flutter.dev/docs/get-started/install) installed
-   [MongoDB](https://www.mongodb.com?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) account. [**Signup**](https://www.mongodb.com/cloud/atlas/register?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) **is completely free**
-   Either [i](https://developer.apple.com/xcode/)[OS Simulator](https://developer.apple.com/xcode/), [Android Studio](https://developer.android.com/studio), or [Chrome web browser](https://support.google.com/chrome/answer/95346?hl=en-GB&co=GENIE.Platform%3DDesktop) to run the application

## Getting started

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_mongo && cd flutter_mongo

## Running the project

First, we need to install the project dependencies by running the command below:

```bash
    flutter pub get
```

Then, run the project using the following command:

```bash
    flutter run
```

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

![Home screen](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679431851954_Screenshot+2023-03-13+at+22.13.15.png)
![Create contact](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679431861514_Screenshot+2023-03-13+at+22.13.22.png)
![Edit contact](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679431898348_Screenshot+2023-03-13+at+22.14.05.png)

## Setting up MongoDB Data API

**Create a Database**

With our application up and running, we need to log in or sign up into our [MongoDB](https://www.mongodb.com?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) account. Click the project dropdown menu and click on the **New Project** button.

![New Project](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679432814506_Screenshot+2023-03-21+at+22.06.23.png)

Enter the `flutter_realm` as the project name, click **Next**, and click **Create Project**.

![enter project name](https://paper-attachments.dropboxusercontent.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640511449262_Screenshot+2021-12-26+at+10.35.59.png)
![Create Project](https://paper-attachments.dropboxusercontent.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640511449329_Screenshot+2021-12-26+at+10.36.27.png)

Click on **Build a Database**

![](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679433314173_Screenshot+2023-03-21+at+22.14.44.png)

Select **Shared** as the type of database and **Create** to set up a cluster.

![Shared highlighted in red](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679433328606_Screenshot+2023-03-13+at+23.11.03.png)

Next, we need to navigate to the **Database** menu, click the **Browse Collection** tab, and click the **Add My Own Data** button to add sample data to our database.

![Navigate to Database](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679434887287_Screenshot+2023-03-13+at+23.19.14.png)
![Click Browse Collection](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679434887311_Screenshot+2023-03-14+at+00.05.31.png)
![click Add My Own Data](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679434887339_Screenshot+2023-03-14+at+00.06.25.png)

To add sample data, first, we need to create a `phonebook` database and a `phonebookCollection` collection.

![Create database and collection](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435258737_Screenshot+2023-03-21+at+22.46.39.png)
![Create database and collection](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435248078_Screenshot+2023-03-14+at+00.07.55.png)

Lastly, we need to add sample data to our `phonebookCollection` as shown below:

| **Key**     | **Value**     | **Object Type** |
| ----------- | ------------- | --------------- |
| fullname    | John Travolta | String          |
| phonenumber | 907865744546  | Int64           |

![](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435525371_Screenshot+2023-03-14+at+00.08.30.png)
![](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435525396_Screenshot+2023-03-14+at+00.10.55.png)

**Setup the Data API**

With our database fully set up, first, we need to navigate to the **Data API** tab, select the Cluster our database was created in and click the **Enable Data Access from the Data API** button.

![configure and create API](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435688654_Screenshot+2023-03-14+at+00.18.59.png)
![Created API](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679435688678_Screenshot+2023-03-14+at+00.36.04.png)

With that done, MongoDB automatically provides us with a fast and secure API service. By default, the provisioned API is inaccessible. We need to create an API key to connect to the API securely. To do this, click the **Create API Key** button, input `flutter_mongo` as the name and **Generate API Key**.

![create API key](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679436305990_Screenshot+2023-03-14+at+00.37.13.png)
![Generate](https://paper-attachments.dropboxusercontent.com/s_4A2089FCC448972BDFC31AE77FEB8F1DF5F0CAE6BDCC15BE9B53E3E7F32AA9B3_1679843610340_Screenshot+2023-03-14+at+00.37.59.png)

**PS**: _We must copy the generated API key. It will come in handy when integrating the API with our Flutter application._

## Integrating MongoDB Data API with Flutter

With all that done, we can start building our application using the API. First, we need to create a model to convert the response sent from the Data API to a Dart object. The model will also cater to JSON serialization. To do this, we need to create a `utils.dart` file in the `lib` folder and add the snippet below:

```dart
    class PhoneBook {
      String? id;
      String fullname;
      int phonenumber;

      PhoneBook({
        this.id,
        required this.fullname,
        required this.phonenumber,
      });
      Map<dynamic, dynamic> toJson() {
        return {
          "fullname": fullname,
          "phonenumber": phonenumber,
        };
      }

      factory PhoneBook.fromJson(Map<dynamic, dynamic> json) {
        return PhoneBook(
          id: json['_id'],
          fullname: json['fullname'],
          phonenumber: json['phonenumber'],
        );
      }
    }
```

Next, we must create a service file to separate the application core logic from the UI. To do this, create a `phone_service.dart` file inside the `lib` directory. Then, add the snippet below:

```dart
    import 'dart:convert';
    import 'package:flutter_mongo/utils.dart';
    import 'package:dio/dio.dart';

    class PhoneService {
      final dio = Dio();
      final String _dataSource = "Cluster0";
      final String _database = "phonebook";
      final String _collection = "phonebookCollection";
      final String _endpoint = "<REPLACE WITH THE ENDPOINT URL>";
      static const _apiKey = "REPLACE WITH THE API KEY";
      var headers = {
        "content-type": "application/json",
        "apiKey": _apiKey,
      };

      Future<List<PhoneBook>> getPhoneContacts() async {
        var response = await dio.post(
          "$_endpoint/action/find",
          options: Options(headers: headers),
          data: jsonEncode(
            {
              "dataSource": _dataSource,
              "database": _database,
              "collection": _collection,
              "filter": {},
            },
          ),
        );
        if (response.statusCode == 200) {
          var respList = response.data['documents'] as List;
          var phoneList = respList.map((json) => PhoneBook.fromJson(json)).toList();
          return phoneList;
        } else {
          throw Exception('Error getting phone contacts');
        }
      }

      Future<PhoneBook> getSinglePhoneContact(String id) async {
        var response = await dio.post(
          "$_endpoint/action/find",
          options: Options(headers: headers),
          data: jsonEncode(
            {
              "dataSource": _dataSource,
              "database": _database,
              "collection": _collection,
              "filter": {
                "_id": {"\$oid": id}
              },
            },
          ),
        );
        if (response.statusCode == 200) {
          var resp = response.data\['documents'\][0];
          var contact = PhoneBook.fromJson(resp);
          return contact;
        } else {
          throw Exception('Error getting phone contact');
        }
      }

      Future updatePhoneContact(String id, String fullname, int phonenumber) async {
        var response = await dio.post(
          "$_endpoint/action/updateOne",
          options: Options(headers: headers),
          data: jsonEncode(
            {
              "dataSource": _dataSource,
              "database": _database,
              "collection": _collection,
              "filter": {
                "_id": {"\$oid": id}
              },
              "update": {
                "\$set": {"fullname": fullname, "phonenumber": phonenumber}
              }
            },
          ),
        );
        if (response.statusCode == 200) {
          return response.data;
        } else {
          throw Exception('Error getting phone contact');
        }
      }

      Future createPhoneContact(String fullname, int phonenumber) async {
        var response = await dio.post(
          "$_endpoint/action/insertOne",
          options: Options(headers: headers),
          data: jsonEncode(
            {
              "dataSource": _dataSource,
              "database": _database,
              "collection": _collection,
              "document": {"fullname": fullname, "phonenumber": phonenumber}
            },
          ),
        );
        if (response.statusCode == 201) {
          return response.data;
        } else {
          throw Exception('Error creating phone contact');
        }
      }

      Future deletePhoneContact(String id) async {
        var response = await dio.post(
          "$_endpoint/action/deleteOne",
          options: Options(headers: headers),
          data: jsonEncode(
            {
              "dataSource": _dataSource,
              "database": _database,
              "collection": _collection,
              "filter": {
                "_id": {"\$oid": id}
              },
            },
          ),
        );
        if (response.statusCode == 200) {
          return response.data;
        } else {
          throw Exception('Error deleting phone contact');
        }
      }
    }
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `PhoneService` class with `_dataSource`, `_database`, `_collection`, `_endpoint`, `_apikey`, and `headers` properties.
-   Creates the `getPhoneContacts`, `getSinglePhoneContact`, `updatePhoneContact`, `createPhoneContact`, and `deletePhoneContact` methods that uses the `Dio` package to configure permissions and make secure HTTPS request to the Data API configured earlier and returns the appropriate responses

## Consuming the service

With that done, we can use the service to perform the required operation.

**Get all contacts**

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 contacts saved in the database:

```dart
    import 'package:flutter/material.dart';
    import 'package:flutter_mongo/phone_service.dart';
    import 'package:flutter_mongo/screens/create.dart';
    import 'package:flutter_mongo/screens/detail.dart';
    import 'package:flutter_mongo/utils.dart';

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

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

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

      getContacts() {
        setState(() {
          _isLoading = true;
        });
        PhoneService().getPhoneContacts().then((value) {
          setState(() {
            contacts = value;
            _isLoading = false;
          });
        }).catchError((onError) {
          setState(() {
            _isLoading = false;
            _isError = true;
          });
        });
      }

      @override
      Widget build(BuildContext context) {
       // UI CODE GOES HERE
      }
    }
```

The snippet above does the following:

-   Imports the required dependencies
-   **Lines 14-16**: Create the `contacts`, `_isLoading`, and `_isError` properties to manage the application state
-   **Lines 19-41**: Create a `getContacts` method to get the list of available contacts on the database using the `PhoneService().getPhoneContacts` and set states accordingly

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

```dart
    //imports goes here

    class Home extends StatefulWidget {
      //code goes here
    }

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

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

      getContacts() {
        //code goes here
      }

      @override
      Widget build(BuildContext context) {
        return _isLoading
            ? const Center(
                child: CircularProgressIndicator(
                color: Colors.blue,
              ))
            : _isError
                ? const Center(
                    child: Text(
                      'Error getting phone contacts',
                      style: TextStyle(
                        color: Colors.red,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  )
                : Scaffold(
                    appBar: AppBar(
                      title: const Text('Phonebook'),
                      backgroundColor: Colors.black,
                    ),
                    body: ListView.builder(
                      itemCount: contacts.length,
                      itemBuilder: (context, index) {
                        return InkWell(
                          onTap: () {
                            Navigator.push(
                              context,
                              MaterialPageRoute(
                                builder: (context) =>
                                    Detail(id: contacts[index].id!),
                              ),
                            );
                          },
                          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(
                                        contacts[index].fullname,
                                        style: TextStyle(
                                            color: Colors.black,
                                            fontWeight: FontWeight.w800),
                                      ),
                                      SizedBox(height: 10.0),
                                      Text(contacts[index].phonenumber.toString())
                                    ],
                                  ),
                                ),
                                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 contact',
                      child: const Icon(Icons.add),
                    ),
                  );
      }
    }
```

**Create contacts**

To create a contact, 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 contact to the database:

```dart
    import 'package:flutter/material.dart';
    import 'package:flutter_mongo/phone_service.dart'; //ADD THIS
    import 'package:flutter_mongo/screens/home.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>();
      final TextEditingController _fullname = TextEditingController();
      final TextEditingController _phonenumber = TextEditingController();
      bool _isLoading = false;

      createContact() {
        setState(() {
          _isLoading = true;
        });
        PhoneService()
            .createPhoneContact(_fullname.text, int.parse(_phonenumber.text))
            .then((value) {
          setState(() {
            _isLoading = false;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Contact 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 contact!')),
          );
        });
      }

      @override
      Widget build(BuildContext context) {
        //UI CODE GOES HERE
      }
    }
```

The snippet above does the following:

-   Import the required dependency
-   **Lines 15-17**: Create the `_fullname`, `_phonenumber`, and `_isLoading` properties to manage the application state
-   **Lines 17-38**: Create a `createContact` method to save the contact using the `PhoneService().createPhoneContact` service, set states accordingly

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

```dart
    //import goes here

    class Create extends StatefulWidget {
      //code goes here
    }

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

      createContact() {
        //code goes here
      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("Create contact"),
            backgroundColor: Colors.black,
          ),
          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(
                            'Fullname',
                            style: TextStyle(
                              color: Colors.grey,
                              fontSize: 14.0,
                            ),
                          ),
                          const SizedBox(height: 5.0),
                          TextFormField(
                            controller: _fullname,
                            validator: (value) {
                              if (value == null || value.isEmpty) {
                                return 'Please input your fullname';
                              }
                              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(
                            'Phone number',
                            style: TextStyle(
                              color: Colors.grey,
                              fontSize: 14.0,
                            ),
                          ),
                          const SizedBox(height: 5.0),
                          TextFormField(
                            controller: _phonenumber,
                            validator: (value) {
                              if (value == null || value.isEmpty) {
                                return 'Please input your phone number';
                              }
                              return null;
                            },
                            decoration: InputDecoration(
                              contentPadding: const EdgeInsets.symmetric(
                                  vertical: 10, horizontal: 20),
                              hintText: "input phone number",
                              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.number,
                          ),
                        ],
                      ),
                    ],
                  ),
                  const SizedBox(height: 30.0),
                  SizedBox(
                    height: 45,
                    width: double.infinity,
                    child: TextButton(
                      onPressed: _isLoading
                          ? null
                          : () {
                              if (_formKey.currentState!.validate()) {
                                createContact();
                              }
                            },
                      style: ButtonStyle(
                        backgroundColor:
                            MaterialStateProperty.all<Color>(Colors.black),
                      ),
                      child: const Text(
                        'Create contact',
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontSize: 14.0,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
```

**Get a contact, edit a contact and delete contacts**

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 contacts.

```dart
    import 'package:flutter/material.dart';
    import 'package:flutter_mongo/phone_service.dart';
    import 'package:flutter_mongo/screens/home.dart';
    import 'package:flutter_mongo/utils.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>();
      final TextEditingController _fullname = TextEditingController();
      final TextEditingController _phonenumber = TextEditingController();
      late PhoneBook contact;
      bool _isLoading = false;
      bool _isSubmitting = false;
      bool _isError = false;

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

      getContacts() {
        setState(() {
          _isLoading = true;
        });
        PhoneService().getSinglePhoneContact(widget.id).then((value) {
          setState(() {
            contact = value;
            _isLoading = false;
          });
          _fullname.text = value.fullname;
          _phonenumber.text = value.phonenumber.toString();
        }).catchError((onError) {
          setState(() {
            _isLoading = false;
            _isError = true;
          });
        });
      }

      updateContact(String fullname, int phonenumber) {
        setState(() {
          _isSubmitting = true;
        });
        PhoneService()
            .updatePhoneContact(widget.id, fullname, phonenumber)
            .then((value) {
          setState(() {
            _isSubmitting = false;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Contact 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 contact!')),
          );
        });
      }

      deleteContact() {
        setState(() {
          _isSubmitting = true;
        });
        PhoneService().deletePhoneContact(widget.id).then((value) {
          setState(() {
            _isSubmitting = false;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Contact 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 contact!')),
          );
        });
      }

      @override
      Widget build(BuildContext context) {
        //UI GOES HERE
      }
    }
```

The snippet above does the following:

-   Import the required dependencies
-   **Lines 15-20**: Create the `_fullname`, `_phonenumber`, `contact`, `_isLoading`, `_isSubmitting`, and `_isError` properties to manage the application state
-   **Lines 17-38**: Create the `getSingleContact`, `updateContact`, and `deleteContact` methods to get details of the selected contact, update it and delete it using the `PhoneService().getSinglePhoneContact`, `PhoneService().updatePhoneContact`, and `PhoneService().deletePhoneContact` service respectively, set states accordingly

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

```dart
    //import goes here

    class Detail extends StatefulWidget {
     //code goes here
    }

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

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

      getSingleContact() {
        //code goes here
      }

      updateContact(String fullname, int phonenumber) {
        //code goes here
      }

      deleteContact() {
        //code goes here
      }

      @override
      Widget build(BuildContext context) {
        return _isLoading
            ? const Center(
                child: CircularProgressIndicator(
                color: Colors.blue,
              ))
            : _isError
                ? const Center(
                    child: Text(
                      'Error getting phone contacts',
                      style: TextStyle(
                        color: Colors.red,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  )
                : Scaffold(
                    appBar: AppBar(
                      title: const Text("Details"),
                      backgroundColor: Colors.black,
                    ),
                    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(
                                      'Fullname',
                                      style: TextStyle(
                                        color: Colors.grey,
                                        fontSize: 14.0,
                                      ),
                                    ),
                                    const SizedBox(height: 5.0),
                                    TextFormField(
                                      controller: _fullname,
                                      validator: (value) {
                                        if (value == null || value.isEmpty) {
                                          return 'Please input your fullname';
                                        }
                                        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(
                                      'Phone number',
                                      style: TextStyle(
                                        color: Colors.grey,
                                        fontSize: 14.0,
                                      ),
                                    ),
                                    const SizedBox(height: 5.0),
                                    TextFormField(
                                      controller: _phonenumber,
                                      validator: (value) {
                                        if (value == null || value.isEmpty) {
                                          return 'Please input your phone number';
                                        }
                                        return null;
                                      },
                                      decoration: InputDecoration(
                                        contentPadding: const EdgeInsets.symmetric(
                                            vertical: 10, horizontal: 20),
                                        hintText: "input phone number",
                                        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.number,
                                    ),
                                  ],
                                ),
                              ],
                            ),
                            const SizedBox(height: 30.0),
                            SizedBox(
                              height: 45,
                              width: double.infinity,
                              child: TextButton(
                                onPressed: _isSubmitting
                                    ? null
                                    : () {
                                        if (_formKey.currentState!.validate()) {
                                          updateContact(
                                            _fullname.text,
                                            int.parse(_phonenumber.text),
                                          );
                                        }
                                      },
                                style: ButtonStyle(
                                  backgroundColor: MaterialStateProperty.all<Color>(
                                      Colors.black),
                                ),
                                child: const Text(
                                  'Update contact',
                                  style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 14.0,
                                  ),
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    floatingActionButton: FloatingActionButton(
                      onPressed: _isSubmitting
                          ? null
                          : () {
                              deleteContact();
                            },
                      backgroundColor: Colors.red,
                      tooltip: 'Delete',
                      child: const Icon(Icons.delete),
                    ),
                  );
      }
    }
```

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

```bash
    flutter run
```

![](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExY2JhOTk4NjFkZWI1MDg5NWMwYmNlYTc3MWI1ZmRhMmQ5YzlhMjU5NSZjdD1n/AMlvBj3N0QUSgm1Wyl/giphy.gif)

## Conclusion

This post discussed how to build a fullstack mobile application using MongoDB Data API and Flutter. With the Data API, organizations can quickly create secure and scalable services that can be used across platforms.

These resources may also be helpful:

-   [MongoDB Data API](https://www.mongodb.com/docs/atlas/api/data-api/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [DIO package](https://github.com/cfug/dio/tree/main/dio?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [MongoDB official documentation](https://www.mongodb.com/docs/atlas/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)