開発日誌Vol.1-1/FlutterでログインUIの作成(ログイン機能編)

今日から、日々の開発日誌を記述していきたいと思います。皆さんの参考になれば幸いです。

目次

初期設定

STEP

新規プロジェクトの作成

Flutterで初めてのプロジェクト」を元に、新規プロジェクトを作成します。

STEP

使用パッケージの選択

今回は、下記のパッケージを使用します。

  • flutter_hooks:一つのWidget内でStateを管理するのに便利
  • hooks_riverpod:hooksとRiverPodの両方を使用できる
  • dio:http通信を行うためのパッケージ(httpパッケージよりも便利?)
  • go_router:ルーティングを管理するパッケージ
  • freezed:イミュータブルなモデルクラスを管理するパッケージ
  • json_serializable:jsonを変換するためのパッケージ
  • flutter_state_notifier:状態管理のためのパッケージ(RiverPodで使用)
  • build_runner:コード自動生成のためのパッケージ(dev_dependenciesにインストール)
  • freezed_annotation:freezedを使用するためのパッケージ
STEP

使用パッケージのインストール

下記コマンドをひとつずつ実行して、パッケージをインストールします。

下記コマンドを一つずつ実行してください。

flutter pub add flutter_hooks
flutter pub add hooks_riverpod
flutter pub add dio
flutter pub add go_router
flutter pub add freezed
flutter pub add json_serializable
flutter pub add flutter_state_notifier

flutter pub add --dev build_runner
flutter pub add --dev freezed_annotation

パッケージのインストールや使用方法の詳細は、「Flutterのパッケージ使用方法」を参照してください。

STEP

ディレクトリの作成

下記のようなディレクトリをlibディレクトリの直下に作成します。

  • models:モデル用のディレクトリ
  • providers:Provider保存用のディレクトリ
  • views:画面表示UI用のディレクトリ
STEP

main.dartの修正

main.dartをRiverpodを使えるように下記のように修正し、とりあえず、Hello Worldを表示します。

app/main.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Alcomate',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Alcomate'),
        ),
        body: const Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

main.phpの解説

  • 4〜6行目:これは、Flutterアプリケーションのエントリーポイントです。
    • runApp関数は、アプリケーションのルートウィジェットを定義します。
    • ProviderScopeはhooks_riverpodパッケージの一部で、アプリケーションのトップレベルに配置されます。これにより、アプリ全体でRiverpodのプロバイダを利用できます。
    • MyAppクラスは、アプリケーションのメインウィジェットです。
  • 8〜29行目:MyAppはHookConsumerWidgetを継承しており、これによりRiverPodの状態管理とHooksの機能を利用できます。
    • 13〜27行目:MaterialAppはマテリアルデザインアプリのルートウィジェットです。
    • titleはアプリケーションのタイトルを設定します。
    • 15行目:デバッグバナーを非表示にします。
    • 16〜18行目:themeでアプリの基本テーマを設定します。ここでは、プライマリカラーにブルーを使用しています。
    • 19〜26行目:homeプロパティは、アプリのホームスクリーンを定義します。
    • Scaffoldはマテリアルデザインのレイアウト構造を提供します。
    • appBarにはアプリバー(ヘッダー部分)が含まれ、アプリのタイトルが表示されます。
    • bodyはアプリのメインコンテンツを含みます。ここでは、Center ウィジェットを使用して中央に Text ウィジェットを配置しています。これにより画面中央に「Hello World」というテキストが表示されます。
STEP

assetsの設定

ルートにassetsディレクトリを作成し、その中のimagesディレクトリを作成します。

pubspec.yamlに作成したディレクトリを登録します。

flutter:
  assets:
    - assets/images/
STEP

ロゴ画像の保存と表示

上記で作成したimagesディレクトリにロゴ画像を保存します。

下記コードでロゴを表示します。

Padding(
  padding: const EdgeInsets.symmetric(vertical: 20.0),
  child: SizedBox(
    width: 150.0,
    height: 150.0,
    child: Image.asset('assets/images/winroad_blue_logo.png'),
  ),
),

ログインUIの作成

次に、ログイン用のUIを作成します。

STEP

TextFieldの追加

TextFieldを2つ並べます。

lib/views/login_page.dart
// 省略
Column(
  children: [
    TextField(
      decoration: InputDecoration(
        labelText: 'Email',
      ),
    ),
    TextField(
      decoration: InputDecoration(
          labelText: 'Password',
      ),
      obscureText: true,
    ),
  ],  
),
// 省略
STEP

Elevatedボタンの追加

データ送信用のElevatedButtonをTextFieldの下に追加します。

// 省略
ElevatedButton(
  child: Text('Login'),
  onPressed: () {
    // ボタンタップ時の処理
  },
),
// 省略
STEP

Formウィジェットでラッピング

上記のTextFieldとElevatedButtonをFormウィジェットでラッピングします。

// 省略
final _formKey = GlobalKey<FormState>();
// 省略
Form(
  key: _formKey,
  child: Column(
    children: [
      TextField(
       // 省略 
      ),
      TextField(
       // 省略  
      ),
      ElevatedButton(
       // 省略  
      ),
    ],
  ),
),
// 省略

ポイントは以下の通りです。

  • FormStateのグローバルキー _formKey を定義
  • Formのkeyプロパティに _formKey を設定

このように設定することで、後から _formKey.currentState などでForm全体にアクセスできるようになります。

バリデーション成功可否のチェックやonSavedメソッドの実行などに活用できます。

STEP

ログインUIの完成

下記のようにレイアウトを若干修正して、ログインUIが完成しました。

lib/views/login_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class LoginPage extends HookConsumerWidget {
  LoginPage({Key? key}) : super(key: key);

  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();
  
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 20.0),
            child: SizedBox(
              width: 150.0,
              height: 150.0,
              child: Image.asset('assets/images/winroad_blue_logo.png'),
            ),
          ),
          Form(
            key: _formKey,
            child: Card(
              elevation: 4.0,
              color: Colors.white,
              margin: const EdgeInsets.only(right: 30, left: 30),
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(15)),
              child: Padding(
                padding: const EdgeInsets.all(24.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    TextFormField(
                      controller: emailController,
                      style: const TextStyle(color: Color(0xFF000000)),
                      cursorColor: const Color(0xFF9b9b9b),
                      keyboardType: TextInputType.emailAddress,
                      decoration: const InputDecoration(
                        prefixIcon: Icon(Icons.email, color: Colors.grey),
                        hintText: 'Email',
                      ),
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'Emailを入力して下さい。';
                        }
                        return null;
                      },
                    ),
                    TextFormField(
                      controller: passwordController,
                      style: const TextStyle(color: Color(0xFF000000)),
                      cursorColor: const Color(0xFF9b9b9b),
                      keyboardType: TextInputType.visiblePassword,
                      obscureText: true,
                      decoration: const InputDecoration(
                          prefixIcon: Icon(Icons.vpn_key, color: Colors.grey),
                          hintText: 'Password'),
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'パスワードを入力して下さい。';
                        }
                        return null;
                      },
                    ),
                    const SizedBox(
                      height: 30,
                    ),
                    SizedBox(
                      height: 40,
                      child: ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            // Todo ログイン用のメソッド作成
                          }
                        },
                        child: const Text(
                          'ログイン',
                          style: TextStyle(fontSize: 18),
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),
          ),
          const Padding(
            padding: EdgeInsets.all(12.0),
            child: Text('パスワードを忘れた方はこちら'),
          ),
        ],
      ),
    );
  }
}

レイアウトは下記のようになっています。

STEP

ログインUIの解説

コードの詳細は、下記を参考にしてください。

  • 4行目:LoginPageクラスは、HookConsumerWidgetを継承しています。これはFlutterのRiverpodライブラリによる状態管理機能を使用するためのクラスです。
  • 5行目:コンストラクタでは、オプショナルなkeyを受け取り、基底クラスのコンストラクタに渡しています。
  • 7〜8行目:emailControllerとpasswordControllerはユーザーの入力を保持するための変数です。
  • 10行目:_formKeyはFormウィジェットの状態を管理するためのキーです。
  • 13行目:buildメソッドは、ウィジェットのレイアウトを定義します。
  • 14行目:Scaffoldは、アプリの基本的なビジュアルレイアウト構造を提供します。
  • 15行目:Columnは、子ウィジェットを垂直(縦)に並べるためのウィジェットです。
  • 17行目:Paddingは、ウィジェットにパディング(余白)を追加します。
  • 19行目:sizedBoxは、特定のサイズを持つボックスを作成し、ここではロゴの画像を表示しています。
  • 25行目:Formウィジェットは、フォームの入力を管理します。
  • 27行目:Cardウィジェットは、内容をカード形式で表示し、視覚的な区別をつけるために使用されます。
  • 38行目、54行目:TextFormFieldは、ユーザーがメールアドレスとパスワードを入力するためのフィールドです。各フィールドには検証ロジックが含まれており、入力が空でないことを確認します。
  • 75行目:ElevatedButtonは、ユーザーがフォームを送信するためのボタンです。このボタンはフォームの状態が有効であればアクションを実行します(現在は、アクションは定義されていませんが、あとで、ログイン用のメソッドを定義します)。
STEP

main.dartの修正

最後に、とりあえずトップに上記のログインページが表示されるように、main.dartを下記のように修正しています。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:win7jp/views/login_page.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Alcomate',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('WinRoad'),
        ),
        body: LoginPage(),
      ),
    );
  }
}
  • 3行目:login_pageをインポートしています。
  • 24行目:bodyプロパティにLoginPage()ウィジェットを配置しています。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次