flutter-setup-declarative-routing について
go_router を使って Flutter の宣言的ルーティングとディープリンクを実装する開発スキルです。ルーター設定、プラットフォーム URL 処理、状態を保つネストナビゲーションを扱います。
主な機能
- 01GoRouter と GoRoute の設定
- 02プラットフォームのディープリンク設定
- 03ネストしたナビゲーションシェルの作成
- 04ナビゲーション分岐の状態保持
- 05Android と iOS のリンク検証
活用シーン
- 01Flutter アプリのルーティング初期設定
- 02プラットフォーム URL から画面を開く
- 03状態を保つボトムナビゲーションの構築
- 04Android と iOS のディープリンクテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-setup-declarative-routingインストール後、Claude Code で/flutter-setup-declarative-routingを入力して読み込まれたか確認します。
詳細
ルーティングとディープリンクの実装
目次
コアコンセプト
Flutter での宣言的ルーティングには go_router パッケージを使用します。複雑なルーティングシナリオ、ディープリンク、ネストされたナビゲーションのための信頼性の高い API を提供します。
- GoRouter: アプリケーションのルートツリーを定義する中心的な構成オブジェクトです。
- GoRoute: URL パスを Flutter の画面にマッピングする標準的なルートです。
- ShellRoute / StatefulShellRoute: 子ルートを永続的な UI シェル(例:
BottomNavigationBar)でラップします。StatefulShellRouteは並列ナビゲーションブランチの状態を維持します。 - パス URL ストラテジー: Web URL からデフォルトの
#フラグメントを削除します。プラットフォーム間でクリーンなディープリンクを行うために不可欠です。
ワークフロー: アプリケーションとルーターの初期化
このワークフローに従って、go_router を使用した新しい Flutter アプリケーションをブートストラップし、ルートルーティングメカニズムを構成します。
タスクの進捗
- Flutter アプリケーションを作成します。
-
go_router依存関係を追加します。 - Web/ディープリンク用の URL ストラテジーを構成します。
-
GoRouter構成を実装します。 - ルーターを
MaterialApp.routerにバインドします。
1. アプリケーションの雛形を作成する
以下のコマンドを実行してアプリを作成し、必要なルーティングパッケージを追加します:
flutter create <app-name>
cd <app-name>
flutter pub add go_router
2. ルーターを構成する
トップレベルの GoRouter インスタンスを定義します。redirect パラメータを使用して認証または状態ベースのルーティングを処理します。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
void main() {
// Use path URL strategy to remove the '#' from web URLs
usePathUrlStrategy();
runApp(const MyApp());
}
final GoRouter _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!),
),
],
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
title: 'Routing App',
);
}
}
ワークフロー: プラットフォームのディープリンクの構成
特定の URL を傍受して Flutter アプリケーションにルーティングするようにネイティブプラットフォームを構成します。
タスクの進捗
- 対象プラットフォーム(iOS、Android、またはその両方)を決定します。
- Android 向けの条件付き構成を適用します(Manifest + Asset Links)。
- iOS 向けの条件付き構成を適用します(Plist + Entitlements + AASA)。
- バリデータを実行 -> エラーを確認 -> 修正します。
Android 向けに構成する場合:
AndroidManifest.xmlを変更する:.MainActivityの<activity>タグ内にインテントフィルターを追加します。
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="yourdomain.com" />
<data android:scheme="https" />
</intent-filter>
assetlinks.jsonをホストする: 以下の JSON をhttps://yourdomain.com/.well-known/assetlinks.jsonで提供します。
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
}
}]
iOS 向けに構成する場合:
Info.plistを変更する: Flutter のデフォルトのディープリンクハンドラーを有効にします。 注: サードパーティのディープリンクプラグイン(例:app_links)を使用する場合は、競合を防ぐためにこれをNOに設定します。
<key>FlutterDeepLinkingEnabled</key>
<true/>
Runner.entitlementsを変更する: 関連ドメインを追加します。
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:yourdomain.com</string>
</array>
apple-app-site-associationをホストする: 以下の JSON(.json拡張子なし)をhttps://yourdomain.com/.well-known/apple-app-site-associationで提供します。
{
"applinks": {
"apps": [],
"details": [{
"appIDs": ["TEAM_ID.com.yourcompany.yourapp"],
"paths": ["*"],
"components": [{"/": "/*"}]
}]
}
}
検証ループ
バリデータを実行 -> エラーを確認 -> 修正します。
- Android: ADB を使用してテストします。
adb shell 'am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourdomain.com/details/123"' com.yourcompany.yourapp - iOS: 起動済みのシミュレータで
xcrunを使用してテストします。xcrun simctl openurl booted https://yourdomain.com/details/123
ワークフロー: ネストされたナビゲーションの実装
StatefulShellRoute を使用して、子ルートの状態を維持する永続的な UI シェル(ボトムナビゲーションバーなど)を実装します。
タスクの進捗
-
GoRouter構成でStatefulShellRoute.indexedStackを定義します。 - 各ナビゲーションタブに対して
StatefulShellBranchインスタンスを作成します。 -
StatefulNavigationShellを使用してシェルウィジェットを実装します。
final GoRouter _router = GoRouter(
initialLocation: '/home',
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/home',
builder: (context, state) => const HomeScreen(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
),
],
),
],
);
例
高忠実度のシェルウィジェットの実装
ブランチの切り替えを処理するために StatefulNavigationShell を利用する UI シェルを実装します。
class ScaffoldWithNavBar extends StatelessWidget {
const ScaffoldWithNavBar({
required this.navigationShell,
super.key,
});
final StatefulNavigationShell navigationShell;
void _goBranch(int index) {
navigationShell.goBranch(
index,
// Support navigating to the initial location when tapping the active tab.
initialLocation: index == navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: NavigationBar(
selectedIndex: navigationShell.currentIndex,
onDestinationSelected: _goBranch,
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
}
}
プログラムによるナビゲーション
go_router が提供する context.go() および context.push() 拡張メソッドを使用します。
// Replaces the current route stack with the target route (Declarative)
context.go('/details/123');
// Pushes the target route onto the existing stack (Imperative)
context.push('/details/123');
// Navigates using a named route and path parameters
context.goNamed('details', pathParameters: {'id': '123'});
// Pops the current route
context.pop();