Flutter Tutorial: Consume CRUD REST API Android and iOS Apps

by Didin J. on Aug 10, 2020 Flutter Tutorial: Consume CRUD REST API Android and iOS Apps

The comprehensive step by step Flutter tutorial on building Android and iOS apps that consume the CRUD REST API

In this Flutter tutorial, we will show you how to build Android and iOS apps that consume the CRUD REST API. Almost all Android and iOS apps access data using REST API. In the Flutter application, these requirements will be done using the HTTP package. We will use our simple Node-Express-MongoDB REST API as the REST API backend. So, we will focus this tutorial on how to access that REST API using the Flutter HTTP package.

This tutorial divided into several steps:

The following tools, frameworks, and libraries are required for this tutorial:

  1. Flutter SDK
  2. Node-Express REST API
  3. Android SDK
  4. XCode
  5. Terminal (on Mac/Linux) or CMD (on Windows)
  6. IDE (Android Studio/IntelliJ/Visual Studio Code)

Let get started to the main steps!

You can watch the video tutorial on our YouTube channel here. Please like, share, comment, and subscribe to ou YouTube channel.


Step #1: Preparation

Install Flutter SDK

To install flutter SDK, first, we have to download flutter_macos_1.20.0-stable.zip then extract the file to your desired location.

cd ~/development
unzip ~/Downloads/flutter_macos_1.20.0-stable.zip

Next, add flutter tools to the path.

export PATH="$PATH:~/development/flutter/bin"

Next, to make iOS and Android binaries can be downloaded ahead of time, type this Flutter command.

flutter precache

Check the Required Dependencies

To check the environment and displays a report to the terminal window to find dependencies that required to install, type this command.

flutter doctor

We have this summary in the Terminal.

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.20.0-stable, on Mac OS X 10.14.6 18G2022,
    locale en-ID)

[!] Android toolchain - develop for Android devices (Android SDK version
    29.0.0-rc1)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor
      --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.37.1)
[!] Connected device
    ! No devices available

! Doctor found issues in 2 categories.

To fixes, the issues like this, just connect the Android device and update the Android license by type this command.

flutter doctor --android-licenses

Type `y` for every question that displayed in the terminal. Next, check the connected Android device by type this command.

adb devices
List of devices attached
FUZDON75YLOVVC5S    device

Setup IDE

We need to set up the IDE to make it working with the Flutter app easily and compatibly. Start the Android Studio (we will use this IDE) then open Android Studio Menu -> Preference.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Android Studio Plugins

Choose plugins in the left pane.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Choose Plugins

Type Flutter in the plugins marketplace then presses Enter. Next, click the Install button on the Flutter. If there's a prompt to install Dart, click Yes. Restart the IDE when prompt to ask restart.

Now, the Flutter is ready to develop Android and iOS mobile apps.


Step #2: Create a Flutter Application

After the IDE setup complete, it will back to the Android starting dialog.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Android Studio Welcome

Choose `Start a New Flutter Project` then choose `Flutter Application`.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - New Flutter Project

Click the Next Button then fill the required fields and choose the previously installed Flutter SDK path.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Project Name

Click the next button then fill the package name with your own domain and leave the "Include Kotlin support for Android code" and "Include Swift support for iOS code" blank.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Package Name

Click the Finish button and the Flutter application creation in progress. Next, run the Flutter application for the first time. In the Android Studio toolbar, choose the device and main.dart then click the play button.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Android Studio Toolbar

The initial Flutter apps look like this in the Android device.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Flutter Demo


Step #3: Create Flutter HTTP Service

As we mention in the first paragraph, we will use the HTTP library package to access the RESTful API from the REST API server. For that, install this package by open and edit pubspec.yaml then add this dependency.


dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.3
  http: ^0.12.2

Next, click the "Packages get" button in the Flutter commands at the top of pubspec.yml content. That command will install the registered dependencies.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Pub Get

Next, create a package and class or object `lib/models/cases.dart` that represent the SQLite table. This class is about coronavirus cases. So, the content of this class should be like this.

class Cases {
  final String id;
  final String name;
  final String gender;
  final int age;
  final String address;
  final String city;
  final String country;
  final String status;
  final String updated;

  Cases({ this.id, this.name, this.gender, this.age, this.address, this.city, this.country, this.status, this.updated });

  factory Cases.fromJson(Map<String, dynamic> json) {
    return Cases(
      id: json['_id'] as String,
      name: json['name'] as String,
      gender: json['gender'] as String,
      age: json['age'] as int,
      address: json['address'] as String,
      city: json['city'] as String,
      country: json['country'] as String,
      status: json['status'] as String,
      updated: json['updated'] as String,
    );
  }

  @override
  String toString() {
    return 'Cases{id: $id, name: $name, age: $age}';
  }
}

Next, create a package and class or object `lib/services/api_service.dart` where we will put all CRUD (POST, GET, PUT, DELETE) methods to the REST API. Fill this class with this CRUD operation of HTTP requests to the REST API.

import 'dart:convert';

import 'package:flutter_restapi/models/cases.dart';
import 'package:http/http.dart';

class ApiService {
  final String apiUrl = "http://192.168.0.7:3000/api";

  Future<List<Cases>> getCases() async {
    Response res = await get(apiUrl);

    if (res.statusCode == 200) {
      List<dynamic> body = jsonDecode(res.body);
      List<Cases> cases = body.map((dynamic item) => Cases.fromJson(item)).toList();
      return cases;
    } else {
      throw "Failed to load cases list";
    }
  }

  Future<Cases> getCaseById(String id) async {
    final response = await get('$apiUrl/$id');

    if (response.statusCode == 200) {
      return Cases.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to load a case');
    }
  }

  Future<Cases> createCase(Cases cases) async {
    Map data = {
      'name': cases.name,
      'gender': cases.gender,
      'age': cases.age,
      'address': cases.address,
      'city': cases.city,
      'country': cases.country,
      'status': cases.status
    };

    final Response response = await post(
      apiUrl,
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(data),
    );
    if (response.statusCode == 200) {
      return Cases.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to post cases');
    }
  }

  Future<Cases> updateCases(String id, Cases cases) async {
    Map data = {
      'name': cases.name,
      'gender': cases.gender,
      'age': cases.age,
      'address': cases.address,
      'city': cases.city,
      'country': cases.country,
      'status': cases.status
    };

    final Response response = await put(
      '$apiUrl/$id',
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(data),
    );
    if (response.statusCode == 200) {
      return Cases.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to update a case');
    }
  }

  Future<void> deleteCase(String id) async {
    Response res = await delete('$apiUrl/$id');

    if (res.statusCode == 200) {
      print("Case deleted");
    } else {
      throw "Failed to delete a case.";
    }
  }

}


Step #4: Display List of Data

We will display the list of data in a separate Dart file that will call from the main.dart home page body. For that, we need a dart file to view the list of data. Create a lib/caseslist.dart file then adds these imports.

import 'package:flutter/material.dart';
import 'package:flutter_restapi/models/cases.dart';
import 'detailwidget.dart';

Create a class name that extends StatelessWidget object.

class CasesList extends StatelessWidget {

}

Inside that class, declare these variables that hold Cases list that loaded from the main.dart and create Key for the list.

  final List<Cases> cases;
  CasesList({Key key, this.cases}) : super(key: key);

Add an override method after the variables to build the ListView widget for the list of cases.

  @override
  Widget build(BuildContext context) {
    return
      ListView.builder(
          itemCount: cases == null ? 0 : cases.length,
          itemBuilder: (BuildContext context, int index) {
            return
              Card(
                  child: InkWell(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => DetailWidget(cases[index])),
                      );
                    },
                    child: ListTile(
                      leading: Icon(Icons.person),
                      title: Text(cases[index].name),
                      subtitle: Text(cases[index].age.toString()),
                    ),
                  )
              );
          });
  }

That ListView builder contains the Card that has the child of InkWell that use to navigate to the DetailWidget using MaterialPageRoute. The child of the Card is ListTile that contains an Icon (leading), Text (title), and Text(subtitle).

The InkWell widget has an onTap event with an action to Navigate to the details page. Container, Column, Image, and Text have their own properties to adjust the style or layout.

Keep in mind, every widget that uses the child only has one widget as its child. If you need to put more than one widget to the parent widget, use children: <Widget> property.

Next, open and edit lib/main.dart then replace all Dart codes with these lines of codes to display the ListView in the main home page.

import 'package:flutter/material.dart';
import 'package:flutter_restapi/adddatawidget.dart';
import 'dart:async';
import 'package:flutter_restapi/models/cases.dart';
import 'package:flutter_restapi/services/api_service.dart';
import 'package:flutter_restapi/caseslist.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
        // This makes the visual density adapt to the platform that you run
        // the app on. For desktop platforms, the controls will be smaller and
        // closer together (more dense) than on mobile platforms.
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ApiService api = ApiService();
  List<Cases> casesList;

  @override
  Widget build(BuildContext context) {
    if(casesList == null) {
      casesList = List<Cases>();
    }
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: new Container(
        child: new Center(
            child: new FutureBuilder(
              future: loadList(),
              builder: (context, snapshot) {
                return casesList.length > 0? new CasesList(cases: casesList):
                new Center(child:
                new Text('No data found, tap plus button to add!', style: Theme.of(context).textTheme.title));
              },
            )
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _navigateToAddScreen(context);
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Future loadList() {
    Future<List<Cases>> futureCases = api.getCases();
    futureCases.then((casesList) {
      setState(() {
        this.casesList = casesList;
      });
    });
    return futureCases;
  }

  _navigateToAddScreen (BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => AddDataWidget()),
    );
  }
}

We use the existing floating button as the add-data button with an action to go to AddDataWidget.dart.


Step #5: Show Data Details

We will display data details to another page that opened when tapping on a list item in the list page. For that, create a Dart file in the lib folder first lib/detailwidget.dart.  We will use a scrollable Card widget to display a detail to prevent overflow if the Card content is longer. Next, open and edit lib/detailwidget.dart then add these imports of Flutter material, database helper, editdatawidget, and cases object model.

import 'package:flutter/material.dart';
import 'services/api_service.dart';
import 'editdatawidget.dart';
import 'models/cases.dart';

Add a DetailWidget class that extends StatefulWidget. This class has a constructor with an object field, a field of Cases object, and _DetailWidgetState that builds the view for data detail.

class DetailWidget extends StatefulWidget {
  DetailWidget(this.cases);

  final Cases cases;

  @override
  _DetailWidgetState createState() => _DetailWidgetState();
}

Add a _DetailWidgetState class that implementing all required widgets to display data details.

class _DetailWidgetState extends State<DetailWidget> {
  _DetailWidgetState();

  final ApiService api = ApiService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Details'),
      ),
      body: SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.all(20.0),
          child: Card(
              child: Container(
                  padding: EdgeInsets.all(10.0),
                  width: 440,
                  child: Column(
                    children: <Widget>[
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Name:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.name, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Gender:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.gender, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Age:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.age.toString(), style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Address:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.address, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('City:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.city, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Country:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.country, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Status:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.status, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            Text('Update Date:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                            Text(widget.cases.updated, style: Theme.of(context).textTheme.title)
                          ],
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                        child: Column(
                          children: <Widget>[
                            RaisedButton(
                              splashColor: Colors.red,
                              onPressed: () {
                                _navigateToEditScreen(context, widget.cases);
                              },
                              child: Text('Edit', style: TextStyle(color: Colors.white)),
                              color: Colors.blue,
                            ),
                            RaisedButton(
                              splashColor: Colors.red,
                              onPressed: () {
                                _confirmDialog();
                              },
                              child: Text('Delete', style: TextStyle(color: Colors.white)),
                              color: Colors.blue,
                            )
                          ],
                        ),
                      ),
                    ],
                  )
              )
          ),
        ),
      ),
    );
  }

}

That codes build widgets combination of Container, Card, Column, Image, Text, and RaisedButton. The RaisedButtons has onPressed event that action to navigate to the EditDataWidget and trigger delete confirm dialog. Next, before the closing of _DetailWidgetState class body add this method or function to navigate to the EditDataWidget with cases object params.

  _navigateToEditScreen (BuildContext context, Cases cases) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => EditDataWidget(cases)),
    );
  }

To handle the delete button, we need to add a method or function after the above method that shows an alert dialog to confirm if data will be deleted.

  Future<void> _confirmDialog() async {
    return showDialog<void>(
      context: context,
      barrierDismissible: false, // user must tap button!
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Warning!'),
          content: SingleChildScrollView(
            child: ListBody(
              children: <Widget>[
                Text('Are you sure want delete this item?'),
              ],
            ),
          ),
          actions: <Widget>[
            FlatButton(
              child: Text('Yes'),
              onPressed: () {
                api.deleteCase(widget.cases.id);
                Navigator.popUntil(context, ModalRoute.withName(Navigator.defaultRouteName));
              },
            ),
            FlatButton(
              child: const Text('No'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }


Step #6: Add a New Data

We will create a new Dart file for the entry form to add new data. In the Form, there will be a TextFieldForm, RadioButton, and Submit Button. Create a new Dart file lib/adddatawidget.dart then add these imports to that file.

import 'package:flutter/material.dart';
import 'package:flutter_restapi/services/api_service.dart';
import 'models/cases.dart';

The TextFormField will use DateTimeField and TextEditingController to binding the value. But for the Radio Button will binding it manually by first adding the enum variables after the imports.

enum Gender { male, female }
enum Status { positive, dead, recovered }

Next, create a class of AddDataWidget that extends StatefulWidget and has a constructor and _AddDataWidgetState initiation.

class AddDataWidget extends StatefulWidget {
  AddDataWidget();

  @override
  _AddDataWidgetState createState() => _AddDataWidgetState();
}

Now, create a class that called in AddDataWidget class that will build the layout for the Add data.

class _AddDataWidgetState extends State<AddDataWidget> {
  _AddDataWidgetState();

  final ApiService api = ApiService();
  final _addFormKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  String gender = 'male';
  Gender _gender = Gender.male;
  final _ageController = TextEditingController();
  final _addressController = TextEditingController();
  final _cityController = TextEditingController();
  final _countryController = TextEditingController();
  String status = 'positive';
  Status _status = Status.positive;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add Cases'),
      ),
      body: Form(
        key: _addFormKey,
        child: SingleChildScrollView(
          child: Container(
            padding: EdgeInsets.all(20.0),
            child: Card(
                child: Container(
                    padding: EdgeInsets.all(10.0),
                    width: 440,
                    child: Column(
                      children: <Widget>[
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Full Name'),
                              TextFormField(
                                controller: _nameController,
                                decoration: const InputDecoration(
                                  hintText: 'Full Name',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter full name';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Gender'),
                              ListTile(
                                title: const Text('Male'),
                                leading: Radio(
                                  value: Gender.male,
                                  groupValue: _gender,
                                  onChanged: (Gender value) {
                                    setState(() {
                                      _gender = value;
                                      gender = 'male';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Female'),
                                leading: Radio(
                                  value: Gender.female,
                                  groupValue: _gender,
                                  onChanged: (Gender value) {
                                    setState(() {
                                      _gender = value;
                                      gender = 'female';
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Age'),
                              TextFormField(
                                controller: _ageController,
                                decoration: const InputDecoration(
                                  hintText: 'Age',
                                ),
                                keyboardType: TextInputType.number,
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter age';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Address'),
                              TextFormField(
                                controller: _addressController,
                                decoration: const InputDecoration(
                                  hintText: 'Address',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter address';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('City'),
                              TextFormField(
                                controller: _cityController,
                                decoration: const InputDecoration(
                                  hintText: 'City',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter city';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Country'),
                              TextFormField(
                                controller: _countryController,
                                decoration: const InputDecoration(
                                  hintText: 'Country',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter country';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Status'),
                              ListTile(
                                title: const Text('Positive'),
                                leading: Radio(
                                  value: Status.positive,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'positive';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Dead'),
                                leading: Radio(
                                  value: Status.dead,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'dead';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Recovered'),
                                leading: Radio(
                                  value: Status.recovered,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'recovered';
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              RaisedButton(
                                splashColor: Colors.red,
                                onPressed: () {
                                  if (_addFormKey.currentState.validate()) {
                                    _addFormKey.currentState.save();
                                    api.createCase(Cases(name: _nameController.text, gender: gender, age: int.parse(_ageController.text), address: _addressController.text, city: _cityController.text, country: _countryController.text, status: status));

                                    Navigator.pop(context) ;
                                  }
                                },
                                child: Text('Save', style: TextStyle(color: Colors.white)),
                                color: Colors.blue,
                              )
                            ],
                          ),
                        ),
                      ],
                    )
                )
            ),
          ),
        ),
      ),
    );
  }
}

That class declares all required variables that will save to the REST API, data binding, database helper initiation, and Form declaration. The form contains the TextFormField, Radio Button, and Submit Button. The action on submit will save the data to the REST API then redirect back to the list view.


Step #7: Edit a Data

The layout for edit data is the same as the add data view with additional object params that get from the details page. This object will fill the default value of the TextFormField, Radio Button, and Submit Button. On the submit it will update the data based on the ID then redirect to the list view. First, create a new dart file in the lib folder lib/edidatawidget.dart. Open and edit that file then add these lines of the dart codes to build the edit form and function to submit this form to the REST API.

import 'package:flutter/material.dart';
import 'package:flutter_restapi/services/api_service.dart';
import 'models/cases.dart';

enum Gender { male, female }
enum Status { positive, dead, recovered }

class EditDataWidget extends StatefulWidget {
  EditDataWidget(this.cases);

  final Cases cases;

  @override
  _EditDataWidgetState createState() => _EditDataWidgetState();
}

class _EditDataWidgetState extends State<EditDataWidget> {
  _EditDataWidgetState();

  final ApiService api = ApiService();
  final _addFormKey = GlobalKey<FormState>();
  String id = '';
  final _nameController = TextEditingController();
  String gender = 'male';
  Gender _gender = Gender.male;
  final _ageController = TextEditingController();
  final _addressController = TextEditingController();
  final _cityController = TextEditingController();
  final _countryController = TextEditingController();
  String status = 'positive';
  Status _status = Status.positive;

  @override
  void initState() {
    id = widget.cases.id;
    _nameController.text = widget.cases.name;
    gender = widget.cases.gender;
    if(widget.cases.gender == 'male') {
      _gender = Gender.male;
    } else {
      _gender = Gender.female;
    }
    _ageController.text = widget.cases.age.toString();
    _addressController.text = widget.cases.address;
    _cityController.text = widget.cases.city;
    _countryController.text = widget.cases.country;
    status = widget.cases.status;
    if(widget.cases.status == 'positive') {
      _status = Status.positive;
    } else if(widget.cases.status == 'dead') {
      _status = Status.dead;
    } else {
      _status = Status.recovered;
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Cases'),
      ),
      body: Form(
        key: _addFormKey,
        child: SingleChildScrollView(
          child: Container(
            padding: EdgeInsets.all(20.0),
            child: Card(
                child: Container(
                    padding: EdgeInsets.all(10.0),
                    width: 440,
                    child: Column(
                      children: <Widget>[
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Full Name'),
                              TextFormField(
                                controller: _nameController,
                                decoration: const InputDecoration(
                                  hintText: 'Full Name',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter full name';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Gender'),
                              ListTile(
                                title: const Text('Male'),
                                leading: Radio(
                                  value: Gender.male,
                                  groupValue: _gender,
                                  onChanged: (Gender value) {
                                    setState(() {
                                      _gender = value;
                                      gender = 'male';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Female'),
                                leading: Radio(
                                  value: Gender.female,
                                  groupValue: _gender,
                                  onChanged: (Gender value) {
                                    setState(() {
                                      _gender = value;
                                      gender = 'female';
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Age'),
                              TextFormField(
                                controller: _ageController,
                                decoration: const InputDecoration(
                                  hintText: 'Age',
                                ),
                                keyboardType: TextInputType.number,
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter age';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Address'),
                              TextFormField(
                                controller: _addressController,
                                decoration: const InputDecoration(
                                  hintText: 'Address',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter address';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('City'),
                              TextFormField(
                                controller: _cityController,
                                decoration: const InputDecoration(
                                  hintText: 'City',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter city';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Country'),
                              TextFormField(
                                controller: _countryController,
                                decoration: const InputDecoration(
                                  hintText: 'Country',
                                ),
                                validator: (value) {
                                  if (value.isEmpty) {
                                    return 'Please enter country';
                                  }
                                  return null;
                                },
                                onChanged: (value) {},
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              Text('Status'),
                              ListTile(
                                title: const Text('Positive'),
                                leading: Radio(
                                  value: Status.positive,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'positive';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Dead'),
                                leading: Radio(
                                  value: Status.dead,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'dead';
                                    });
                                  },
                                ),
                              ),
                              ListTile(
                                title: const Text('Recovered'),
                                leading: Radio(
                                  value: Status.recovered,
                                  groupValue: _status,
                                  onChanged: (Status value) {
                                    setState(() {
                                      _status = value;
                                      status = 'recovered';
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                          child: Column(
                            children: <Widget>[
                              RaisedButton(
                                splashColor: Colors.red,
                                onPressed: () {
                                  if (_addFormKey.currentState.validate()) {
                                    _addFormKey.currentState.save();
                                    api.updateCases(id, Cases(name: _nameController.text, gender: gender, age: int.parse(_ageController.text), address: _addressController.text, city: _cityController.text, country: _countryController.text, status: status));

                                    Navigator.pop(context) ;
                                  }
                                },
                                child: Text('Save', style: TextStyle(color: Colors.white)),
                                color: Colors.blue,
                              )
                            ],
                          ),
                        ),
                      ],
                    )
                )
            ),
          ),
        ),
      ),
    );
  }

}


Step #8: Run and Test Flutter Application to Android and iOS Devices

To run this Flutter apps to Android, simply click the play button in the Android Studio toolbar. Make sure your Android device is connected to your computer and appear in Android Studio Toolbar.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Android Studio Toolbar

Here the working Flutter apps on Android device look like.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - Android Demo

To run this Flutter apps to the iOS device, at least you must have Apple Developer personal account with your own domain (our example: com.djamware) as a bundle or package. Next, open the ios/Runner.xcworkspace in XCode then click Runner in the Project Navigator.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - XCode Settings

In Runner.xcodeproj click the Build Settings tab.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - XCode Build Settings

Scroll down and find Signing then choose Development Team to your Apple Developer personal account. Now, you can run the Flutter apps to iOS Device from Xcode or Android Studio. Here are the Flutter apps on iOS devices look like.

Flutter Tutorial: Consume CRUD REST API Android and iOS Apps - iOS Demo

That it's, the Flutter Tutorial: Consume CRUD REST API Android and iOS Apps. You can get the full source code from our GitHub.

That just the basic. If you need more deep learning about Flutter, Dart, or related you can take the following cheap course:

Thanks!

Loading…