Skip to content

Repository files navigation

Device Info Plugin

Read this in other languages: English, TΓΌrkΓ§e

Dart Flutter Pigeon Melos Very Good Analysis

Educational Project β€” This project was developed as a practical reference to teach Flutter's Federated Plugin architecture, type-safe platform communication with Pigeon, and the proper use of the plugin_platform_interface package.


πŸ“– Table of Contents


🎯 About the Project

device_info_plugin is a simple Flutter plugin that queries device information (model name and operating system version). The main goal of this project is to provide an end-to-end example of the following concepts used in the real world:

Concept In This Project
Federated Plugin Architecture 4 separate packages in a monorepo (app-facing, platform_interface, ios, android)
Pigeon Type-safe host API bindings for iOS (Swift) and Android (Kotlin)
plugin_platform_interface Secure platform interface with token-based validation
Melos Workspace management, version control, and automatic code generation
Dart 3 Pattern Matching Use of switch expressions in the example app
Mockito + build_runner Unit tests across all layers

πŸ— Architecture Overview

This project strictly follows the Federated Plugin structure recommended by the Flutter team. It uses the exact same architecture as official plugins like url_launcher.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Flutter App                               β”‚
β”‚                    (example/lib/main.dart)                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ depends on
                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              device_info_plugin  (App-Facing Package)             β”‚
β”‚                                                                  β”‚
β”‚  DeviceInfoPlugin.getDeviceInfo()                                β”‚
β”‚       └─→ DeviceInfoPluginPlatform.instance.getDeviceInfo()      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ depends on
                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      device_info_plugin_platform_interface  (Common Contract)    β”‚
β”‚                                                                  β”‚
β”‚  abstract DeviceInfoPluginPlatform extends PlatformInterface     β”‚
β”‚  class DeviceInfo { deviceModel, osVersion }                     β”‚
β”‚  class DeviceInfoPluginMethodChannel (fallback)                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    implements   β”‚                         β”‚  implements
                 β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  device_info_plugin_ios    β”‚ β”‚  device_info_plugin_android       β”‚
β”‚                            β”‚ β”‚                                   β”‚
β”‚  Pigeon β†’ Swift (UIKit)    β”‚ β”‚  Pigeon β†’ Kotlin (android.os)     β”‚
β”‚  UIDevice.current.model    β”‚ β”‚  Build.MODEL                      β”‚
β”‚  UIDevice.current.version  β”‚ β”‚  Build.VERSION.RELEASE            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow

App β†’ DeviceInfoPlugin β†’ DeviceInfoPluginPlatform.instance
                              β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  (at runtime)     β”‚
                    β–Ό                   β–Ό
          iOS Plugin (Swift)    Android Plugin (Kotlin)
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚ Pigeon Host  β”‚      β”‚ Pigeon Host       β”‚
          β”‚ API call     β”‚      β”‚ API call          β”‚
          β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚                      β”‚
                 β–Ό                      β–Ό
          UIDevice API           android.os.Build

πŸ“¦ Package Structure

Package Role Version
device_info_plugin App-Facing β€” The public API directly used by the developer 0.0.6
device_info_plugin_platform_interface Platform Interface β€” The contract all platforms must obey 0.0.4
device_info_plugin_ios iOS Implementation β€” Native access via Pigeon + Swift 0.0.6
device_info_plugin_android Android Implementation β€” Native access via Pigeon + Kotlin 0.0.4

πŸ›  Technologies Used

Technology Purpose
Pigeon v27.1.0 Type-safe, code-generated message passing between Dart ↔ Native (Swift/Kotlin)
plugin_platform_interface v2.1.8 Token-based protection of the platform interface (PlatformInterface.verifyToken)
Melos v7.8.2 Monorepo workspace management, automatic versioning, and changelog
Mockito Mock-based unit testing
build_runner Code generation for Mockito mock classes
very_good_analysis v10.2.0 Strict lint rules
FVM Flutter version management

πŸš€ Installation

Requirements

  • Dart SDK ^3.11.5
  • Flutter SDK >=1.17.0
  • FVM (recommended)
  • Melos (workspace management)

Cloning and Initializing the Project

# Clone the repo
git clone https://github.com/Thixq/device_info_plugin.git
cd device_info_plugin

# Setup Flutter SDK with FVM (optional)
fvm install
fvm use

# Install dependencies
dart pub get

# Melos bootstrap (all packages + code generation)
dart run melos bootstrap

The melos bootstrap command automatically:

  1. Runs pub get in all packages
  2. Generates Mockito mocks using build_runner
  3. Generates Dart/Swift/Kotlin binding files using pigeon

πŸ’‘ Usage

Basic Usage

import 'package:device_info_plugin/device_info_plugin.dart';

final plugin = DeviceInfoPlugin();
final info = await plugin.getDeviceInfo();

print(info?.deviceModel); // "iPhone" or "Pixel 8"
print(info?.osVersion);   // "17.4" or "14"

Example App

The project contains a fully working example app:

cd device_info_plugin/example
flutter run

The example app manages loading/error/success states using Dart 3's pattern matching (switch expressions):

child: switch ((_isLoading, _error, _deviceInfo)) {
  (true, _, _) => const CircularProgressIndicator(),
  (_, final String error, _) => _ErrorContent(error: error, onRetry: _fetchDeviceInfo),
  (_, _, final DeviceInfo? info) => _DeviceInfoContent(deviceInfo: info, onRefresh: _fetchDeviceInfo),
},

🐦 Pigeon Integration

Pigeon provides type-safe communication between Dart and native platform code. Unlike the string-based approach of MethodChannel, it offers compile-time safety.

Pigeon Definition File (Android)

// device_info_plugin_android/pigeons/messages.dart

@ConfigurePigeon(
  PigeonOptions(
    dartOut: 'lib/src/messages.g.dart',
    kotlinOut: 'android/src/main/kotlin/.../Messages.g.kt',
    kotlinOptions: KotlinOptions(
      package: 'com.thixq.deviceinfo.device_info_plugin_android',
    ),
  ),
)
class DeviceInfoAndroid {
  String? deviceModel;
  String? osVersion;
}

@HostApi()
abstract class DeviceInfoHostApi {
  DeviceInfoAndroid getDeviceInfo();
}

Pigeon Definition File (iOS)

// device_info_plugin_ios/pigeons/messages.dart

@ConfigurePigeon(
  PigeonOptions(
    dartOut: 'lib/src/messages.g.dart',
    swiftOut: 'ios/Classes/Messages.g.swift',
  ),
)
class DeviceInfoIOS {
  String? deviceModel;
  String? osVersion;
}

@HostApi()
abstract class DeviceInfoHostApi {
  DeviceInfoIOS getDeviceInfo();
}

Native Implementations

πŸ€– Android (Kotlin)
class DeviceInfoPluginAndroidPlugin : FlutterPlugin, DeviceInfoHostApi {

    override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        DeviceInfoHostApi.setUp(flutterPluginBinding.binaryMessenger, this)
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        DeviceInfoHostApi.setUp(binding.binaryMessenger, null)
    }

    override fun getDeviceInfo(): DeviceInfoAndroid {
        return DeviceInfoAndroid(
            deviceModel = Build.MODEL,
            osVersion = Build.VERSION.RELEASE
        )
    }
}
🍎 iOS (Swift)
public class DeviceInfoPluginIosPlugin: NSObject, FlutterPlugin, DeviceInfoHostApi {
    public static func register(with registrar: FlutterPluginRegistrar) {
        let instance = DeviceInfoPluginIosPlugin()
        DeviceInfoHostApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance)
    }

    func getDeviceInfo() throws -> DeviceInfoIOS {
        let deviceModel = UIDevice.current.model
        let osVersion = UIDevice.current.systemVersion
        return DeviceInfoIOS(deviceModel: deviceModel, osVersion: osVersion)
    }
}

Regenerating Pigeon Code

# For a single package
cd device_info_plugin_ios
dart run pigeon --input pigeons/messages.dart

# or for all packages with Melos (bootstrap hook)
dart run melos bootstrap

πŸ”Œ Platform Interface Structure

The PlatformInterface class provided by the plugin_platform_interface package ensures that platform implementations can be swapped securely.

Why use PlatformInterface?

  • Token validation: Prevents fake implementations made using implements instead of extends.
  • Default fallback: Provides a MethodChannel based default via DeviceInfoPluginMethodChannel.
  • Single instance management: Manages the platform implementation globally with the Singleton pattern.
abstract class DeviceInfoPluginPlatform extends PlatformInterface {
  DeviceInfoPluginPlatform() : super(token: _token);

  static final Object _token = Object();

  // Default: MethodChannel implementation
  static DeviceInfoPluginPlatform _instance = DeviceInfoPluginMethodChannel();

  static DeviceInfoPluginPlatform get instance => _instance;

  static set instance(DeviceInfoPluginPlatform instance) {
    // Token validation β€” security layer
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  Future<DeviceInfo?> getDeviceInfo() {
    throw UnimplementedError('getDeviceInfo() is not implemented.');
  }
}

Platform Registration Mechanism

Every platform implementation registers itself with the static registerWith() method. Flutter's default_package / dartPluginClass mechanism calls this method automatically:

# device_info_plugin/pubspec.yaml
flutter:
  plugin:
    platforms:
      ios:
        default_package: device_info_plugin_ios
      android:
        default_package: device_info_plugin_android
// device_info_plugin_ios/lib/device_info_plugin_ios.dart
static void registerWith() {
  DeviceInfoPluginPlatform.instance = DeviceInfoPluginIosPlugin();
}

πŸ§ͺ Testing

Each package contains its own unit tests. Tests use mock objects with Mockito and platform interface validation with MockPlatformInterfaceMixin.

Testing Strategy

Package What is Tested Tools
device_info_plugin App-facing API delegating to the platform interface Mockito + MockPlatformInterfaceMixin
device_info_plugin_platform_interface Default instance, MethodChannel fallback, token validation Mockito + setMockMethodCallHandler
device_info_plugin_ios Pigeon HostApi call and response mapping Mockito (DeviceInfoHostApi mock)
device_info_plugin_android Pigeon HostApi call and response mapping Mockito (DeviceInfoHostApi mock)

Running Tests

# Tests for all packages
dart run melos exec -- flutter test

# A specific package
cd device_info_plugin
flutter test

# A specific test file
flutter test test/device_info_plugin_test.dart

πŸ—‚ Project Structure

device_info_plugin/                     # πŸ“ Monorepo root
β”œβ”€β”€ pubspec.yaml                        # Workspace definition + Melos configuration
β”œβ”€β”€ CHANGELOG.md                        # Auto-generated changelog (Melos)
β”‚
β”œβ”€β”€ device_info_plugin/                 # πŸ“¦ App-Facing Package
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ device_info_plugin.dart     #   Public barrel export
β”‚   β”‚   └── src/
β”‚   β”‚       └── device_info_plugin_base.dart  # DeviceInfoPlugin class
β”‚   β”œβ”€β”€ test/
β”‚   β”‚   └── device_info_plugin_test.dart
β”‚   └── example/                        # πŸ“± Example Flutter application
β”‚       └── lib/main.dart
β”‚
β”œβ”€β”€ device_info_plugin_platform_interface/  # πŸ“¦ Platform Interface
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ device_info_plugin_platform_interface.dart  # Barrel export
β”‚   β”‚   β”œβ”€β”€ device_info_plugin_method_channel.dart      # MethodChannel fallback
β”‚   β”‚   └── src/
β”‚   β”‚       β”œβ”€β”€ device_info_model.dart                  # DeviceInfo data class
β”‚   β”‚       └── device_info_plugin_platform.dart        # Abstract platform class
β”‚   └── test/
β”‚       └── device_info_plugin_platform_interface_test.dart
β”‚
β”œβ”€β”€ device_info_plugin_ios/             # πŸ“¦ iOS Implementation
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ device_info_plugin_ios.dart #   Dart side (Pigeon API call)
β”‚   β”‚   └── src/
β”‚   β”‚       └── messages.g.dart         #   πŸ€– Pigeon-generated Dart bindings
β”‚   β”œβ”€β”€ pigeons/
β”‚   β”‚   └── messages.dart               #   Pigeon definition file
β”‚   β”œβ”€β”€ ios/Classes/
β”‚   β”‚   β”œβ”€β”€ DeviceInfoPluginIosPlugin.swift   # Native Swift implementation
β”‚   β”‚   └── Messages.g.swift                  # πŸ€– Pigeon-generated Swift bindings
β”‚   └── test/
β”‚       └── device_info_plugin_ios_test.dart
β”‚
└── device_info_plugin_android/         # πŸ“¦ Android Implementation
    β”œβ”€β”€ lib/
    β”‚   β”œβ”€β”€ device_info_plugin_android.dart  # Dart side (Pigeon API call)
    β”‚   └── src/
    β”‚       └── messages.g.dart              # πŸ€– Pigeon-generated Dart bindings
    β”œβ”€β”€ pigeons/
    β”‚   └── messages.dart                    # Pigeon definition file
    β”œβ”€β”€ android/src/main/kotlin/.../
    β”‚   β”œβ”€β”€ DeviceInfoPluginAndroidPlugin.kt # Native Kotlin implementation
    β”‚   └── Messages.g.kt                   # πŸ€– Pigeon-generated Kotlin bindings
    └── test/
        └── device_info_plugin_android_test.dart

βš™οΈ Development

Melos Commands

# Workspace bootstrap (pub get + code generation)
dart run melos bootstrap

# Analysis across all packages
dart run melos exec -- dart analyze

# Testing across all packages
dart run melos exec -- flutter test

# Version bumping and changelog generation
dart run melos version

Adding a New Platform

  1. Create a device_info_plugin_<platform>/ directory
  2. Write the Pigeon definition in pigeons/messages.dart
  3. Create a class extending DeviceInfoPluginPlatform
  4. Add the registerWith() static method
  5. Add as default_package in the main device_info_plugin/pubspec.yaml
  6. Add to the workspace: list in the root pubspec.yaml

πŸ“š Learning Resources

You can utilize the following resources while examining this project:


πŸ“„ License

This project was developed for educational purposes.


Developed by Thixq as an educational reference for Flutter Federated Plugin Architecture.

About

An educational Flutter plugin demonstrating the Federated Plugin architecture, type-safe platform communication with Pigeon, and the plugin_platform_interface package.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages