🤾

generatorでgo routerを使ってみる

2023/10/07に公開

Overview

最近話題のriverpod generatorをgo routerで使ってみた。go routerの記事を何回書いただろうか。。。
今回はこのパッケージが必要なので追加する。
https://pub.dev/packages/go_router

summary

今回は、おまじないみたいなコードを使って、riverpodのプロバイダーを使ってみましょう。
まずは、公式を参考にコマンドを実行して、必要なパッケージを追加する。

flutter pub add \
  flutter_riverpod \
  riverpod_annotation \
  dev:riverpod_generator \
  dev:build_runner \
  dev:custom_lint \
  dev:riverpod_lint

ネストしたルートで画面遷移するコード。名前付きルートでないとうまくいかない?
パスを直接書くとなぜか成功する🙃

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:route_generate/common/route_path.dart';

class HomePage extends ConsumerWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: Center(
        child: ElevatedButton(
            onPressed: () {
              context.goNamed(RoutePath.next.name);
            },
            child: const Text('Go to')),
      ),
    );
  }
}

class NextPage extends StatelessWidget {
  const NextPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Next Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.pop();
          },
          child: const Text('Go to Home'),
        ),
      ),
    );
  }
}

パスパラメーターを渡して、受け取ったidが指定されたものであれば、決まったページへ画面遷移するコード

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

class RoomPage extends ConsumerWidget {
  const RoomPage({super.key, required this.id});

  final String id;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Room Page1'),
      ),
      body: Center(child: Text('id: $id')),
    );
  }
}

class RoomSecondPage extends ConsumerWidget {
  const RoomSecondPage({super.key, required this.id});

  final String id;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Enum Room Page2'),
      ),
      body: Center(child: Text('id: $id')),
    );
  }
}

class RoomThreePage extends ConsumerWidget {
  const RoomThreePage({super.key, required this.id});

  final String id;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Enum Room Page3'),
      ),
      body: Center(child: Text('id: $id')),
    );
  }
}

class NotFoundPage extends ConsumerWidget {
  const NotFoundPage({super.key, required this.id});

  final String id;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('404 Not Found Page'),
      ),
    );
  }
}

ページとパスを指定するenumを定義する。enumを使うべきなのかなと悩むところです。フルパスを指定するのに使ったがなぜか失敗する?
この書き方だと、requiredをつけると引数を渡すのが必須になる。

enum RoutePath {
  home(path: '/', name: 'home'),
  room(path: '/room', name: 'room'),
  next(path: 'next', name: 'next');

  const RoutePath({required this.path, required this.name});
  final String path;
  final String name;
}

ルートを定義する
go_routerでルーティングを定義する。generatorを使うと書き方が結構独特ですね。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:route_generate/common/route_path.dart';
import 'package:route_generate/pages/home_page.dart';
import 'package:route_generate/pages/room_page.dart';

part 'router.g.dart';
// パラメーターを受け取るためのenum
enum RoomType {
  one,
  two,
  three,
  unknown,
}
// enumからRoomTypeを取得する関数
RoomType getRoomTypeFromId(String id) {
  switch (id) {
    case '1':
      return RoomType.one;
    case '2':
      return RoomType.two;
    case '3':
      return RoomType.three;
    default:
      return RoomType.unknown;
  }
}
// riverpod generateでプロバイダーを生成

GoRouter router(RouterRef ref) {
  return GoRouter(
      routes: [
        GoRoute(
          path: RoutePath.home.path,
          name: RoutePath.home.name,
          builder: (context, state) => const HomePage(),
          routes: [
            GoRoute(
              path: RoutePath.next.path,
              name: RoutePath.next.name,
              builder: (context, state) => const NextPage(),
            ),]
        ),
        GoRoute(
          // 指定したパスのみを受け付ける
          path: '${RoutePath.room.path}/:id',
          name: RoutePath.room.name,
          builder: (context, state) {
            final id = state.pathParameters['id'] ?? '';
            final roomType = getRoomTypeFromId(id);
            switch (roomType) {
              case RoomType.one:// 1の場合はRoomPageを表示
                return RoomPage(id: id);
              case RoomType.two:// 2の場合はRoomSecondPageを表示
                return RoomSecondPage(id: id);
              case RoomType.three:// 3の場合はRoomThreePageを表示
                return RoomThreePage(id: id);
              case RoomType.unknown:// それ以外の場合はNotFoundPageを表示
              default:
                return NotFoundPage(id: id);
            }
          },
        ),
      ],
      // 404ページを指定
      errorPageBuilder: (context, state) {
        return const MaterialPage(
            child: Scaffold(
          body: Center(
            child: Text('Page not found'),
          ),
        ));
      });
}

ファイルの自動生成はこちらのコマンドを実行する

flutter pub run build_runner watch --delete-conflicting-outputs

🧑‍🎓自動生成されたプロバイダーを読み込んでみる。

これが自動生成されたプロバイダー

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'router.dart';

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$routerHash() => r'8ee7f6f2862b88516b9594cbf69e3fdb6be20482';

/// See also [router].
(router)
final routerProvider = AutoDisposeProvider<GoRouter>.internal(
  router,
  name: r'routerProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$routerHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter

main.dartでgo routerのプロバイダーを読み込んで使用する。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:route_generate/common/router.dart';

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp.router(
      routerConfig: ref.watch(routerProvider),
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
    );
  }
}

main.dartはこのように書く。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:route_generate/myapp.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

パスパラメーターを使うとこんな感じで画面遷移するページに値を渡せる。

thoughts

今日は実験するだけのご紹介でした。皆さんもgo routerをriverpod generatorで使ってみてください。
go routerを最近は、使うことが多くてパスパラメーターを使うことが少なかったのでいい体験になりました。
こちらが完成品のソースコードです。参考にしてみてください。

https://github.com/sakurakotubaki/GoRouterGenerater

Discussion