Blog
ブログ

2023年01月17日

[Python]Fletによるクロスプラットフォームアプリ開発[Flutter]

新年あけましておめでとうございます。
2023年最初の社員ブログ投稿(予定)です。

さて、今回はFletを用いたクロスプラットフォームアプリケーション開発について紹介したいと思います。


Fletとは

FletはPythonでWeb, Mobile, Desktopのアプリケーションをフロントエンドの経験不要で作成できるフレームワークです。


主な特徴として、以下が挙げられます。

  • 見栄えのする社内ツールや忠実度の高いプロトタイプをすばやく作成可能
  • JavaScriptフロントエンドやREST APIバックエンド、データベースとキャッシュなど複雑なアーキテクチャを用いずSPAを作成
  • アセットホスティングとデスクトップクライアントのためのウェブサーバが組み込み済み
  • Flutterで構築されておりプロフェッショナルな見た目のアプリをクロスプラットフォームに配信可能

Fletは現在v0.3.2 (2023年1月16日現在) とメジャーバージョン0ではあるものの、意欲的なロードマップが公開されており、今後のアップデートにもかなり期待が持てます。
PythonによるGUIアプリ開発の選択肢として、Pythonの標準ライブラリであるTkinterや、以前弊社ブログにて紹介があったKivyなどがありますが、今回紹介するFletもFlutterベースのアプリ開発フレームワークとして有力な選択肢になるのではないかと思います。

開発環境の準備

Python3.7以降の開発環境があれば、fletモジュールをインストールするだけですぐに開発をはじめられます。
fletモジュールはPyPIに登録されており、pipを用いて簡単にインストール可能です。

pip install flet

基本的なアプリの構成

さっそく、最初のFletアプリを作ってみましょう。
基本的なFletアプリの構成は下記のようになっています。

import flet as ft


def main(page: ft.Page):
    pass

ft.app(target=main)

ft.app()の呼び出しで新たなユーザーセッションを待ち受けるようになります。
targetで指定したmain関数がアプリのエントリーポイントとなり、ユーザーセッションごとにPageと呼ばれるコンテナが作成されます。
FletではPageにControl(と呼ばれるウィジェット)を追加/削除したり、Controlのプロパティを更新することでアプリのUIを構築します。


上記のコードを実行すると、空白のPageを持つFletアプリがOSのネイティブウィンドウで起動します。


The first flet app


また、下記のようにviewにft.WEB_BROWSERを指定して実行すると、Fletアプリがウェブブラウザで起動します。

ft.app(target=main, view=ft.WEB_BROWSER)

Flet app in web browser


Controlの追加

FletアプリにおけるPageはControlと呼ばれるウィジェットのコンテナであると説明しましたが、具体的にはPageのプロパティであるcontrolsにControlを追加/削除することで表示するControlを制御しています。
controlsは表示されるControlのリストになっており、以下のようにして追加を行います。

page.controls.append(ft.Text('Hello'))
page.update()
または
page.add(ft.Text('Hello'))

ft.Text()は、その名の通りテキストを表示するためのControlで、上記の例では”Hello”と表示するControlになっています。
ここまでのコードと実行結果は以下のようになっているかと思います。

import flet as ft


def main(page: ft.Page):
    page.add(ft.Text("Hello"))


ft.app(target=main)
Hello Flet app

UserControl

Fletではft.Textの他にも、すぐに利用可能なControlが多数用意されています。
また、UserControlを用いることで既存のControlを組み合わせて独自のプロパティ、メソッドを持つControlを定義することも可能です。

FletにおけるControlはPythonのクラスで実装されており、buildメソッドにて表示するControlのインスタンスを返します。


独自のUserControlを定義する場合は、以下のようにUserControlを継承するクラスとして定義してbuildメソッドを実装します。

import flet as ft


class MyControl(ft.UserControl):
    def build(self):
        return ft.Card(
            content=ft.Container(
                content=ft.Row(
                    [
                        ft.CircleAvatar(content=ft.Text("UN"), bgcolor=ft.colors.DEEP_PURPLE_300),
                        ft.Column(
                            controls=[
                                ft.Text("UserName", style=ft.TextThemeStyle.TITLE_MEDIUM),
                                ft.Text("email@example.com", color=ft.colors.BLACK38),
                            ],
                            alignment=ft.MainAxisAlignment.SPACE_AROUND,
                        ),
                    ]
                ),
                width=400,
                padding=16,
            )
        )


def main(page: ft.Page):
    for _ in range(4):
        page.add(MyControl())


ft.app(target=main)



さらに、コンストラクタやライフサイクルフックのようなメソッドを追加することも可能です。
試しに、OpenCVを用いてキャプチャされたカメラの映像を描画するControlを作成してみたいと思います。

import base64
import threading
from time import sleep

import cv2
import flet as ft


class CameraCaptureControl(ft.UserControl):
    def __init__(self):
        super().__init__()
        self.capture = cv2.VideoCapture(0)
        self.latency = 1 / self.capture.get(cv2.CAP_PROP_FPS)

    def did_mount(self):
        self.running = True
        self.thread = threading.Thread(target=self.update_frame, args=(), daemon=True)
        self.thread.start()

    def will_unmount(self):
        self.running = False

    def update_frame(self):
        while self.capture.isOpened() and self.running:
            # TODO retvalのチェックとハンドリングを実装
            retval, frame = self.capture.read()
            retval, frame = cv2.imencode(".jpg", frame)
            data = base64.b64encode(frame)
            self.image_control.src_base64 = data.decode()
            self.update()
            sleep(self.latency)

    def build(self):
        self.image_control = ft.Image(
            width=self.capture.get(cv2.CAP_PROP_FRAME_WIDTH),
            height=self.capture.get(cv2.CAP_PROP_FRAME_HEIGHT),
            fit=ft.ImageFit.FIT_WIDTH,
        )
        return self.image_control



既存のControlであるImageのプロパティを、取得したフレームに更新した後、UserControlのupdateメソッドを呼び出すことで描画が更新されています。
作成したFletアプリはPyInstallerを用いてデスクトップアプリとしてビルドしたり、WebSocketに対応したホスティングサービスを用いてWebアプリとしてデプロイすることが可能です。

おわりに

今回は、Pythonのクロスプラットフォームアプリケーション開発フレームワークであるFletについて、概要をご紹介しました。
手早くきれいなGUIアプリを作成可能なFletは公式ドキュメントも読みやすく、開発環境の構築も容易だと感じました。

社内ツールやデモアプリの作成する際や、GUIアプリ作成に興味を抱いた方は、ぜひFletを試してみてください。


とりとめのない記事になってしまいましたが、お読みいただきありがとうございました。

このページの先頭へ