1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutter DeepLinkにおける画面遷移実装の比較:go_router と Navigator 2.0

1
Last updated at Posted at 2026-01-30

前書き

モバイルアプリにおいて「特定のページに直接遷移させる」DeepLinkは、使い方によってはユーザー体験を向上させる機能となり得ます。
Flutterにおいて、このDeepLinkを実装するにはgo_routerパッケージを用いるのが一般的ではありますが、既存プロジェクトの構成やプロジェクトの方針(純粋なFlutter APIに依存したいなど)により、標準の Navigator 2.0 (Router API) を使わなければならないケースもあります。

本記事では、両方の実装パターンを比較しながら、どのようにDeepLinkに対応させるかを解説します。

本記事で記載すること(対象となる人)
・これからDeepLinkを実装する上で、画面遷移処理の仕組みを知りたい人
・サードパーティ製のSDK(AdjustやAppsflyer等)を用いてDeepLinkを実現する場合には、ブラックボックス化しがちな部分でもあるので理解を深めたい人

本記事で記載しないこと
・DeepLinkそのものの実装方法

以下のようなページ構成のサンプルを記載します。
・HomeScreen:起点となるページ
・ProductScreen:指定した「id」の商品ページを表示する

1.go_routerを使った実装

go_routerは、Navigator 2.0 の構造をラップし、URLベースのルーティングを直感的に記述できるようにしたパッケージです。DeepLinkのパス解析も自動で行ってくれます。

go_router
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(const GoRouterApp());

// ルーティングの設定
final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        // ネストさせたパス: /product/:id
        GoRoute(
          path: 'product/:id',
          builder: (context, state) {
            final id = state.pathParameters['id'] ?? '0';
            return ProductScreen(id: id);
          },
        ),
      ],
    ),
  ],
);

class GoRouterApp extends StatelessWidget {
  const GoRouterApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
      title: 'Go Router Sample',
    );
  }
}

2.Navigator 2.0 (Router API)を使った実装

外部パッケージを使わず、Flutter標準の機能だけで実装する場合、URLとアプリの状態を同期させるための「中継役」を自作する必要があります。
これは「どのURL(文字列)」が「どの画面の状態」に対応するかを自分で定義する作業です。

実装の概要
RoutePath: アプリ内の「画面の状態」を表すクラス。
RouteInformationParser: URL文字列をRoutePathに変換する。
RouterDelegate: RoutePathに基づいてページスタックを構築する。

Navigator 2.0
import 'package:flutter/material.dart';

void main() {
  runApp(const NavigatorApp());
}

class AppRoutePath {
  final String? productId;

  AppRoutePath.home() : productId = null;
  AppRoutePath.product(this.productId);
  AppRoutePath.unknown() : productId = null;

  bool get isHomePage => productId == null;
  bool get isProductPage => productId != null;
}

class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> {
  @override
  Future<AppRoutePath> parseRouteInformation(RouteInformation routeInformation) async {
    final uri = routeInformation.uri;

    // パスが '/' の場合
    if (uri.pathSegments.isEmpty) {
      return AppRoutePath.home();
    }

    // パスが '/product/:id' の場合
    if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'product') {
      return AppRoutePath.product(uri.pathSegments[1]);
    }

    // それ以外
    return AppRoutePath.unknown();
  }

  @override
  RouteInformation? restoreRouteInformation(AppRoutePath configuration) {
    if (configuration.isHomePage) return RouteInformation(uri: Uri.parse('/'));
    if (configuration.isProductPage) {
      return RouteInformation(uri: Uri.parse('/product/${configuration.productId}'));
    }
    return null;
  }
}

class AppRouterDelegate extends RouterDelegate<AppRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {

  @override
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  String? _selectedProductId;

  @override
  AppRoutePath get currentConfiguration {
    return _selectedProductId == null
        ? AppRoutePath.home()
        : AppRoutePath.product(_selectedProductId);
  }

  @override
  Future<void> setNewRoutePath(AppRoutePath configuration) async {
    _selectedProductId = configuration.productId;
  }

  void _handleProductTapped(String id) {
    _selectedProductId = id;
    notifyListeners();
  }

  void _handleHomeTapped() {
    _selectedProductId = null;
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: const ValueKey('HomePage'),
          child: HomeScreen(onProductTap: _handleProductTapped),
        ),
        if (_selectedProductId != null)
          MaterialPage(
            key: ValueKey('ProductPage-$_selectedProductId'),
            child: ProductScreen(id: _selectedProductId!, onHomeTap: _handleHomeTapped),
          ),
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) return false;
        _selectedProductId = null;
        notifyListeners();
        return true;
      },
    );
  }
}

class NavigatorApp extends StatelessWidget {
  const NavigatorApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Navigator 2.0 DeepLink Sample',
      routeInformationParser: AppRouteInformationParser(),
      routerDelegate: AppRouterDelegate(),
    );
  }
}

3. go_router と Navigator 2.0 の差分

ページ遷移処理
go_routercontext.go('/product/1') と呼び出すだけで遷移が完結します。Navigator 2.0 は、「今の状態ならこのページのスタックを表示する」というページの状態の構築を自前で行う必要があります。

パス解析の自動化
go_router:id のような変数抽出を自動で行います。
Navigator 2.0 では Uri.pathSegments を自力で分解し、自分で型変換する必要があります。
また上述したコードのようにそのためのボイラープレートコードを記載する必要があります。

4. サードパーティSDK利用時のDeepLink実装のポイント

AdjustやAppsFlyerなどのSDKを用いてDeepLinkを実現する場合、多くはSDKのリスナーが「URL」をキャッチし、それをアプリ側に通知します。

SDKは、URLを受け取った後に「どの画面を開くか」を開発者に委ねます。
この時に多くのSDKでも go_router を推奨していますが、もう少し高度なルーティングを行いたい場合もあると思います。

この時に、Navigator 2.0 を使用し、構造を理解していれば、内部で独自処理を行うことができます。

また go_routerNavigator 2.0 の画面遷移処理を行う前に、何か処理を行いたい場合にも、SDKが実態としてどのようにアプリに「URL」を通知し、画面遷移を行うか理解することで、独自の実装を迷わずに入れることができるのではないかと思います。

終わりに

手軽に導入するならgo_router一択ですが、依存関係を最小限にしたいプロジェクトではNavigator 2.0 の知識が役に立つと思います。

また、サードパーティ製のSDK(AdjustやAppsflyer等)を用いてDeepLinkを実現する場合にも、ブラックボックス化しがちな動作の理解に役立つかと思います。

参考

Flutter でディープリンクを設定する
go_routerパッケージ

問い合わせ先

案件のご依頼・ご相談は、以下までご連絡ください。
info@lightcafe.co.jp

We are hiring!

ライトカフェはエンジニアを積極採用中です。

常にお客様に寄り添い課題を認識し、解決の手助けをさせていただく。業務SIerにとって当たり前の事ですが、わたしたちはそれを一番大切にしています。
この会社コンセプトに共感して頂ける方、世の中のWEBサービス開発に関わりたい方をお待ちしています。

#イツモトナリニライトカフェ

ライトカフェ採用ページはこちら

ライトカフェクリエイション採用ページはこちら

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?