Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
762a559
Implement camera switching for Android
ikbendewilliam Dec 7, 2021
c42e65a
Add method canChangeCamera
ikbendewilliam Dec 8, 2021
22d7372
Switch camera on iOS
herre Dec 9, 2021
7e5e25e
Fix build issue
ikbendewilliam Dec 9, 2021
dd764a8
Merge pull request #1 from ikbendewilliam/feature/can-switch-camera
ikbendewilliam Dec 9, 2021
8f5c69a
Fixed PR Comments
ikbendewilliam Dec 28, 2021
4ff1eee
Enable pause & resume on continuous scanning mode in iOS
rwillemsandroid Apr 21, 2022
4aca0cd
Merge pull request #2 from rwillemsandroid/feature/pause-resume-in-co…
ikbendewilliam Apr 21, 2022
c734a89
Update kotlin version
ikbendewilliam Jul 17, 2023
dfbd6ea
Flutter update
ikbendewilliam Oct 26, 2023
8a8f02d
renamed to icapps
jorre127 Nov 25, 2024
328f777
update android version
jorre127 Nov 25, 2024
6b44afb
fix android
jorre127 Nov 25, 2024
8bf93a3
fix ios
jorre127 Nov 25, 2024
b0eb637
updated changelog
jorre127 Nov 25, 2024
af1e57d
dart format
jorre127 Nov 25, 2024
d0830fa
updated version
jorre127 Nov 25, 2024
636425b
updated platform interface version
jorre127 Nov 25, 2024
351b145
Merge pull request #4 from jorre127/feature/android-update
jorre127 Nov 25, 2024
39cb4f2
publishing fix
jorre127 Nov 25, 2024
9e5c5ed
further rename
jorre127 Nov 25, 2024
69cb061
further rename to icapps
jorre127 Nov 25, 2024
c6e1a86
Merge pull request #5 from jorre127/feature/android-update
jorre127 Nov 25, 2024
35d7e48
added namespace
jorre127 Jun 27, 2025
bacfdab
Merge pull request #6 from icapps/feature/add-namespace
jorre127 Jun 27, 2025
546b9d0
Update dependencies
jorre127 Nov 5, 2025
fcb0c88
update plugin
jorre127 Nov 5, 2025
fb83485
updated package so it's 16kb compatible
jorre127 Nov 5, 2025
7ce6669
updated change log
jorre127 Nov 5, 2025
7b16b04
Merge pull request #7 from icapps/feature/update-dependencies
jorre127 Nov 6, 2025
f6791a3
possible fix for crash
jorre127 Nov 6, 2025
cfc959c
add import
jorre127 Nov 6, 2025
735b62b
update version
jorre127 Nov 6, 2025
b6025f9
Merge pull request #8 from icapps/feature/update-dependencies
jorre127 Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import java.util.ArrayList
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

data class CameraConfig(val formats: IntArray, val mode: DetectionMode, val resolution: Resolution, val framerate: Framerate, val position: CameraPosition)
data class CameraConfig(val formats: IntArray, val mode: DetectionMode, val resolution: Resolution, val framerate: Framerate, var position: CameraPosition)

class BarcodeReader(private val flutterTextureEntry: TextureRegistry.SurfaceTextureEntry, private val listener: (List<Barcode>) -> Unit) : RequestPermissionsResultListener {
/* Android Lifecycle */
Expand Down Expand Up @@ -106,6 +106,28 @@ class BarcodeReader(private val flutterTextureEntry: TextureRegistry.SurfaceText
}, ContextCompat.getMainExecutor(activity))
}

fun canChangeCamera(result: Result) {
try {
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity!!)
cameraProviderFuture.addListener(Runnable {
val cameraProviderForChangeCamera = cameraProviderFuture.get()
val hasFrontCamera = cameraProviderForChangeCamera.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA)
val hasBackCamera = cameraProviderForChangeCamera.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)
result.success(hasFrontCamera && hasBackCamera)
}, ContextCompat.getMainExecutor(activity!!))
} catch (exc: Exception) {
result.success(false)
}
}

fun changeCamera(position: String, result: Result) {
cameraConfig.position = when (position) {
"front" -> CameraPosition.front
else -> CameraPosition.back
}
initCamera()
}

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(activity!!.applicationContext, it) == PackageManager.PERMISSION_GRANTED
}
Expand Down Expand Up @@ -148,12 +170,8 @@ class BarcodeReader(private val flutterTextureEntry: TextureRegistry.SurfaceText
// Select camera
val selectorBuilder = CameraSelector.Builder()
when (cameraConfig.position) {
CameraPosition.front -> {
selectorBuilder.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
}
CameraPosition.back -> {
selectorBuilder.requireLensFacing(CameraSelector.LENS_FACING_BACK)
}
CameraPosition.front -> selectorBuilder.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
CameraPosition.back -> selectorBuilder.requireLensFacing(CameraSelector.LENS_FACING_BACK)
}
cameraSelector = selectorBuilder.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class FastBarcodeScannerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware
"pause" -> reader.stop(result)
"resume" -> reader.resume(result)
"toggleTorch" -> reader.toggleTorch(result)
"canChangeCamera" -> reader.canChangeCamera(result)
"changeCamera" -> reader.changeCamera(call.arguments as String, result)
else -> result.notImplemented()
}
}
Expand Down
33 changes: 22 additions & 11 deletions fast_barcode_scanner/example/lib/scanner_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ class ScannerScreen extends StatefulWidget {

class _ScannerScreenState extends State<ScannerScreen> {
final _torchIconState = ValueNotifier(false);
var _canChangeCamera = false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good practice sake should declare type bool

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Xazin , actually the Dart style guide encourages the use of var/final for local variables instead of declaring types::
https://dart.dev/guides/language/effective-dart/design#dont-redundantly-type-annotate-initialized-local-variables

Copy link

@Xazin Xazin Jan 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be final, and I cannot find the place in the effective dart guidelines where it says to prefer var over declaring it the known type bool. And it depends highly on how narrow the scope the variable is used in is. In my opinion this is not a narrow scope.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internally we always use var, but as this is not our package, and I can't find any best practise references I've changed it to bool

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be final, and I cannot find the place in the effective dart guidelines where it says to prefer var over declaring it the known type bool. And it depends highly on how narrow the scope the variable is used in is. In my opinion this is not a narrow scope.

@Xazin sorry that my comment wasn't clear. I mentioned both var and final. Yes, this can't be final since you need to mutate the value, but it should be var. The part of the dart style guide that mentions this best practice is here:

https://dart.dev/guides/language/effective-dart/design#do-type-annotate-fields-and-top-level-variables-if-the-type-isnt-obvious

In this case the initializer makes the type obvious; it's initialized to false so it's obviously a bool and typing it out doesn't add any clarity.

So I believe the dart guide here recommends that this be var rather than bool.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but they also recommend doing this mostly in smaller functions. When in doubt always declare type, they even mention this themselves.

Although the subject is a bit contradictory even from their own documentation, I do agree if we're talking about functions of 10 lines or less, it makes no sense to declare type, but this is a local variable with a scope of 50+ lines.

Whether it adds clarity to type the variable, I can also agree with you that it's not because it has super much value, because the naming of the variable now correctly reflects what type it is as well. But with a larger scope and 1 character difference, I don't see a good enough reason to omit the type in this scenario.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Xazin I hope you don't mind a continued discussion on this. It seems like such a small thing; but since we're talking about enforcing a convention I'm more interested in making sure we get the convention right than about this particular line of code. As as a contributor to this package I want to make sure my own contributions meet the package's standards.

Ultimately, it's neither right nor wrong to add the type annotation in this location. I think it should be fine if we do either; though I personally I take a minimalistic approach and prefer to leave them off when the annotation doesn't add value.

From the style guide section I shared it specifically says this:

In some cases, though, the type is so obvious that writing it is pointless:

And then as you pointed out it also says it's ok to add them even if it's obvious:

When in doubt, add a type annotation.

And so the use of the annotations requires some judgement. In addition to considering the size of the function I think it is also useful to consider the complexity of the expression the produces the type. Complex expressions can make the type more difficult for the reader to infer. consider these examples:

complex expression

final firstList = <int>[1, 2, 3, 4];
final secondList = firstList
    .map((e) => e.toString())
    .map((e) => e.length)
    .map((e) => e % 2 == 0 ? "even" : "odd")
    .toList();

In this first example you would have to mentally evaluate the expression before you know what type secondList is. This would probably be fine in a small function where the logical flow of the program is easy to evaluate and follow. But in a bigger scope a type annotation would be useful in this spot to save the mental energy. Furthermore, a type annotation is helpful to avoid an accidental List<dynamic> inference which is easy to do when mutating lists and the tools wouldn't necessarily help you see the issue.

This would be much better with generic types and type annotations:

final firstList = <int>[1, 2, 3, 4];
final List<String> secondList = firstList
    .map<String>((int e) => e.toString())
    .map<int>((String e) => e.length)
    .map<String>((int e) => e % 2 == 0 ? "even" : "odd")
    .toList();

simple expression

var _canChangeCamera = false;

In this second example, taken from the code in question, false is a reserved primitive type that is completely obvious. Adding a type annotation to a simple and unambiguous expressions like this can be avoided.

In the end I think our opinion here is actually very close. You don't find a good enough reason to omit the type and I don't find a good enough reason to add the type. And so ultimately I think we should support either style; and in this case, leave this line how it is. A rule to always add or always omit isn't workable.


@override
void initState() {
super.initState();
_checkCamera();
}

Future<void> _checkCamera() async {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming sense here could be better, you're not checking the camera, you're checking if it's possible to change camera.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest: _validateCameraSwitching

final canChangeCamera = await CameraController.instance.canChangeCamera();
setState(() {
_canChangeCamera = canChangeCamera;
Copy link

@Xazin Xazin Dec 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can change this to

setState(() async {
  _canChangeCamera = await CameraController.instance.canChangeCamera();
});

No need for variable declaration.

});
}

@override
Widget build(BuildContext context) {
Expand All @@ -32,25 +46,22 @@ class _ScannerScreenState extends State<ScannerScreen> {
ValueListenableBuilder<bool>(
valueListenable: _torchIconState,
builder: (context, state, _) => IconButton(
icon: state
? const Icon(Icons.flash_on)
: const Icon(Icons.flash_off),
icon: state ? const Icon(Icons.flash_on) : const Icon(Icons.flash_off),
onPressed: () async {
await CameraController.instance.toggleTorch();
_torchIconState.value =
CameraController.instance.state.torchState;
_torchIconState.value = CameraController.instance.state.torchState;
},
),
),
if (_canChangeCamera)
IconButton(
icon: const Icon(Icons.cameraswitch),
onPressed: CameraController.instance.toggleCamera,
),
],
),
body: BarcodeCamera(
types: const [
BarcodeType.ean8,
BarcodeType.ean13,
BarcodeType.code128,
BarcodeType.qr
],
types: const [BarcodeType.ean8, BarcodeType.ean13, BarcodeType.code128, BarcodeType.qr],
resolution: Resolution.hd720,
framerate: Framerate.fps30,
mode: DetectionMode.pauseVideo,
Expand Down
5 changes: 5 additions & 0 deletions fast_barcode_scanner/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: fast_barcode_scanner_example
description: Demonstrates how to use the fast_barcode_scanner plugin.
publish_to: 'none'
version: 0.0.1+1

environment:
sdk: '>=2.12.0 <3.0.0'
Expand All @@ -15,5 +16,9 @@ dependencies:
dev_dependencies:
flutter_lints: ^1.0.4

dependency_overrides:
fast_barcode_scanner_platform_interface:
path: ../../fast_barcode_scanner_platform_interface/

flutter:
uses-material-design: true
116 changes: 87 additions & 29 deletions fast_barcode_scanner/ios/Classes/BarcodeReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,30 +88,50 @@ class BarcodeReader: NSObject {
var captureDevice: AVCaptureDevice!
var captureSession: AVCaptureSession
let dataOutput: AVCaptureVideoDataOutput

var metadataOutput: AVCaptureMetadataOutput
let codeCallback: ([String]) -> Void
let detectionMode: DetectionMode
let position: AVCaptureDevice.Position

var position: AVCaptureDevice.Position
let detectionMode: DetectionMode
let framerate: Framerate
let resolution: Resolution
let codes: [String]

var torchActiveOnStop = false
var previewSize: CMVideoDimensions!

init(textureRegistry: FlutterTextureRegistry,
arguments: StartArgs,
codeCallback: @escaping ([String]) -> Void) throws {
self.textureRegistry = textureRegistry
arguments: StartArgs,
codeCallback: @escaping ([String]) -> Void) throws {
self.textureRegistry = textureRegistry
self.codeCallback = codeCallback
self.captureSession = AVCaptureSession()

self.captureSession = AVCaptureSession()
self.dataOutput = AVCaptureVideoDataOutput()
self.metadataOutput = AVCaptureMetadataOutput()
self.detectionMode = arguments.detectionMode

self.detectionMode = arguments.detectionMode
self.position = arguments.position
self.framerate = arguments.framerate
self.resolution = arguments.resolution
self.codes = arguments.codes

super.init()

captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position)
do {
try setupCaptureDevice(arguments)
} catch {
throw error
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting in this file seems very off.


private func setupCaptureDevice(_ arguments: StartArgs) throws {
captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position)

guard captureDevice != nil else {
throw ReaderError.noInputDevice
}
guard captureDevice != nil else {
throw ReaderError.noInputDevice
}

do {
let input = try AVCaptureDeviceInput(device: captureDevice)
Expand All @@ -126,25 +146,25 @@ class BarcodeReader: NSObject {
captureSession.addOutput(dataOutput)
captureSession.addOutput(metadataOutput)

dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
dataOutput.connection(with: .video)?.videoOrientation = .portrait
dataOutput.alwaysDiscardsLateVideoFrames = true
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .default))
dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
dataOutput.connection(with: .video)?.videoOrientation = .portrait
dataOutput.alwaysDiscardsLateVideoFrames = true
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .default))

metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.global(qos: .default))
metadataOutput.metadataObjectTypes = arguments.codes.compactMap { avMetadataObjectTypes[$0] }
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.global(qos: .default))
metadataOutput.metadataObjectTypes = arguments.codes.compactMap { avMetadataObjectTypes[$0] }

guard let optimalFormat = captureDevice.formats.first(where: {
let dimensions = CMVideoFormatDescriptionGetDimensions($0.formatDescription)
let mediaSubType = CMFormatDescriptionGetMediaSubType($0.formatDescription).toString()
guard let optimalFormat = captureDevice.formats.first(where: {
let dimensions = CMVideoFormatDescriptionGetDimensions($0.formatDescription)
let mediaSubType = CMFormatDescriptionGetMediaSubType($0.formatDescription).toString()

return $0.videoSupportedFrameRateRanges.first!.maxFrameRate >= arguments.framerate.doubleValue
&& dimensions.height >= arguments.resolution.height
&& dimensions.width >= arguments.resolution.width
&& mediaSubType == "420f" // maybe 420v is also ok? Who knows...
}) else {
throw ReaderError.cameraNotSuitable(arguments.resolution, arguments.framerate)
}
return $0.videoSupportedFrameRateRanges.first!.maxFrameRate >= arguments.framerate.doubleValue
&& dimensions.height >= arguments.resolution.height
&& dimensions.width >= arguments.resolution.width
&& mediaSubType == "420f" // maybe 420v is also ok? Who knows...
}) else {
throw ReaderError.cameraNotSuitable(arguments.resolution, arguments.framerate)
}

do {
try captureDevice.lockForConfiguration()
Expand All @@ -158,8 +178,8 @@ class BarcodeReader: NSObject {
throw ReaderError.configurationLockError(error)
}

previewSize = CMVideoFormatDescriptionGetDimensions(captureDevice.activeFormat.formatDescription)
}
previewSize = CMVideoFormatDescriptionGetDimensions(captureDevice.activeFormat.formatDescription)
}

func start(fromPause: Bool) throws {
guard captureDevice != nil else { return }
Expand Down Expand Up @@ -237,9 +257,47 @@ class BarcodeReader: NSObject {
try start(fromPause: true)
}
}

func changeCamera(type: String) {
position = type == "front" ? .front : .back
reloadCamera()
}

func toggleCamera() {
position = position.toggled()
reloadCamera()
}

private func reloadCamera() {
captureSession.stopRunning()

captureSession.outputs.forEach { captureSession.removeOutput($0) }
captureSession.inputs.forEach { captureSession.removeInput($0) }

do {
let arguments = StartArgs(position: position, detectionMode: detectionMode, framerate: framerate, resolution: resolution, codes: codes)
try setupCaptureDevice(arguments)
} catch {
print(error)
}

captureSession.startRunning()
}

}

private extension AVCaptureDevice.Position {

func toggled() -> AVCaptureDevice.Position {
switch self {
case .back:
return .front
default:
return .back
}
}
}

extension BarcodeReader: FlutterTexture {
func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
pixelBuffer == nil ? nil : .passRetained(pixelBuffer!)
Expand Down
29 changes: 29 additions & 0 deletions fast_barcode_scanner/ios/Classes/FastBarcodeScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ struct StartArgs {
self.detectionMode = detectionMode
self.codes = codes
}

init(position: AVCaptureDevice.Position, detectionMode: DetectionMode, framerate: Framerate, resolution: Resolution, codes: [String]) {
self.position = position
self.detectionMode = detectionMode
self.framerate = framerate
self.resolution = resolution
self.codes = codes
}

let position: AVCaptureDevice.Position
let framerate: Framerate
Expand Down Expand Up @@ -55,7 +63,10 @@ public class FastBarcodeScannerPlugin: NSObject, FlutterPlugin {
case "pause": pause(result: result)
case "resume": try resume(result: result)
case "toggleTorch": toggleTorch(result: result)
case "canChangeCamera": canChangeCamera(result: result)
case "heartBeat": result(nil)
case "changeCamera": changeCamera(call: call, result: result)
case "toggleCamera": toggleCamera(result: result)
default: result(FlutterMethodNotImplemented)
}
} catch {
Expand Down Expand Up @@ -131,9 +142,27 @@ public class FastBarcodeScannerPlugin: NSObject, FlutterPlugin {
result(reader?.toggleTorch())
}

func canChangeCamera(result: @escaping FlutterResult) {
result(true)
}

func stop(result: @escaping FlutterResult) {
reader?.stop(pause: false)
reader = nil
result(nil)
}

func changeCamera(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let type = call.arguments as? String else {
result(false)
return
}
reader?.changeCamera(type: type)
result(true)
}

func toggleCamera(result: @escaping FlutterResult) {
reader?.toggleCamera()
result(true)
}
}
Loading