This document helps you understand how to get started with developing Flutter plugins for Tizen devices. This document assumes you already have basic understanding of how plugins are different from general Dart packages and how platform channels work in Flutter.
Here are a few things you might consider when developing Flutter plugins for Tizen (and other platforms).
- C++ or C# (using platform channels)
- Dart (using Dart FFI)
Typical Flutter plugins are written in their platform native languages, such as Java on Android and C++ on Tizen. However, some Windows plugins such as path_provider_windows
and Tizen plugins such as path_provider_tizen
are written in pure Dart using Dart FFI without any native code.
This document only covers native Tizen plugins written in C++. To learn more about FFI-based plugins on other platforms, you might read Flutter Docs: Binding to native Android code using dart:ffi. If you want to learn how to write a native plugin in C#, you might read Writing a plugin in C#.
A Flutter plugin may support more than one platforms. For example, each of the Flutter 1st-party plugins developed by the Flutter team supports at least two (Android, iOS) or more platforms (web, macOS, Windows, Linux), based on the team's priority and the availability of the functionality on the platforms. Federated plugins are a way of splitting support for different platforms into separate packages. A federated plugin consists of an app-facing package, a platform interface package, and platform package(s) for each platform.
On the other hand, it is also possible for a plugin to support only a single particular platform, e.g. flutter_plugin_android_lifecycle
for Android and wearable_rotary
for Tizen. In this case, there's no need to create a platform interface package. Instead, you can put everything into a single package.
Adding a new platform support to an existing plugin is simple: create a platform package that implements the platform interface of the plugin. It is technically not required for the original plugin author to endorse your new platform implementation. A developer can use the unendorsed platform implementation in their app, but must specify both package dependencies in the app's pubspec file. For example, if there is a foobar_tizen
implementation for the foobar
plugin, the app's pubspec file must include both the foobar
and foobar_tizen
dependencies, unless the original foobar
author adds foobar_tizen
as a dependency in the pubspec of foobar
.
Note: Even if the original plugin is not a federated plugin (has no platform interface package), it is possible to create an unendorsed platform implementation of the plugin by implicitly implementing the plugin's platform channels.
You can also create a new plugin from scratch, if the functionality you want to implement is not implemented by any other plugin, or is specific to Tizen. The new plugin can be either a single package plugin or a federated plugin, depending on whether you want to target the Tizen platform only or other platforms as well.
If you're to extend an existing plugin for Tizen, it is common to add the _tizen
suffix to the package name:
flutter-tizen create --platforms tizen --template plugin foobar_tizen
Otherwise, follow the lowercase_with_underscores
style convention to name your package:
flutter-tizen create --platforms tizen --template plugin plugin_name
Once the package is created, you will be prompted to add some information to its pubspec file. Open the main plugin_name/
directory in VS Code, locate the pubspec.yaml
file, and replace the some_platform:
map with tizen:
as suggested by the tool. This information is needed by the flutter-tizen tool to find and register the plugin when building an app that depends on the plugin.
You've created a plugin project that doesn't yet support any platforms.
...
Make sure your plugin_name/pubspec.yaml contains the following lines.
flutter:
plugin:
platforms:
tizen:
pluginClass: PluginNamePlugin
fileName: plugin_name_plugin.h
The created package contains an example app in the example/
directory. You can run it using the flutter-tizen run
command:
$ cd plugin_name/example
$ flutter-tizen run
The API of the plugin package is defined in Dart code. Open the lib/plugin_name.dart
file in VS Code, and then you will see the getPlatformVersion
method defined in the plugin's main class. Invoking this method will eventually invoke the method channel method getPlatformVersion
defined in the plugin_name_method_channel.dart
file. You have to replace this template code with the actual implementation for your plugin.
Note: You don't need these Dart files if you're extending an existing plugin for Tizen, so you can safely remove them.
Note: Before getting started, you may apply recommended settings for plugin developers in VS Code.
The implementation of the plugin can be found in the tizen/src/plugin_name_plugin.cc
file. In this file, you will see:
PluginNamePluginRegisterWithRegistrar()
: This function is called by an app that depends on this plugin on startup to set up theplugin_name
channel.HandleMethodCall()
: This method handles thegetPlatformVersion
method (or whatever methods you defined in Dart code) and returns the result to the caller.
The result of the method call can be either:
Success()
: Indicates that the call completed successfully. The argument can be either empty or of theflutter::EncodableValue
type.Error()
: Indicates that the call was understood but handling failed in some way. The error can be caught as aPlatformException
by the caller.NotImplemented()
Arguments to the method call can be retrieved from the method_call
variable. For example, if a map<String, dynamic>
is passed from Dart code like:
await _channel.invokeMethod<void>(
'create',
<String, dynamic>{'cameraName': name},
);
then it can be parsed as a flutter::EncodableMap
in C++ code as follows:
template <typename T>
static bool GetValueFromEncodableMap(const flutter::EncodableMap *map,
const char *key, T &out) {
auto iter = map->find(flutter::EncodableValue(key));
if (iter != map->end() && !iter->second.IsNull()) {
if (auto *value = std::get_if<T>(&iter->second)) {
out = *value;
return true;
}
}
return false;
}
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
const auto &method_name = method_call.method_name();
if (method_name == "create") {
const auto *arguments =
std::get_if<flutter::EncodableMap>(method_call.arguments());
if (!arguments) {
result->Error("Invalid arguments", "Invalid argument type.");
return;
}
std::string camera_name;
if (!GetValueFromEncodableMap(arguments, "cameraName", camera_name)) {
result->Error("Invalid arguments", "No cameraName provided.");
return;
}
// The implementation goes here.
...
result->Success();
}
}
Make sure either Success
, Error
, or NotImplemented
is called on result
after the method call is handled (otherwise, the Future
returned by MethodChannel.invokeMethod
will remain uncompleted forever). Note that the result can be passed either synchronously or asynchronously (e.g. from a callback) as long as the ownership of result
is retained properly.
The standard platform channels use a standard message codec that supports efficient binary serialization of simple JSON-like values, such as booleans, numbers, Strings, byte buffers, and Lists and Maps of these (see StandardMessageCodec
for details). The following table shows how Dart types are mapped with flutter::EncodableValue
variant types on the platform side.
Dart | C++ |
---|---|
null |
std::monostate |
bool |
bool |
int |
int32_t or int64_t |
double |
double |
String |
std::string |
Uint8List |
std::vector<uint8_t> |
Int32List |
std::vector<int32_t> |
Int64List |
std::vector<int64_t> |
Float64List |
std::vector<double> |
List |
flutter::EncodableList |
Map |
flutter::EncodableMap |
Types such as flutter::MethodCall
and flutter::EncodableValue
in the template code are defined in cpp_client_wrapper headers. APIs that you can use in your plugin code include:
- C++17 standards
- cpp_client_wrapper
- Tizen native APIs
- External native libraries, if any (static/shared)
Note: The API references for Tizen TV are not publicly available. However, most of the Tizen core APIs are common to both wearable and TV profiles, so you may refer to the wearable API references when developing plugins for TV devices.
Besides the above mentioned MethodChannel, you can also use other types of platform channels to transfer data between Dart and native code:
- BasicMessageChannel: For basic asynchronous message passing.
- EventChannel: For asynchronous event streaming.
If any privileges are required to run your plugin code, you have to list them in the plugin's README so that app developers can properly add them to their tizen-manifest.xml
.
If one or more privileges are privacy-related privileges, permissions must be granted by user at runtime. To request permissions at runtime, use the Privacy Privilege Manager API (example).
On TV devices, permissions are already granted to apps by default. Invoking permission-related APIs will result in a library loading error on TV devices. If you want to run your plugin on different types of devices using a single codebase, consider using the TV_PROFILE
macro to separate the TV-specific code (example).
To share your plugin with other developers, you can publish it on pub.dev. You may refer to the following pages for detailed instructions.
Note: You can use the flutter-tizen pub
command instead of flutter pub
if flutter
is not in your PATH.