Going fullstack with Flutter and MongoDB Atlas Data API

Learn how to use MongoDB Data API and Flutter to build a mobile fullstack application.

avatar

Demola Malomo

Mar 28 2023

10 min read

avatar

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.

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 API response

Prerequisites

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

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:

1 flutter pub get

Then, run the project using the following command:

1 flutter run

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

Home screen Create contact Edit contact

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 account. Click the project dropdown menu and click on the New Project button.

New Project

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

enter project name Create Project

Click on Build a Database

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

Shared highlighted in red

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 Click Browse Collection click Add My Own Data

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

Create database and collection Create database and collection

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

KeyValueObject Type
fullnameJohn TravoltaString
phonenumber907865744546Int64

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 Created API

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 Generate

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:

1 class PhoneBook { 2 String? id; 3 String fullname; 4 int phonenumber; 5 6 PhoneBook({ 7 this.id, 8 required this.fullname, 9 required this.phonenumber, 10 }); 11 Map<dynamic, dynamic> toJson() { 12 return { 13 "fullname": fullname, 14 "phonenumber": phonenumber, 15 }; 16 } 17 18 factory PhoneBook.fromJson(Map<dynamic, dynamic> json) { 19 return PhoneBook( 20 id: json['_id'], 21 fullname: json['fullname'], 22 phonenumber: json['phonenumber'], 23 ); 24 } 25 }

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:

1 import 'dart:convert'; 2 import 'package:flutter_mongo/utils.dart'; 3 import 'package:dio/dio.dart'; 4 5 class PhoneService { 6 final dio = Dio(); 7 final String _dataSource = "Cluster0"; 8 final String _database = "phonebook"; 9 final String _collection = "phonebookCollection"; 10 final String _endpoint = "<REPLACE WITH THE ENDPOINT URL>"; 11 static const _apiKey = "REPLACE WITH THE API KEY"; 12 var headers = { 13 "content-type": "application/json", 14 "apiKey": _apiKey, 15 }; 16 17 Future<List<PhoneBook>> getPhoneContacts() async { 18 var response = await dio.post( 19 "$_endpoint/action/find", 20 options: Options(headers: headers), 21 data: jsonEncode( 22 { 23 "dataSource": _dataSource, 24 "database": _database, 25 "collection": _collection, 26 "filter": {}, 27 }, 28 ), 29 ); 30 if (response.statusCode == 200) { 31 var respList = response.data['documents'] as List; 32 var phoneList = respList.map((json) => PhoneBook.fromJson(json)).toList(); 33 return phoneList; 34 } else { 35 throw Exception('Error getting phone contacts'); 36 } 37 } 38 39 Future<PhoneBook> getSinglePhoneContact(String id) async { 40 var response = await dio.post( 41 "$_endpoint/action/find", 42 options: Options(headers: headers), 43 data: jsonEncode( 44 { 45 "dataSource": _dataSource, 46 "database": _database, 47 "collection": _collection, 48 "filter": { 49 "_id": {"\$oid": id} 50 }, 51 }, 52 ), 53 ); 54 if (response.statusCode == 200) { 55 var resp = response.data\['documents'\][0]; 56 var contact = PhoneBook.fromJson(resp); 57 return contact; 58 } else { 59 throw Exception('Error getting phone contact'); 60 } 61 } 62 63 Future updatePhoneContact(String id, String fullname, int phonenumber) async { 64 var response = await dio.post( 65 "$_endpoint/action/updateOne", 66 options: Options(headers: headers), 67 data: jsonEncode( 68 { 69 "dataSource": _dataSource, 70 "database": _database, 71 "collection": _collection, 72 "filter": { 73 "_id": {"\$oid": id} 74 }, 75 "update": { 76 "\$set": {"fullname": fullname, "phonenumber": phonenumber} 77 } 78 }, 79 ), 80 ); 81 if (response.statusCode == 200) { 82 return response.data; 83 } else { 84 throw Exception('Error getting phone contact'); 85 } 86 } 87 88 Future createPhoneContact(String fullname, int phonenumber) async { 89 var response = await dio.post( 90 "$_endpoint/action/insertOne", 91 options: Options(headers: headers), 92 data: jsonEncode( 93 { 94 "dataSource": _dataSource, 95 "database": _database, 96 "collection": _collection, 97 "document": {"fullname": fullname, "phonenumber": phonenumber} 98 }, 99 ), 100 ); 101 if (response.statusCode == 201) { 102 return response.data; 103 } else { 104 throw Exception('Error creating phone contact'); 105 } 106 } 107 108 Future deletePhoneContact(String id) async { 109 var response = await dio.post( 110 "$_endpoint/action/deleteOne", 111 options: Options(headers: headers), 112 data: jsonEncode( 113 { 114 "dataSource": _dataSource, 115 "database": _database, 116 "collection": _collection, 117 "filter": { 118 "_id": {"\$oid": id} 119 }, 120 }, 121 ), 122 ); 123 if (response.statusCode == 200) { 124 return response.data; 125 } else { 126 throw Exception('Error deleting phone contact'); 127 } 128 } 129 }

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:

1 import 'package:flutter/material.dart'; 2 import 'package:flutter_mongo/phone_service.dart'; 3 import 'package:flutter_mongo/screens/create.dart'; 4 import 'package:flutter_mongo/screens/detail.dart'; 5 import 'package:flutter_mongo/utils.dart'; 6 7 class Home extends StatefulWidget { 8 const Home({super.key}); 9 10 State<Home> createState() => _HomeState(); 11 } 12 13 class _HomeState extends State<Home> { 14 late List<PhoneBook> contacts; 15 bool _isLoading = false; 16 bool _isError = false; 17 18 19 void initState() { 20 getContacts(); 21 super.initState(); 22 } 23 24 getContacts() { 25 setState(() { 26 _isLoading = true; 27 }); 28 PhoneService().getPhoneContacts().then((value) { 29 setState(() { 30 contacts = value; 31 _isLoading = false; 32 }); 33 }).catchError((onError) { 34 setState(() { 35 _isLoading = false; 36 _isError = true; 37 }); 38 }); 39 } 40 41 42 Widget build(BuildContext context) { 43 // UI CODE GOES HERE 44 } 45 }

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.

1 //imports goes here 2 3 class Home extends StatefulWidget { 4 //code goes here 5 } 6 7 class _HomeState extends State<Home> { 8 //state goes here 9 10 11 void initState() { 12 /code goes here 13 } 14 15 getContacts() { 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 phone contacts', 30 style: TextStyle( 31 color: Colors.red, 32 fontWeight: FontWeight.bold, 33 ), 34 ), 35 ) 36 : Scaffold( 37 appBar: AppBar( 38 title: const Text('Phonebook'), 39 backgroundColor: Colors.black, 40 ), 41 body: ListView.builder( 42 itemCount: contacts.length, 43 itemBuilder: (context, index) { 44 return InkWell( 45 onTap: () { 46 Navigator.push( 47 context, 48 MaterialPageRoute( 49 builder: (context) => 50 Detail(id: contacts[index].id!), 51 ), 52 ); 53 }, 54 child: Container( 55 decoration: const BoxDecoration( 56 border: Border( 57 bottom: BorderSide(width: .5, color: Colors.grey), 58 ), 59 ), 60 padding: EdgeInsets.fromLTRB(10, 20, 10, 20), 61 child: Row( 62 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 63 children: [ 64 Expanded( 65 flex: 7, 66 child: Column( 67 crossAxisAlignment: CrossAxisAlignment.start, 68 children: [ 69 Text( 70 contacts[index].fullname, 71 style: TextStyle( 72 color: Colors.black, 73 fontWeight: FontWeight.w800), 74 ), 75 SizedBox(height: 10.0), 76 Text(contacts[index].phonenumber.toString()) 77 ], 78 ), 79 ), 80 Column( 81 crossAxisAlignment: CrossAxisAlignment.end, 82 children: [ 83 SizedBox(height: 10.0), 84 Icon(Icons.arrow_forward_ios_rounded) 85 ], 86 ), 87 ], 88 ), 89 ), 90 ); 91 }, 92 ), 93 floatingActionButton: FloatingActionButton( 94 onPressed: () { 95 Navigator.push( 96 context, 97 MaterialPageRoute( 98 builder: (context) => const Create(), 99 ), 100 ); 101 }, 102 backgroundColor: Colors.black, 103 tooltip: 'Create contact', 104 child: const Icon(Icons.add), 105 ), 106 ); 107 } 108 }

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:

1 import 'package:flutter/material.dart'; 2 import 'package:flutter_mongo/phone_service.dart'; //ADD THIS 3 import 'package:flutter_mongo/screens/home.dart'; 4 5 class Create extends StatefulWidget { 6 const Create({ 7 Key? key, 8 }) : super(key: key); 9 10 State<Create> createState() => _CreateState(); 11 } 12 13 class _CreateState extends State<Create> { 14 final _formKey = GlobalKey<FormState>(); 15 final TextEditingController _fullname = TextEditingController(); 16 final TextEditingController _phonenumber = TextEditingController(); 17 bool _isLoading = false; 18 19 createContact() { 20 setState(() { 21 _isLoading = true; 22 }); 23 PhoneService() 24 .createPhoneContact(_fullname.text, int.parse(_phonenumber.text)) 25 .then((value) { 26 setState(() { 27 _isLoading = false; 28 }); 29 ScaffoldMessenger.of(context).showSnackBar( 30 const SnackBar(content: Text('Contact created successfully!')), 31 ); 32 Navigator.push( 33 context, 34 MaterialPageRoute(builder: (context) => const Home()), 35 ); 36 }).catchError((onError) { 37 setState(() { 38 _isLoading = false; 39 }); 40 ScaffoldMessenger.of(context).showSnackBar( 41 const SnackBar(content: Text('Error creating contact!')), 42 ); 43 }); 44 } 45 46 47 Widget build(BuildContext context) { 48 //UI CODE GOES HERE 49 } 50 }

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.

1 //import goes here 2 3 class Create extends StatefulWidget { 4 //code goes here 5 } 6 7 class _CreateState extends State<Create> { 8 //state goes here 9 10 createContact() { 11 //code goes here 12 } 13 14 15 Widget build(BuildContext context) { 16 return Scaffold( 17 appBar: AppBar( 18 title: const Text("Create contact"), 19 backgroundColor: Colors.black, 20 ), 21 body: Padding( 22 padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0), 23 child: Form( 24 key: _formKey, 25 child: Column( 26 children: [ 27 Column( 28 crossAxisAlignment: CrossAxisAlignment.start, 29 children: [ 30 Column( 31 crossAxisAlignment: CrossAxisAlignment.start, 32 children: [ 33 const Text( 34 'Fullname', 35 style: TextStyle( 36 color: Colors.grey, 37 fontSize: 14.0, 38 ), 39 ), 40 const SizedBox(height: 5.0), 41 TextFormField( 42 controller: _fullname, 43 validator: (value) { 44 if (value == null || value.isEmpty) { 45 return 'Please input your fullname'; 46 } 47 return null; 48 }, 49 decoration: InputDecoration( 50 contentPadding: const EdgeInsets.symmetric( 51 vertical: 10, horizontal: 20), 52 hintText: "input name", 53 fillColor: Colors.white, 54 focusedBorder: OutlineInputBorder( 55 borderRadius: BorderRadius.circular(10), 56 borderSide: const BorderSide(color: Colors.grey), 57 ), 58 enabledBorder: OutlineInputBorder( 59 borderRadius: BorderRadius.circular(10), 60 borderSide: const BorderSide(color: Colors.grey), 61 ), 62 errorBorder: OutlineInputBorder( 63 borderRadius: BorderRadius.circular(10), 64 borderSide: const BorderSide(color: Colors.red), 65 ), 66 ), 67 keyboardType: TextInputType.text, 68 maxLines: null, 69 ), 70 const SizedBox(height: 30.0), 71 const Text( 72 'Phone number', 73 style: TextStyle( 74 color: Colors.grey, 75 fontSize: 14.0, 76 ), 77 ), 78 const SizedBox(height: 5.0), 79 TextFormField( 80 controller: _phonenumber, 81 validator: (value) { 82 if (value == null || value.isEmpty) { 83 return 'Please input your phone number'; 84 } 85 return null; 86 }, 87 decoration: InputDecoration( 88 contentPadding: const EdgeInsets.symmetric( 89 vertical: 10, horizontal: 20), 90 hintText: "input phone number", 91 fillColor: Colors.white, 92 focusedBorder: OutlineInputBorder( 93 borderRadius: BorderRadius.circular(10), 94 borderSide: const BorderSide(color: Colors.grey), 95 ), 96 enabledBorder: OutlineInputBorder( 97 borderRadius: BorderRadius.circular(10), 98 borderSide: const BorderSide(color: Colors.grey), 99 ), 100 errorBorder: OutlineInputBorder( 101 borderRadius: BorderRadius.circular(10), 102 borderSide: const BorderSide(color: Colors.red), 103 ), 104 ), 105 keyboardType: TextInputType.number, 106 ), 107 ], 108 ), 109 ], 110 ), 111 const SizedBox(height: 30.0), 112 SizedBox( 113 height: 45, 114 width: double.infinity, 115 child: TextButton( 116 onPressed: _isLoading 117 ? null 118 : () { 119 if (_formKey.currentState!.validate()) { 120 createContact(); 121 } 122 }, 123 style: ButtonStyle( 124 backgroundColor: 125 MaterialStateProperty.all<Color>(Colors.black), 126 ), 127 child: const Text( 128 'Create contact', 129 style: TextStyle( 130 color: Colors.white, 131 fontWeight: FontWeight.bold, 132 fontSize: 14.0, 133 ), 134 ), 135 ), 136 ), 137 ], 138 ), 139 ), 140 ), 141 ); 142 } 143 }

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.

1 import 'package:flutter/material.dart'; 2 import 'package:flutter_mongo/phone_service.dart'; 3 import 'package:flutter_mongo/screens/home.dart'; 4 import 'package:flutter_mongo/utils.dart'; 5 6 class 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 13 class _DetailState extends State<Detail> { 14 final _formKey = GlobalKey<FormState>(); 15 final TextEditingController _fullname = TextEditingController(); 16 final TextEditingController _phonenumber = TextEditingController(); 17 late PhoneBook contact; 18 bool _isLoading = false; 19 bool _isSubmitting = false; 20 bool _isError = false; 21 22 23 void initState() { 24 getContacts(); 25 super.initState(); 26 } 27 28 getContacts() { 29 setState(() { 30 _isLoading = true; 31 }); 32 PhoneService().getSinglePhoneContact(widget.id).then((value) { 33 setState(() { 34 contact = value; 35 _isLoading = false; 36 }); 37 _fullname.text = value.fullname; 38 _phonenumber.text = value.phonenumber.toString(); 39 }).catchError((onError) { 40 setState(() { 41 _isLoading = false; 42 _isError = true; 43 }); 44 }); 45 } 46 47 updateContact(String fullname, int phonenumber) { 48 setState(() { 49 _isSubmitting = true; 50 }); 51 PhoneService() 52 .updatePhoneContact(widget.id, fullname, phonenumber) 53 .then((value) { 54 setState(() { 55 _isSubmitting = false; 56 }); 57 ScaffoldMessenger.of(context).showSnackBar( 58 const SnackBar(content: Text('Contact updated successfully!')), 59 ); 60 Navigator.push( 61 context, 62 MaterialPageRoute(builder: (context) => const Home()), 63 ); 64 }).catchError((onError) { 65 setState(() { 66 _isSubmitting = false; 67 _isError = true; 68 }); 69 ScaffoldMessenger.of(context).showSnackBar( 70 const SnackBar(content: Text('Error updating contact!')), 71 ); 72 }); 73 } 74 75 deleteContact() { 76 setState(() { 77 _isSubmitting = true; 78 }); 79 PhoneService().deletePhoneContact(widget.id).then((value) { 80 setState(() { 81 _isSubmitting = false; 82 }); 83 ScaffoldMessenger.of(context).showSnackBar( 84 const SnackBar(content: Text('Contact deleted successfully!')), 85 ); 86 Navigator.push( 87 context, 88 MaterialPageRoute(builder: (context) => const Home()), 89 ); 90 }).catchError((onError) { 91 setState(() { 92 _isSubmitting = false; 93 _isError = true; 94 }); 95 ScaffoldMessenger.of(context).showSnackBar( 96 const SnackBar(content: Text('Error deleting contact!')), 97 ); 98 }); 99 } 100 101 102 Widget build(BuildContext context) { 103 //UI GOES HERE 104 } 105 }

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.

1 //import goes here 2 3 class Detail extends StatefulWidget { 4 //code goes here 5 } 6 7 class _DetailState extends State<Detail> { 8 //states goes here 9 10 11 void initState() { 12 //code goes here 13 } 14 15 getSingleContact() { 16 //code goes here 17 } 18 19 updateContact(String fullname, int phonenumber) { 20 //code goes here 21 } 22 23 deleteContact() { 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 phone contacts', 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 backgroundColor: Colors.black, 48 ), 49 body: Padding( 50 padding: const EdgeInsets.symmetric( 51 horizontal: 16.0, vertical: 30.0), 52 child: Form( 53 key: _formKey, 54 child: Column( 55 children: [ 56 Column( 57 crossAxisAlignment: CrossAxisAlignment.start, 58 children: [ 59 Column( 60 crossAxisAlignment: CrossAxisAlignment.start, 61 children: [ 62 const Text( 63 'Fullname', 64 style: TextStyle( 65 color: Colors.grey, 66 fontSize: 14.0, 67 ), 68 ), 69 const SizedBox(height: 5.0), 70 TextFormField( 71 controller: _fullname, 72 validator: (value) { 73 if (value == null || value.isEmpty) { 74 return 'Please input your fullname'; 75 } 76 return null; 77 }, 78 decoration: InputDecoration( 79 contentPadding: const EdgeInsets.symmetric( 80 vertical: 10, horizontal: 20), 81 hintText: "input name", 82 fillColor: Colors.white, 83 focusedBorder: OutlineInputBorder( 84 borderRadius: BorderRadius.circular(10), 85 borderSide: 86 const BorderSide(color: Colors.grey), 87 ), 88 enabledBorder: OutlineInputBorder( 89 borderRadius: BorderRadius.circular(10), 90 borderSide: 91 const BorderSide(color: Colors.grey), 92 ), 93 errorBorder: OutlineInputBorder( 94 borderRadius: BorderRadius.circular(10), 95 borderSide: 96 const BorderSide(color: Colors.red), 97 ), 98 ), 99 keyboardType: TextInputType.text, 100 maxLines: null, 101 ), 102 const SizedBox(height: 30.0), 103 const Text( 104 'Phone number', 105 style: TextStyle( 106 color: Colors.grey, 107 fontSize: 14.0, 108 ), 109 ), 110 const SizedBox(height: 5.0), 111 TextFormField( 112 controller: _phonenumber, 113 validator: (value) { 114 if (value == null || value.isEmpty) { 115 return 'Please input your phone number'; 116 } 117 return null; 118 }, 119 decoration: InputDecoration( 120 contentPadding: const EdgeInsets.symmetric( 121 vertical: 10, horizontal: 20), 122 hintText: "input phone number", 123 fillColor: Colors.white, 124 focusedBorder: OutlineInputBorder( 125 borderRadius: BorderRadius.circular(10), 126 borderSide: 127 const BorderSide(color: Colors.grey), 128 ), 129 enabledBorder: OutlineInputBorder( 130 borderRadius: BorderRadius.circular(10), 131 borderSide: 132 const BorderSide(color: Colors.grey), 133 ), 134 errorBorder: OutlineInputBorder( 135 borderRadius: BorderRadius.circular(10), 136 borderSide: 137 const BorderSide(color: Colors.red), 138 ), 139 ), 140 keyboardType: TextInputType.number, 141 ), 142 ], 143 ), 144 ], 145 ), 146 const SizedBox(height: 30.0), 147 SizedBox( 148 height: 45, 149 width: double.infinity, 150 child: TextButton( 151 onPressed: _isSubmitting 152 ? null 153 : () { 154 if (_formKey.currentState!.validate()) { 155 updateContact( 156 _fullname.text, 157 int.parse(_phonenumber.text), 158 ); 159 } 160 }, 161 style: ButtonStyle( 162 backgroundColor: MaterialStateProperty.all<Color>( 163 Colors.black), 164 ), 165 child: const Text( 166 'Update contact', 167 style: TextStyle( 168 color: Colors.white, 169 fontWeight: FontWeight.bold, 170 fontSize: 14.0, 171 ), 172 ), 173 ), 174 ), 175 ], 176 ), 177 ), 178 ), 179 floatingActionButton: FloatingActionButton( 180 onPressed: _isSubmitting 181 ? null 182 : () { 183 deleteContact(); 184 }, 185 backgroundColor: Colors.red, 186 tooltip: 'Delete', 187 child: const Icon(Icons.delete), 188 ), 189 ); 190 } 191 }

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

1 flutter run

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:

Related posts