Skip to content

CACHET Flutter Plugins and Packages

Thomas Nilsson edited this page Jan 31, 2021 · 2 revisions

The role of these packages and plugins is largely to allow the programmer to infer the user's state/context, using a high level of abstraction.

What is a package?

A Flutter project containing Dart source code exclusively. An example of this is the weather package which produces weather forecasts by querying a web server.

Generating a new package from template

flutter create --org dk.cachet --template=package hello_world_package

What is a plugin?

A Flutter project containing Dart source code as well as native iOS and Android code. An example of this is the Health plugin which queries the native APIs (Apple HealthKit and Google Fit) to read the user's health data.

For plugins, the sensor data readings are performed natively and then sent to the Dart source code, where the results are processed afterward. Processing can include streamlining the sensor data such that the results and formats are the same on both platforms.

Generating a new plugin from template

flutter create --org dk.cachet --template=plugin --platforms=android,ios -i swift -a java hello_world_plugin

Note, you can exchange kotlin for java if you want. I don't recommend using obj-c instead of swift, however.

Making a package/plugin

File structure of the source code

Make src directory inside the lib directory which contains all the source code. Inside lib should also be a dart file named after the plugin/package, ex health:

lib/
├── health.dart
├── src/
│   ├── data_types.dart
│   ├── functions.dart
│   ├── health_data_point.dart
│   ├── health_factory.dart
│   └── units.dart

This dart file is a library, which contains a reference to all the other files inside src. The content of the file should look like so:

/// Library declaration
library health;

/// Other packages to import
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;
import 'package:device_id/device_id.dart';

/// Files to include (use the part keyword)
part 'package:health/src/units.dart';
part 'package:health/src/data_types.dart';
part 'package:health/src/functions.dart';
part 'package:health/src/health_data_point.dart';
part 'package:health/src/health_factory.dart';

Each of the source code should have no imports, but should instead use the part of keyword, to declare that it belongs to the library. Any imports made in the library will be available in the source code files.

Example:

part of health;

/// Source code below
String _enumToString(enumItem) => enumItem.toString().split('.').last;

enum PlatformType { IOS, ANDROID }

Filling out the pubspec.yaml

You must provide a description, version and webpage. See the example below

name: health
description: Wrapper for the iOS HealthKit and Android GoogleFit services.
version: 2.0.2
homepage: https://github.com/cph-cachet/flutter-plugins

It is sufficient to link to the root of this repository since the issue tracker is the same for all plugins.

Filling out the CHANGELOG.md

State what has been done in the update you are publishing, example:

## 2.0.2
* Updated the API to take a list of types rather than a single type, when requesting health data.

Example application

You must provide an example to demonstrate how the package or plugin can be used. Often this is done by making a Flutter application which uses the plugin/package to produce some result. This example application is located in <package name>/example. For plugins, this application is auto generated, but for packages you must do this yourself. This can be done with:

flutter create example

Publishing

Run the following command in the root of the project:

flutter packages pub publish

On plugins

Debugging plugins

This is hard to do, the best way is usually to make a native app that uses the sensors to verify that your native source code works, and then copy-pasting this source code into your Flutter project.

Writing plugins - iOS

Use an editor such as vscode or the like, XCode won't give you syntax highlights. This is why you should make sure the source code works before copy pasting it into the Flutter project.

Writing plugins - Android

Open the <package name>/example/android project in Android studio and in the file explorer on the left, navigate to the directory named after the package, ex health, rather than the app directory. This will give you syntax highlighting when you edit the native source code.

Permissions

Permissions cannot be inherited from a package, this means that the programmer must specify permissions in their Flutter app, and you must specify this in the example app as well.

On Android, permissions are specified inside the AndroidManifest.xml file, found at android/app/src/main/AndroidManifest.xml.

On iOS, permissions are specified inside the Info.plist file, found at ios/Runner/Info.plist

Method channel

This channel is used for simple 'get' calls, ex 'get the battery level'. A single result is produced.

Example:

/// Set parameters
Map<String, int> interval = {'start': start, 'end': end};

/// Get result and parse it as a Map of <String, double>
Map usage = await _methodChannel.invokeMethod('getUsage', interval);

Event channel

This channel is used for listening to events, i.e. a stream is returned which produces one or more events, until canceled. Example:

static const EventChannel _noiseEventChannel = EventChannel(EVENT_CHANNEL_NAME);
Stream<List<double>> _stream;
StreamSubscription<List<dynamic>> _subscription;

Stream<List<double>> get audioStream {
    if (_stream == null) {
        _stream = _noiseEventChannel
            .receiveBroadcastStream()
            .map((buffer) => buffer as List<dynamic>)
            .map((list) => list.map((e) => double.parse('$e')).toList());
    }
    return _stream;
}