One Plugin,

Six Platforms

Majid Hajian

mhadaily

Majid Hajian

mhadaily

    if (Platform.isIOS) {
      //
    }
    if (Platform.isAndroid) {
      //
    }
    if (Platform.isLinux) {
      //
    }
    if (Platform.isMacOS) {
      //
    }
    if (Platform.isWindows) {
      //
    }
    if (kIsWeb) {
      //
    }

mhadaily

mhadaily

  • Kotlin
  • Swift
  • Java
  • Objective-C
  • C
  • C++

mhadaily

Agenda

  • Package vs Plugins
  • Platform Communication
  • Open Source efforts
  • Lessons learned
  • Community
  • Take aways

ME.dart

import 'package:flutter/material.dart';
MaterialApp(
   ThemeData(
        name: "Majid Hajian",
        location: "Oslo, Norway",
        description: '''
                Google Developer Expert
        	Passionate Software engineer, 
	        Community Leader, Author and international Speaker
         ''',
        main: "Head of DevRel at Invertase.io",
        homepage: "https://www.majidhajian.com",
        socials: {
          twitter: "https://www.twitter.com/mhadaily",
          github: "https://www.github.com/mhadaily"
        },
        author: {
          Pluralsight: "www.pluralsight.com/authors/majid-hajian",
          Apress: "Progressive Web App with Angular, Book",
          PacktPub: "PWA development",
          Udemy: "PWA development",
        }
        founder: "Softiware As (www.Softiware.com)"
        devDependencies: {
          tea: "Ginger", 
          mac: "10.14+",
        },
        community: {
          MobileEraConference: "Orginizer",
          FlutterVikings: "Orginizer", 
          FlutterDartOslo: "Orginizer",
          GDGOslo: "Co-Orginizer",
          DevFestNorway: "Orginizer",
          ...more
        }));

mhadaily

Find me on the internet by

Head of DevRel at Invertase

mhadaily

Platform channels

mhadaily

MethodChannel

EventChannel

WEB

Platform-specific code for the web generally uses JS interoperability or the dart:html library instead.

mhadaily

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
  static const platform = MethodChannel('plus.fluttercommunity.dev/battery');

  String _batteryLevel = 'Unknown battery level.';

  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }
 }

mhadaily

// MainActivity.kt
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
  private val CHANNEL = "plus.fluttercommunity.dev/battery"
  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      if (call.method == "getBatteryLevel") {
        val batteryLevel = getBatteryLevel()

        if (batteryLevel != -1) {
          result.success(batteryLevel)
        } else {
          result.error("UNAVAILABLE", "Battery level not available.", null)
        }
      } else {
        result.notImplemented()
      }
    }

  }
}

mhadaily

typedef flutter::MethodChannel<flutter::EncodableValue> FlMethodChannel;
typedef flutter::EventChannel<flutter::EncodableValue> FlEventChannel;

BatteryPlusWindowsPlugin::BatteryPlusWindowsPlugin(
    flutter::PluginRegistrarWindows *registrar) {
  auto methodChannel = std::make_unique<FlMethodChannel>(
      registrar->messenger(), "dev.fluttercommunity.plus/battery",
      &flutter::StandardMethodCodec::GetInstance());

  methodChannel->SetMethodCallHandler([this](const auto &call, auto result) {
    HandleMethodCall(call, std::move(result));
  });

  auto eventChannel = std::make_unique<FlEventChannel>(
      registrar->messenger(), "dev.fluttercommunity.plus/charging",
      &flutter::StandardMethodCodec::GetInstance());

  eventChannel->SetStreamHandler(
      std::make_unique<BatteryStatusStreamHandler>(registrar));
}

mhadaily

void BatteryPlusWindowsPlugin::HandleMethodCall(
    const FlMethodCall &method_call, std::unique_ptr<FlMethodResult> result) {
  if (method_call.method_name().compare("isInBatterySaveMode") == 0) {
    SystemBattery battery;
    int batteryStatus = battery.GetBatterySaveMode();
    if (batteryStatus == 0 || batteryStatus == 1) {
      bool isBatteryMode = batteryStatus == 1;
      result->Success(flutter::EncodableValue(isBatteryMode));
    } else {
      result->Error(std::to_string(battery.GetError()),
                    battery.GetErrorString());
    }
  } else if (method_call.method_name().compare("getBatteryLevel") == 0) {
    SystemBattery battery;
    int level = battery.GetLevel();
    if (level >= 0) {
      result->Success(flutter::EncodableValue(level));
    } else {
      result->Error(std::to_string(battery.GetError()),
                    battery.GetErrorString());
    }
  } else if (method_call.method_name().compare("getBatteryState") == 0) {
    SystemBattery battery;
    result->Success(flutter::EncodableValue(battery.GetStatusString()));
  } else {
    result->NotImplemented();
  }
}

mhadaily

https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html

https://docs.flutter.dev/development/platform-integration/platform-channels

mhadaily

Pigeon

  1. A code-gen tool for better type-safe communication
  2. Uses StandardMessageCodec
  3. Supports Android & iOS only at the moment
  4. Created by Flutter team

mhadaily

mhadaily

https://pub.dev/packages/pigeon

class Value {
  int? number;
}

@HostApi()
abstract class Api2Host {
  @async
  Value calculate(Value value);
}
// Objc
@protocol Api2Host
-(void)calculate:(nullable Value *)input 
      completion:(void(^)(Value *_Nullable, FlutterError *_Nullable))completion;
@end

// ----
// Java
public interface Result<T> {
   void success(T result);
}

/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
public interface Api2Host {
   void calculate(Value arg, Result<Value> result);
}
// ----
// C++

/** Generated class from Pigeon that represents a handler of messages from Flutter.*/
class Api2Host {
public:
    virtual void calculate(Value value, flutter::MessageReply<Value> result) = 0;
}

Ecosystem

mhadaily

mhadaily

  • Scalable
  • Clean
  • Maintainable
  • Solid
  • Extendable
  • Consistent

Federated

Plugins

mhadaily

name: battery_plus_example
description: Demonstrates how to use the battery_plus plugin.

environment:
  sdk: '>=2.12.0 <3.0.0'

dependencies:
  flutter:
    sdk: flutter
  battery_plus:

dev_dependencies:
  flutter_driver:
    sdk: flutter
  integration_test:
    sdk: flutter
  flutter_lints: ^1.0.4

flutter:
  uses-material-design: true

https://plus.fluttercommunity.dev/

mhadaily

https://firebase.flutter.dev/

mhadaily

mhadaily

Platform package(s)

Platform interface package

App-facing package

A federated plugin requires:

https://medium.com/flutter/modern-flutter-plugin-development-4c3ee015cf5a

mhadaily

flutter create --template=plugin --platforms=linux battery_plus_linux

--platforms=android, ios, web, linux, macos, windows.

flutter create --template=package battery_plus_platform_interface

mhadaily

// Pubspec.yaml

flutter:
  plugin:
    platforms:
      android:
        package: dev.fluttercommunity.battery_plus
        pluginClass: BatteryPlusPlugin
      ios:
        pluginClass: BatteryPlusPlugin
      macos:
        pluginClass: BatteryPlusPlugin
      web:
        pluginClass: BatteryPlusPlugin
        fileName: battery_plus_web.dart

mhadaily

// pubspec.yaml
flutter:
  plugin:
    platforms:
      android:
        package: dev.fluttercommunity.plus.battery
        pluginClass: BatteryPlusPlugin
      ios:
        pluginClass: FLTBatteryPlusPlugin
      linux:
        default_package: battery_plus_linux
      macos:
        default_package: battery_plus_macos
      web:
        default_package: battery_plus_web
      windows:
        default_package: battery_plus_windows

dependencies:
  flutter:
    sdk: flutter
  meta: ^1.7.0
  battery_plus_platform_interface: ^1.2.0
  battery_plus_linux: ^1.1.0
  battery_plus_macos: ^1.1.0
  battery_plus_web: ^1.1.0
  battery_plus_windows: ^1.1.0

mhadaily

// batter_plus_linux/pubspec.yaml 

flutter:
  plugin:
    implements: battery_plus
    platforms:
      linux:
        dartPluginClass: BatteryPlusLinux
        pluginClass: none

dependencies:
  flutter:
    sdk: flutter
  battery_plus_platform_interface: ^1.2.0
  dbus: ^0.6.6
  meta: ^1.7.0

mhadaily

// batter_plus_web/pubspec.yaml 

flutter:
  plugin:
    platforms:
      web:
        pluginClass: BatteryPlusPlugin
        fileName: battery_plus_web.dart

dependencies:
  battery_plus_platform_interface: ^1.2.0
  flutter_web_plugins:
    sdk: flutter
  flutter:
    sdk: flutter

mhadaily

import 'dart:async';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'method_channel_battery_plus.dart';
import 'src/enums.dart';

abstract class BatteryPlatform extends PlatformInterface {
  BatteryPlatform() : super(token: _token);

  static final Object _token = Object();

  static BatteryPlatform _instance = MethodChannelBattery();

  static BatteryPlatform get instance => _instance;

  static set instance(BatteryPlatform instance) {
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  Future<int> get batteryLevel {
    throw UnimplementedError('batteryLevel() has not been implemented.');
  }

  Future<bool> get isInBatterySaveMode {
    throw UnimplementedError('isInBatterySaveMode() has not been implemented.');
  }

  Future<BatteryState> get batteryState {
    throw UnimplementedError('batteryState() has not been implemented.');
  }

  Stream<BatteryState> get onBatteryStateChanged {
    throw UnimplementedError(
        'get onBatteryStateChanged has not been implemented.');
  }
}

mhadaily

class MethodChannelBattery extends BatteryPlatform {
  @visibleForTesting
  MethodChannel methodChannel =
      const MethodChannel('dev.fluttercommunity.plus/battery');

  @visibleForTesting
  EventChannel eventChannel =
      const EventChannel('dev.fluttercommunity.plus/charging');

  Stream<BatteryState>? _onBatteryStateChanged;

  @override
  Future<int> get batteryLevel => methodChannel
      .invokeMethod<int>('getBatteryLevel')
      .then<int>((dynamic result) => result);

  @override
  Future<bool> get isInBatterySaveMode => methodChannel
      .invokeMethod<bool>('isInBatterySaveMode')
      .then<bool>((dynamic result) => result);

  @override
  Future<BatteryState> get batteryState => methodChannel
      .invokeMethod<String>('getBatteryState')
      .then<BatteryState>((dynamic result) => parseBatteryState(result));

  @override
  Stream<BatteryState> get onBatteryStateChanged {
    _onBatteryStateChanged ??= eventChannel
        .receiveBroadcastStream()
        .map((dynamic event) => parseBatteryState(event));
    return _onBatteryStateChanged!;
  }
}

mhadaily

import 'dart:async';
import 'package:battery_plus_platform_interface/battery_plus_platform_interface.dart';
export 'package:battery_plus_platform_interface/battery_plus_platform_interface.dart'
    show BatteryState;

class Battery {
  factory Battery() {
    _singleton ??= Battery._();
    return _singleton!;
  }
  Battery._();
  static Battery? _singleton;
  static BatteryPlatform get _platform {
    return BatteryPlatform.instance;
  }
  Future<int> get batteryLevel {
    return _platform.batteryLevel;
  }
  Future<bool> get isInBatterySaveMode {
    return _platform.isInBatterySaveMode;
  }
  Future<BatteryState> get batteryState {
    return _platform.batteryState;
  }
  Stream<BatteryState> get onBatteryStateChanged {
    return _platform.onBatteryStateChanged;
  }
}

mhadaily

// battery_plus_web/lib/battery_plus_web.dart

import 'dart:async';
import 'dart:html' as html show window, BatteryManager, Navigator;
import 'package:battery_plus_platform_interface/battery_plus_platform_interface.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

class BatteryPlusPlugin extends BatteryPlatform {
  BatteryPlusPlugin(html.Navigator navigator)
      : _getBattery = navigator.getBattery;

  bool get isSupported => html.window.navigator.getBattery != null;

  late final Future<dynamic> Function() _getBattery;

  static void registerWith(Registrar registrar) {
    BatteryPlatform.instance = BatteryPlusPlugin(html.window.navigator);
  }

  @override
  Future<int> get batteryLevel async {
    if (isSupported) {
      //  level is a number representing the system's battery charge level scaled to a value between 0.0 and 1.0
      final batteryManager = await _getBattery() as html.BatteryManager;
      final level = batteryManager.level ?? 0;
      return level * 100 as int;
    }
    return 0;
  }
}

mhadaily

😅

Phew!

mhadaily

Local package development

mhadaily

Versioning & Releasing

mhadaily

Melos

melos.invertase.dev

mhadaily

Testing

mhadaily

Code Quality

mhadaily

Automate, Automate, Automate

mhadaily

Parnertship 

FlutterFire Desktop invertase.link/flutterfire-desktop

mhadaily

mhadaily

  • Transprency
  • Empowering Devs
  • Openness
  • Hacktoberfest
  • Power of Social Media
  • Face-2-face meetings
  • Incentives

Summary

mhadaily

  • Package vs Plugins
  • Platform Communication
  • Cross-platform development, New Era
  • Open Source efforts
  • Lessons learned
  • Community
  • Take aways

Majid Hajian

find me on internet 

mhadaily

Slides and link to source code

slides.com/mhadaily

One Plugin, Six Platforms

By Majid Hajian

One Plugin, Six Platforms

  • 105