How to Build a Barcode Scanner App with Flutter Step by Step
Tags: programming, webdev, flutter, dart
Published: 2023-07-12 02:52:35

Barcode scanner apps can be used across a variety of industries, including **retail**, **manufacturing**, **logistics**, and **healthcare**. They are used to track inventory, manage assets, and improve the efficiency of business operations. In this article, we will walk through the steps of building a multiple barcode and QR code scanner app with Flutter and [Dynamsoft Barcode Reader](https://www.dynamsoft.com/barcode-reader/overview/) on **Windows**, **Android**, **iOS** and **Web** platforms.

<video src="https://user-images.githubusercontent.com/2202306/250517378-c49620d8-34e2-42f0-bd68-f674c5ef9778.mp4" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## Try Online Demo with Your Mobile Devices
![Flutter barcode scanner online demo](https://www.dynamsoft.com/codepool/img/2023/07/flutter-barcode-scanner-demo.png)

[https://yushulx.me/flutter-barcode-scanner/](https://yushulx.me/flutter-barcode-scanner/)

## Needed Flutter Plugins
- [flutter_barcode_sdk](https://pub.dev/packages/flutter_barcode_sdk): Wraps Dynamsoft Barcode Reader SDK for Flutter, supporting Windows, Linux, macOS, Android, iOS and Web. A valid [license key](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr) is required to use the plugin.
- [image_picker](https://pub.dev/packages/image_picker): Provides an easy way to pick an image/video from the image library, or to take a picture/video with the camera.
- [shared_preferences](https://pub.dev/packages/shared_preferences): Wraps platform-specific persistent storage for simple data (NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.).
- [camera](https://pub.dev/packages/camera): Provides APIs for identifying cameras, displaying a preview, and capturing images or video from a camera.
- [share_plus](https://pub.dev/packages/share_plus): Shares content via the platform share UI.
- [url_launcher](https://pub.dev/packages/url_launcher): Launches URLs, making it easy to open a web page.
- [flutter_exif_rotation](https://pub.dev/packages/flutter_exif_rotation): Rotates images automatically based on the EXIF orientation on Android and iOS.

## Getting Started with the App
1. Create a new Flutter project with the command:

    ```bash
    flutter create barcodescanner
    ```
2. Add the dependencies to `pubspec.yaml`:

    ```yaml
    dependencies:
      flutter_barcode_sdk: ^2.2.4
      image_picker: ^1.0.0
      shared_preferences: ^2.1.1
      camera: 
        git:
          url: https://github.com/yushulx/flutter_camera.git
      camera_windows: 
        git:
          url: https://github.com/yushulx/flutter_camera_windows.git
      share_plus: ^7.0.2
      url_launcher: ^6.1.11
      flutter_exif_rotation: ^0.5.1
    ```

    Here we use a forked version of the Flutter camera plugin, which relies on a modified [camera_web](https://github.com/flutter/packages/tree/main/packages/camera/camera_web) plugin to solve the issue of camera preview flipping on the desktop web platform. 

    ```yaml
    camera_web: 
      git:
        url: https://github.com/yushulx/flutter_camera_web.git
    ```

    The `camera_windows` plugin is used to support image streaming from the camera on Windows. It is ported from [https://github.com/flutter/packages/tree/main/packages/camera/camera_windows](https://github.com/flutter/packages/tree/main/packages/camera/camera_windows).

3. Create a `global.dart` file to store global variables:

    ```dart
    import 'package:flutter_barcode_sdk/flutter_barcode_sdk.dart';

    FlutterBarcodeSdk barcodeReader = FlutterBarcodeSdk();
    bool isLicenseValid = false;

    Future<int> initBarcodeSDK() async {
      int ret = await barcodeReader.setLicense(
          'DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==');
      if (ret == 0) isLicenseValid = true;
      await barcodeReader.init();
      await barcodeReader.setBarcodeFormats(BarcodeFormat.ALL);
      return ret;
    }
    ```
4. Replace the contents in `lib/main.dart` with the following code:

    ```dart
    import 'package:flutter/material.dart';
    import 'tab_page.dart';
    import 'dart:async';
    import 'global.dart';

    Future<void> main() async {
      runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
      const MyApp({super.key});

      Future<int> loadData() async {
        return await initBarcodeSDK();
      }

      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Dynamsoft Barcode Detection',
          theme: ThemeData(
            scaffoldBackgroundColor: colorMainTheme,
          ),
          home: FutureBuilder<int>(
            future: loadData(),
            builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
              if (!snapshot.hasData) {
                return const CircularProgressIndicator(); 
              }
              Future.microtask(() {
                Navigator.pushReplacement(context,
                    MaterialPageRoute(builder: (context) => const TabPage()));
              });
              return Container();
            },
          ),
        );
      }
    }
    ```

    When launching the app, the `FutureBuilder` widget will display a loading indicator until the barcode reader is initialized. After that, the `Navigator` widget will redirect the user to the `TabPage` widget.

## Building the User Interface
In the subsequent sections, we will adhere to the [UI design guidelines](https://xd.adobe.com/view/17dd83a0-6511-41c9-8207-8b6ea5b5b96c-428e/grid) to fully develop the barcode scanner application.

![Barcode scanner UI design](https://www.dynamsoft.com/codepool/img/2023/07/barcode-scanner-ui-design.png)

### Tab Bar
The tab bar is used to navigate between the home page, history page, and about page. 

![tab bar](https://www.dynamsoft.com/codepool/img/2023/07/flutter-tab-bar.png)

It is implemented with the `TabBar`, `TabController` and `TabBarView` widgets:

```dart
class _TabPageState extends State<TabPage> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  final List<CustomTab> myTabs = <CustomTab>[
    CustomTab(
        text: 'Home',
        icon: 'images/icon-home-gray.png',
        selectedIcon: 'images/icon-home-orange.png'),
    CustomTab(
        text: 'Scan History',
        icon: 'images/icon-history-gray.png',
        selectedIcon: 'images/icon-history-orange.png'),
    CustomTab(
        text: 'About',
        icon: 'images/icon-about-gray.png',
        selectedIcon: 'images/icon-about-orange.png'),
  ];

  int selectedIndex = 0;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: 3);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: TabBarView(
          controller: _tabController,
          children: const [
            HomePage(),
            HistoryPage(),
            AboutPage(),
          ],
        ),
        bottomNavigationBar: SizedBox(
          height: 83,
          child: TabBar(
            labelColor: Colors.blue,
            controller: _tabController,
            onTap: (index) {
              setState(() {
                selectedIndex = index;
              });
            },
            tabs: myTabs.map((CustomTab tab) {
              return MyTab(
                  tab: tab, isSelected: myTabs.indexOf(tab) == selectedIndex);
            }).toList(),
          ),
        ));
  }
}
```

The tabs could be customized with the `CustomTab` class. In our case, when a tab is selected, the icon and text will change color.

```dart
class MyTab extends StatelessWidget {
  final CustomTab tab;
  final bool isSelected;

  const MyTab({super.key, required this.tab, required this.isSelected});

  @override
  Widget build(BuildContext context) {
    return Tab(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Image.asset(
            isSelected ? tab.selectedIcon : tab.icon,
            width: 48,
            height: 32,
          ),
          Text(tab.text,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                fontSize: 9,
                color: isSelected ? colorOrange : colorSelect,
              ))
        ],
      ),
    );
  }
}

class CustomTab {
  final String text;
  final String icon;
  final String selectedIcon;

  CustomTab(
      {required this.text, required this.icon, required this.selectedIcon});
}
```

### Home Page
The home page contains a title, a description, a pair of buttons, and a banner. The title, description and buttons are centered horizontally. The banner fills the remaining space.

![home page](https://www.dynamsoft.com/codepool/img/2023/07/barcode-scanner-home-page.jpg)

- The title is aligned with the `Padding` widget and the `TextAlign.center` property:

  ```dart
  var title = const Padding(
      padding: EdgeInsets.only(
        top: 32,
      ),
      child: Text('BARCODE SCANNER',
          textAlign: TextAlign.center,
          style: TextStyle(
            fontSize: 32,
            color: Colors.white,
          )),
    );
  ```

- The description is aligned with the `Padding` widget and the `EdgeInsets.only` property:

  ```dart
  var description = Padding(
        padding: const EdgeInsets.only(top: 7, left: 33, right: 33),
        child: Text(
            "Whether it's distorted, dark, distant, blurred, batch or moving, we can scan it. At speed.",
            style: TextStyle(
              fontSize: 18,
              color: colorTitle,
            )));
  ```
- There are two buttons in the home page. The first button is used to launch the camera scanning page. The second button is used to pick an image file. To customize the buttons with `onTap()` event, the `GestureDetector` and `Container` widgets are used:
 
  ```dart
  final buttons = Padding(
        padding: const EdgeInsets.only(top: 44),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            GestureDetector(
                onTap: () {
                  if (!kIsWeb && Platform.isLinux) {
                    showAlert(context, "Warning",
                        "${Platform.operatingSystem} is not supported");
                    return;
                  }

                  Navigator.push(context, MaterialPageRoute(builder: (context) {
                    return const CameraPage();
                  }));
                },
                child: Container(
                  width: 150,
                  height: 125,
                  decoration: BoxDecoration(
                    color: colorOrange,
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Image.asset(
                        "images/icon-camera.png",
                        width: 90,
                        height: 60,
                      ),
                      const Text(
                        "Camera Scan",
                        overflow: TextOverflow.ellipsis,
                        style: TextStyle(fontSize: 16, color: Colors.white),
                      )
                    ],
                  ),
                )),
            GestureDetector(
                onTap: () {
                  scanImage();
                },
                child: Container(
                  width: 150,
                  height: 125,
                  decoration: BoxDecoration(
                    color: colorBackground,
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Image.asset(
                        "images/icon-image.png",
                        width: 90,
                        height: 60,
                      ),
                      const Text(
                        "Image Scan",
                        overflow: TextOverflow.ellipsis,
                        style: TextStyle(fontSize: 16, color: Colors.white),
                      )
                    ],
                  ),
                ))
          ],
        ));
  ```
- To fill the remaining space, the banner is implemented with the `Expanded` and `Positioned` widgets inside a `Stack` widget. A warning message is displayed on top of the banner if the license is invalid or expired:

  ```dart
  final image = Image.asset(
      "images/image-barcode.png",
      width: MediaQuery.of(context).size.width,
      fit: BoxFit.cover,
    );

  Expanded(
              child: Stack(
            children: [
              Positioned.fill(
                child: image,
              ),
              if (!isLicenseValid)
                Opacity(
                  opacity: 0.8,
                  child: Container(
                      width: MediaQuery.of(context).size.width,
                      height: 40,
                      color: const Color(0xffFF1A1A),
                      padding: const EdgeInsets.only(left: 20, right: 20),
                      child: InkWell(
                          onTap: () {
                            launchUrlString(
                                'https://www.dynamsoft.com/customer/license/trialLicense?product=dbr');
                          },
                          child: const Row(
                            children: [
                              Icon(Icons.warning_amber_rounded,
                                  color: Colors.white, size: 20),
                              Text(
                                "  License expired! Renew your license ->",
                                style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold),
                              ),
                            ],
                          ))),
                )
            ],
          ))
  ```

### Camera Scanning Page
The camera scanning page contains a camera preview, a barcode type setting button, a camera stop button and a camera flip button.
 
![camera preview](https://www.dynamsoft.com/codepool/img/2023/07/barcode-scanner-camera-preview.jpg)

To display the camera preview, we use the `CameraPreview` widget:

```dart
CameraController? controller;

Widget getPreview() {
    if (controller == null || !controller!.value.isInitialized || isFinished) {
      return Container(
        child: const Text('No camera available!'),
      );
    }

    return CameraPreview(controller!);
  }
```

The flip button is used to switch between the front and back cameras. 
```dart
Future<void> switchCamera() async {
    if (_cameras.length == 1) return;
    isFinished = true;

    if (kIsWeb) {
      await waitForStop();
    }

    cameraIndex = cameraIndex == 0 ? 1 : 0;
    toggleCamera(cameraIndex);
  }

Future<void> toggleCamera(int index) async {
    ResolutionPreset preset = ResolutionPreset.high;
    controller = CameraController(_cameras[index], preset);
    controller!.initialize().then((_) {
      if (!cbIsMounted()) {
        return;
      }

      previewSize = controller!.value.previewSize;

      startVideo();
    }).catchError((Object e) {
      if (e is CameraException) {
        switch (e.code) {
          case 'CameraAccessDenied':
            break;
          default:
            break;
        }
      }
    });
  }
```

The setting button is used to open the barcode type setting page. 

```dart
onPressed: () {
  _cameraManager.pauseCamera();
  Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => const SettingPage()))
      .then((value) {
    _cameraManager.resumeCamera();
  });
},
```

As the stop button is pressed, it will stop the camera stream and then navigate to the result page. The `pushReplacement` method is used to replace the current camera page with the result page:

```dart
Navigator.pushReplacement(
        context,
        MaterialPageRoute(
          builder: (context) => ResultPage(barcodeResults: order),
        ));
```

### Barcode Type Setting Page
The barcode type setting page contains a list of barcode types that can be expanded or collapsed. 

![barcode type setting](https://www.dynamsoft.com/codepool/img/2023/07/barcode-type-setting.jpg)

The `ExpansionPanelList` and `ExpansionPanel` widgets are used to implement the expansion and collapse effect:

```dart
class _ExpansionPanelListFormatsState extends State<ExpansionPanelListFormats> {
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Container(
        child: _buildPanel(),
      ),
    );
  }

  Widget _buildPanel() {
    return ExpansionPanelList(
      expandIconColor: Colors.white,
      dividerColor: Colors.grey[800],
      expansionCallback: (int index, bool isExpanded) {
        setState(() {
          allFormats[index].isExpanded = !isExpanded;
        });
      },
      children: allFormats.map<ExpansionPanel>((Item item) {
        return ExpansionPanel(
          backgroundColor: Colors.black,
          headerBuilder: (BuildContext context, bool isExpanded) {
            return SizedBox(
                height: 60,
                child: Container(
                  padding: const EdgeInsets.only(left: 20, right: 20),
                  child: Row(
                    children: [
                      Container(
                        width: 24,
                        height: 24,
                        color: colorOrange,
                        child: Checkbox(
                          fillColor: MaterialStateColor.resolveWith(
                              (states) => colorOrange),
                          checkColor: Colors.black,
                          activeColor: Colors.white,
                          value: item.isAllSelected,
                          onChanged: (bool? value) {
                            setState(() {
                              item.isAllSelected = value!;
                              if (item.isAllSelected) {
                                item.selectedOptions.clear();
                                item.selectedOptions.addAll(item.expandedValue);
                              } else {
                                item.selectedOptions.clear();
                              }
                            });
                            updateFormats();
                          },
                        ),
                      ),
                      const SizedBox(width: 20),
                      Text(
                        item.headerValue,
                        style:
                            const TextStyle(color: Colors.white, fontSize: 16),
                      )
                    ],
                  ),
                ));
          },
          body: ListView(
            children: item.expandedValue.map<Widget>((String value) {
              return SizedBox(
                  height: 40,
                  child: Container(
                      padding: const EdgeInsets.only(left: 20, right: 20),
                      child: Row(
                        children: [
                          Text(
                            value,
                            style: TextStyle(color: colorText, fontSize: 14),
                          ),
                          Expanded(child: Container()),
                          Container(
                            width: 24,
                            height: 24,
                            color: Colors.white,
                            child: Checkbox(
                              fillColor: MaterialStateColor.resolveWith(
                                  (states) => Colors.white),
                              checkColor: item.selectedOptions.contains(value)
                                  ? Colors.black
                                  : null,
                              activeColor: Colors.white,
                              value: item.selectedOptions.contains(value),
                              onChanged: (bool? changedValue) {
                                setState(() {
                                  item.selectedOptions.contains(value)
                                      ? item.selectedOptions.remove(value)
                                      : item.selectedOptions.add(value);
                                });
                                updateFormats();
                              },
                            ),
                          ),
                        ],
                      )));
            }).toList(),
          ),
          isExpanded: item.isExpanded,
        );
      }).toList(),
    );
  }
}
```

### Result Page
The result page displays the barcode detection results. The user can copy the barcode text to the clipboard, share them with other apps, and save them to the local storage.

![barcode detection result](https://www.dynamsoft.com/codepool/img/2023/07/barcode-detection-result.jpg)

To display each barcode result with a copy button, the `ListView` and `MyCustomWidget` widgets are used:

```dart

final resultList = Expanded(
        child: ListView.builder(
            itemCount: widget.barcodeResults.length,
            itemBuilder: (context, index) {
              return MyCustomWidget(
                index: index,
                result: widget.barcodeResults[index],
              );
            }));

class MyCustomWidget extends StatelessWidget {
  final BarcodeResult result;
  final int index;

  const MyCustomWidget({
    super.key,
    required this.index,
    required this.result,
  });

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
        decoration: const BoxDecoration(color: Colors.black),
        child: Padding(
            padding:
                const EdgeInsets.only(top: 13, bottom: 14, left: 20, right: 19),
            child: Row(
              children: [
                Text(
                  '${index + 1}',
                  style: TextStyle(color: colorGreen, fontSize: 14),
                ),
                const SizedBox(width: 14),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Format: ${result.format}',
                      style: const TextStyle(
                          color: Colors.white,
                          fontSize: 14,
                          overflow: TextOverflow.ellipsis),
                    ),
                    SizedBox(
                      width: MediaQuery.of(context).size.width - 110,
                      child: Text(
                        'Text: ${result.text}',
                        style: const TextStyle(
                            color: Colors.white,
                            fontSize: 14,
                            overflow: TextOverflow.ellipsis),
                      ),
                    ),
                  ],
                ),
                Expanded(child: Container()),
                InkWell(
                  onTap: () {
                    Clipboard.setData(ClipboardData(
                        text:
                            'Format: ${result.format}, Text: ${result.text}'));
                  },
                  child: Text('Copy',
                      style: TextStyle(color: colorGreen, fontSize: 14)),
                ),
              ],
            )));
  }
}
```

We use the `SharedPreferences` plugin to save the results to the local storage:

```dart
onPressed: () async {
  final SharedPreferences prefs =
      await SharedPreferences.getInstance();
  var results = prefs.getStringList('barcode_data');
  List<String> jsonList = <String>[];
  for (BarcodeResult result in widget.barcodeResults) {
    jsonList.add(jsonEncode(result.toJson()));
  }
  if (results == null) {
    prefs.setStringList('barcode_data', jsonList);
  } else {
    results.addAll(jsonList);
    prefs.setStringList('barcode_data', results);
  }
}
```

The content share feature can be implemented with the `Share.share` method:

```dart
onPressed: () {
  String result = '';
  for (BarcodeResult barcodeResult in widget.barcodeResults) {
    result +=
        'Format: ${barcodeResult.format}, Text: ${barcodeResult.text}\n';
  }
  Share.share(result);
}
```

### History Page
The history page displays the barcode detection results that are saved on the local storage. It contains a delete button that can be used to delete all the results, and a more button that can pop up a context menu to delete or copy a single result.

![barcode detection history](https://www.dynamsoft.com/codepool/img/2023/07/barcode-detection-history.jpg)

As the page is initialized, the `initState` method is called to load the saved results from the local storage:

```dart
@override
void initState() {
  super.initState();
  loadHistory();
}

Future<void> loadHistory() async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  var data = prefs.getStringList('barcode_data');
  if (data != null) {
    _barcodeHistory.clear();
    for (String json in data) {
      BarcodeResult barcodeResult = BarcodeResult.fromJson(jsonDecode(json));
      _barcodeHistory.add(barcodeResult);
    }
  }
  setState(() {
    _isLoaded = true;
  });
}
```

The barcode results will then be fed to the `ListView` widget:

```dart
var listView = Expanded(
        child: ListView.builder(
            itemCount: _barcodeHistory.length,
            itemBuilder: (context, index) {
              return MyCustomWidget(
                  result: _barcodeHistory[index],
                  cbDeleted: () async {
                    _barcodeHistory.removeAt(index);
                    final SharedPreferences prefs =
                        await SharedPreferences.getInstance();
                    List<String> data =
                        prefs.getStringList('barcode_data') as List<String>;
                    data.removeAt(index);
                    prefs.setStringList('barcode_data', data);
                    setState(() {});
                  },
                  cbOpenResultPage: () {});
            }));
```

The `MyCustomWidget` widget is used to display a single barcode result and provide the delete and copy functions:

```dart
class MyCustomWidget extends StatelessWidget {
  final BarcodeResult result;
  final Function cbDeleted;
  final Function cbOpenResultPage;

  const MyCustomWidget({
    super.key,
    required this.result,
    required this.cbDeleted,
    required this.cbOpenResultPage,
  });

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
        decoration: const BoxDecoration(color: Colors.black),
        child: Padding(
            padding: const EdgeInsets.only(top: 18, bottom: 16, left: 30),
            child: Row(
              children: [
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      result.format,
                      style: const TextStyle(color: Colors.white),
                    ),
                    SizedBox(
                      width: MediaQuery.of(context).size.width - 105,
                      child: Text(
                        result.text,
                        style: TextStyle(
                            color: colorSubtitle,
                            fontSize: 14,
                            overflow: TextOverflow.ellipsis),
                      ),
                    ),
                  ],
                ),
                Expanded(child: Container()),
                Padding(
                  padding: const EdgeInsets.only(right: 0),
                  child: IconButton(
                    icon: const Icon(Icons.more_vert),
                    color: Colors.white,
                    onPressed: () async {
                      final RenderBox button =
                          context.findRenderObject() as RenderBox;

                      final RelativeRect position = RelativeRect.fromLTRB(
                        100,
                        button.localToGlobal(Offset.zero).dy,
                        40,
                        0,
                      );

                      final selected = await showMenu(
                        context: context,
                        position: position,
                        color: colorBackground,
                        items: [
                          const PopupMenuItem<int>(
                              value: 0,
                              child: Text(
                                'Delete',
                                style: TextStyle(color: Colors.white),
                              )),
                          const PopupMenuItem<int>(
                              value: 1,
                              child: Text(
                                'Copy',
                                style: TextStyle(color: Colors.white),
                              )),
                        ],
                      );

                      if (selected != null) {
                        if (selected == 0) {
                          // delete
                          cbDeleted();
                        } else if (selected == 1) {
                          // copy
                          Clipboard.setData(ClipboardData(
                              text:
                                  'Format: ${result.format}, Text: ${result.text}'));
                        }
                      }
                    },
                  ),
                ),
              ],
            )));
  }
}
```

The delete button is located at the top right corner of the page. It is implemented with the `IconButton` widget inside the `AppBar` widget:

```dart
actions: [
  Padding(
      padding: const EdgeInsets.only(right: 30),
      child: IconButton(
        onPressed: () async {
          final SharedPreferences prefs =
              await SharedPreferences.getInstance();
          await prefs.remove('barcode_data');
          setState(() {
            _barcodeHistory.clear();
          });
        },
        icon: Image.asset(
          "images/icon-delete.png",
          width: 26,
          height: 26,
          fit: BoxFit.cover,
        ),
      ))
],
```

### About Page
The about page displays the SDK version and some clickable links.

![about page](https://www.dynamsoft.com/codepool/img/2023/07/barcode-scanner-about-page.jpg)

- Title:

  ```dart
  final title = Container(
      padding: const EdgeInsets.only(top: 50, left: 39, bottom: 5, right: 39),
      child: Row(
        children: [
          Image.asset(
            "images/logo-dbr.png",
            width: MediaQuery.of(context).size.width - 80,
          ),
        ],
      ),
    );
  ```

- Description:

  ```dart
  final description = Container(
        padding: const EdgeInsets.only(left: 44, right: 39, top: 18),
        child: const Center(
          child: Text(
            'Developers can easily add an enterprise-grade barcode scanning module to their business applications by using our SDKs. With the great flexibility of our APIs, you can customize our SDKs to meet your unique usage scenario.',
            style: TextStyle(color: Colors.white, wordSpacing: 2),
            textAlign: TextAlign.center,
          ),
        ));
  ```

- Button:

  ```dart
  final button = Container(
      padding: const EdgeInsets.only(top: 48, left: 91, right: 91, bottom: 69),
      child: MaterialButton(
        minWidth: 208,
        height: 44,
        color: colorOrange,
        onPressed: () {
          launchUrlString(
              'https://www.dynamsoft.com/barcode-reader/downloads/');
        },
        child: const Text(
          'GET FREE TRIAL SDK',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  ```

- Links:

  ```dart
  final links = Padding(
      padding: const EdgeInsets.only(
        left: 15,
        right: 15,
      ),
      child: Container(
        width: MediaQuery.of(context).size.width - 30,
        padding: const EdgeInsets.only(left: 20),
        decoration: const BoxDecoration(color: Colors.black),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.only(top: 13, bottom: 15),
              child: InkWell(
                  onTap: () {
                    launchUrlString(
                        'https://www.dynamsoft.com/barcode-reader/overview/');
                  },
                  child: Text(
                    'Dynamsoft Barcode Reader overview >',
                    style: TextStyle(color: colorOrange, fontSize: 16),
                  )),
            ),
            Container(
              height: 1,
              color: colorMainTheme,
            ),
            Padding(
              padding: const EdgeInsets.only(
                top: 13,
                bottom: 15,
              ),
              child: InkWell(
                  onTap: () {
                    launchUrlString('https://www.dynamsoft.com/company/about/');
                  },
                  child: Text(
                    'Contact us >',
                    style: TextStyle(color: colorOrange, fontSize: 16),
                  )),
            ),
          ],
        ),
      ),
    );
  ```

- Version:

  ```dart
  final version = Padding(
        padding: const EdgeInsets.only(left: 15, right: 15, top: 10),
        child: Container(
          width: MediaQuery.of(context).size.width - 30,
          height: 49,
          padding: const EdgeInsets.only(left: 20, right: 24),
          decoration: const BoxDecoration(color: Colors.black),
          child: Row(
            children: [
              const Text(
                'SDK Version',
                style: TextStyle(color: Colors.white, fontSize: 14),
              ),
              Expanded(child: Container()),
              Text(
                '9.6.20',
                style: TextStyle(color: colorText, fontSize: 15),
              )
            ],
          ),
        ));
  ```

- Source Code:

  ```dart
  final sourceCode = Padding(
        padding: const EdgeInsets.only(left: 15, right: 15, top: 1),
        child: Container(
          width: MediaQuery.of(context).size.width - 30,
          height: 49,
          padding: const EdgeInsets.only(left: 20, right: 24),
          decoration: const BoxDecoration(color: Colors.black),
          child: Row(
            children: [
              InkWell(
                  onTap: () {
                    launchUrlString(
                        'https://github.com/yushulx/flutter-barcode-scanner');
                  },
                  child: const Text(
                    'App Source Code >',
                    style: TextStyle(color: Colors.white, fontSize: 14),
                  )),
              Expanded(child: Container()),
            ],
          ),
        ));
  ```

## Source Code
[https://github.com/yushulx/flutter-barcode-scanner](https://github.com/yushulx/flutter-barcode-scanner)