How to Scan QR Codes in Flutter using the mobile_scanner package

Flutter Jul 22, 2023

QR codes have become an essential part of our daily lives, allowing quick data exchange through smartphones. As a Flutter developer, you may want to add QR code scanning to your application. In this post, we will go through the implementation of QR code scanning using the mobile_scanner package. We will go over the implementation step-by-step, making it easy to integrate QR code scanning into your Flutter application.

1. Starting code

Let us start by setting up our project by creating the following main.dart and home_screen.dart files.

main.dart

import 'package:codeonwards_demo/home_screen.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

In the main.dart file, we create the MyApp widget, which returns a MaterialApp widget that renders our HomeScreen widget.

home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('QR-code scanner'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('No result'),
            ElevatedButton(
              child: Text('Scan QR code'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }
}

In the home_screen.dart file, we create the main screen of our application using a Scaffold widget. The screen has a centered Column widget containing two widgets: a Text widget showing "No result" (which will later display the scanned QR code) and a button labeled "Scan QR code" (which will later open the QR code scanner when clicked).

flutter_scaffold_widget_with_column_containing_text_and_elevated_button_widget

2. Installing

To get started with implementing the QR code scanner, we need to install the mobile_scanner package into our project. The installation process is simple. Just execute the following command: flutter pub add mobile_scanner.

Once the command is executed, make sure to check your pubspec.yaml file for the added dependencies. You should see the mobile_scanner package included in the dependencies section, like this:

dependencies:
  flutter:
    sdk: flutter
  mobile_scanner: ^3.3.0

3. Change the MinSdkVersion for Android

Currently, the mobile_scanner package requires a minimum Android SDK version of 21.

flutter_min_sdk_version_warning

To ensure compatibility with the package, we need to make some changes to the build.gradle file. Look for the defaultConfig section in the build.gradle file that is located in android/app/build.gradle and modify it as follows:

defaultConfig {
    applicationId "com.example.codeonwards_demo"
    minSdkVersion 21
    targetSdkVersion flutter.targetSdkVersion
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

In this code snippet, we updated the minSdkVersion to 21, which ensures that our application supports Android devices running on SDK version 21 and above.

If you want to know more about the minSdkVersion and how it affects your application, you can find additional information in my related post:

How to change the Minimum SDK version in Flutter
When using certain packages in your Flutter project, you may encounter error messages like the following: “uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:device_info_plus]”. This error message indicates that you need to adjust the minimum SDK

4. Add NSCameraUsageDescription to Info.plist for iOS

To enable camera access for QR code scanning on iOS, we need to add an entry for the NSCameraUsageDescription key in the Info.plist file. You can find this file at ios/Runner/Info.plist in your project.

Within the <dict> element of the Info.plist file, you will find various keys. To add the necessary entry, simply include the following lines, making sure they are placed one below the other:

<dict>
    <key>NSCameraUsageDescription</key>
    <string>This app needs camera access to scan QR codes</string>
    ...
</dict>

By adding this entry, we provide a clear message to users, explaining why our application requires access to the camera.

5. Scanning QR Codes

Now that our project is set up, it is time to begin the implementation process. We will start by creating a new file called qr_code_scanner.dart. Inside this file, we will define the QrCodeScanner class, which will handle all the QR code related functionality. For this purpose, we will be using the mobile_scanner package.

qr_code_scanner.dart

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

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

  @override
  Widget build(BuildContext context) {
    final MobileScannerController controller = MobileScannerController();

    return MobileScanner(
      controller: controller,
      onDetect: (BarcodeCapture capture) {
        final List<Barcode> barcodes = capture.barcodes;

        for (final barcode in barcodes) {
          print(barcode.rawValue);
        }
      },
    );
  }
}

The QrCodeScanner widget imports the mobile_scanner/mobile_scanner.dart package for the QR code scanning capabilities.

Inside the build method of the QrCodeScanner widget, we create a MobileScannerController instance and assign it to the mobileScannerController variable. This controller will handle the QR code scanning process.

The main part of the code is the MobileScanner widget, which is responsible for displaying and handling the QR code scanning. It receives the previously created MobileScannerController as its controller parameter.

Furthermore, we set the required onDetect property, which activates when the scanner identifies a QR code. When the onDetect property is triggered, it provides us with a BarcodeCapture instance. From this instance, we can access the barcodes, which is a List of barcodes. For the current implementation, we loop through this list and print out the rawValue of each barcode. This enables us to see the content of each scanned QR code in the console.

Now inside our HomePage widget, we want the ElevatedButton widget to display our QrCodeScanner widget when clicked, see the following code snippet:

home_screen.dart

import 'package:codeonwards_demo/qr_code_scanner.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('QR-code scanner'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('No result'),
            ElevatedButton(
              child: Text('Scan QR-code'),
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => QrCodeScanner(),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

In the provided code snippet, we have made changes to the onPressed property of the ElevatedButton widget, enabling it to display the QrCodeScanner. When the user taps the button, the code executes an anonymous function that uses Navigator.of(context).push. This function navigates the application to a new screen.

The argument passed to push() is a MaterialPageRoute. This type of route handles the transition to the new screen in a way consistent with Material Design guidelines. The builder property of MaterialPageRoute takes a callback function that returns the widget to be displayed on the new screen.

In this case, the builder function creates an instance of the QrCodeScanner widget and returns it. This means that when the button is pressed, the application will transition to a new screen showing the QR code scanner interface.

Scanning QR Codes with Android emulator

Let us put our new QR code scanning functionality to the test using an Android emulator. Make sure you have started your Android emulator and run the main.dart file of your application.

If you have not set up your Android emulator yet and you want to follow along please check my related post:

How to setup an Android Emulator on Windows
Are you looking for a convenient way to test your Flutter or Android applications on your Windows computer? Setting up an Android emulator can be a practical solution. An emulator is a virtual Android device that runs on your computer, allowing you to simulate the experience of using a real device.

After running the main function, the application will build and display the HomeScreen widget. Upon pressing the "Scan QR-code" button, a pop-up will appear, requesting camera access permission. Once granted, the camera will open, and the Android emulator will simulate a room environment.

flutter_allowing_camera_access_on_android_emulator

Inside the simulation room, you can look around by holding the alt key and using your mouse to drag the screen. To move around, use the WASDQE keys while holding the alt key. Once familiar with the keys make your way toward the dining room as done in the GIF below:

flutter_android_emulator_navigating_to_dining_room_in_simulation

In the dining room, you will notice a placeholder on the wall. We have the option to replace this placeholder with an image of our choice, which in this case will be a QR code. I have generated the QR code in this example by using this QR-code generator.

To replace the placeholder on the wall we need to go through the following steps:

1. Click the three dots on the right side of the emulator to open the options:

flutter_android_emulator_wall_placeholder_in_room_simulation

2. Navigate to "Camera", and click on "Add Image" to select an image for the wall.

flutter_android_emulator_camera_options

3.  Once you selected an image your "Virtual scene images" options should look like this:

flutter_android_emulator_changing_virtual_scene_image

4. Now, as we run our application again, you will notice that the QR code is displayed on the wall:

flutter_display_new_qr_code_on_wall_of_room_simulation

With the emulator now set up correctly, let us proceed to test our QR code scanner. If you keep your application running, you will notice that the QR code scanner continuously detects and scans the QR code, displaying the QR code in the terminal.

flutter_displaying_output_of_qr_code_scanner_in_terminal

In this example, the QR code used returns the value https://www.codeonwards.com.

Finalizing the QR Code Scanner

Now, let us complete the implementation of our QR code scanner. We want to ensure that the camera closes as soon as we have scanned the QR code, and assign the QR code to our Text widget.

qr_code_scanner.dart

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

class QrCodeScanner extends StatefulWidget {
  const QrCodeScanner({required this.setResult, super.key});

  final Function setResult;

  @override
  State<QrCodeScanner> createState() => _QrCodeScannerState();
}

class _QrCodeScannerState extends State<QrCodeScanner> {
  final MobileScannerController controller = MobileScannerController();

  @override
  Widget build(BuildContext context) {
    return MobileScanner(
      controller: controller,
      onDetect: (BarcodeCapture capture) async {
        final List<Barcode> barcodes = capture.barcodes;
        Barcode barcode = barcodes.first;

        if (barcode.rawValue == 'https://www.codeonwards.com') {
          widget.setResult(barcode.rawValue);

          await controller.stop().then((value) => controller.dispose());

          Navigator.of(context).pop();
        }
      },
    );
  }
}

In this code snippet, we have added the setResult property which will be assigned via the constructor. Inside the onDetect property of the MobileScanner class, we made the following changes. We take the first barcode from the barcodes list by using barcodes.first, this value will be assigned to a barcode variable.

we have added the setResult property to our QrCodeScanner class, which will be assigned through the constructor. Inside the onDetect property of the MobileScanner class, we made the following changes. We take the first barcode from the barcodes list using barcodes.first, and this value is assigned to the barcode variable.

We created an if statement to check if the rawValue of our barcode variable is equal to https://www.codeonwards.com. In real applications you probably want to use a different check, so feel free to change it. If the check returns true, we set the result using widget.setResults(barcode.rawValue). Since we already have the result, we can stop our controller by calling controller.stop. Once this is completed, we dispose of our controller by calling controller.dispose. Lastly, we want to navigate back to our HomePage widget, so we use Navigator.of(context).pop().

Now that our QrCodeScanner widget is finished we need to update our HomeScreen widget accordingly.

home_screen.dart

import 'package:codeonwards_demo/qr_code_scanner.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String? _result;

  void setResult(String result) {
    setState(() => _result = result);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('QR-code scanner'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_result ?? 'No result'),
            ElevatedButton(
              child: Text('Scan QR-code'),
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => QrCodeScanner(setResult: setResult),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

In our HomePage widget, we added a new private variable called _result and function called setResult, which takes a String parameter and updates our _result variable using setState. Finally, we passed this setResult function as a parameter to the QrCodeScanner widget.

Now we can finally assign the scanned QR code to our Text widget, as done in the GIF below:

flutter_scanning_qr_codes_using_android_emulator_and_assigning_the_value_to_text_widget

Conclusion

In this post, we have learned how to implement QR code scanning in Flutter using the mobile_scanner package. We started by setting up our project and installing the necessary dependencies. Then, we made the required changes for Android and iOS to enable camera access for QR code scanning.

Next, we created the QrCodeScanner widget, which handles all the QR code related functionality using the mobile_scanner package. We tested our QR code scanner using an Android emulator and saw how the scanner continuously detects and scans QR codes.

To finalize the implementation, we made sure to close the camera as soon as we scanned the QR code and assigned its value to our Text widget. This way, the scanned QR code is displayed on the screen, allowing users to interact with it.

By following these steps, you can easily integrate QR code scanning functionality into your Flutter application, enhancing user experience and allowing for smooth data exchange.

Tags