🏰

GoRouterで次のページにListの値を渡したい

2023/06/12に公開

工夫しないと上手くいかなかった!

go_routerを使用して、画面遷移した先のページにString型の値を渡す記事はよく見かける気がしますが、Listを使った記事はないような気がしたので、書くことにしました。

モデルクラスを作る

今回はダミーのデータを保存するモデルクラスを作ります。

domain/item_model.dart
// Listで表示するアイテムのモデルを定義します。
class Item {
  Item({required this.name});// コンストラクターで、nameプロパティを必須にしています。
  final String name;// このクラスは、名前を表すnameプロパティを持っています。
}

作ったモデルクラスは、状態を保つことができる Notifierで使用する。今回は追加と削除ができる機能を作っています。これは実験用に作ったコードなので、いいコードではないかもです🙅

application/item_state.dart
import 'package:design_patterns/domain/item_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final errorMessageProvider = StateProvider<String?>((ref) => null);

final itemListProvider =
    NotifierProvider<ItemListNotifier, List<Item>>(ItemListNotifier.new);

class ItemListNotifier extends Notifier<List<Item>> {
  
  build() {
    return [];
  }

  void addItem(String name) {
    try {
      if (name.isEmpty) {
        ref.read(errorMessageProvider.notifier).state = 'nullはダメです!';
      }
      state = [...state, Item(name: name)];
    } catch (e) {
      throw (e.toString());
    }
  }

  void removeItem(int index) {
    state = [...state]..removeAt(index);
  }
}

画面遷移するページ

こちらのページで入力フォームをから新しいリストを作成して次のページへ値を渡します。

lib/presentation/ui/item_list.dart
import 'package:design_patterns/application/item_state.dart';
import 'package:design_patterns/presentation/router_path.dart';
import 'package:design_patterns/presentation/snackbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

final controllerProvider = StateProvider((ref) => TextEditingController());
final errorMessageProvider = StateProvider<String?>((ref) => null);

class ListItemPage extends ConsumerWidget {
  const ListItemPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final itemList = ref.watch(itemListProvider);
    final _controller = ref.watch(controllerProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Item List'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _controller,
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Item Name',
                ),
              ),
            ),
            ElevatedButton(
                onPressed: () {
                  ref.read(itemListProvider.notifier).addItem(_controller.text);
                  SnackBarUtil.show(context, 'Item added');
                },
                child: const Text('Add Item')),
            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');
                      },
                    ),
                  );
                },
              ),
            ),
            const SizedBox(height: 16),
          ],
        ),
      ),
    );
  }
}

画面遷移したページ

コンストラクタを作って前のページから値を受け取って、UIに表示できる様にしています。

lib/presentation/ui/detail.dart
import 'package:design_patterns/domain/item_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class DetailPage extends ConsumerWidget {
  const DetailPage({super.key, required this.item});

  final Item item;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(item.name),
          ],
        ),
      ),
    );
  }
}

GoRouterの設定ファイル

Listの値を次のページへ渡すには、以下のように書きます。やったことなかったので結構躓きました!
extra propertyを使用して、画面遷移の情報とオブジェクトを次のページへ渡すことができるそうです。
if文でnullチェックをして、値があれば画面遷移先に値を渡すことができて、nullだったら、SomeErrorPageへ画面遷移するようになっています。でもダミーデータがnullだと、SomeErrorPageへ画面遷移しなくなった?
後でコードを書き換えたら、SomeErrorPageへ画面遷移しなくなりました😅

https://pub.dev/documentation/go_router/latest/go_router/GoRouterState/extra.html

router.dart
import 'package:design_patterns/domain/item_model.dart';
import 'package:design_patterns/presentation/router_path.dart';
import 'package:design_patterns/presentation/ui/detail.dart';
import 'package:design_patterns/presentation/ui/home.dart';
import 'package:design_patterns/presentation/ui/item_list.dart';
import 'package:design_patterns/presentation/ui/some_error.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

// 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(); // 適切なエラーページに差し替えてください
          }
        },
      ),
    ],
  );
});


まとめ

ざっくりとですが、解説してみました。もっと凝ったものを作りたかったのですが、良いアイディアがなかったので、ダミーのデータを使ったものにしました。誰かのお役に立てると嬉しいです。

全体のソースコードはこちらのリポジトリにあります。
https://github.com/sakurakotubaki/GoRouterFullPath

Discussion