Khám phá công nghệ

Khám phá các công cụ và kỹ thuật hữu ich cho Lập trình viên

Hướng dẫn tạo Flutter Plugin đơn giản

Thắng Nguyễn

Mon, 29 Apr 2024

Hướng dẫn tạo Flutter Plugin đơn giản blog_flutter_plugin.md

Trước tiên chúng tôi sẽ cho bạn biết plugin là gì. Có hai loại gói (packages) trong hệ sinh thái Flutter:

  • Dart packages: gói chung, chỉ bao gồm mã dart (vd. path package)
  • Plugin packages: gói đặc biệt kết hợp mã gốc với dart và cho phép bạn sử dụng mã dành riêng cho nền tảng (vd. url_launcher package)

Để tạo plugin, bạn có thể sử dụng dòng lệnh:

flutter create --template=plugin

Bạn cũng có thể chỉ định nền tảng mà bạn muốn phát triển plugin và ngôn ngữ bạn sẽ sử dụng:

flutter create --org com.example --template=plugin --platforms=android,ios -I swift -a kotlin flutter_is_awesome

Điều này sẽ tạo ra một plugin được gọi là flutter_is_awesome với phần iOS được viết bằng Swift và phần Android được viết bằng Kotlin.

Chúng ta sẽ tạo một plugin sẽ hiển thị bộ chọn liên lạc hệ thống.

Cấu trúc Plugin:


Chúng ta có:

  • Thư mục android: chúng ta sẽ đặt tất cả mã cụ thể của Android ở đây.
  • Thư mục iOS: giống như trên, nhưng cho iOS.
  • Thư mục lib: thư mục này sẽ chứa tất cả phần Dart của plugin, sẽ được gọi bởi ứng dụng.
  • Thư mục test: Ở đây bạn sẽ phát triển các bài kiểm tra cho plugin của bạn.
  • Thư mục example: Một ứng dụng nhập gói của chúng tôi là phụ thuộc, bạn có thể sử dụng điều này để thử plugin mà không cần nhập nó vào một ứng dụng khác.

Plugin sử dụng một kênh gọi là MethodChannel để giao tiếp giữa phần Dart và phần native, nó giống như một đường ống nơi phần dart gửi các tin nhắn và phần native lắng nghe những tin nhắn đó và phản ứng với chúng. Nó cũng có thể được sử dụng để gửi tin nhắn từ phần native đến phần dart, nhưng điều này sẽ không được đề cập trong bài viết này.

Plugin của chúng tôi sẽ chỉ có một chức năng duy nhất sẽ hiển thị bộ chọn liên lạc native, cho phép người dùng chọn một liên lạc, và trả về tên của liên lạc được chọn cho người dùng.

Tiếp theo, chúng ta cần làm những việc sau:

  • Viết mã Android của plugin
  • Viết mã iOS của plugin
  • Viết mã dart của plugin

1. Mã Android cho Flutter plugin

Khi plugin đã được tạo, lớp mặc định đã được tạo ra, nó chứa hai phương thức: onAttachedToEngine và onMethodCall. Phương thức đầu tiên được gọi khi plugin được khởi tạo, nó tạo ra kênh giao tiếp với phần dart và bắt đầu lắng nghe kênh để nhận tin nhắn. Phương thức thứ hai được gọi mỗi khi có một tin nhắn mới trong kênh; nó có 2 tham số: call chứa các chi tiết của việc gọi (phương thức và các tham số có thể có) và tham số result sẽ được sử dụng để gửi kết quả trở lại cho phần dart.

class FlutterIsAwesomePlugin: FlutterPlugin, MethodCallHandler { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_is_awesome") channel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getAContact") { } else { result.notImplemented() } }

Định nghĩa một số biến cho plugin của chúng ta sẽ được sử dụng sau này

val PICK_CONTACT_RESULT_CODE = 36 var act: android.app.Activity? = null private lateinit var channel : MethodChannel private lateinit var result: Result

Bây giờ chúng ta cần triển khai ActivityAware và PluginRegistry.ActivityResultListenerprotocols, chúng ta cần chúng để lấy hoạt động mà sẽ được sử dụng để hiển thị bộ chọn liên lạc của chúng tôi.

override fun onAttachedToActivity(binding: ActivityPluginBinding) { act = binding.activity binding.addActivityResultListener(this) } override fun onDetachedFromActivityForConfigChanges() { act = null; } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { act = binding.activity binding.addActivityResultListener(this) } override fun onDetachedFromActivity() { act = null; } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { return false }

Phần duy nhất còn thiếu là mã để hiển thị bộ chọn liên lạc và lấy kết quả. Trong onMethodCall của chúng tôi, bắt đầu hoạt động để chọn một liên lạc và trong onActivityResult, lấy kết quả và gửi nó đến phần dart bằng đối tượng Result mà chúng tôi đã lưu trước đó.

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { this.result = result //lưu kết quả để gọi nó khi người dùng chọn một liên lạc if (call.method == "getAContact") { val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) act?.startActivityForResult(intent, PICK_CONTACT_RESULT_CODE) } else { result.notImplemented() } } ... override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { if (requestCode == PICK_CONTACT_RESULT_CODE) { if (resultCode == Activity.RESULT_OK) { if (data != null) { val contactData = data.data val c = act!!.contentResolver.query(contactData!!, null, null, null, null) if (c!!.moveToFirst()) { val name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) result.success(name) return true } } } } return false }

Phần code Android đã hoàn thành! ✅

2. Mã iOS cho Flutter plugin

Phần iOS sẽ rất tương tự như phần Android, ở đây chúng ta có hàm register, là tương đương của onAttachedToEngine mà chúng ta đã thấy cho Android, và hàm xử lý, sẽ được gọi khi tin nhắn mới đến.

public class SwiftFlutterIsAwesomePlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "flutter_is_awesome", binaryMessenger: registrar.messenger()) let instance = SwiftFlutterIsAwesomePlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if call.method == "getAContact" { } else { result(FlutterMethodNotImplemented) } } }

Bây giờ chúng ta cần tạo một lớp ContactPickerDelegate mới sẽ nhận các cuộc gọi lại từ bộ chọn liên lạc (khi một liên lạc được chọn hoặc khi bộ chọn bị bỏ qua) và sẽ thông báo cho lớp plugin. Nó có 2 biến khối onSelectContact và onCancel sẽ được gọi khi bộ chọn thông báo cho lớp này với giao thức CNContactPickerDelegate.

import Foundation import ContactsUI class ContactPickerDelegate: NSObject, CNContactPickerDelegate { public var onSelectContact: (CNContact) -> Void public var onCancel: () -> Void init(onSelectContact: @escaping (CNContact) -> Void, onCancel: @escaping () -> Void) { self.onSelectContact = onSelectContact self.onCancel = onCancel super.init() } func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { picker.presentingViewController?.dismiss(animated: true, completion: nil) onSelectContact(contact) } func contactPickerDidCancel(_ picker: CNContactPickerViewController) { picker.presentingViewController?.dismiss(animated: true, completion: nil) onCancel() } }

Những gì chúng ta cần làm bây giờ là hiển thị bộ chọn liên lạc khi nhận được tin nhắn getAContact, vì vậy hãy thêm điều này vào lớp plugin:

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if call.method == "getAContact" { getAContact(withResult: result) } else { result(FlutterMethodNotImplemented) } } //lưu bộ chọn liên lạc, để nó không bị giải phóng var contactPickerDelegate: ContactPickerDelegate? private func getAContact(withResult result: @escaping FlutterResult) { let contactPicker = CNContactPickerViewController() contactPickerDelegate = ContactPickerDelegate(onSelectContact: { contact in //gửi kết quả trở lại dart result(contact.givenName + contact.familyName) self.contactPickerDelegate = nil //đặt về nil, để nó được loại bỏ khỏi bộ nhớ }, onCancel: { result(nil) self.contactPickerDelegate = nil //đặt về nil, để nó được loại bỏ khỏi bộ nhớ }) contactPicker.delegate = contactPickerDelegate let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) let rootViewController = keyWindow?.rootViewController DispatchQueue.main.async { rootViewController?.present(contactPicker, animated: true) } }

Phần iOS đã hoàn thành! ✅

3. Mã Dart cho Flutter plugin

Phần cuối cùng là tạo mã Dart của plugin sẽ liên kết tất cả các phần còn lại với nhau.

class FlutterIsAwesome { static const MethodChannel _channel = const MethodChannel('flutter_is_awesome'); static Future<String> getAContact() async { final String contact = await _channel.invokeMethod('getAContact'); return contact; } }

Như bạn có thể thấy, nó rất đơn giản, nó gọi phương thức getAContact trên channel và chờ kết quả và trả về nó. Lưu ý rằng tất cả các hàm tương tác với phần native đều là bất đồng bộ và sẽ trả về một Future.

Bây giờ chúng ta chỉ cần kiểm tra plugin của chúng ta, chúng ta có một ứng dụng đơn giản trong thư mục example, chỉ có một nút và một nhãn để kiểm tra xem mọi thứ có hoạt động tốt không.

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_is_awesome/flutter_is_awesome.dart'; void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String _contact = 'Unknown'; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Flutter is Awesome'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ MaterialButton( color: Colors.red, textColor: Colors.white, child: Text('Picker'), onPressed: () => _getAContact(), ), Text(_contact ?? '') ], ), ), ), ); } _getAContact() async { String contact; try { contact = await FlutterIsAwesome.getAContact(); } on PlatformException { contact = 'Failed to get contact.'; } if (!mounted) return; setState(() { _contact = contact; }); } }

Kết quả cuối cùng của chúng ta như sau:

0 Comments

Để lại một bình luận