🦭

【Flutter】GetX ルーティング実践マニュアル: アプリを実装し、基礎~応用まで学ぶ3ステップ

2023/04/29に公開

こんにちは、Saeです。
「さえたエンジニア」へなるために、日々鍛錬に勤しんでます。

本記事では、「GetX」というPackageを使用したルーティングの基礎から応用を解説し、最後にルーティングを使用したアプリを作成していきます。

今回最後に紹介しているアプリのサンプルコードはGitHubで公開しておりますので、参考になれば幸いです。
https://github.com/Sae-Eng/getx_root_app

0. 忙しい人のための「GetXのルーティング」。

忙しいエンジニアへ向けたシリーズ、「GetXでのDI」についての説明です。
ここだけ見れば、なんとなくわかります。

  1. GetXルーティングでは、GetMaterialAppを使用し、Get.to()Get.off()Get.offAll()を使って画面遷移を制御。
  2. 通常の遷移はGet.to()を使用し、新しい画面がスタックに追加される。
  3. Get.off()は新しい画面に遷移しつつ、現在の画面をスタックから削除。
  4. Get.offAll()はスタック内の全ての画面を削除し、新しい画面に遷移。
  5. 名前付きルートはGetPageを使って定義し、Get.toNamed()で遷移。
// GetMaterialAppを使用する
GetMaterialApp(
  getPages: routes,
);

// Get.to()で通常の遷移を行う
Get.to(SecondPage());

// Get.off()で現在の画面を破棄して遷移
Get.off(SecondPage());

// Get.offAll()で全ての画面を破棄して遷移
Get.offAll(HomePage());

// 名前付きルートの定義
final routes = [
  GetPage(
    name: '/home',
    page: () => HomePage(),
  ),
  GetPage(
    name: '/details',
    page: () => DetailsPage(),
  ),
];

// 名前付きルートへの遷移
Get.toNamed('/details');

// 名前付きルートでGet.off()とGet.offAll()を使用
Get.offNamed('/second');
Get.offAllNamed('/home');

1. GetXルーティング 基本編

GetXを使ったルーティングは簡単で直感的な特徴的です。
この基本編では、基本的なルート遷移から、実際のアプリでよくみる形の名前付きルートの実装方法まで解説していきます

事前準備

GetXでRoute機能を使用するには、元になるアプリをMaterialAppからGetMaterialAppに変更する必要があります。
変更することでRoute機能が使用できるようになります。

GetMaterialApp( // 変更前: MaterialApp(
  home: MyHome(),
)

ルート遷移

GetXでは、Get.to()Get.off()Get.offAll()などのメソッドを使って遷移を制御できます。
これらは、それぞれ通常の遷移、現在の画面を破棄して遷移、すべての画面を破棄して遷移を行います。

1. Get.to()

Get.to()は、新しい画面に遷移する際に使用されます。
新しい画面がスタックに追加され、ナビゲーション履歴が保持されます。


// 引数に遷移先の画面を定義
Get.to(SecondPage());

2. Get.off()

Get.off()は、新しい画面に遷移し、現在の画面をスタックから削除します。
これにより、ユーザーが戻るボタンを押しても元の画面に戻れなくなります。


Get.off(SecondPage());

3. Get.offAll()

Get.offAll()は、スタック内のすべての画面を削除し、新しい画面に遷移します。
これは、ログイン画面からダッシュボード画面に遷移する際や、ログアウト時に使用すると便利です。


Get.offAll(HomePage());

名前付きルート

名前付きルートの定義

GetXでは、GetPageを使って名前付きルートを定義します。
nameには遷移先の名称を、pageには遷移先の画面を入力します。


final routes = [
  GetPage(
    name: '/home', // 遷移先の名称
    page: () => HomePage(), // 遷移先の画面
  ),
  GetPage(
    name: '/details',
    page: () => DetailsPage(),
  ),
];

定義したルートはGetMaterialAppgetPagesに登録することで遷移できるようになります。


GetMaterialApp(
  getPages: routes,
)

名前付きルートへの遷移

Get.toNamed()を使って名前付きルートへ遷移します。
引数には先ほどnameに定義した、遷移先の名称を入力します。


Get.toNamed('/details');

また先ほど紹介した通常の遷移方法と同様にoff,offAllを使用することが可能です。


Get.toNamed('/second'); // 新しい画面に遷移し、現在の画面をスタックから削除
Get.offAllNamed('/home'); // スタック内のすべての画面を削除し、新しい画面に遷移

2. GetXルーティング 応用編

ここからは、基本的なルーティングを習得した上で、さらに応用的なルーティングテクニックについて解説していきます。

カスタム遷移

GetXでは、transitiondurationに値を入れることで、遷移アニメーションや遷移デュレーションをカスタマイズできます。

Get.to(
  SecondPage(),
  transition: Transition.zoom,
  duration: Duration(milliseconds: 500),
);

ミドルウェア

ミドルウェアは、ルートに対して実行される一連の処理です。
グローバルミドルウェアはすべてのルートに適用され、ルートミドルウェアは特定のルートに適用されます。

またミドルウェアは主に以下のようなケースで使用されることが多いです。

  1. 認証・認可:
    • ユーザーが特定のページにアクセスする前に、ログイン状態や権限をチェックするために使用されます。
    • 認証が必要なページに未認証のユーザーがアクセスしようとすると、ログインページにリダイレクトするなどの処理を行います。
  2. ロギング:
    • ページ遷移が発生したときに、遷移先のルート名や遷移元の情報をログに出力するために使用されます。
    • これにより、アプリケーションの動作を追跡し、デバッグやパフォーマンス改善に役立てることができます。
  3. データの前処理・後処理:
    • ページ遷移の前後で共通のデータ処理が必要な場合、ミドルウェアで実装することができます。
    • 例えば、ユーザーが言語設定を変更したときに、遷移先のページで適切な言語リソースを読み込むようにすることができます。
  4. アニメーションやトランジションのカスタマイズ:
    • ページ遷移の際に、特定のアニメーションやトランジションを適用するためにミドルウェアを使用することができます。
    • これにより、アプリケーション全体で一貫した遷移効果を実現することができます。

ミドルウェアの定義

まずミドルウェアで、Getxのルーティング処理の前後に実行されるカスタムロジックを定義しましょう。以下の例では、MyMiddlewareというミドルウェアを定義しています。

class MyMiddleware extends GetMiddleware {
  
  RouteSettings? redirect(String? route) {
    // ログインしていない場合はログイン画面へリダイレクト
    return isLoggedIn() ? null : RouteSettings(name: '/login');
  }
}

このMyMiddlewareは、ユーザーがログインしていない場合にログイン画面へリダイレクトする機能を提供します。

redirectメソッドをオーバーライドして、isLoggedIn()関数の結果に基づいてリダイレクトの処理を行っています。

ミドルウェアには他にも以下のようなメソッドがあります。

  • redirect(String? route):
    • ルーティング時にリダイレクトが必要かどうかを判断し、必要であればリダイレクト先のルートを指定します。
    • 戻り値としてRouteSettingsオブジェクトを返すことでリダイレクトが実行されます。リダイレクトが不要な場合は、nullを返します。
    • 主に認証が必要なページへのアクセス制御や、特定の条件下でのリダイレクト処理に使用されます。
  • onPageCalled(GetPage<dynamic>? page):
    • ページが呼び出される前に実行されるメソッドです。
    • ページの表示を制御したり、特定の条件下で表示を変更することができます。
  • onPageDispose(GetPage<dynamic>? page):
    • ページが破棄される前に実行されるメソッドです。
    • ページ破棄時のリソースの解放や後処理を行うことができます。
  • onBindingsStart():
    • バインディングが開始される前に実行されるメソッドです。
    • バインディング処理の前に特定の処理を挟むことができます。
  • onPageBuildStart():
    • ページのビルドが開始される前に実行されるメソッドです。
    • ページ構築前の処理を実行することができます。

グローバルミドルウェア

グローバルミドルウェアは、GetMiddlewareを継承したクラスを作成して定義し、GetMaterialAppのmiddlewareプロパティで登録します。

// GetMaterialAppでミドルウェアを登録
GetMaterialApp(
  getPages: AppRoutes.routes,
  middleware: [
    MyMiddleware(),
  ],
);

ルートミドルウェア

ルートミドルウェアは、特定のルートに適用されます。ルート定義時にmiddlewareプロパティで指定します。

GetPage(
  name: '/dashboard',
  page: () => DashboardPage(),
  middleware: [
    MyMiddleware(),
  ],
);

他の遷移関係のメソッド

GetXでは、さまざまな遷移関連のメソッドが提供されています。ここではそれらのメソッドについて解説します。

Get.back()

Get.back()メソッドは、1つ前の画面に戻るためのメソッドです。

Get.back();

またGet.back()メソッドには、resultパラメータがあります。これを使って、戻る際に結果を遷移元の画面に返すことができます。

Get.back(result: 'Success');

Get.back()する前の遷移元の画面では、Get.to()Get.toNamed()の戻り値を使って、遷移先から返された結果を受け取ることができます。

final result = await Get.toNamed('/details'); // 画面に戻ってきたタイミングでGet.backのresultの結果が入ってくる(awaitは付けること)
print('Result from details page: $result');

Get.until()

Get.until()メソッドは、指定した条件に一致する画面まで遷移履歴を戻すメソッドです。
以下の例では、HomePageがスタックにある場合、その画面まで戻ります。

Get.until((route) => route.isFirst);

また、以下の例では、route.settings.nameを使って特定の名前付きルートまで戻ります。

Get.until((route) => route.settings.name == '/home');

Get.removeRoute()

Get.removeRoute()メソッドは、指定したルートを遷移履歴から削除するメソッドです。
以下の例では、特定の名前付きルートを削除します。(route.settings.nameを指定することも可能。)

Get.removeRoute(GetPageRoute(
  routeName: '/details',
  page: () => DetailsPage(),
));

Get.removeUntil()

Get.removeUntil()メソッドは、指定した条件に一致する画面までの遷移履歴を削除するメソッドです。
以下の例では、HomePageがスタックにある場合、その画面までの遷移履歴を削除します。

Get.removeUntil((route) => route.settings.name == '/home');

また以下のようにルートの始めまでのスタックを削除することもできます。

Get.removeUntil((route) => route.isFirst);

3. 実践編: GetXルーティングを用いたアプリを作成

ここでは、GetXのルーティング機能を活用して、実際のアプリケーションを構築していきます。
具体的には、ログイン画面〜ダッシュボード画面まであるアプリを作成することで、ルーティングの実践的な使用方法を学んでいきます。

アプリ概要

このサンプルアプリケーションでは、以下の機能が実装されています。

  1. ログイン画面
  2. ダッシュボード画面
  3. ログイン失敗時のエラーダイアログ表示
  4. ログイン成功時のダッシュボード画面への遷移
  5. ユーザー名の表示
  6. ログアウト機能
  7. ルート遷移時のロギング

また事前にプロジェクトの作成、およびpubspec.yamlでのパッケージ登録の準備をお願いいたします。

1: main.dartを作成

lib/main.dart ファイルは、アプリケーションのエントリーポイントです。

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_root_app/route/routes.dart';

import 'vm/login_view_model.dart';

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

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

  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      // 名前付きルートを設定
      getPages: AppRoutes.routes,
      initialBinding: BindingsBuilder(() {
        Get.put(LoginViewModel());
      }),
    );
  }
}

ここでは、GetMaterialApp を使用して、名前付きルートと初期バインディングを設定しています。

2. ログイン用のViewModelを作成

lib/vm/login_view_model.dartファイルでは、ログイン画面のロジックを扱う ViewModel を定義しています。

import 'package:get/get.dart';

enum ActionType { showErrorDialog, navigateToDashboard }

class LoginViewModel extends GetxController {
  // アクション用のRx<ActionType>
  final action = Rx<ActionType?>(null);

  // ユーザーID
  String userId = '';

  void login({required String userId, required String password}) {
    action.value = null;
    if (userId == 'user' && password == 'password') {
      // ユーザーIDを保持
      this.userId = userId;
      // ログイン成功時、ダッシュボード画面へ遷移
      action.value = ActionType.navigateToDashboard;
    } else {
      // ログイン失敗時、エラーダイアログ表示
      action.value = ActionType.showErrorDialog;
    }
  }
}

LoginViewModelでは、アクション用のRx変数: action を定義し、ログイン処理を行っています。ログインに成功した場合は、ダッシュボードへの遷移を指示し、失敗した場合はエラーダイアログの表示を指示します。

3. ログイン画面の作成

lib/view/login_page.dartファイルでは、ログイン画面の UI を定義しています。

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

import '../vm/login_view_model.dart';

class LoginPage extends StatelessWidget {
  // ユーザーIDとパスワードを保持するコントローラー
  final userIdController = TextEditingController();
  final passwordController = TextEditingController();

  LoginPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final vm = Get.find<LoginViewModel>();

    // actionをリッスンし、適切な処理を行う
    vm.action.listen((value) {
      if (value == ActionType.showErrorDialog) {
        Get.dialog(
          AlertDialog(
            title: const Text('エラー'),
            content: const Text('ユーザーIDまたはパスワードが違います。'),
            actions: [
              TextButton(
                onPressed: () => Get.back(),
                child: const Text('OK'),
              ),
            ],
          ),
        );
      } else if (value == ActionType.navigateToDashboard) {
        Get.offNamed('/dashboard', arguments: {'username': vm.userId});
      }
    });

    return Scaffold(
      appBar: AppBar(title: const Text('ログイン')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(15.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // ユーザーID入力フォーム
              TextField(
                controller: userIdController,
                decoration: const InputDecoration(
                  labelText: 'ユーザーID',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 16),
              // パスワード入力フォーム
              TextField(
                controller: passwordController,
                decoration: const InputDecoration(
                  labelText: 'パスワード',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              const SizedBox(height: 16),
              // ログインボタン
              ElevatedButton(
                onPressed: () {
                  // ログイン判定処理をViewModelに移動
                  vm.login(
                    userId: userIdController.text,
                    password: passwordController.text,
                  );
                },
                child: const Text('ログイン'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

このLoginPageウィジェットでは、ユーザーIDとパスワードの入力フォームとログインボタンが表示されます。ログインボタンが押されると、LoginViewModelloginメソッドが呼び出されます。また、vm.actionをリッスンして、エラーダイアログを表示したり、ダッシュボード画面へ遷移する処理を行います。

4: ダッシュボード画面の作成

lib/view/dashboard_page.dartファイルでは、ダッシュボード画面のUIを定義しています。

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

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

  // ダッシュボード画面
  
  Widget build(BuildContext context) {
    final String? username = Get.arguments != null ? Get.arguments['username'] : null;

    return Scaffold(
      appBar: AppBar(title: const Text('ダッシュボード')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('ダッシュボード画面'),
            if (username != null) Text('ユーザー名: $username'),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                Get.offNamed('/');
              },
              child: const Text('ログアウト'),
            ),
          ],
        ),
      ),
    );
  }
}

DashboardPageウィジェットでは、ユーザー名とログアウトボタンが表示されます。ログアウトボタンが押されると、ログイン画面に遷移します。

5. ルート定義

lib/route/routes.dartファイルでは、アプリケーションの名前付きルートを定義しています。

import 'package:get/get.dart';

import '../middleware/logging_middleware.dart';
import '../view/dashboard_page.dart';
import '../view/login_page.dart';

class AppRoutes {
  static final routes = [
    GetPage(name: '/', page: () => LoginPage()),
    GetPage(
      name: '/dashboard',
      page: () => const DashboardPage(),
      middlewares: [LoggingMiddleware()], // ロギング用のミドルウェアを追加
    ),
  ];
}

Step 6: ミドルウェアを作成

lib/middleware/logging_middleware.dart ファイルでは、ルート遷移時にログを出力するミドルウェアを定義しています。

import 'package:get/get.dart';

class LoggingMiddleware extends GetMiddleware {
  
  GetPage<dynamic>? onPageCalled(GetPage<dynamic>? page) {
    // ルート名を取得
    String? routeName = page?.name;
    // ルート名をログに出力
    // ignore: avoid_print
    print('Navigating to route: $routeName');
    return page;
  }
}

このミドルウェアは、ルート遷移時にルート名をログに出力します。これにより、アプリケーションの動作を追跡しやすくなります。

こちらでアプリ実装完了となります!お疲れ様でした!

まとめ

いかがでしたでしょうか。
GetXのルーティングの使用方法が少しでも伝わりましたら幸いです。

みなさんもGetXを活用し、素敵なFlutterライフをお過ごしくださいませ。では〜。

Discussion