🙄

初めてFlutterを触ってみた- Flutter × Laravel 認証- ④

2024/10/17に公開

初めてFlutterを触ってみた④

過去の記事の続きになります。

この記事では、Flutterの画面遷移や、これまで触れてこなかったFlutterライブラリについて、ゆるく解説します。

Flutterのスタックについて

Flutterの画面遷移は「スタック」を使って管理され、操作は主に以下の3つに分類されます:

1. Push操作

新しい画面をスタックに積み上げる。

初期スタック状態:
+-----------+
|   画面A   | ← 現在表示中の画面
+-----------+

↓ Push操作

実行後スタック状態:
+-----------+
|   画面B   | ← 新しく追加された画面
+-----------+
|   画面A   | ← スタックに残る
+-----------+

2. Pop操作

現在の画面を取り除き、前の画面に戻る。

初期スタック状態:
+-----------+
|   画面B   | ← 現在表示中の画面
+-----------+
|   画面A   |
+-----------+

↓ Pop操作

実行後スタック状態:
+-----------+
|   画面A   | ← 表示される画面に戻る
+-----------+

3. Replace操作

現在の画面を新しい画面に置き換える。

初期スタック状態:
+-----------+
|   画面A   | ← 現在表示中の画面
+-----------+

↓ Replace操作

実行後スタック状態:
+-----------+
|   画面C   | ← 置き換え後の画面
+-----------+

画面遷移の方法

1. Navigator.push()

  • 分類: Push操作
  • 解説: 現在の画面の上に新しい画面を積み上げる。元の画面はスタックに保持されます。
  • 使用例:
    Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => NewScreen()),
    );
    
  • 図解:
    初期スタック状態:
    +-----------+
    | Dashboard | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => NewScreen()),
      );
    
    実行後スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    | Dashboard |
    +-----------+
    |   Home    |
    +-----------+
    
  • ケース: 詳細画面への遷移や、ウィザード形式の次のステップに進む場合など。

2. Navigator.pop()

  • 分類: Pop操作
  • 解説: 現在の画面を取り除き、1つ前の画面に戻る。取り除いた画面はスタックから削除されます。
  • 使用例
    Navigator.pop(context);
    
  • 図解
    初期スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    | Dashboard |
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.pop(context);
    
    実行後スタック状態:
    +-----------+
    | Dashboard | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
  • ケース:詳細画面からリスト画面に戻るときなど。

3. Navigator.pushReplacement()

  • 分類: Replace操作
  • 解説: 現在の画面を削除し、新しい画面に置き換える。元の画面はスタックに残りません。
  • 使用例:
    Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => NewScreen()),
    );
    
  • 図解
    初期スタック状態:
    +-----------+
    | Dashboard | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => NewScreen()),
      );
     
    実行後スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
  • ケース:認証が完了した後、ホーム画面に移動する際など。

4. Navigator.pushAndRemoveUntil()

  • 分類: Push操作 + 条件付きPop操作
  • 解説: 新しい画面を積み上げると同時に、指定条件に合うまでスタック内の画面を削除します。
  • 使用例:
    Navigator.pushAndRemoveUntil(
      context,
      MaterialPageRoute(builder: (context) => NewScreen()),
      (Route<dynamic> route) => route.isFirst,
    );
    
  • 図解:
    初期スタック状態:
    +-----------+
    | Dashboard | ← 現在表示中の画面
    +-----------+
    |   Login   |
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.pushAndRemoveUntil(
        context,
        MaterialPageRoute(builder: (context) => NewScreen()),
        (Route<dynamic> route) => route.isFirst,
      );
    
    実行後スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
  • ケース: ユーザーがログインした後、ログイン画面に戻れないようにする場合。

5. Navigator.popUntil()

  • 分類: 条件付きPop操作
  • 解説: 条件に合うまでスタックから画面を取り除き、指定の画面に戻ります。
  • 使用例:
    Navigator.popUntil(context, (route) => route.isFirst);
    
  • 図解:
    初期スタック状態:
    +-----------+
    | Settings  | ← 現在表示中の画面
    +-----------+
    | Dashboard |
    +-----------+
    |   Login   |
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.popUntil(context, (route) => route.isFirst);
    
    実行後スタック状態:
    +-----------+
    |   Home    | ← 現在表示中の画面
    +-----------+
    
  • ケース: 複数の画面を経由してホーム画面に戻る場合。

6. Navigator.pushNamed()

  • 分類: Push操作
  • 解説: ルート名を使用して、新しい画面をスタックに積み上げます。
  • 使用例:
    Navigator.pushNamed(context, '/newScreen');
    
  • 図解:
    初期スタック状態:
    +-----------+
    | Dashboard | ← 現在表示中の画面
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.pushNamed(context, '/newScreen');
    
    実行後スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    | Dashboard |
    +-----------+
    |   Home    |
    +-----------+
    
  • ケース: 画面遷移の際に名前付きルートを使いたい場合。

7. Navigator.pushNamedAndRemoveUntil()

  • 分類: Push操作 + 条件付きのPop操作
  • 解説: ルート名を使って新しい画面を追加し、条件に合うまでスタックを削除します。
  • 使用例:
    Navigator.pushNamedAndRemoveUntil(context, '/newScreen', (route) => false);
    
  • 図解:
    初期スタック状態:
    +-----------+
    | Settings  | ← 現在表示中の画面
    +-----------+
    | Dashboard |
    +-----------+
    |   Login   |
    +-----------+
    |   Home    |
    +-----------+
    
    ↓ Navigator.pushNamedAndRemoveUntil(context, '/newScreen', (route) => false);
    
    実行後スタック状態:
    +-----------+
    | NewScreen | ← 現在表示中の画面
    +-----------+
    
  • ケース: ナビゲーションをリセットして新しい画面に遷移する場合。

試してみたFlutterライブラリ

1. flutter_dotenv

flutter_dotenvは、.envファイルを使って環境変数をFlutterアプリに読み込むライブラリです。APIキーやURLなど、アプリにハードコーディングしないほうが良い情報を管理するのに役立ちます。

以下は、flutter_dotenvを使用してAPIのエンドポイントURLを設定する例です。

1.1 .envファイルの設定

.envファイルをプロジェクトのルートディレクトリに作成し、APIのベースURLを定義します。
これは、ハードコーディングを避け、環境ごとに異なる設定を簡単に切り替えるためです。

.env
BASE_API_URL='http://localhost/api'

1.2 dotenvパッケージの読み込み

アプリの起動時に.envファイルを読み込むために、main.dartdotenv.load()を呼び出します。

main.dart
void main() async {
  // .envファイルの読み込み
  await dotenv.load(fileName: ".env");
  runApp(ChangeNotifierProvider(
    create: (conntext) => AuthProvider(),
    child: const MyApp(),
  ));
}

1.3 環境変数を定数として定義

.envファイルから読み込んだ環境変数をAppConstクラスに定義してアプリ内で使用します。
例えば、APIリクエストの際にベースURLとして定義するコードです。

constants/app_const.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';

class AppConst {
  static final String baseApiUrl = dotenv.env['BASE_API_URL'] ?? 'http://localhost:3000/api';
  ...
}

上記のコードでは、dotenv.env['BASE_API_URL'].envファイルのBASE_API_URLを取得します。
もし取得できなかった場合には、デフォルトのURL(例:http://localhost:3000/api)を使うようにしています。

1.4 実際の使用例

例えば、ServiceUtilsクラスでAPIのベースURLを使ってリクエストを行うときに利用できます。

services/auth_service.dart
import 'package:flutter_sample/src/constants/app_const.dart';
import 'package:http/http.dart' as http;
...

class ServiceUtils {
  
  static final String baseApiUrl = AppConst.baseApiUrl;
  ...
  // POSTリクエスト
  static postRequest(String uri, String? token, String? data) async {
    final url = Uri.parse('$baseApiUrl/$uri');
    final headers = getHeaders(token);
    final response = await http.post(
      url,
      headers: headers,
      body: data,
    );
    return response;
  }
    
}

2. shimmer

shimmerは、読み込み中のプレースホルダーとして、光の反射のようなアニメーションを表示するために使われます。
データがロードされるまでの間に、ユーザー体験を向上させることができます。

今回は、HomeScreenでデータを取得するまでの間、ローディングプレースホルダーとしてshimmerを使用しました。以下は具体的な実装例です。

2.1 HomeScreenのローディング時にshimmerを使用

データがまだロード中の場合は、ListViewで複数のHomeCardプレースホルダーを表示します。
データがロードされたら、実際のデータを表示するようにしています。

screens/home/home_screen.dart
  
  Widget build(BuildContext context) {
    // AuthProviderの状態に応じた処理
    return Scaffold(
      body: _isLoading 
        ? ListView.builder(
            itemCount: 4,
            itemBuilder: (context, index) => const HomeCard(), // shimmer使用Widget
        ) : ListView.builder(
          controller: _scrollController,
          itemCount: data.length + (_isLoadingMore ? 1 : 0), // ローディング中のインジケーター用に1アイテム追加
          itemBuilder: (context, index) {
            if (index < data.length) {
              // 通常のデータをリスト表示
              return _buildHomeCard(
                data[index].image,
                data[index].title,
                data[index].id
              );
            } else {
              // 追加データ取得中のローディングインジケーター
              return const Padding(
                padding: EdgeInsets.all(8.0),
                child: Center(child: CircularProgressIndicator()),
              );
            }
          },
        ),
    );    
  }

2.2 HomeCardウィジェットでshimmerアニメーションの表示

HomeCardウィジェットでは、Shimmer.fromColorsを使用して、プレースホルダーに光の反射アニメーションを追加しています。
baseColorhighlightColorを指定することで、煌めきの色味や強調効果を調整することが可能です。

widgets/home_cart.dart
  Widget _buildShimmerEffect() {
    return Shimmer.fromColors(
      baseColor: Colors.grey.withOpacity(0.2), // 煌めきの色
      highlightColor: Colors.white,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
      child: Container(
        width: width, // お好みのサイズ
        height: height, // お好みのサイズ
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8.0),
          boxShadow: [
            BoxShadow(
              color: Colors.grey.withOpacity(0.5),
              spreadRadius: 1,
              blurRadius: 7,
              offset: const Offset(0, 3),
            ),
          ]
        ),
        child: const Center(
          child: Text(
            'Loading...',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
              color: Colors.black,
            ),
          ),
        ),
      ),
      ),
    );
  }

3. flutter_html

flutter_htmlは、Flutterウィジェット内でHTMLコンテンツをレンダリングするためのライブラリです。

今回の例では、データベースに保存されたアイテムの説明文にHTMLタグを含めておき、HomeDetailScreenでその説明文を表示する際にflutter_htmlを使用しました。
説明文に含まれるスタイル(例:文字色の変更)が正しく反映されるかを試しています。

3.1 Laravelでのデータ準備

Laravel側では、ItemTableSeederを使用してアイテム情報をデータベースに保存しました。
説明文には、HTMLタグ(例:<div style='color: red;'>)を含め、レンダリング時にそのスタイルが反映されるようにしています。

database/seeders/ItemTableSeeder.php
namespace Database\Seeders;

use Carbon\Carbon;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ItemTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        for ($i = 1; $i <= 10; $i++) {
            DB::table('items')->upsert([
                'id' => $i,
                'title' => "Item {$i}",
                'image' => "https://placehold.jp/ff006f/ffffff/150x150.png?text=Item{$i}",
                'description' => "Description of Item {$i}\n
                    <div style='color: red;'>This is a sample description.</div>\n", //説明文
                'created_at' => Carbon::now(),
                'updated_at' => Carbon::now(),
            ], [
                'id'
            ], [
                'title',
                'image',
                'description',
                'created_at',
                'updated_at',
            ]);
        }

    }
}

3.2 FlutterでのHTML表示

HomeDetailScreenで、サーバーから取得したアイテムデータをflutter_htmlライブラリを使って表示します。
Htmlウィジェットを利用することで、説明文に含まれるHTMLタグが解釈され、スタイルが適用されます。

screens/home/home_detail_screen.dart
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _isLoading
          ? const CircularProgressIndicator()
          : Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Image.network(_data.image),
              const SizedBox(height: 20),
              Text(
                _data.title,
                style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 10),
              Html(
                data: _data.description, // HTML形式の説明文を表示
              ),
            ],
          ),
      ),
    );
  }

まとめ

今回は、簡単にですが、画面遷移の方法と触れられてなかったライブラリの話をしました。
また何か思いついたら、この記事を更新していこうと思います。
Flutterに詳しい方や初心者の方からのアドバイスも大歓迎ですので、ぜひコメントいただけると嬉しいです!

Discussion