🐢

go_router + Riverpod2.0で画面遷移をする

2023/03/12に公開

パッケージ独特のエラーにハマった!

最近画面遷移は、go_routerを使うことが増えてきたのですが、設定が原因なのか、パッケージ独特のエラーに悩まされました。
最近、go_toureの記事を色々書きましたが、設定が人によって違うのか真似するだけだと変なエラーに悩まされます😇

今回はpub.devを参考にルートの設定をしたデモアプリを作りました。
https://www.youtube.com/watch?v=fVxOXLld220

遭遇したエラー

'package:go_router/src/configuration.dart': Failed assertion: line 37 pos 18: 'route.path.startsWith('/')': top-level path must 

'package:go_router/src/configuration.dart': Failed assertion: line 37 pos 18: 'route.path.startsWith('/')': トップレベルのパスが必要です。

解決方法
パスに / をつけてトップレベルのルートを指定する。これができていないとパッケージ独特のエラーが発生する。他にも原因不明のエラーが多かったので、海外の人のコードを参考に作ったのが原因だと思い、ゲッターをなくして、コンストラクターをつけて、名前付きルートの作成に使用しました。

まずは表示する画面を作成

lib配下にapplicationディレクトリを作成してuiディレクトリを作成しファイルを追加していきます。

最初に表示される画面

ここから、画面遷移をして他のページへ移動する。ネストしているルートと、していないルートだと、戻るボタンが表示されなかったり、context.popが使用できないのがわかった😁

application/ui/start_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:go_routet_redirect/application/ui/next_page.dart';
import 'package:go_routet_redirect/go_router_riverpod/home_page.dart';

class StartScreen extends ConsumerWidget {
  const StartScreen({super.key});
  static const rootName = '/'; // 名前付きルートで使うコンストラクターを定義しておく.

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text("Start Screen")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text("Wellcome"),
            ElevatedButton(
              onPressed: () {
                // 名前付きルートで画面遷移するコード.
                // ネストしたルートに画面遷移する.
                context.goNamed(NextPage.rootName);
              },
              child: const Text("NextPage"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 名前付きルートで画面遷移するコード.
                // 通常の画面遷移をするコード.
                context.goNamed(HomePage.rootName);
              },
              child: const Text("HomePage"),
            ),
          ],
        ),
      ),
    );
  }
}

ネストしたルート

こちらのページでは、AppBarに戻るボタンが表示されて、context.popを使用することができる。

application/ui/next_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

class NextPage extends ConsumerWidget {
  const NextPage({super.key});
  static const rootName = 'nextPage';

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text("NextPage")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text("Welcome to NextPage"),
            ElevatedButton(
              onPressed: () {
                // 前のページへ戻るコード
                context.pop();
              },
              child: const Text("Back"),
            ),
          ],
        ),
      ),
    );
  }
}

ネストしていないルート

こちらのページでは、AppBarに戻るボタンが表示されず、context.popを使用することができない。使うとエラーが発生する😱
前のページに戻りたいときは、context.go, context.goNamedを使用する。

application/ui/start_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:go_routet_redirect/application/ui/next_page.dart';
import 'package:go_routet_redirect/go_router_riverpod/home_page.dart';

class StartScreen extends ConsumerWidget {
  const StartScreen({super.key});
  static const rootName = '/'; // 名前付きルートで使うコンストラクターを定義しておく.

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text("Start Screen")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text("Wellcome"),
            ElevatedButton(
              onPressed: () {
                // 名前付きルートで画面遷移するコード.
                // ネストしたルートに画面遷移する.
                context.goNamed(NextPage.rootName);
              },
              child: const Text("NextPage"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 名前付きルートで画面遷移するコード.
                // 通常の画面遷移をするコード.
                context.goNamed(HomePage.rootName);
              },
              child: const Text("HomePage"),
            ),
          ],
        ),
      ),
    );
  }
}

ルートの設定ファイル

Riverpodを使用しているので、go_toureをプロバイダーでラップして、どこからでもアクセスできるように設定します。
参考になったサイト
https://codewithandrea.com/articles/flutter-navigate-without-context-gorouter-riverpod/#3-navigate-without-context-using-refreadgorouterprovider

ルートの設定ファイル

applicationディレクトリにルートの設定ファイルを作成します。

application/router.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:go_routet_redirect/application/ui/home_page.dart';
import 'package:go_routet_redirect/application/ui/next_page.dart';
import 'package:go_routet_redirect/application/ui/start_screen.dart';

final goRouterProvider = Provider<GoRouter>((ref) {
  return GoRouter(
    routes: <RouteBase>[
      // 最初に表示されるページ.
      GoRoute(
        path: '/', // トップレベルのパスが必要なので指定する.
        name: StartScreen.rootName, // 名前付きルートを指定する.
        builder: (BuildContext context, GoRouterState state) {
          return const StartScreen();
        },
        // ネストしたルートを指定する.
        routes: <RouteBase>[
          GoRoute(
            path: 'next',
            name: NextPage.rootName,
            builder: (BuildContext context, GoRouterState state) {
              return const NextPage();
            },
          ),
        ],
      ),
      // ネストしていないルート。戻るボタンがAppBarに表示されない.
      GoRoute(
        path: '/home',
        name: HomePage.rootName,
        builder: (BuildContext context, GoRouterState state) {
          return const HomePage();
        },
      ),
    ],
  );
});

main.dartでgo_routerを使えるように設定をする
Firebaseのコードが書いてありますが、こちらは気にしないでください。認証機能の実験で使っていたので、書きました。一旦コメントアウトしてます。
リダイレクトの実験をしていたのですが、上手くいかなくて悩まされております😭

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

import 'application/router.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const ProviderScope(child: MyApp()));
}

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    // go_routerをプロバイダーで監視する.
    final goRouter = ref.watch(goRouterProvider);

    return MaterialApp.router(// MaterialAppに.routerを追加する.
      routerConfig: goRouter,// このコードを追加すると画面遷移ができるようになる.
      title: 'flutter_riverpod + go_router Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

まとめ

go_routerを使い始めて、パッケージ独特のエラーにハマるので、今回は簡単な画面遷移をRiverpodで使う方法を記事にしてみました。

新たにページを追加したのですが、またエラーが出てきました😅

'package:go_router/src/configuration.dart': Failed assertion: line 37 pos 18: 'route.path.startsWith('/')': top-level path must 

'package:go_router/src/configuration.dart': Failed assertion: line 37 pos 18: 'route.path.startsWith('/')': トップレベルのパスが必要です。

ネストしていないところは、トップレベルになるそうで / をつける。この後エラーが表示されなくなりました。

トップレベルのページを追加したコード

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:go_routet_redirect/auth/auth.dart';
import 'package:go_routet_redirect/auth/ui/home_page.dart';
import 'package:go_routet_redirect/auth/ui/next_page.dart';
import 'package:go_routet_redirect/auth/ui/signin_page.dart';
import 'package:go_routet_redirect/auth/ui/signup_page.dart';
import 'package:go_routet_redirect/auth/ui/start_screen.dart';

final goRouterProvider = Provider<GoRouter>((ref) {
  final authState = ref.watch(authProvider);

  return GoRouter(
    initialLocation: '/',
    routes: <RouteBase>[
      // 最初に表示されるページ.
      GoRoute(
        path: '/', // トップレベルのパスが必要なので指定する.
        name: StartScreen.rootName, // 名前付きルートを指定する.
        builder: (BuildContext context, GoRouterState state) {
          return const StartScreen();
        },
        // ネストしたルートを指定する.
        routes: <RouteBase>[
          GoRoute(
            path: 'next',
            name: NextPage.rootName,
            builder: (BuildContext context, GoRouterState state) {
              return const NextPage();
            },
          ),
        ],
      ),
      // ネストしていないルート。戻るボタンがAppBarに表示されない.
      // トップレベルのルートになるので、 / をつける
      GoRoute(
        path: '/home',
        name: HomePage.rootName,
        builder: (BuildContext context, GoRouterState state) {
          return const HomePage();
        },
      ),
      GoRoute(
        path: '/signin',
        name: SignInPage.rootName,
        builder: (BuildContext context, GoRouterState state) {
          return const HomePage();
        },
      ),
      GoRoute(
        path: '/signup',
        name: SignUpPage.rootName,
        builder: (BuildContext context, GoRouterState state) {
          return const HomePage();
        },
      ),
    ],
  );
});

Discussion