[基礎知識]画面遷移を実現するパッケージ/flutter_bloc

flutter_blocを使って画面遷移を行う場合、Blocの状態を使ってナビゲーションのロジックを制御することができます。
以下は、flutter_blocを使用して画面遷移を実装するためのサンプルコードとその解説です。
このサンプルでは、ログイン画面からホーム画面への遷移を実装します。

1.サンプル(動作)

以下は、2つの画面(PageA画面(page_a.dart)、PageB画面(page_b.dart))を用意し、相互の画面遷移を行うサンプルです。

①PageA画面で、ElevatedButton「Go to PageB」をクリックします。

②するとPageB画面に遷移します。
 ここで、ElevatedButton「Go to PageA」をクリックします。

③するとPageA画面に遷移し(戻り)ます。

2.サンプル(コード)

2ー1.ファイルの配置

各ファイルを以下の様に配置することとします。

Dart
lib/
  ├── main.dart
  ├── navigation_event.dart
  ├── navigation_state.dart
  ├── navigation_bloc.dart
  ├── page_a.dart
  └── page_b.dart

2ー2.各ファイルのコード

(1)main.dart

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';  // Blocを使用するためのパッケージをインポート

import 'navigation_bloc.dart';  // NavigationBlocをインポート
import 'navigation_state.dart'; // NavigationStateをインポート
import 'page_a.dart'; // PageAのウィジェットをインポート
import 'page_b.dart'; // PageBのウィジェットをインポート

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

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

  @override
  Widget build(BuildContext context) {
    // BlocProviderを使ってNavigationBlocをアプリ全体に提供
    return BlocProvider(
      create: (context) => NavigationBloc(),
      // MaterialAppはFlutterアプリの基本的な構造を提供
      child: const MaterialApp(
        // 最初に表示するウィジェットをNavigationに設定
        home: Navigation(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // BlocBuilderは、NavigationBlocの状態に応じてUIを再構築
    return BlocBuilder<NavigationBloc, NavigationState>(
      builder: (context, state) {
        if (state is PageA) {
          // stateがPageAのとき、PageAのウィジェットを表示
          return const PageAScreen();
        } else if (state is PageB) {
          // stateがPageBのとき、PageBのウィジェットを表示
          return const PageBScreen();
        }
        // それ以外の状態(初期状態など)はローディングインジケーターを表示
        return const Scaffold(body: Center(child: CircularProgressIndicator()));
      },
    );
  }
}

(2)navigation_event.dart

Dart
// イベントの基底クラスを定義します。
// すべてのナビゲーションイベントはこのクラスを継承します。
// イベントクラスは、ユーザーのアクションやアプリの状態変化
// を表現するために使用されます。
abstract class NavigationEvent {}

// PageAに遷移するためのイベントクラスです。
// このイベントが発行されると、アプリはPageAに遷移します。
class NavigateToPageA extends NavigationEvent {}

// PageBに遷移するためのイベントクラスです。
// このイベントが発行されると、アプリはPageBに遷移します。
class NavigateToPageB extends NavigationEvent {}

(3)navigation_state.dart

Dart
// 状態の基底クラスを定義します。
// すべてのナビゲーション状態はこのクラスを継承します。
abstract class NavigationState {}

// PageAの状態を表すクラスです。
// このクラスは、現在アプリがPageAにいることを示します。
class PageA extends NavigationState {}

// PageBの状態を表すクラスです。
// このクラスは、現在アプリがPageBにいることを示します。
class PageB extends NavigationState {}

(4)navigation_bloc.dart

Dart
import 'package:bloc/bloc.dart';  // Blocパッケージをインポート

import 'navigation_event.dart'; // NavigationEventをインポート
import 'navigation_state.dart'; // NavigationStateをインポート

// NavigationBlocクラスは、ナビゲーションイベントとナビゲーション状態を管理するBlocです。
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
  // コンストラクタで初期状態をPageAに設定します。
  NavigationBloc() : super(PageA()) {
    // on<NavigateToPageA>メソッドは、NavigateToPageAイベントが発生したときにPageA状態に遷移するようにします。
    on<NavigateToPageA>((event, emit) => emit(PageA()));  // 状態をPageAに遷移させます。
    // on<NavigateToPageB>メソッドは、NavigateToPageBイベントが発生したときにPageB状態に遷移するようにします
    on<NavigateToPageB>((event, emit) => emit(PageB()));  // 状態をPageBに遷移させます。
  }
}

// このように、NavigationBlocはナビゲーションイベントに応じて状態を変更し、それに基づいてUIを更新します。
// これにより、アプリケーションの状態管理と画面遷移がシンプルかつ効果的に行われます。

(5)page_a.dart

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';  // Blocを使用するためのパッケージ

import 'navigation_bloc.dart';  // NavigationBlocをインポート
import 'navigation_event.dart'; // NavigationEventをインポート

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Page A')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Blocを通じて(NavigationBlocインスタンス取得して)
            // NavigateToPageBイベントを発行し、
            // Page Bに遷移するように指示します。
            context.read<NavigationBloc>().add(NavigateToPageB());
          },
          child: const Text('Go to Page B'),
        ),
      ),
    );
  }
}

(6)page_b.dart

コード説明は上記(5)に準じます。

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'navigation_bloc.dart';
import 'navigation_event.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Page B')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.read<NavigationBloc>().add(NavigateToPageA());
          },
          child: const Text('Go to Page A'),
        ),
      ),
    );
  }
}

3.総括(処理の流れ)

上記の画面遷移処理は、以下の様な流れで処理されています。

(1) アプリ起動 (main.dart)

  • BlocProviderを使ってNavigationBlocのインスタンスを作成し、アプリ全体で使用できるように設定します。この設定により、アプリ内のどこからでもNavigationBlocにアクセスできるようになります。
  • MaterialAppが表示され、homeプロパティにNavigationウィジェットが設定されます。これにより、アプリの初期画面が設定され、アプリのビルドが開始されます。
Dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // BlocProviderを使ってNavigationBlocをアプリ全体に提供
<mark style="background-color:#7bdcb5" class="has-inline-color">    return <strong>BlocProvider</strong>(
      create: (context) => <strong>NavigationBloc</strong>(),</mark>
      // MaterialAppはFlutterアプリの基本的な構造を提供
      child: const MaterialApp(
        // 最初に表示するウィジェットをNavigationに設定
        home: <strong>Navigation</strong>(),
      ),
    );
  }
}

(2) 初期画面表示 (main.dart)

  • Navigationクラスが最初に表示されます。
  • BlocBuilderNavigationBlocの状態に基づいてUIを構築します。
  • 初期状態はPageAなので、PageAScreenが表示されます。
Dart
class <strong>Navigation </strong>extends StatelessWidget {
  const Navigation({super.key});

  @override
  Widget build(BuildContext context) {
    // BlocBuilderは、NavigationBlocの状態に応じてUIを再構築
    return<mark style="background-color:#7bdcb5" class="has-inline-color"> <strong>BlocBuilder</strong><<strong>NavigationBloc</strong>, NavigationState>(</mark>
      builder: (context, state) {
        <mark style="background-color:#fcb900" class="has-inline-color">if (state is PageA) {</mark>
          // stateがPageAのとき、PageAのウィジェットを表示
          <mark style="background-color:#fcb900" class="has-inline-color has-black-color">return const PageAScreen();</mark>
        } else if (state is PageB) {
          // stateがPageBのとき、PageBのウィジェットを表示
          return const PageBScreen();
        }
        // それ以外の状態(初期状態など)はローディングインジケーターを表示
        return const Scaffold(body: Center(child: CircularProgressIndicator()));
      },
    <mark style="background-color:#7bdcb5" class="has-inline-color">);</mark>
  }

(3) ページAの表示 (page_a.dart)

  • PageAScreenが表示されます。
  • ボタンを押すとNavigateToPageBイベントが発行されます。
Dart
class PageAScreen extends StatelessWidget {
  const PageAScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Page A')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Blocを通じて(NavigationBlocインスタンス取得して)
            // NavigateToPageBイベントを発行し、
            // Page Bに遷移するように指示します。
            <mark style="background-color:#7bdcb5" class="has-inline-color">context.read<NavigationBloc>().add(<strong>NavigateToPageB</strong>());</mark>
          },
          child: const Text('Go to Page B'),
        ),
      ),
    );
  }
}

(4) イベント処理 (navigation_bloc.dart)

  • NavigationBloc(上記の)NavigateToPageBイベントを受け取り状態をPageBに変更します。
Dart
// NavigationBlocクラスは、ナビゲーションイベントとナビゲーション状態を管理するBlocです。
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
  // コンストラクタで初期状態をPageAに設定します。
  <mark style="background-color:#fcb900" class="has-inline-color"><strong>NavigationBloc</strong>() : super(PageA()) {</mark>
    // on<NavigateToPageA>メソッドは、NavigateToPageAイベントが発生したときにPageA状態に遷移するようにします。
    on<NavigateToPageA>((event, emit) => emit(PageA()));  // 状態をPageAに遷移させます。
    // on<NavigateToPageB>メソッドは、NavigateToPageBイベントが発生したときにPageB状態に遷移するようにします
    <mark style="background-color:#7bdcb5" class="has-inline-color">on<<strong>NavigateToPageB</strong>>((event, emit)</mark> => <mark style="background-color:#8ed1fc" class="has-inline-color">emit(PageB()))</mark>;  // 状態をPageBに遷移させます。
  <mark style="background-color:#fcb900" class="has-inline-color">}</mark>
}

(5) 状態変更とページBの表示 (main.dart)

  • BlocBuilderが新しい状態(PageB)を検出し、PageBScreenを表示します。
Dart
class Navigation extends StatelessWidget {
  const Navigation({super.key});

  @override
  Widget build(BuildContext context) {
    // BlocBuilderは、NavigationBlocの状態に応じてUIを再構築
    return <mark style="background-color:#7bdcb5" class="has-inline-color"><strong>BlocBuilder</strong><<strong>NavigationBloc</strong>, NavigationState>(</mark>
      builder: (context, state) {
        if (state is PageA) {
          // stateがPageAのとき、PageAのウィジェットを表示
          return const PageAScreen();
        } else <mark style="background-color:#7bdcb5" class="has-inline-color">if (state is PageB) {</mark>
          // stateがPageBのとき、PageBのウィジェットを表示
          <mark style="background-color:#fcb900" class="has-inline-color">return const PageBScreen();</mark>
        }
        // それ以外の状態(初期状態など)はローディングインジケーターを表示
        return const Scaffold(body: Center(child: CircularProgressIndicator()));
      },
    <mark style="background-color:#7bdcb5" class="has-inline-color">);</mark>
  }
}

(6) ページBの表示 (page_b.dart)

  • PageBScreenが表示されます。
  • ボタンを押すとNavigateToPageAイベントが発行されます。
Dart
class <strong><mark style="background-color:#7bdcb5" class="has-inline-color">PageBScreen </mark></strong>extends StatelessWidget {
  const PageBScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Page B')),
      body: Center(
        child: ElevatedButton(
          <mark style="background-color:#fcb900" class="has-inline-color">onPressed: () {</mark>
            // Blocを通じて(NavigationBlocインスタンス取得して)
            // NavigateToPageAイベントを発行し、
            // Page Aに遷移するように指示します。
            <mark style="background-color:#fcb900" class="has-inline-color">context.read<<strong>NavigationBloc</strong>>().add(<strong>NavigateToPageA</strong>());</mark>
          },
          child: const Text('Go to Page A'),
        ),
      ),
    );
  }
}

(7) イベント処理 (navigation_bloc.dart)

  • NavigationBlocNavigateToPageAイベントを受け取り、状態をPageAに変更します。
Dart
// NavigationBlocクラスは、ナビゲーションイベントとナビゲーション状態を管理するBlocです。
class <strong>NavigationBloc </strong>extends Bloc<NavigationEvent, NavigationState> {
  // コンストラクタで初期状態をPageAに設定します。
  <mark style="background-color:#7bdcb5" class="has-inline-color"><strong>NavigationBloc</strong>() : super(PageA()) {</mark>
    // on<NavigateToPageA>メソッドは、NavigateToPageAイベントが発生したときにPageA状態に遷移するようにします。
    <mark style="background-color:#fcb900" class="has-inline-color">on<<strong>NavigateToPageA</strong>>((event, emit) => emit(<strong>PageA</strong>()));</mark>  // 状態をPageAに遷移させます。
    // on<NavigateToPageB>メソッドは、NavigateToPageBイベントが発生したときにPageB状態に遷移するようにします
    on<NavigateToPageB>((event, emit) => emit(PageB()));  // 状態をPageBに遷移させます。
  <mark style="background-color:#7bdcb5" class="has-inline-color">}</mark>
}

(8) 状態変更とページAの表示 (main.dart)

  • BlocBuilder新しい状態(PageA)を検出し、再びPageAScreenを表示します。
Dart
class Navigation extends StatelessWidget {
  const Navigation({super.key});

  @override
  Widget build(BuildContext context) {
    // BlocBuilderは、NavigationBlocの状態に応じてUIを再構築
    <mark style="background-color:#7bdcb5" class="has-inline-color">return <strong>BlocBuilder</strong><NavigationBloc, NavigationState>(</mark>
      builder: (context, state) {
        if (state is PageA) {
          // stateがPageAのとき、PageAのウィジェットを表示
          return const PageAScreen();
        } else <mark style="background-color:#fcb900" class="has-inline-color has-black-color">if (state is PageB) {</mark>
          // stateがPageBのとき、PageBのウィジェットを表示
         <mark style="background-color:#fcb900" class="has-inline-color"> return const PageBScreen();</mark>
        }
        // それ以外の状態(初期状態など)はローディングインジケーターを表示
        return const Scaffold(body: Center(child: CircularProgressIndicator()));
      },
    <mark style="background-color:#7bdcb5" class="has-inline-color">);</mark>
  }
}

この一連の流れにより、flutter_blocを使用して状態管理と画面遷移を実現しています。イベントが発行されると、Blocがそのイベントを処理し、適切な状態に遷移することで、UIが更新されます。

次回内容> [基礎知識]画面遷移を実現するパッケージ/go_router

go_router は、Googleが開発したパッケージで、シンプルで強力なルーティングシステムを提供し、Flutterアプリケーションの開発を効率的に進めるための優れたツールです。
特にURLベースのナビゲーションを必要とする場合や、Flutter Webとの統合を考慮する場合に非常に有用です。
1.サンプル(動作)
以下は、(前回記事と同じ仕様ですが)2つの画面(PageA画面(page_a.dart)、PageB画面(page_b.dart))を用意し、相互の画面遷移を行うサンプルです。

コメントを残す