Dynamic Theme in a Flutter Desktop App

Picture by Vidar Nordli-Mathisen on Unsplash

Synopsis: Discover ways to set a WNDPROC callback perform from Flutter utilizing Platform Channels in a Plugin. The instance reveals learn how to dynamically change the Theme in your App.

Two months in the past Flutter 2.10 introduced steady Home windows help. Okay, this wasn’t information, Flutter compiled purposes for Home windows earlier than, why is it so vital now? The reply is an easy phrase: “steady”, trigger now Google declared it’s, we are able to resolve to speculate extra of our time and assets in creating Apps for Home windows utilizing this implausible instrument.

Because of the work of a number of enthusiastic folks, loads of packages on pub.dev help Home windows growth, and the record is rising. Nonetheless, as informed before, designing our shiny new Home windows App in Flutter typically there may very well be one thing that we miss.

For instance, we may need that our App ought to react to Home windows system-wide occasions, and alter its habits accordingly. Flutter doesn’t have a ready-made interface for doing this. To learn the way it may be made we are going to implement an Occasion Channel that may make these occasions out there in our Dart code.

In case you are already skilled in writing Plugins utilizing Platform Channels, leap to the following paragraph, if not, you possibly can learn the earlier story:

Suppose we wish our App to obtain an occasion each time that the consumer switches Darkish Mode on or off in Home windows.

Doing this in a Win32 App, utilizing C++ or different “native” languages, appears simple: we’ve got to register an handler to pay attention for Home windows WM_ occasions that our App receives:

HWND deal with = GetActiveWindow();
oldProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(deal with, GWLP_WNDPROC));
SetWindowLongPtr(deal with, GWLP_WNDPROC, (LONG_PTR)MyWndProc);

that is our new handler that shall be referred to as each time a Home windows WM_ message “arrives”:

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)

if (iMessage == WM_SETTINGCHANGE)

if (!lstrcmp(LPCTSTR(lParam), L"ImmersiveColorSet"))

ChangeOurAppTheme();


return oldProc(hWnd, iMessage, wParam, lParam);

When a WM_SETTINGCHANGE message is intercepted, if lParam factors to “ImmersiveColorSet”` Unicode String, the OS is signaling that one thing has modified within the present UI Theme.

Being in a Flutter App, issues are a bit of totally different. Let’s begin by cloning this Repository and opening the mission in our IDE.

As we all know, utilizing a Technique Channel in Flutter is like calling a distant API, however now we have to subscribe to a Stream. To do that, we’ll use Occasion Channels, let’s open lib/windows_dark_mode.dart in our most popular IDE:

Now we’re going to add the next code in WindowsDarkMode class:

Doing so, we’re:

  1. declaring _eventChannel as an EventChannel occasion. This shall be our “transport connection” from Dart to Platform code. Within the constructor, we move the channel title. Certainly, we’ve got structured this title as a “path” akin to our plugin title and a correct title, that identifies the suitable stream. I’ve used this conference to raised establish the channel and to keep away from the chance of collision with different packages, certainly you possibly can title a channel as you need.
  2. applied the static technique DarkModeStream() that “interprets“ the occasions from the platform. Inside, we name the strategy receiveBroadcastStream() that gives a Stream<dynamic>, the cascading technique map(), that maps the dynamic worth to ThemeMode, and distinct(), that debounces occasions in case two or extra consecutive identical values ought to arrive.

Let’s go at “BackEnd”, opening /home windows/windows_dark_mode_plugin.cpp :

WindowsDarkModePlugin already accommodates the code applied in my earlier tutorial. When requested it supplies the present standing of Darkish Mode.

In RegisterWithRegistrar present we’ve got solely the technique channel declaration, to obtain queries from Dart:

In identical technique, we’ll should implement the Platform occasion channel:

plugin->m_event_channel = std::make_unique<flutter::EventChannel<flutter::EncodableValue>> (
registrar->messenger (), "windows_dark_mode/dark_mode_callback",
&flutter::StandardMethodCodec::GetInstance ()
);

m_event_channel must be declared at school interface, non-public:

std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> m_event_channel;

And these embrace should be added:

#embrace <flutter/event_channel.h>
#embrace <mutex>

The occasion channel is prepared, however to ship occasions it wants a StreamHandler that can enqueue occasions from platform to engine, so we embrace in the identical namespace our new class:

Declare a variable in WindowsDarkModePlugin interface:

MyStreamHandler<> *m_handler;

Instantiate it, and assign to the occasion channel in RegisterWithRegistrar,

MyStreamHandler<> *_handler=new MyStreamHandler<> ();
plugin->m_handler = _handler;
auto _obj_stm_handle = static_cast<flutter::StreamHandler<flutter::EncodableValue>*> (plugin->m_handler);
std::unique_ptr<flutter::StreamHandler<flutter::EncodableValue>> _ptr _obj_stm_handle;
plugin->m_event_channel->SetStreamHandler (std::transfer (_ptr));

Now we’ve got to intercept WM_ messages. We are able to name SetWindowLongPtr to set our WNDPROC handler, proper?

However we received’t, as a result of additionally Flutter Engine registered his handler, and it may give us the chance to faucet within the message stream, so we’ll use RegisterTopLevelWindowProcDelegate , a technique offered platform facet by Flutter Engine. So we are going to exchange WindowsDarkModePlugin constructor advert destructor:

Now, we’ve got set a callback to hook WM_ messages, and unset it when the WindowsDarkMode’s occasion is disposed of.

Then we’ll have to change the WindowsDarkModePlugin class interface:

  • altering the constructor signature to simply accept the registrar parameter;
  • declaring int window_proc_id = -1; within the non-public part, this identifies the handler that we’re registering, it will likely be helpful for deregistration;
  • declaring flutter::PluginRegistrarWindows* registrar; within the non-public part, we want the registrar to unregister our hook when disposing of;
  • declaring std::non-obligatory<LRESULT> HandleWindowProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam); header in non-public part;

Then we’ll exchange plugin instantiation in RegisterWithRegistrar static technique, merely including registrar parameter:
auto plugin = std::make_unique<WindowsDarkModePlugin>(registrar);

We’ll implement HandleWindowProc within the class physique:

We’re calling on_callback technique on our stream handler, to ship the results of isDarkModeAppEnabled() The latter technique give us a bool worth, however we’re boxing it utilizing flutter::EncodableValue(), That is the strategy that we use to ship information from platform to Dart Aspect of our App, where our bool value will be unboxed.

Now we’ll attempt to compile the mission, simply to test if the syntax is nice thus far, if sure….

Now we are going to modify the principle physique in “instance/lib/essential.dart” wrapping our MaterialApp in a StreamBuilder, so our App theme will change after we’ll swap themes in Home windows:

Let’s hit Run:

I hope that this tutorial shall be helpful for you. Quickly I’ll proceed on this subject with different tutorials, displaying how can debug the C++code of a Plugin in Home windows, and the way we are able to get Home windows Theme coloration.

More Posts