HomeOur Team
Hướng dẫn tạo thư viện đơn giản cho Flutter (Phần 2)

Hướng dẫn tạo thư viện đơn giản cho Flutter (Phần 2)

By thao.vu
Published in Solutions
December 06, 2022
1 min read

Mở Đầu

Phần trước chúng ta đã tích hợp thành công phần code native android lên flutter. Trong phần này sẽ tiếp tục tích hợp phần code native ios đến flutter.

Tiến hành

Đầu tiên chạy project example trong lib để kiểm tra ứng dụng hiển thị như thế nào trên các thiết bị iphone Ta được kết quả như hình dưới: ![File.jpg](https://tda-blog-resources.s3.ap-southeast-1.amazonaws.com/File_64c1083d11.jpg)

Check log trên android studio ta thấy lỗi: log_error.png

  • Lỗi trên xảy ra khi chưa tạo channel “com.demo.preview_camera/method” trên native code IOS.

Tiếp theo để tiến hành triển khai preview camera cho native code IOS, ta mở project example trên xcode bằng cách mở file Runner.xcodeproj. Ta được kết quả như hình: Screen Shot 2022-12-06 at 15.19.28.png

chúng ta sẽ làm việc chủ yếu trên folder “preview_lib/../../example/ios/.symlinks/ios/Classes/”, folder này tương ứng với folder “preview_lib/ios/Classes” ở trên lib.

ios_folder.png

Tiếp theo, tạo enum class PreviewCameraError để handle error như sau:

import Foundation
enum PreviewCameraError: Error {
case noCamera
case alreadyStarted
case alreadyStopped
case torchError(_ error: Error)
case cameraError(_ error: Error)
case torchWhenStopped
case analyzerError(_ error: Error)
}

Sau đó tạo class PreviewCamera chứ các hàm để khởi tạo camera trên ios:

import Foundation
import AVFoundation
public class PreviewCamera: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture {
/// Image to be sent to the texture
var latestBuffer: CVImageBuffer!
/// Capture session of the camera
var captureSession: AVCaptureSession!
/// The selected camera
var device: AVCaptureDevice!
/// Texture id of the camera preview for Flutter
private var textureId: Int64!
/// Default position of camera
var videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back
/// If provided, the Flutter registry will be used to send the output of the CaptureOutput to a Flutter texture.
private let registry: FlutterTextureRegistry?
init(registry: FlutterTextureRegistry?) {
self.registry = registry
super.init()
}
/// Check permissions for video
func checkPermission() -> Int {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .notDetermined:
return 0
case .authorized:
return 1
default:
return 2
}
}
/// Request permissions for video
func requestPermission(_ result: @escaping FlutterResult) {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
}
/// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
print("Failed to get image buffer from sample buffer.")
return
}
latestBuffer = imageBuffer
registry?.textureFrameAvailable(textureId)
}
/// Start scanning for barcodes
func start(cameraPosition: AVCaptureDevice.Position) throws -> MobileScannerStartParameters {
if (device != nil) {
throw PreviewCameraError.alreadyStarted
}
captureSession = AVCaptureSession()
textureId = registry?.register(self)
print("Thao: \(String(describing: textureId)) \(String(describing: registry)) ")
// Open the camera device
if #available(iOS 10.0, *) {
device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: cameraPosition).devices.first
} else {
device = AVCaptureDevice.devices(for: .video).filter({$0.position == cameraPosition}).first
}
if (device == nil) {
throw PreviewCameraError.noCamera
}
captureSession.beginConfiguration()
// Add device input
do {
let input = try AVCaptureDeviceInput(device: device)
captureSession.addInput(input)
} catch {
throw PreviewCameraError.cameraError(error)
}
captureSession.sessionPreset = AVCaptureSession.Preset.photo;
// Add video output.
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
videoOutput.alwaysDiscardsLateVideoFrames = true
videoPosition = cameraPosition
// calls captureOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
captureSession.addOutput(videoOutput)
for connection in videoOutput.connections {
connection.videoOrientation = .portrait
if cameraPosition == .front && connection.isVideoMirroringSupported {
connection.isVideoMirrored = true
}
}
captureSession.commitConfiguration()
captureSession.startRunning()
let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription)
return MobileScannerStartParameters(width: Double(dimensions.height), height: Double(dimensions.width), hasTorch: device.hasTorch, textureId: textureId)
}
/// Sends output of OutputBuffer to a Flutter texture
public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
if latestBuffer == nil {
return nil
}
return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
}
struct MobileScannerStartParameters {
var width: Double = 0.0
var height: Double = 0.0
var hasTorch = false
var textureId: Int64 = 0
}
}
  • class PreviewCamera kế thứ từ AVCaptureVideoDataOutputSampleBufferDelegate( dùng để truy cập camera trên iphone), FlutterTexture(dùng để tạo textureId trên iphone)
  • Tương tự như trên android, PreviewCamera sẽ có những hàm chính như checkPermission, requestPermission, start. Những hàm này sẽ được gọi từ flutter, khi channel được khởi tạo.

Tiếp theo,  Mở class SwiftPreviewLibPlugin và sửa lại như class dưới đây:

import Flutter
import UIKit
import AVFoundation
public class SwiftPreviewLibPlugin: NSObject, FlutterPlugin {
private let previewCamera :PreviewCamera
init(registry: FlutterTextureRegistry) {
self.previewCamera = PreviewCamera(registry: registry)
super.init()
}
public static func register(with registrar: FlutterPluginRegistrar) {
let methodChannel = FlutterMethodChannel(name: "com.demo.preview_camera/method", binaryMessenger: registrar.messenger())
let instance = SwiftPreviewLibPlugin(registry: registrar.textures())
registrar.addMethodCallDelegate(instance, channel: methodChannel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "state":
result(previewCamera.checkPermission())
case "request":
AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
case "start":
start(call, result)
default:
result(FlutterMethodNotImplemented)
}
}
private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
print("start")
let position = AVCaptureDevice.Position.back
do {
let parameters = try previewCamera.start(cameraPosition: position)
result(["textureId": parameters.textureId, "size": ["width": parameters.width, "height": parameters.height], "torchable": parameters.hasTorch])
} catch PreviewCameraError.alreadyStarted {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
} catch PreviewCameraError.noCamera {
result(FlutterError(code: "MobileScanner",
message: "No camera found or failed to open camera!",
details: nil))
} catch PreviewCameraError.torchError(let error) {
result(FlutterError(code: "MobileScanner",
message: "Error occured when setting toch!",
details: error))
} catch PreviewCameraError.cameraError(let error) {
result(FlutterError(code: "MobileScanner",
message: "Error occured when setting up camera!",
details: error))
} catch {
result(FlutterError(code: "MobileScanner",
message: "Unknown error occured..",
details: nil))
}
}
}
  • Hàm register để đăng ký channel “com.demo.preview_camera/method” lên flutter. Lưu ý channel này phải giống với channel trong class PreviewController.
  • Các hàm được gọi trong class PreviewController qua phương thức invokeMethod sẽ trigger hàm handle. Với mỗi keyword start, state ta sẽ gọi method previewCamera tương ứng.
  • hàm result() là giá trị trả về giá trị tương ứng từ native cho flutter.

Tiếp theo hãy chạy app trên iphone, ta thấy app bị crash và hiển thị lỗi này dưới log: permission_error.png

  • Lỗi này xuất hiện do chưa add quyền camera trong file Info.plist

Hãy mở file Info.plist trong đường dẫn example/ios/Runner/Info.plist và thêm quyền camera như sau:

<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) camera description.</string>

Sau đó hãy chạy lại app lần nữa để xem app còn bị lỗi nữa không. Và ta được kết quả như hình: i01_camera_ios.jpeg

Kết luận

Vậy là chúng ta đã hoàn thiện thư viện camera cho flutter trên ios và android. Để xem chi tiết hơn hãy check qua nhánh lib_ios Hi vọng sau 2 phần này sẽ giúp các bạn các bước cơ bản để tạo thư viện trên flutter.

source code


Tags

flutter

Share

thao.vu

Mobile Developer

Expertise

Android
Flutter

Related Posts

Custom Marker và Info Window trong GoogleMap Flutter - Tập cuối
Solutions
Custom Marker và Info Window trong GoogleMap Flutter - Tập cuối
December 21, 2022
2 min
© 2023, All Rights Reserved.
Powered By

Quick Links

HomeOur Team

Social Media