LoginSignup
16
20

More than 3 years have passed since last update.

FlutterでWindowsデスクトップアプリのネイティブ連携をする

Posted at

概要

掲題のとおりです。2019年5月7日のGoogle I/O にてFlutterのデスクトップアプリ対応が発表され、すでに開発はできるようになっているのですが、まだまだ全然資料がないのでここで環境構築しつつネイティブのAPIを呼び出すところまでを記載します。

動作環境

  • Windows 10
  • Flutter 1.19.0-2.0.pre.211
  • Dart 2.9.0
  • Java 11 (ただしJava8も使う)
  • Visual Studio 2019 Community
  • Android Studio 4.0

Flutterの開発環境インストール (通常版)

最初の部分はiOS / Android の構築と同じなので、ハマりポイント以外割愛します。
そこからという方は、以下がわかりやすかったです。

Windowsでflutterインストール

一点、ゼロから環境を作る際、Android SDK のライセンスが認識されないというのがありましたのでそこだけ解決法を。
※ Windows アプリを作るとしてもそのライセンス問題を解決しないとビルドできません。

[√] Flutter (Channel master, 1.19.0-2.0.pre.211, on Microsoft Windows [Version 10.0.18363.836], locale ja-JP)
// 以下で Android license status unknown. と出ちゃう場合
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.6.0)
[√] Android Studio (version 4.0)
[√] Connected device (2 available)

ライセンスを承認するには、単純に $ flutter doctor --android-licenses とやればいいのですが、環境が Java9 以上の場合、こんなエラーが出ちゃいます。

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)

これは、Androidの sdkmanager が Java8 に依存しちゃってるからっぽいです。
そんな時には Java8 をダウンロードしてきて、 環境変数の JAVA_HOME にJava8のルートを設定してあげることでうまくいきます。
sdkmanager.batJAVA_HOME を参照してるので、単純にPATHに入れても引き続き同じエラーがでます(ここで暫くハマった…)

Windows デスクトップアプリを作れるようにする

そんなわけで、ここからFlutterでWindowsデスクトップアプリを作れるようにします。
既にFlutterのプロジェクトを作成済で、Androidでは動作確認ができている&Visual Studioは入れてあるのを前提とします。

Visual Studio の必須コンポーネントのインストール

Flutter for Desktop (Windows) は、以下のコンポーネントに依存しています。
※ Visual Studio 2019の場合

  • C++ によるデスクトップ開発
    • MSBuild
    • MSVC v142 - VS 2019 C++ x64/x86 ビルドツール
    • Windows 10 SDK (最新)

それらを、Visual Studio インストーラから入れておきます。

image.png

image.png

なお、 MS Build は 個別のコンポーネント の箇所にあります。

Flutter の設定

あとは、こちらに書いてる通りにやればできます(多分)

Desktop support for Flutter
https://flutter.dev/desktop

$ flutter channel master
$ flutter upgrade
$ flutter config --enable-windows-desktop

その後、以下のように表示されたら設定はうまくできてます。

$ flutter devices
Windows  • Windows          • windows-x64   • Microsoft Windows [Version 10.0.18363.836]

作成したプロジェクトへの反映

あとは、作成したプロジェクトのルートディレクトリにて以下のコマンドを打つことでプロジェクトルート直下にWindowsフォルダなどが作成されます。

$ cd <プロジェクトルート>
$ flutter create .
# 色々ダウンロードされ、最後に flutter doctor の内容が表示されればOK

その後、 $ flutter run -d windows とすることでビルドされて最低限動くようになります。

image.png

ハマりポイント:Run時に「Error: クライアントは要求された特権を保有していません。 」と出る

コマンドプロンプトから実行した場合、「Error: クライアントは要求された特権を保有していません。 」と出ることがあります。
その場合、管理者権限でコマンドプロンプトを実行するか、Android Studio のターミナルから実行することで回避ができます。

ネイティブ連携する

ここからが本番です。
まだ Flutter for Desktop はできたばかりっぽいので、ほとんど公開されているプラグインがありません。なので、ネイティブの実行などをやりたかったりすると自作しないといけなかったりするかと思います。
なのでひとまず、Windowsの外部アプリを起動する というものを作ってみようかと思います。

ちなみに、以下のデスクトップ用のプラグインもありました。

Desktop Embedding for Flutter
https://github.com/google/flutter-desktop-embedding

Windows側の実装

結論を書くと、以下の感じで実装できます。

  • <ルートディレクトリ>/windows/Runner.sln を開く
  • ソリューションエクスプローラにある Runner/Source Files/Client Wrappperstandard_codec.cc を追加する
    ※ 以下がないとネイティブ連携を実装してもビルドが通りません(そしてなぜかデフォルトでは standard_codec.cc だけない…)。
    image.png
    standard_codec.cc<プロジェクトルート>/windows/flutter/ephemeral/cpp_client_wrapper にあります。
  • 以下のように実装する
とりあえずflutter_windows.cpp内
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>
// 省略

void FlutterWindow::OnCreate() {
   // 省略
   RegisterPlugins(flutter_controller_.get());
   // ここから
   auto reg = flutter_controller_->GetRegistrarForPlugin("<適当なかぶらない名前>");
   flutter::PluginRegistrarWindows* manager = flutter::PluginRegistrarManager::GetInstance()->GetRegistrar<flutter::PluginRegistrarWindows>(reg);
   auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
                     manager->messenger(), "チャンネル名", 
                     &flutter::StandardMethodCodec::GetInstance()
                  );

   // ネイティブ連携のハンドラーメソッドを登録する
   channel->SetMethodCallHandler([](const flutter::MethodCall<EncodableValue>& method_call,
                                    std::unique_ptr<flutter::MethodResult<EncodableValue>> result) {
      if (method_call.method_name().compare("launch") == 0) {
          std::string path;
          if (method_call.arguments() && method_call.arguments()->IsMap()) {
              const EncodableMap& arguments = method_call.arguments()->MapValue();
              auto path_it = arguments.find(EncodableValue("path"));
              if (path_it != arguments.end()) {
                  path = path_it->second.StringValue();
              }
          }
          if (path.empty()) {
              result->Error("argument_error", "No Path provided");
              return;
          }

          size_t size = path.length() + 1;
          std::wstring wpath;
          wpath.reserve(size);
          size_t outSize;
          mbstowcs_s(&outSize, &wpath[0], size, path.c_str(), size - 1);
          // 外部アプリの実行
          ShellExecute(0, 0, wpath.c_str(), 0, 0, SW_SHOWDEFAULT);

          result->Success();
       } else {
          result->NotImplemented();
       }
  });
   // 以下略
}

Flutter側の実装

こちらは、iOS / Android のものと変わりません。
以下のような感じでできます。

  void launch() async {
    const platform = const MethodChannel('<チャンネル名>');
    await platform.invokeMethod("launch", { "path" : "<起動させたいアプリやファイル>"});
  }
16
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
20