How to start developing Flutter for windows plug-ins using the Win32 API
Introduction.
Who are you?
Hi, I'm arasan01_dev (@arasan01_dev). I've been thinking about starting youtube lately, and when I was thinking about stuff, I created a plugin for Flutter for windows.
I thought I would have to prepare a lot of things when I started my activities, so I got the domain name arasan01.dev, which was just right because the domain name is required to register a publisher on the "pub.dev". I got the domain name from Google Domains, so the "pub.dev" authentication was fast because it was in Google. I'm pleased about that.
I've been away from Flutter for a few years now, having only tried it out for iOS and Android development, and came back when I heard that it could be used for Windows development. So my experience with Flutter development is almost zero. I usually develop iOS apps (Swift is good).
What did you make?
Flutter for windows, as is, cannot receive files by drag and drop. Of course, you can't export a file by dragging out a widget, etc.
This time, I decided to experiment with drag-and-drop, which I thought would be easy to implement, and it was enjoyable since it was my first time to use a plugin that spanned native Flutter.
It was my first time creating a plugin, even though it was simple, and it took me a lot of time to search for something that calls Flutter for windows natively. So, in this article, I will introduce creating a plugin as a beginner who knows nothing about Flutter.
The main subject of development
The environment surrounding development
Flutter for windows has been in the news recently, and I don't think many libraries use the platform features to the fullest. It's also more difficult to approach than iOS, Android, and Web.
As far as I can tell, it will be a plugin development using Windows Embedder, and the primary language is C++. However, many people take time to get used to C++, such as pointers, which are the enemy of beginners, move semantics, which is popular in Rust, and generics (templates, though not strictly speaking), which are also familiar in other languages. It depends on the person, but I think many parts are more troublesome to write than Dart.
The Win32 API has been in use for a long, long time. You can also see a web site on how to use it written in the old VB written more than 10 years ago. You can find the official specifications in the Microsoft documentation. Be aware that the notation is broken despite the history except for the English version of the site. If you are fluent in a language other than English, you may want to use Chrome's intra-site translation to view the English version of the site. I had a bad experience with the Japanese site.
I've been using Flutter for windows development for a while now, and it's starting to look like a lot of fun, but what do I need to know? I've been working on this for a few years now.
However, Flutter for windows, based on the Win32 API, is different. The concept has been the same for a long time, so the implementation written in Visual Basic and other languages is helpful. I had a look at the details of how the process works by browsing around the website and pulling down the official specifications.
My knowledge of the Win32 API has been understood from the above. Now, I would like to know to create the platform side of Flutter for windows; where can I find it?
As far as I've been able to find out, a full-text search of all the repositories for the code you're interested in on GitHub can be helpful, as people are doing similar things. Also, looking at the windows implementation of flutter/plugins on GitHub can be pretty helpful. Even if you can't write C++, you can get by reading the atmosphere and writing similar code, and even I, who can't write C++ very well, could get by copying it.
If it gets too hard, go back to the beginning and read Announcing flutter for windows. Here you will find the Flutter Architecture for Windows. Seeing the big picture can help you find a solution.
Announcing Flutter for WindowsLet's try it
So far, we have talked about where to look and how to think before starting development. Now let's begin the actual development.
The development method is the same as for iOS and Android. Just do flutter creates
as usual to create a template.
flutter create --template=plugin --platforms=windows <my-plugin>
We won't look at dart too much this time because it usually works if you write it, and we will mainly look at the plugin itself in windows/
.
The first thing to look at is the header file, which includes and is the basis of this plugin. Look at functions like <PluginName>RegisterWithRegister
.
This function is called from the main body generated_plugin_registrant.cc
by Flutter's auto-generated code. In other words, it is the entry point of the plugin, like an initializer.
There is a lot more in the header file, but this is the only point to look at. The extern "C"
and __declspec(dllexport|dllimport)
are added to the template for Flutter's convenience and the plugin's convenience. It's a pain in the ass to remove them, so it's best to leave them in.
Next, let's look at the main file. The skeleton uses MethodChannel to call functions between Dart <-> C++. For MethodChannel, etc., I think you should look at the official explanation there. There is also an EventChannel that is similar to MethodChannel. It's not quite right, but you can use MethodChannel as a Future and EventChannel as a Stream.
If you don't want to make a big plugin, you can implement everything in a class that inherits from flutter::Plugin
. This is how it looks like in the one I developed.
class DragAndDropWindowsPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
DragAndDropWindowsPlugin(flutter::BinaryMessenger* messenger);
virtual ~DragAndDropWindowsPlugin();
private:
std::optional<LRESULT> MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink_;
std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> event_channel_;
};
All functions and variables are concentrated in this class. All parts and variables are focused on this class because there is not enough to separate them at this scale. Now we will focus on the RegisterWithRegistrar
function.
We have to register this plugin with Flutter using the registrar
passed from the first call point. To write it, we need to materialize and hold this class. Therefore, we will look at the code step by step.
void DragAndDropWindowsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
DragAndDropWindowsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}
In this code, the RegistrarRef
passed from the function defined in the header is used to get the registrar
that will be used and register it as an argument. In GetRegistrar
, you get PluginRegistrarWindows
, a mediator for Windows events. Since a pointer to it is obtained, the pointer is placed in the argument.
Let's look at the implementation of the class RegisterWithRegistrar
.
// static
void DragAndDropWindowsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar) {
auto plugin = std::make_unique<DragAndDropWindowsPlugin>(registrar->messenger());
flutter::WindowProcDelegate delegate([plugin_pointer = plugin.get()](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return plugin_pointer->MessageHandler(hwnd, message, wparam, lparam);
});
registrar->RegisterTopLevelWindowProcDelegate(delegate);
registrar->AddPlugin(std::move(plugin));
}
The registrar
has a messenger
, which we will get. The messenger
is a way to exchange information between the app and the plugin. You can use it to create a Channel
. Materialize the class using make_unique
, which creates a unique pointer (unique_ptr
, a pointer that only one person can own), a kind of pointer. After the class is materialized, register it with the registrar
. Transfer the ownership to registrar
with AddPlugin(std::move(plugin))
.
One of the critical points here is the RegisterTopLevelWindowProcDelegate
. If you pass a function of a specific type to it, it will handle the window events. The critical part here is that the plugin
is a unique pointer, so it will be challenging to call your handling functions since it will be managed by the party to whom you transferred the ownership. We will define plugin
as a new variable using Generalized capture
in lambda expressions, introduced in C++14. This lambda capture method can simplify the handling of unique pointers. It seems to be used a lot in plugin development.
The registration process is now complete. Now we need to write the part that handles the events. It is relatively easy since the code similar to iOS and Android is expressed in C++.
DragAndDropWindowsPlugin::DragAndDropWindowsPlugin(flutter::BinaryMessenger* messenger) {
event_channel_ = std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
messenger,
kDragAndDropWindowsChannelName,
&flutter::StandardMethodCodec::GetInstance());
auto handler = std::make_unique<flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
[this](
const flutter::EncodableValue* arguments,
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>&& events) {
// onListen
event_sink_ = std::move(events);
return nullptr;
},
[this](const flutter::EncodableValue* arguments) {
// onCancel
event_sink_ = nullptr;
return nullptr;
}
);
event_channel_->SetStreamHandler(std::move(handler));
}
DragAndDropWindowsPlugin::~DragAndDropWindowsPlugin() {
event_channel_->SetStreamHandler(nullptr);
}
You have to create an EventChannel and define it to pass values to the app in EventSink if the channel is connected.
&flutter::StandardMethodCodec::GetInstance()
is a codec that interacts with the app, and there are several other codecs available. There are several different codecs provided that you can use. In most cases, you should select Standard and choose the one you are comfortable with, depending on your situation.
std::optional<LRESULT> DragAndDropWindowsPlugin::MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept {
std::optional<LRESULT> result;
switch (message) {
case WM_ACTIVATEAPP: {
DragAcceptFiles(window, true);
return 0;
}
case WM_DROPFILES: {
HDROP hdrop = reinterpret_cast<HDROP>(wparam);
UINT file_count = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
if (file_count == 0) {
return 0;
}
flutter::EncodableList files;
for (UINT i = 0; i < file_count; ++i) {
wchar_t filename[MAX_PATH];
if (DragQueryFileW(hdrop, i, filename, MAX_PATH)) {
int iBufferSize = ::WideCharToMultiByte(CP_UTF8, 0, filename, -1, NULL, 0, NULL, NULL);
char* cpBufUTF8 = new char[iBufferSize];
::WideCharToMultiByte(CP_UTF8, 0, filename, -1, cpBufUTF8, iBufferSize, NULL, NULL);
std::string s(cpBufUTF8, cpBufUTF8 + iBufferSize - 1);
delete[] cpBufUTF8;
flutter::EncodableValue file(std::move(s));
files.push_back(std::move(file));
}
}
DragFinish(hdrop);
if (event_sink_) event_sink_->Success(files);
return 0;
}
}
return std::nullopt;
}
This code will be the last one; it's just a standard Win32 API. Once the value you want to return is created, you can fill the EncodableList type with the weight and stream it to sink, where the Dart side will receive it.
The Dart side can receive the value in a Stream as usual.
const EventChannel _eventChannel = EventChannel('drag_and_drop_windows');
final Stream<List<String>> _dropEventStream = _eventChannel
.receiveBroadcastStream()
.map((event) => List<String>.from(event))
.asBroadcastStream();
Stream<List<String>> get dropEventStream => _dropEventStream;
Conclusion
I made this plugin because I wanted to introduce drag-and-drop functionality to an app I'm experimenting with in Flutter for windows. At first, I just changed windows/runner
in the app itself, but I thought it would be more convenient to make it a plugin, so I made it.
It was my first time developing a plugin for the Flutter for windows platform, but I'm glad I could publish it and write a technical article (this one) soon without getting into too much trouble.
I hope that Flutter for windows will become a de facto standard for development because I use Windows as my main machine most of the time, and I feel that Windows is convenient in some way.
publicity
As I mentioned initially, I plan to start a youtube channel for technical explanation, and I developed this plugin as a story. I'll be posting videos on Flutter for Windows, iOS, etc., so please subscribe to my channel. I'm writing this article to pressure myself to start a youtube channel.