Home
Softono
j

juliansteenbakker

Professional software vendor delivering innovative solutions on the Softono platform. Specialized in both open-source and proprietary software development.

Total Products
2

Software by juliansteenbakker

flutter_secure_storage
Open Source

flutter_secure_storage

# flutter_secure_storage [![Pub Version](https://img.shields.io/pub/v/flutter_secure_storage.svg)](https://pub.dev/packages/flutter_secure_storage) [![Pub Version Prerelease](https://img.shields.io/pub/v/flutter_secure_storage.svg?include_prereleases)](https://pub.dev/packages/flutter_secure_storage) [![Build Status](https://github.com/mogol/flutter_secure_storage/actions/workflows/code-quality.yml/badge.svg)](https://github.com/juliansteenbakker/flutter_secure_storage/actions/workflows/code-quality.yml) [![Code Quality: Very Good Analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis) [![Codecov](https://codecov.io/gh/juliansteenbakker/flutter_secure_storage/graph/badge.svg?token=UUVTJ6MS4A)](https://codecov.io/gh/juliansteenbakker/flutter_secure_storage) [![GitHub Sponsors](https://img.shields.io/github/sponsors/juliansteenbakker)](https://github.com/sponsors/juliansteenbakker) A Flutter plugin to securely store sensitive data in a key-value pair format using platform-specific secure storage solutions. It supports Android, iOS, macOS, Windows, and Linux. ## Features - **Secure Data Storage**: Uses Keychain for iOS/macOS, custom secure ciphers with optional biometric authentication for Android, and platform-specific secure mechanisms for Windows, Linux, and Web. - **Encryption**: Encrypts data before storing it using platform-specific encryption (RSA OAEP + AES-GCM on Android by default). - **Cross-Platform**: Works seamlessly across Android, iOS, macOS, Windows, Linux, and Web. - **Biometric Authentication**: Optional biometric authentication support on Android (API 23+) and iOS/macOS. - **Customizable Options**: Configure encryption algorithms, accessibility attributes, biometric requirements, and more. ## Important notice for Android Version 10.0.0 introduces a major security update with custom cipher implementations. The deprecated Jetpack Security library's `encryptedSharedPreferences` is no longer recommended. **Key Changes:** - New default ciphers: RSA OAEP (key cipher) + AES-GCM (storage cipher) - New `AndroidOptions()` and `AndroidOptions.biometric()` constructors - Automatic migration from old ciphers via `migrateOnAlgorithmChange` (enabled by default) - Minimum Android SDK is now 23 (Android 6.0+) - Enhanced biometric authentication with graceful degradation ## Important notice for Web flutter_secure_storage only works on HTTPS or localhost environments. [Please see this issue for more information.](https://github.com/juliansteenbakker/flutter_secure_storage/issues/320#issuecomment-976308930) ## Installation If not present already, please call WidgetsFlutterBinding.ensureInitialized() in your main before you do anything with the MethodChannel. [Please see this issue for more info.](https://github.com/juliansteenbakker/flutter_secure_storage/issues/336) Add the dependency in your `pubspec.yaml` file: ``` dependencies: flutter_secure_storage: ^<latest_version> ``` Then run: `flutter pub get` ## Usage ### Import the Package `import 'package:flutter_secure_storage/flutter_secure_storage.dart';` ### Create an Instance ```dart // Default secure storage - Uses RSA OAEP + AES-GCM (recommended) final storage = FlutterSecureStorage(); // Or with explicit Android options final storage = FlutterSecureStorage( aOptions: AndroidOptions(), ); // Biometric storage with graceful degradation final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( enforceBiometrics: false, // Works without biometrics biometricPromptTitle: 'Authenticate to access data', ), ); // Strict biometric enforcement (requires device security) final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( enforceBiometrics: true, // Requires biometric/PIN/pattern biometricPromptTitle: 'Authentication Required', ), ); ``` ### Write Data `await storage.write(key: 'username', value: 'flutter_user');` ### Read Data `String? username = await storage.read(key: 'username');` ### Delete Data `await storage.delete(key: 'username');` ### Delete All Data `await storage.deleteAll();` ### Check for Key Existence `bool containsKey = await storage.containsKey(key: 'username');` ## Configuration Each platform provides its own set of configuration options to tailor secure storage behavior. For example, on iOS, the `IOSOptions` class includes an `accessibility` option that determines when the app can access secure values stored in the Keychain. The `accessibility` option allows you to specify conditions under which secure values are accessible. For instance: - `first_unlock`: Enables access to secure values after the device is unlocked for the first time after a reboot. - `first_unlock_this_device`: Allows access to secure values only after the device is unlocked for the first time since installation on this device. - `unlocked` (default): Values are accessible only when the device is unlocked. Here’s an example of configuring the accessibility option on iOS: ```dart final options = IOSOptions(accessibility: KeychainAccessibility.first_unlock); await storage.write(key: key, value: value, iOptions: options); ``` By setting `accessibility`, you can control when secure values are accessible, enhancing security and usability for your app on iOS. Similar platform-specific options are available for other platforms as well. ### Android #### Disabling Auto Backup _Note_ By default Android backups data on Google Drive. It can cause exception `java.security.InvalidKeyException: Failed to unwrap key`. You need to: - [Disable autobackup](https://developer.android.com/guide/topics/data/autobackup#EnablingAutoBackup), [details](https://github.com/juliansteenbakker/flutter_secure_storage/issues/13#issuecomment-421083742) - [Exclude sharedprefs](https://developer.android.com/guide/topics/data/autobackup#IncludingFiles) used by `FlutterSecureStorage`, [details](https://github.com/juliansteenbakker/flutter_secure_storage/issues/43#issuecomment-471642126) Add the following to your `android/app/src/main/AndroidManifest.xml`: ```xml <application android:allowBackup="false" ...> </application> ``` #### Encryption Options (Version 10.0.0+) Version 10 introduces new cipher options and biometric support. Choose the configuration that fits your security requirements: | Constructor | Key Cipher | Storage Cipher | Biometric Support | Description | |----------------------------------------------------------------------------------------------------------|---------------------------------------|-------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `AndroidOptions()` | RSA/ECB/OAEPWithSHA-256AndMGF1Padding | AES/GCM/NoPadding | No | **Default.** Standard secure storage with RSA OAEP key wrapping. Strong authenticated encryption without biometrics. Recommended for most use cases. | | `AndroidOptions.biometric(enforceBiometrics: false)` | AES/GCM/NoPadding | AES/GCM/NoPadding | Optional | KeyStore-based with optional biometric authentication. Gracefully degrades if biometrics unavailable. | | `AndroidOptions.biometric(enforceBiometrics: true)` | AES/GCM/NoPadding | AES/GCM/NoPadding | Required | KeyStore-based requiring biometric/PIN authentication. Throws error if device security not available. Requires API 28+ for biometric enforcement. | | `AndroidOptions.biometric(enforceBiometrics: true, biometricType: AndroidBiometricType.strongBiometricOnly)` | AES/GCM/NoPadding | AES/GCM/NoPadding | Required (strong) | Same as above but restricts authentication to Class 3 (strong) biometrics only. Device credentials (PIN/pattern/password) are rejected. | #### Custom Cipher Combinations (Advanced) For advanced users, all combinations below are supported using the `AndroidOptions()` constructor with custom parameters: | Key Cipher Algorithm | Storage Cipher Algorithm | Implementation | Biometric Support | |-----------------------------------------|--------------------------|-----------------|------------------------------------| | `RSA_ECB_PKCS1Padding` | `AES_CBC_PKCS7Padding` | RSA-wrapped AES | No | | `RSA_ECB_PKCS1Padding` | `AES_GCM_NoPadding` | RSA-wrapped AES | No | | `RSA_ECB_OAEPwithSHA_256andMGF1Padding` | `AES_CBC_PKCS7Padding` | RSA-wrapped AES | No | | `RSA_ECB_OAEPwithSHA_256andMGF1Padding` | `AES_GCM_NoPadding` | RSA-wrapped AES | No | | `AES_GCM_NoPadding` | `AES_CBC_PKCS7Padding` | KeyStore AES | Optional (via `enforceBiometrics`) | | `AES_GCM_NoPadding` | `AES_GCM_NoPadding` | KeyStore AES | Optional (via `enforceBiometrics`) | **Notes:** - **RSA key ciphers** wrap the AES encryption key with RSA. No biometric support. - **AES key cipher** stores the key directly in Android KeyStore. Supports optional biometric authentication. - **`enforceBiometrics` parameter** (default: `false`): - `false`: Gracefully degrades if biometrics unavailable - `true`: Strictly requires device security (PIN/pattern/biometric), throws exception if unavailable #### Biometric Authentication Flutter Secure Storage supports biometric authentication (fingerprint, face recognition, etc.) on Android API 23+. ##### Required Permissions To use biometric authentication, add the following permission to your `android/app/src/main/AndroidManifest.xml`: ```xml <uses-permission android:name="android.permission.USE_BIOMETRIC"/> ``` For devices running Android 9.0 (API 28) and above, `USE_BIOMETRIC` is the recommended permission. For backward compatibility with devices running Android 6.0 - 8.1 (API 23-27), you may also need: ```xml <uses-permission android:name="android.permission.USE_FINGERPRINT"/> ``` ##### Using Biometric Authentication You can enable biometric authentication using the `AndroidOptions.biometric()` constructor: ```dart // Optional biometric authentication (graceful degradation) final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( enforceBiometrics: false, // Default - works without biometrics biometricPromptTitle: 'Unlock to access your data', biometricPromptSubtitle: 'Use fingerprint or face unlock', ), ); // Strict biometric enforcement (requires device security) final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( enforceBiometrics: true, // Requires biometric/PIN/pattern biometricPromptTitle: 'Biometric authentication required', ), ); // Strong biometric only — device credentials (PIN/pattern/password) rejected final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( enforceBiometrics: true, biometricType: AndroidBiometricType.strongBiometricOnly, biometricPromptTitle: 'Fingerprint required', ), ); ``` **Note:** When `enforceBiometrics: true`, the app will throw an exception if the device has no PIN, pattern, password, or biometric enrolled. **`biometricType`** controls which authentication methods satisfy the biometric prompt (only applies when using `AES_GCM_NoPadding` key cipher): | Value | Accepted methods | |---------------------------------------------|---------------------------------------------------------| | `AndroidBiometricType.biometricOrDeviceCredential` | Class 3 biometrics **or** PIN / pattern / password (default) | | `AndroidBiometricType.strongBiometricOnly` | Class 3 (strong) biometrics only — credentials rejected | > **Note:** On Android 10 (API level 29) and lower, `setAllowedAuthenticators` is unavailable. Device credentials (PIN/pattern/password) are not accepted on these versions — only Class 3 (strong) biometrics work, regardless of the `biometricType` setting. ##### Requirements - **API Level**: Android 6.0 (API 23) minimum for basic encryption - **API Level**: Android 9.0 (API 28) minimum for enforced biometric authentication - **API Level**: Android 11.0 (API 30) minimum for `AndroidBiometricType.strongBiometricOnly` to be fully enforced - **Device Security**: Device must have a PIN, pattern, password, or biometric enrolled (when using `enforceBiometrics: true`) - **Permissions**: `USE_BIOMETRIC` permission in AndroidManifest.xml #### Migration from Version 9.x Version 10 automatically migrates data from older cipher algorithms when `migrateOnAlgorithmChange: true` (enabled by default). If you were using `encryptedSharedPreferences` in version 9, the data will be automatically migrated to the new cipher implementation. To disable automatic migration: ```dart final storage = FlutterSecureStorage( aOptions: AndroidOptions( migrateOnAlgorithmChange: false, ), ); ``` ### macOS & iOS #### Secure Enclave (iOS/macOS) You can opt-in to hardware-backed protection using the Secure Enclave by enabling `useSecureEnclave` in `AppleOptions` (iOS/macOS). When enabled, each stored value is encrypted with a randomly generated AES key. That AES key is then wrapped (encrypted) using an Elliptic Curve private key that lives exclusively inside the device's Secure Enclave — it can never be extracted from the hardware. Decryption therefore requires the physical device, and access can be gated behind Face ID, Touch ID, or the device passcode via `accessControlFlags`. Example: ```dart final storage = FlutterSecureStorage(); await storage.write( key: 'token', value: 'secret', iOptions: IOSOptions( useSecureEnclave: true, accessControlFlags: const [ AccessControlFlag.userPresence, // require Face ID/Touch ID or passcode ], ), mOptions: MacOsOptions( useSecureEnclave: true, accessControlFlags: const [AccessControlFlag.userPresence], ), ); ``` **Enabling Secure Enclave on an existing app** `useSecureEnclave` is a per-operation option, not a global flag. Existing items written without `useSecureEnclave` are stored as standard Keychain entries and are **not** automatically re-encrypted. If you read a key with `useSecureEnclave: true` that was previously written without it, the plugin looks for the Secure Enclave–wrapped key companion entry. Because none exists, it returns `null` — the original Keychain item is still there but is invisible via the SE path. To avoid data loss when adopting Secure Enclave in an existing app: 1. Read each existing value with `useSecureEnclave: false` (or `IOSOptions.defaultOptions`). 2. Write it back with `useSecureEnclave: true`. 3. Delete the old entry with `useSecureEnclave: false`. A first-class migration helper (`migrateToSecureEnclave`) is planned for a future release. **Notes:** - If Secure Enclave is unavailable (simulator or devices without Enclave), the plugin gracefully falls back to storing the value using standard Keychain with your configured `accessControlFlags`. - `synchronizable` is ignored for Enclave-backed items — they are device-bound by design. - On macOS, `kSecUseDataProtectionKeychain` remains enabled when available. You also need to add Keychain Sharing as capability to your macOS runner. To achieve this, please add the following in *both* your `macos/Runner/DebugProfile.entitlements` *and* `macos/Runner/Release.entitlements` for macOS or for iOS `ios/Runner/DebugProfile.entitlements` *and* `ios/Runner/Release.entitlements`. ``` <key>keychain-access-groups</key> <array/> ``` If you have set your application up to use App Groups then you will need to add the name of the App Group to the `keychain-access-groups` argument above. Failure to do so will result in values appearing to be written successfully but never actually being written at all. For example if your app has an App Group named "aoeu" then your value for above would instead read: ``` <key>keychain-access-groups</key> <array> <string>$(AppIdentifierPrefix)aoeu</string> </array> ``` If you are configuring this value through XCode then the string you set in the Keychain Sharing section would simply read "aoeu" with XCode appending the `$(AppIdentifierPrefix)` when it saves the configuration. #### Troubleshooting: Key lookup returns null after hot restart on iOS If your app returns `null` when reading keys after a hot restart on a physical iOS device, this is typically caused by missing or incorrectly configured Keychain Sharing entitlements across all build modes. **Step 1 — Add Keychain Sharing capability in Xcode** Open your project in Xcode. Click on **Runner** in the left bar, then click **Runner** under Targets. Go to **Signing and Capabilities**, click **+ Capability**, search for **Keychain Sharing** and add it. Click on each sub-tab (**Debug**, **Profile**, **Release**) and make sure the capability is present. If it is not, press **+** in **Keychain Groups** and a prompt will appear to create the entitlements file. This generates the following entitlement files: - `Runner/Runner.entitlements` - `Runner/RunnerDebug.entitlements` - `Runner/RunnerProfile.entitlements` **Step 2 — Set Code Signing Entitlements paths in Build Settings** In Xcode, click **Runner** in the left bar, then click **Runner** under **Project** (not Targets). Go to **Build Settings** and search for **Code Signing Entitlements**. Manually enter the relative path for all three build modes: - Debug: `Runner/RunnerDebug.entitlements` - Profile: `Runner/RunnerProfile.entitlements` - Release: `Runner/Runner.entitlements` **Step 3 — Clean and rebuild** ```bash flutter clean rm -Rf ios/Pods flutter pub get cd ios && pod install && cd .. flutter run ``` After completing these steps, key lookups should work correctly after hot restart on physical iOS devices. ### Web Flutter Secure Storage uses an experimental implementation using WebCrypto. Use at your own risk at this time. Feedback welcome to improve it. The intent is that the browser is creating the private key, and as a result, the encrypted strings in local_storage are not portable to other browsers or other machines and will only work on the same domain. **It is VERY important that you have HTTP Strict Forward Secrecy enabled and the proper headers applied to your responses or you could be subject to a javascript hijack.** Please see: - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security - https://www.netsparker.com/blog/web-security/http-security-headers/ #### application-specific key option On the web, all keys are stored in LocalStorage. flutter_secure_storage has an option for the web to wrap this stored key with an application-specific key to make it more difficult to analyze. ```dart final _storage = const FlutterSecureStorage( webOptions: WebOptions( wrapKey: '${your_application_specific_key}', wrapKeyIv: '${your_application_specific_iv}', ), ); ``` ### Windows You need the C++ ATL libraries installed along with the rest of Visual Studio Build Tools. Download them from [here](https://visualstudio.microsoft.com/downloads/?q=build+tools) and make sure the C++ ATL under optional is installed as well. ### Linux You need `libsecret-1-dev` on your machine to build the project, and `libsecret-1-0` to run the application (add it as a dependency after packaging your app). If you using snapcraft to build the project use the following ```yaml parts: uet-lms: source: . plugin: flutter flutter-target: lib/main.dart build-packages: - libsecret-1-dev stage-packages: - libsecret-1-0 ``` Apart from `libsecret` you also need a keyring service, for that you need either [`gnome-keyring`](https://wiki.gnome.org/Projects/GnomeKeyring) (for Gnome users) or [`kwalletmanager`](https://wiki.archlinux.org/title/KDE_Wallet) (for KDE users) or other light provider like [`secret-service`](https://github.com/yousefvand/secret-service). ## Integration Tests To run the integration tests, navigate to the `example` directory and execute the following command: `flutter drive --target=test_driver/app.dart` This will launch the integration tests specified in the `test_driver` directory. ## Contributing We welcome contributions to this project! To set up your workspace after cloning the repository, follow these steps: 1. Fetch the Flutter dependencies: `flutter pub get` 2. Activate `melos`: `dart pub global activate melos` 3. (Optional) Add pub executables to your path: `export PATH="$PATH":"$HOME/.pub-cache/bin"` 4. Bootstrap the workspace with `melos`: `melos bootstrap` This will prepare the project for development by linking and configuring all required dependencies. ## API Reference For a complete list of available methods and configuration options, refer to the [API documentation](https://pub.dev/documentation/flutter_secure_storage/latest/). ## License This project is licensed under the BSD 3 License. See the [LICENSE](LICENSE) file for details.

Mobile Development Password Managers
1.3K Github Stars
mobile_scanner
Open Source

mobile_scanner

# mobile_scanner [![Pub Version](https://img.shields.io/pub/v/mobile_scanner.svg)](https://pub.dev/packages/mobile_scanner) [![Pub Version Prerelease](https://img.shields.io/pub/v/mobile_scanner.svg?include_prereleases)](https://pub.dev/packages/mobile_scanner) [![Build Status](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/code-coverage.yml/badge.svg)](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/code-coverage.yml) [![Style: Very Good Analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis) [![Codecov](https://codecov.io/gh/juliansteenbakker/mobile_scanner/graph/badge.svg?token=RGE4XVOGJ5)](https://codecov.io/gh/juliansteenbakker/mobile_scanner) [![GitHub Sponsors](https://img.shields.io/github/sponsors/juliansteenbakker)](https://github.com/sponsors/juliansteenbakker) A fast and lightweight Flutter plugin for scanning barcodes and QR codes using the device’s camera. It supports multiple barcode formats, real-time detection, and customization options for an optimized scanning experience on multiple platforms. ## Features - Fast barcode and QR code scanning - Supports multiple barcode formats - Real-time detection - Customizable camera and scanner behavior See the [examples](example/README.md) for runnable examples of various usages, such as the basic usage, applying a scan window, or retrieving images from the barcodes. ## Platform Support | Android | iOS | macOS | Web | Linux | Windows | |---------|-----|-------|-----|-------|---------| | ✔ | ✔ | ✔ | ✔ | :x: | :x: | ### Features Supported See the example app for detailed implementation information. | Features | Android | iOS | macOS | Web | |--------------|--------------------|--------------------|--------------------|-----| | analyzeImage | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | | returnImage | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | | scanWindow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | | autoZoom | :heavy_check_mark: | :x: | :x: | :x: | | lensType | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | ## Installation Add the dependency in your `pubspec.yaml` file: ``` dependencies: mobile_scanner: ^<latest_version> ``` Then run: `flutter pub get` ## Configuration ### Android This package uses by default the **bundled version** of MLKit Barcode-scanning for Android. This version is immediately available to the device. But it will increase the size of the app by approximately 3 to 10 MB. The alternative is to use the **unbundled version** of MLKit Barcode-scanning for Android. This version is downloaded on first use via Google Play Services. It increases the app size by around 600KB. [You can read more about the difference between the two versions here.](https://developers.google.com/ml-kit/vision/barcode-scanning/android) To use the **unbundled version** of the MLKit Barcode-scanning, add the following line to your `/android/gradle.properties` file: ``` dev.steenbakker.mobile_scanner.useUnbundled=true ``` ### iOS Since the scanner needs to use the camera, add the following keys to your Info.plist file. (located in <project root>/ios/Runner/Info.plist) NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor. If you want to use the local gallery feature from [image_picker](https://pub.dev/packages/image_picker), you also need to add the following key. NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor. Example, ``` <key>NSCameraUsageDescription</key> <string>This app needs camera access to scan QR codes</string> <key>NSPhotoLibraryUsageDescription</key> <string>This app needs photos access to get QR code from photo library</string> ``` ### macOS Ensure that you granted camera permission in XCode -> Signing & Capabilities: <img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png"> ### Web As of version 5.0.0 adding the barcode scanning library script to the `index.html` is no longer required, as the script is automatically loaded on first use. #### Providing a mirror for the barcode scanning library If a different mirror is needed to load the barcode scanning library, the source URL can be set beforehand. ```dart import 'package:flutter/foundation.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; final String scriptUrl = // ... if (kIsWeb) { MobileScannerPlatform.instance.setBarcodeLibraryScriptUrl(scriptUrl); } ``` ## Usage ### Simple Import the package with `package:mobile_scanner/mobile_scanner.dart`. The only required parameter is `onDetect`, which returns the scanned barcode or qr code. ```dart MobileScanner( onDetect: (result) { print(result.barcodes.first.rawValue); }, ), ``` ### Advanced If you want more control over the scanner, you need to create a new `MobileScannerController` controller. The controller contains multiple parameters to adjust the scanner. ```dart final MobileScannerController controller = MobileScannerController( cameraResolution: size, detectionSpeed: detectionSpeed, detectionTimeoutMs: detectionTimeout, formats: selectedFormats, returnImage: returnImage, torchEnabled: true, invertImage: invertImage, autoZoom: autoZoom, ); ``` ```dart MobileScanner( controller: controller, onDetect: (result) { print(result.barcodes.first.rawValue); }, ); ``` #### Switching lens types On devices with multiple cameras (normal, wide, zoom), you can switch between lens types: ```dart // Toggle through available lens types (normal -> wide -> zoom -> normal) await controller.switchCamera(const ToggleLensType()); // Or select a specific lens type await controller.switchCamera( const SelectCamera(lensType: CameraLensType.wide), ); // Get supported lens types for the current camera final Set<CameraLensType> supportedLenses = await controller.getSupportedLenses(); ``` #### Lifecycle changes If you want to pause the scanner when the app is inactive, you need to use `WidgetsBindingObserver`. First, provide a `StreamSubscription` for the barcode events. Also, make sure to create a `MobileScannerController` with `autoStart` set to false, since we will be handling the lifecycle ourself. ```dart final MobileScannerController controller = MobileScannerController( autoStart: false, ); StreamSubscription<Object?>? _subscription; ``` Then, ensure that your `State` class mixes in `WidgetsBindingObserver`, to handle lifecyle changes, and add the required logic to the `didChangeAppLifecycleState` function: ```dart class MyState extends State<MyStatefulWidget> with WidgetsBindingObserver { // ... @override void didChangeAppLifecycleState(AppLifecycleState state) { // If the controller is not ready, do not try to start or stop it. // Permission dialogs can trigger lifecycle changes before the controller is ready. if (!controller.value.hasCameraPermission) { return; } switch (state) { case AppLifecycleState.detached: case AppLifecycleState.hidden: case AppLifecycleState.paused: return; case AppLifecycleState.resumed: // Restart the scanner when the app is resumed. // Don't forget to resume listening to the barcode events. _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); case AppLifecycleState.inactive: // Stop the scanner when the app is paused. // Also stop the barcode events subscription. unawaited(_subscription?.cancel()); _subscription = null; unawaited(controller.stop()); } } // ... } ``` Then, start the scanner in `void initState()`: ```dart @override void initState() { super.initState(); // Start listening to lifecycle changes. WidgetsBinding.instance.addObserver(this); // Start listening to the barcode events. _subscription = controller.barcodes.listen(_handleBarcode); // Finally, start the scanner itself. unawaited(controller.start()); } ``` Finally, dispose of the the `MobileScannerController` when you are done with it. ```dart @override Future<void> dispose() async { // Stop listening to lifecycle changes. WidgetsBinding.instance.removeObserver(this); // Stop listening to the barcode events. unawaited(_subscription?.cancel()); _subscription = null; // Dispose the widget itself. super.dispose(); // Finally, dispose of the controller. await controller.dispose(); } ``` ## Known Limitations ### `rawBytes` on iOS and macOS Apple's Vision framework does not provide a direct API for reading the raw payload bytes of a scanned barcode. The `rawBytes` field is populated on a best-effort basis using two strategies, each with constraints. #### QR codes Two strategies are used in combination. For Byte-mode segments the error-corrected bit stream from `CIQRCodeDescriptor` is parsed directly. For all other modes the decoded string from `payloadStringValue` is re-encoded to Latin-1 as a fallback. | Scenario | `rawBytes` result | |-------------------------------------------------|----------------------------------------------------------------| | Byte mode (UTF-8, arbitrary binary data) | Correct — parsed directly from bit stream | | Numeric mode (digits only) | Correct — recovered via string fallback | | Alphanumeric mode (uppercase + allowed symbols) | Correct — recovered via string fallback | | Kanji mode | `null` — Japanese characters cannot round-trip through Latin-1 | #### Aztec, DataMatrix, PDF417 and linear formats (Code 128, EAN, etc.) Apple Vision decodes the payload as a string internally using a Latin-1 (ISO-8859-1) interpretation of the raw bytes. `rawBytes` is recovered by re-encoding that string back to Latin-1. | Byte value range | `rawBytes` result | |-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| | `0x00`–`0x7F` (ASCII) | Correct | | `0xA0`–`0xFF` (upper Latin-1, includes `ø`, `é`, `ü`, etc.) | Correct | | `0x80`–`0x9F` (Windows-1252 special range) | `null` — Apple maps these to Unicode code points above U+00FF, which cannot be round-tripped through Latin-1 | This means arbitrary binary payloads that happen to contain bytes in the `0x80`–`0x9F` range will result in `rawBytes` being `null` for those formats. #### Android and Web `rawBytes` is fully supported for all formats and encoding modes via MLKit (Android) and the ZXing-based library (Web).

Mobile Development Screenshot & OCR
1.2K Github Stars