Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
MethodChannel
Written by Hồng Vân Lê on June 3, 2022

Platform-specific code with MethodChannel – Flutter

Flutter & Dart

Contents

  • Chuẩn bị
  • 1. MethodChannel là gì?
  • 2. Tương tác với native code
    • 2.1. Cấu hình cho iOS
    • 2.2. Cấu hình cho Android
    • 2.3. Cấu hình cho phía Flutter
    • 2.4. Kết quả
  • 3. Ứng dụng MethodChannel vào Flutter Flavor
    • 3.1. Sử dụng MethodChannel để đọc flavor của mỗi platform
      • 3.1.1. Cấu hình cho iOS
      • 3.1.2. Cấu hình cho Android
    • 3.2. Thực hiện việc gọi hàm đọc flavor ở phía Flutter client
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Bài viết này sẽ tiếp tục chuyến phiêu lưu vào đa vũ trụ Flutter. Chủ đề là MethodChannel. Nó giúp bạn tương tác với từng nền tảng code (native code) trong một số trường hợp cần thiết. Đây cũng là một chủ đề quan trọng khi bạn muốn chinh phục Flutter.

Tác giả bài viết là bạn Hồng Vân. Mọi người có thể theo dõi thêm các bài viết từ GitHub chính chủ của bạn nhóe!

Còn nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Về mặt kiến thức, bạn cần thông thạo Flutter cơ bản trước nhóe. Còn về mặt tools & versions, mình sẽ sử dụng các công cụ như sau:

  • IDE:

    • Visual Studio Code version 1.67.0

    • Android Studio Chipmunk 2021.2.1

    • XCode version 13.3.1

  • Flutter SDK version 2.10.5

Ngoài ra, đây cũng là phần tiếp theo của 2 bài viết liên quan tới Flavor trong Flutter. Nên nếu bạn chưa đọc qua nó, thì có thể tham khảo ở link sau:

  • Flavor Configurations:
    • Github doc: https://github.com/vanle57/flutter-flavor
    • FxStudio post: https://fxstudio.dev/flavor-configurations-flutter/
  • Customize Run and Debug:
    • Github doc: https://github.com/vanle57/flutter-customize-run
    • FxStudio post: https://fxstudio.dev/customize-run-and-debug-flutter/

1. MethodChannel là gì?

Flutter cho phép 1 base code có thể build được cho nhiều nền tảng. Tuy nhiên, trong lập trình, đôi khi bạn sẽ gặp phải tình huống cần truy cập API dành riêng cho nền tảng bằng ngôn ngữ hoạt động trực tiếp với các API đó (native code). Mình lấy ví dụ một số tình huống như:

  • Lấy lượng pin của device

  • Truy cập camera hoặc thư viện ảnh của device

  • …

MethodChannel sẽ giúp bạn làm điều đó!

Cách hoạt động của MethodChannel được mô tả theo mô hình bên dưới:

MethodChannel

MethodChannel hoạt động dựa trên tin nhắn nhị phân (binnary message) và kênh nền tảng (platform channel).

Ở phía client (Flutter UI), MethodChannel cho phép gửi tin nhắn qua các cuộc gọi phương thức. Ở phía platform, MethodChannel trên Android và FlutterMethodChannel trên iOS nhận các lời gọi method này và trả kết quả về lại. Nên nhớ một điều rằng API sẽ không thực sự “gọi hàm” thay cho bạn. Phần kiểm tra các phương thức được gọi và trả kết quả về sẽ do bạn thực hiện. MethodChannel chỉ “lắng nghe” các lời gọi này mà thôi.

Để đảm bảo cho tương tác của người dùng trên app vẫn mượt mà, các tin nhắn và phản hồi sẽ được gửi 1 cách bất đồng bộ.

2. Tương tác với native code

Bạn tải project template tại đây về để thực hành.

Mình sẽ làm 1 cái demo nho nhỏ là đọc package name (Android) / bundle identifier (iOS) để các bạn biết hình dung ra cách làm việc với MethodChannel nhé!

2.1. Cấu hình cho iOS

Bạn vào iOS/Runner/AppDelegate.swift và cấu hình cho FlutterMethodChannel như sau:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      guard let controller = window?.rootViewController as? FlutterViewController else {
          return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
      // 1
      let flavorChannel = FlutterMethodChannel(name: "demo", binaryMessenger: controller.binaryMessenger)
      // 2
      flavorChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
          switch call.method {
          // 3
          case "getPackage":
              let bundleId = Bundle.main.infoDictionary?["CFBundleIdentifier"]
              result(bundleId)
          default:
              // 4
              result(FlutterMethodNotImplemented)
          }
      })
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Giải thích:

  1. Khởi tạo FlutterMethodChannel với name là demo. Name giống như là id cho kênh, vậy nên nó phải giống nhau ở cả iOS, Android và Flutter.

  2. Đăng ký lắng nghe các lời gọi hàm.

  3. Với lời gọi hàm là getPackage, ta thực hiện việc trả về CFBundleIdentifier được đọc từ Info.plist.

  4. Xử lý những lời gọi hàm không xác định.

2.2. Cấu hình cho Android

Bạn mở file android/app/src/main/kotlin/MainActivity.kt và thay thế đoạn code này vào sau dòng package.

// 1
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        //2
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo").setMethodCallHandler {
            // 3
            call, result -> 
            when (call.method) {
                "getPackage" -> {
                    result.success(BuildConfig.APPLICATION_ID)
                }
                else -> result.notImplemented()
            }

        }
    }
}

Giải thích:

  1. Bạn nhớ import tất cả các package cần thiết nha!

  2. Tương tự như ở iOS, bạn khởi tạo MethodChannel với name là demo.

  3. Trả về giá trị BuildConfig.APPLICATION_ID, là giá trị có sẵn của Android với lời gọi hàm là getPackage.

  4. Xử lý những lời gọi hàm không xác định.

2.3. Cấu hình cho phía Flutter

Bạn tiếp tục vào file lib/main.dart và chỉnh sửa hàm main

// 1
Future<void> main() async {
  // 2
  WidgetsFlutterBinding.ensureInitialized();
  // 3
  final package =
      await MethodChannel('demo').invokeMethod<String>("getPackage");
  print(package);
  runApp(const MyApp());
}

Giải thích:

  1. Mình sửa lại kiểu trả về của hàm main là Future với từ khoá async

  2. Bước này đặc biệt quan trọng. Điều này là bắt buộc để truy cập kênh nền tảng trước khi khởi chạy ứng dụng.

  3. Khời tạo đối tượng của MethodChannel với name demo. Gọi hàm getPackage với kiểu trả về là String. invokeMethod() phải đi kèm từ khoá await vì nó sẽ trả về 1 Future.

2.4. Kết quả

Bạn run thử và xem kết quả nào trên Debug Console nào!

flutter run

Hoặc bấm nút Run ở tab Run and Debug

Kết quả:

  • iOS: com.demoFlavor.dev

  • Android: com.example.demo_flavor.dev

3. Ứng dụng MethodChannel vào Flutter Flavor

Nếu chưa biết Flutter Flavor là gì?

Bạn có thể tham khảo bài viết tại GitHub – vanle57/flutter-flavor: Guide to flavoring a Flutter app.

3.1. Sử dụng MethodChannel để đọc flavor của mỗi platform

3.1.1. Cấu hình cho iOS

Bạn phải thực hiện trên XCode nha!

  • Bước 1: Thêm APP_FLAVOR vào User-Defined Setting

    • Vào project Runner -> chọn tab Build Settings -> click vào dấu + chọn Add User-Defined Setting.

MethodChannel

    • Đặt tên là APP_FLAVOR và định nghĩa các giá trị là các flavor tương ứng cho từng cấu hình.

  • Bước 2: Định nghĩa AppFlavor trong file Info.plist. Có 2 cách các bạn có thể làm ở bước này.
    • Cách 1: Thêm vào trên giao diện file .plist

Giải thích 1 chút: $(APP_FLAVOR) sẽ tương ứng với biến APP_FLAVOR bạn đã tạo ở bước 1 nhé.

    • Cách 2: Thêm dưới dạng source code. Các bạn có thể mở file Info.plist bằng VSCode hoặc click chuột phải vào file Info.plist -> chọn Open as source code trên giao diện XCode.
<key>AppFlavor</key>
<string>$(APP_FLAVOR)</string>
  • Bước 3: Cấu hình cho FlutterMethodChannel trong AppDelegate. Tham khảo đoạn code sau nhóe!

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      guard let controller = window?.rootViewController as? FlutterViewController else {
          return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
      let flavorChannel = FlutterMethodChannel(name: "demo", binaryMessenger: controller.binaryMessenger)
      flavorChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
          switch call.method {
          case "getPackage":
              let bundleId = Bundle.main.infoDictionary?["CFBundleIdentifier"]
              result(bundleId)
          // NOTE: Add new case
          case "getFlavor":
              let flavor = Bundle.main.infoDictionary?["AppFlavor"]
              result(flavor)
          default:
              result(FlutterMethodNotImplemented)
          }
      })
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Giải thích:

Ở đây chúng ta thêm case là “getFlavor” để khi nhận được lời gọi hàm này, ta sẽ xử lý trả về AppFlavor được đọc từ Info.plist.

3.1.2. Cấu hình cho Android

Tương tự, bạn mở file cũng android/app/src/main/kotlin/MainActivity.kt. Bạn muốn làm giao diện VSCode hay Android Studio đều được.

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo").setMethodCallHandler {
            call, result -> 
            when (call.method) {
                "getPackage" -> {
                    result.success(BuildConfig.FLAVOR)
                }
                // NOTE: Add new case
                "getFlavor" -> {
                    result.success(BuildConfig.FLAVOR)
                }
                else -> result.notImplemented()
            }
        }
    }
}

Giải thích code:

Ở đây, bạn cũng add thêm case “getFlavor” và trả về BuildConfig.FLAVOR.

3.2. Thực hiện việc gọi hàm đọc flavor ở phía Flutter client

Ý tưởng ở đây là mình sẽ xây dựng lớp FlavorConfig và sử dụng MethodChannel để gọi phương thức getFlavor.

import 'package:flutter/services.dart';

class FlavorConfig {
  // 1
  Future<void> getFlavor() async {
    // 2
    const methodChannel = MethodChannel('demo');
    // 3
    final flavor = await methodChannel.invokeMethod<String>('getFlavor');
    // 4
    if (flavor == 'dev') {
      print('Flavor: dev');
    } else if (flavor == 'staging') {
      print('Flavor: staging');
    } else if (flavor == 'product') {
      print('Flavor: product');
    }
  }
}

Giải thích:

  1. Khai báo hàm getFlavor trả về 1 Future với từ khoá async (Bạn hãy nhớ lại mình đã đề cập ở phần 1 là tin nhắn và phản hồi sẽ được gửi bất đồng bộ nhé!)

  2. Khởi tạo MethodChannel với name giống như bạn đã đặt ở phần cấu hình cho iOS và Android.

  3. Gọi hàm getFlavor với kiểu trả về là String.

  4. So sánh kết quả trả về để print ra console.

Okay! Tiếp theo là mình sẽ gọi hàm getFlavor() của lớp FlavorConfig để xem nó hoạt động như thế nào. Bạn vào hàm main để gọi nhé!

// 1
Future<void> main() async {
  // 2
  WidgetsFlutterBinding.ensureInitialized();
  final package = await MethodChannel('demo').invokeMethod<String>("getPackage");
  print(package)
  // 3
  await FlavorConfig().getFlavor();
  runApp(const MyApp());
}

Bây giờ thì run app và xem kết quả thôi!

  • iOS

MethodChannel

 

  • Android

Biến tấu một chút.

Giả sử như có 3 cái api url khác nhau cho mỗi flavor, thì mình sẽ xử lý như thế nào?

// 1
enum AppFlavor { dev, stg, prod }

// 2
extension AppFlavorExtension on AppFlavor {
  String get apiURL {
    switch (this) {
      case AppFlavor.dev:
        return "https://example.dev.com/";
      case AppFlavor.stg:
        return "https://example.stg.com/";
      case AppFlavor.prod:
        return "https://example.com/";
    }
  }
}

Giải thích:

  1. Mình sẽ tạo enum AppFlavor với 3 case tương ứng với 3 flavor hiện có trong app.

  2. Tạo extension của AppFlavor để định nghĩa biến apiURL kiểu String là đại diện cho các link api khác nhau của mỗi flavor.

Mình sửa lại 1 tí ở hàm getFlavor của lớp FlavorConfig là sẽ trả về kiểu Future<AppFlavor?> thay vì kiểu Future như lúc nãy.

Future<AppFlavor?> getFlavor() async {
  const methodChannel = MethodChannel('flavor');
  final flavor = await methodChannel.invokeMethod<String>('getFlavor');
  if (flavor == 'dev') {
    print('Flavor: dev');
    return AppFlavor.dev;
  } else if (flavor == 'staging') {
    print('Flavor: staging');
    return AppFlavor.stg;
  } else if (flavor == 'product') {
    print('Flavor: product');
    return AppFlavor.prod;
  }
}

Tiếp theo, sửa thêm 1 chút ở hàm main để print ra api url.

final appFlavor = await FlavorConfig().getFlavor();
print(appFlavor?.apiURL);

Run và xem kết quả thôi! (Mình build trên iOS nha).

MethodChannel

Tạm kết

  • Tìm hiểu về MethodChannel
  • Cách tương tác với các native code ở các nền tảng khác nhau
  • Cấu hình và sử dụng kết hợp với Flavor

Vậy là đã kết thúc những vấn đề liên quan đến cấu hình flavor hoàn chỉnh cho 1 project. Nếu các bạn thấy cách này quá quằn quại và phức tạp, thì có thể sử dụng dart define. Hãy đón chờ bài viết tiếp theo của mình về chủ đề này nhé!

Okay! Tới đây, mình xin kết thúc bài viết về MethodChannel trong Flutter . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bạn có thể checkout source code tại đây.
  • Nguồn tham khảo:
    • Writing custom platform-specific code | Flutter

    • Platform-Specific Code With Flutter Method Channel: Getting Started | Raywernderlich

    • Using Flutter flavors to separate the DEV and LIVE environment – Christian Weiss

  • Follow me more:

    • Facebook: https://www.facebook.com/van.may.750/
    • Email : lehongvan.develop@gmail.com
    • LinkedIn: https://www.linkedin.com/in/le-van-935231150/

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares93

Related Posts:

  • Make color App Flutter
    Make color App Flutter
  • Customize Run and Debug - Flutter
    Customize Run and Debug - Flutter
  • Luyện tập code hằng ngày bằng cách nào?
    Luyện tập code hằng ngày bằng cách nào?
  • Flavor & Câu chuyện config trong Flutter
    Flavor & Câu chuyện config trong Flutter
Tags: flavor, flutter
Written by Hồng Vân Lê

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • Customize Run and Debug - Flutter
    Customize Run and Debug - Flutter
  • Cài đặt Flutter SDK & Hello world
    Cài đặt Flutter SDK & Hello world
  • Flavor Configurations - Flutter
    Flavor Configurations - Flutter
  • Make color App Flutter
    Make color App Flutter
  • Dart Defines trong Flutter và sức mạnh của nó
    Dart Defines trong Flutter và sức mạnh của nó

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!