🤗

auto_routeで、次のページに値を渡す

2024/06/10に公開

対象者

  • auto_routeを使ったことがある
  • auto_routeに興味がある
  • タイプセーフに扱えるらしいのを知りたい

https://pub.dev/packages/auto_route

プロジェクトの説明

go_routerだと、タイプセーフにデータを扱えないと聞く?
何それ...

「タイプセーフ」または「型安全」とは、プログラミング言語の特性の一つで、その言語が型エラー(データ型が期待するものと異なるために発生するエラー)を防止する仕組みを持っていることを指します。

次のページに値を渡したいユースケースで意識するようになった。通常は、Stringしか渡せないらしい?
過去に書いた記事で紹介しているのですが、state.extraを使う必要がありました😱
go_router_builder使えばタイプセーフに扱えるそうだが...

https://zenn.dev/joo_hashi/articles/5bf8d16df7f187

勉強会でも聞いたことあるが、auto_routeだとタイプセーフに扱うことができるらしい?
今回だと、 Bookクラスをコンストラクタ引数に渡すのですが、Book型だから、そのまま渡すだけだが、go_routerだと簡単にはいかない💦

go_routerの場合

ルートを定義しますよね。そして、次の画面に値渡すけどお決まりのコードを書かないと型のエラーが出てくる?
ルートの定義しているファイルで、パスを設定してextraasで型変換を行う必要がありました。最近これが面倒臭いと感じるようになってきた💦

// goRouterProviderは、GoRouterを提供するProviderです。
final goRouterProvider = Provider<GoRouter>((ref) {
  return GoRouter(
    routes: [
      GoRoute(
        path: Routes.home, // pathは、GoRouterで使用するパスを定義します。
        builder: (context, state) {
          return const HomePage();
        },
      ),
      GoRoute(
        path: Routes.item,
        builder: (context, state) {
          return const ListItemPage();
        },
      ),
      // 次のページに遷移する際に、extraでItemクラスのnameプロパティを渡しています。
      GoRoute(
        path: Routes.detail,
        builder: (context, state) {
          final item = state.extra as Item?;
          // nullチェックを行います。
          if (item != null) {
            // extraで渡されたItemクラスのnameプロパティを表示するぺージに遷移します。
            return DetailPage(item: item);
          } else {
            // エラーが発生した場合は、SomeErrorPageに遷移します。
            return const SomeErrorPage(); // 適切なエラーページに差し替えてください
          }
        },
      ),
    ],
  );
});

UI側のコードはコンスタラクタ引数に、extra: itemList[index]で渡してあげる必要があります。Navigator1.0(Navigator.push)はこれを意識しなく良い。最近これを書くのが辛くなってきた笑

Expanded(
      child: ListView.builder(
        itemCount: itemList.length,
        itemBuilder: (context, index) {
          return ListTile(
            onTap: () {
              // pushの中に、extraとしてitemList[index]を渡す
              context.push(Routes.detail, extra: itemList[index]);
            },
            title: Text(itemList[index].name),
            trailing: IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () {
                ref.read(itemListProvider.notifier).removeItem(index);
                SnackBarUtil.show(context, 'Item removed');
              },
            ),
          );
        },
      ),
    ),

auto_routeだとどうなのか?

Navigator.pushと変わらないみたいだ😅
何も特別なことはしていません。

ファイルはこれだけです。

モデルを作る

データを保持する入れものを作りましょう。

class Book {
  final String title;

  Book(this.title);
}

2. ページを作る

値を渡すページ。コンストラクタの引数を必須にしておく。Bookクラスをデータ型に指定する。

import 'package:auto_route/auto_route.dart';
import 'package:auto_route_app/screen/book.dart';
import 'package:flutter/material.dart';

()
class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key, required this.book});

  final Book book;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.indigo,
        title: Text(book.title),
      ),
      body: Center(
        child: Text(book.title),
      ),
    );
  }
}

ダミーのデータを自動生成して、リストに表示してタップすると、次のページへ渡せるようにしてます。 go_routerみたいにおまじないみたいなコードは書きません。画面遷移するコードとコンストラクタ引数に値を渡すだけですね。

リストを表示するページ

import 'package:auto_route/auto_route.dart';
import 'package:auto_route_app/screen/book.dart';
import 'package:auto_route_app/screen/router.gr.dart';
import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    final books = List.generate(10, (index) => Book('Book $index'));
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.indigo,
        title: const Text('Book'),
      ),
      body: ListView.builder(
        itemCount: books.length,
        itemBuilder: (context, index) {
          final book = books[index];
          return ListTile(
            onTap: () {
              final book = books[index];
              // Data Type Bookを引数に渡す
              context.router.push(DetailScreen(book: book));
            },
            title: Text(book.title),
          );
        },
      ),
    );
  }
}

2. ルートを定義する

ページを作ったらルートを定義しましょう。build_runerのコマンドを実行するだけです。routesの中のAutoRouteは、コードが自動生成されないと、コード保管で出てこないので注意。

import 'package:auto_route/auto_route.dart';
import 'package:auto_route_app/screen/router.gr.dart';

// flutter pub run build_runner watch --delete-conflicting-outputs
(replaceInRouteName: 'Page,Route')
class AppRouter extends $AppRouter {
  
  List<AutoRoute> get routes => [
        AutoRoute(page: HomeScreen.page, initial: true),
        AutoRoute(page: DetailScreen.page),
      ];
}

あとは、実行するだけですね。

main.dart
import 'package:auto_route_app/screen/router.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  final _appRouter = AppRouter(); // auto_routeのrouterを追加

  
  Widget build(BuildContext context) {
    return MaterialApp.router( // MaterialApp.routerに変更
      routerConfig: _appRouter.config(), // auto_routeのrouterを追加
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

[home page]

[detail page]

感想

どうです。「簡単でしよ」なぜこんな便利なパッケージが流行っていないのか謎だ.....
流行っていないら流行らせようかな。情報が少ないから、機能実装するときに困るんですよね😅

Discussion