前回、Fletのレイアウトがエラーもコンソール警告も出さずに崩れる、という話を書きました。
今回は画面遷移(ルーティング)を試したのですが、ここでも同じ「エラーが出ないのに動かない」パターンに出会いました。AIに「設定画面への遷移を追加して」と頼むと、page.go("/settings")を使ったコードが返ってきます。ところがFlet 1.0でこれを実行すると、そもそもpage.goというメソッド自体が存在しません。さらに厄介なことに、これを素直に直したつもりのコードでも、画面が真っ白になって固まることがありました。
この記事では、Fletの画面遷移まわりでFlet 1.0が変えた3つのポイントと、実際に踏んだ「エラーなしで真っ白になる」不具合の原因、正しい直し方までをまとめます。
この記事で分かること
- Fletで画面遷移を実装する基本(
page.views・ft.View・on_route_change) page.go()がFlet 1.0で完全に削除されている件ft.Viewの引数順序が変わり、エラーなく画面が真っ白になる罠- 初期表示を
page.navigate(page.route)任せにすると何も起きない理由 ft.TemplateRouteで動的ルート(/customers/:id)を扱う方法- 顧客一覧→詳細ページを例にしたBefore/Afterの直し方
Fletのpage/route入門:正しい画面遷移の書き方
Fletで複数の画面を切り替えるには、page.viewsというリストにft.Viewを積んで、URLの変化(page.route)に応じて中身を入れ替える、という仕組みを使います。まずは正しく動く最小構成から見てみます。
import flet as ft
from nav_common import setup_page
def main(page: ft.Page):
setup_page(page, "Route Basics")
def route_change():
page.views.clear()
page.views.append(
ft.View(
route="/",
controls=[
ft.Text("ホーム画面", size=22, weight=ft.FontWeight.BOLD),
ft.Text("page.route = " + page.route, size=13, color=ft.Colors.GREY_600),
ft.Container(height=16),
ft.Button(
"設定画面へ",
icon=ft.Icons.SETTINGS,
on_click=lambda e: page.navigate("/settings"),
),
],
padding=30,
)
)
if page.route == "/settings":
page.views.append(
ft.View(
route="/settings",
controls=[
ft.Text("設定画面", size=22, weight=ft.FontWeight.BOLD),
ft.Text("page.route = " + page.route, size=13, color=ft.Colors.GREY_600),
ft.Container(height=16),
ft.Button(
"ホームへ戻る",
icon=ft.Icons.ARROW_BACK,
on_click=lambda e: page.navigate("/"),
),
],
padding=30,
)
)
page.update()
page.on_route_change = route_change
route_change()
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8580)nav_commonのsetup_pageは、これまでの記事と同じく共通の初期設定をまとめた補助関数です。本記事のコードでは検証環境固有のフォント設定は省いており、手元の環境では日本語表示に問題は起きません。
import flet as ft
def setup_page(page: ft.Page, title="Flet Navigation Demo", bgcolor=None):
page.title = title
page.theme = ft.Theme(color_scheme_seed=ft.Colors.INDIGO)
page.bgcolor = bgcolor or ft.Colors.WHITEボタンを押すと、ホーム画面と設定画面が正しく切り替わります。
ft.View(route=..., controls=[...]): 1つの画面を表す部品。どのURL(route)でどの中身(controls)を出すかをセットで持つpage.on_route_change:page.routeが変化するたびに呼ばれる関数を登録するpage.navigate("/settings"): URLを変更し、on_route_changeを発火させて画面を切り替える
CustomTkinterやtkinterには無い、Webアプリらしい仕組みです。ここまでは素直に動くのですが、AIが出しがちなコードには、Flet 1.0で変わった3つのポイントが引っかかることがあります。
核心①:page.go()はFlet 1.0で完全に削除されている
旧バージョンのFletでは、画面遷移にpage.go("/settings")という書き方が定番でした。ネット上の情報にはこの書き方がまだ数多く残っているため、AIも当然のようにこの形で出してきます。
import flet as ft
from nav_common import setup_page
def main(page: ft.Page):
setup_page(page, "Nav Old Style (page.go)")
def route_change(e):
page.views.clear()
page.views.append(
ft.View(
"/",
[
ft.Text("ホーム画面"),
ft.Button("設定へ", on_click=lambda _: page.go("/settings")),
],
)
)
page.update()
page.on_route_change = route_change
page.go(page.route) # Flet 1.0には存在しないメソッド
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8581)Flet 1.0のPageクラスを調べると、hasattr(ft.Page, "go")はFalseです。実行すると、ブラウザ画面に赤い「Error running app」バナーでエラー内容がそのまま表示されます。
ここは親切な部類です。画面に直接エラー文が出るので、気づくのは難しくありません。後継のメソッドは、同期コールバック用のpage.navigate()と、async用のpage.push_route()です。この記事ではpage.navigate()を使います。
核心②:ft.Viewの引数順序が変わり、エラーなく中身がすり替わる
ここからが今回の核心です。ft.Viewのコンストラクタを実際に調べると、第1引数がcontrols、第2引数がrouteでした。以前のバージョンではrouteが先だったため、多くのチュートリアルやAIの出力に見られるft.View("/", [...])という書き方は、Flet 1.0ではそのまま使えません。
import flet as ft
from nav_common import setup_page
def main(page: ft.Page):
setup_page(page, "Nav View Arg Swap")
def route_change():
page.views.clear()
page.views.append(
ft.View(
"/", # 1番目の位置引数のつもりが、実際はcontrolsに入ってしまう
[
ft.Text("ホーム画面"),
ft.Button("設定へ", on_click=lambda e: page.navigate("/settings")),
],
)
)
page.update()
page.on_route_change = route_change
route_change()
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8582)型チェックも例外も発生しません。試しにPythonのインタラクティブシェルで中身を確認すると、routeとcontrolsがそっくり入れ替わっていることが分かります。
>>> v = ft.View("/", [ft.Text("hi")]) # 昔ながらの書き方
>>> v.route
[Text(value='hi', ...)] # controlsのリストが入っている
>>> v.controls
'/' # route文字列が入っているこのコードでアプリを起動すると、画面には何も表示されません(グレーの何もないキャンバスのまま止まります)。
ターミナルにもブラウザの開発者コンソールにも、エラーや警告は一切出ません。開発者コンソールを”error”・”route”・”view”のキーワードで検索しても、該当するメッセージは0件でした。
前回のレイアウト崩壊編で見つけた「エラーが出ないまま崩れる」というFletの特徴が、ルーティングでも同じ形で現れた形です。直し方は、位置引数ではなくキーワード引数で明示することです。
ft.View(
route="/",
controls=[...],
)route=とcontrols=さえ書いておけば、バージョンによる引数順の違いに影響されません。
核心③:初期表示をpage.navigate(page.route)任せにすると何も起きない
route=・controls=を正しく直しても、もう一つ引っかかるポイントがあります。旧APIでは、起動時にpage.go(page.route)を呼んで最初の画面を描画する書き方が定番でした。これを素直にpage.navigate(page.route)に置き換えても、期待通りには動きません。
import flet as ft
from nav_common import setup_page
def main(page: ft.Page):
setup_page(page, "Nav navigate() no-op")
def route_change(e=None):
page.views.clear()
page.views.append(
ft.View(
route="/",
controls=[
ft.Text("ホーム画面"),
ft.Button("設定へ", on_click=lambda e: page.navigate("/settings")),
],
)
)
page.update()
page.on_route_change = route_change
page.navigate(page.route) # 同じルートへのnavigateはon_route_changeを発火させない
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8583)
route_change関数の中にprintを仕込んで確かめたところ、この関数自体が一度も呼ばれていませんでした。page.routeはすでに"/"になっているため、page.navigate(page.route)は「ルートを変化させない呼び出し」になり、on_route_changeが発火しないというわけです。
Flet公式のpush_routeのdocstring内サンプルを確認すると、正しい書き方は「初期表示はルート変更用の関数を自分で直接1回呼ぶ」というものでした。1つ目のサンプルコード(route_basics)のように、page.on_route_change = route_changeの直後にroute_change()を素直に呼び出すだけで解決します。
AIへの丸投げで画面遷移を直す4ステップ
ここまでの内容を踏まえて、AIが生成した画面遷移のコードを手元で直していく一連の流れを4ステップのアニメーションにまとめました。
途中経過のうち、崩壊した瞬間と直った後の画面だけを静止画でも載せておきます。
ポイントは、AIが出したft.View("/", [...])のような位置引数のコードを見たら、まずroute=・controls=のキーワード引数に書き換えることです。プロンプトを工夫する場合は、「Flet 1.0の書き方で、ft.Viewはroute=とcontrols=を明示して」のように具体的に指定すると、この手の入れ替わりを避けやすくなります。
実践:顧客一覧→詳細ページをBefore/Afterで直す
最後に、ここまでの内容をまとめて、「顧客一覧から詳細ページへ遷移する」業務アプリ風の画面をBefore/Afterで直してみます。
Beforeは、AIが出しそうな旧来スタイルのコードです。page.go・ft.Viewの位置引数・ft.ElevatedButtonをそのまま使っています。
import flet as ft
from nav_common import setup_page
CUSTOMERS = {
"1": "株式会社アルファ",
"2": "ベータ商事",
"3": "ガンマ工業",
}
def main(page: ft.Page):
setup_page(page, "顧客管理(Before)")
def route_change(e):
page.views.clear()
page.views.append(
ft.View(
"/",
[
ft.Text("顧客一覧", size=20),
*[
ft.ElevatedButton(name, on_click=lambda _, cid=cid: page.go(f"/customers/{cid}"))
for cid, name in CUSTOMERS.items()
],
],
)
)
if page.route.startswith("/customers/"):
cid = page.route.split("/")[-1]
page.views.append(
ft.View(
page.route,
[
ft.Text(f"顧客詳細: {CUSTOMERS.get(cid, '不明')}", size=20),
ft.ElevatedButton("一覧へ戻る", on_click=lambda _: page.go("/")),
],
)
)
page.update()
page.on_route_change = route_change
page.go(page.route)
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8584)
Afterは、route=・controls=を明示し、page.navigateとft.TemplateRouteで動的な顧客詳細ページ(/customers/:id)まで組み直したものです。
import flet as ft
from nav_common import setup_page
CUSTOMERS = {
"1": "株式会社アルファ",
"2": "ベータ商事",
"3": "ガンマ工業",
}
def main(page: ft.Page):
setup_page(page, "顧客管理(After)")
def route_change(e=None):
page.views.clear()
page.views.append(
ft.View(
route="/",
controls=[
ft.Text("顧客一覧", size=20, weight=ft.FontWeight.BOLD),
ft.Container(height=8),
*[
ft.Button(
name,
icon=ft.Icons.PERSON,
on_click=lambda e, cid=cid: page.navigate(f"/customers/{cid}"),
)
for cid, name in CUSTOMERS.items()
],
],
padding=30,
)
)
troute = ft.TemplateRoute(page.route)
if troute.match("/customers/:id"):
name = CUSTOMERS.get(troute.id, "不明な顧客")
page.views.append(
ft.View(
route=page.route,
controls=[
ft.Text("顧客詳細", size=20, weight=ft.FontWeight.BOLD),
ft.Text(f"ID: {troute.id}", size=13, color=ft.Colors.GREY_600),
ft.Text(name, size=17),
ft.Container(height=16),
ft.Button(
"一覧へ戻る",
icon=ft.Icons.ARROW_BACK,
on_click=lambda e: page.navigate("/"),
),
],
padding=30,
)
)
page.update()
page.on_route_change = route_change
route_change()
ft.run(main, view=ft.AppView.WEB_BROWSER, port=8585)ft.TemplateRoute(page.route)に対して.match("/customers/:id")を呼ぶと、URLのパターンが一致するかを判定でき、一致した場合はtroute.idでパスの値(この場合は顧客ID)を取り出せます。
page.goをpage.navigateに、位置引数をroute=・controls=に置き換えるだけで、動的なURLを扱う画面遷移まで正しく動くようになります。
サンプルコードのダウンロード
この記事で紹介した7本のサンプルコード(route入門、page.goエラー再現、View引数入れ替わり、navigate no-op、顧客一覧/詳細のBefore/After、共通ヘルパー)は、まとめてダウンロードできます。
📦 サンプルコード一式をダウンロード(zip)よくある質問(FAQ)
Qpage.goとpage.navigateの違いは何ですか?
Apage.goはFlet 1.0より前の画面遷移メソッドで、1.0では完全に削除されています。後継は同期コールバック用のpage.navigate()と、async用のpage.push_route()です。通常のon_clickハンドラからはpage.navigate()を使います。
Qft.Viewはなぜキーワード引数で書いた方がいいのですか?
Aft.Viewの位置引数の順序はバージョンによって変わっており、Flet 1.0では(controls, route)の順です。route=・controls=を明示しておけば、この順序変更の影響を受けません。
Q画面が真っ白になったとき、何を確認すればいいですか?
Aまずはターミナルとブラウザの開発者コンソールを確認しますが、この記事で紹介したような不具合はエラーも警告も出ません。ft.Viewの引数がキーワード引数になっているか、on_route_changeが実際に呼ばれているか(printを仕込んで確認)を1つずつ疑うと原因にたどり着けます。
Q初期画面が表示されないときは何を疑えばいいですか?
Apage.navigate(page.route)のように、現在のルートと同じ値でnavigateしていないか確認してください。ルートが変化しない呼び出しはon_route_changeを発火させないため、on_route_changeに登録した関数を直接1回呼ぶ書き方に直す必要があります。
Q動的なURL(/customers/1のような)はどう扱えばいいですか?
Aft.TemplateRoute(page.route)を作り、.match("/customers/:id")のようにパターンを渡します。一致すればtroute.idでパスの中の値を取り出せます。
まとめ
この記事では、Fletの画面遷移でFlet 1.0が変えた3つのポイント(page.goの削除、ft.Viewの引数順序、初期表示のnavigateno-op)と、エラーなく画面が真っ白になる不具合の直し方を、動かした画面とあわせて紹介しました。
page.go()はFlet 1.0で完全に削除されている。後継はpage.navigate()(同期)とpage.push_route()(async)ft.Viewは位置引数の順序が変わっているため、route=・controls=のキーワード引数で書くpage.navigate(page.route)のような「変化しないnavigate」はon_route_changeを発火させない。初期表示はルート変更用の関数を直接呼ぶft.TemplateRouteを使うと、/customers/:idのような動的なURLからIDなどの値を取り出せる- Fletのレイアウト崩壊(前回)と同じく、画面遷移の不具合もエラーや警告が出ないまま起きる
Flet Series Navigation
Fletシリーズ(全4回):AIが壊すFletコードとその直し方
【保存版】Fletアイコン一覧&使い方|バージョンで名前が変わってAIが誤爆する理由
→ 2Fletのレイアウトが崩れてもエラーが出ない理由|Row/Column・expand・ResponsiveRowで直す方法
→Fletで画面遷移が動かない理由|page.go廃止とft.View引数順の罠、TemplateRouteの使い方
この記事【無料配布】Flet 1.0テンプレート集|基本構成+Excel取込対応の社内ツールひな形
→Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォロー


コメント