わんぱく Flutter! 第五回 Firebase にログイン!

f:id:Akihiro_Kashiwagi:20210416102435j:plain

わんぱく Flutter! 第五回 Firebase にログイン!

 

 

 

 

 第四回で画像認識に使用した Firebase ですが、主な機能は機械学習ではなく、〜base と付いているだけあって、データベースが中心のサービスです。今回はその Firebase を使用する為のにログイン機能を組んでみます。 とは言っても、初期化して ID、Password を API に渡すだけですから、簡単です。しかし、Firebase も Flutter のパッケージも日々、更新されていますので、使い方がどんどん変わっています。今回も、最新の情報に気をつけて作成して下さい。この記事は、2021年4月24日の情報を元に執筆しています。

 

 

1. Firebase の設定

 こちらは、「第四回 いきなりっ! 画像認識をしてみよう。 - 1.Firebase を使えるようにする」を参考に、Firebase のウェブサイトから、新しく Firebase にプロジェクトを作って下さい。或いは、第四回で使用したプロジェクトを流用して、Flutter のプログラムファイル main.dart のファイル名を何か別の名前に変更して、新しく main.dart ファイルを作ってもいいです。

 

今回は firebase_ml_vision は使用しないので、firebase_ml_visionAPI の設定などは必要ありませんが、特にしてあっても問題はありません。気を付けなければならない点としては、Flutter のパッケージ名を Firebase 側の設定と合わせる事と、google-service.json を app レベルに保存しておくことでしょうか。

 

 

 

2. Firebase Authentication の準備

 FIrebase の設定が終わったら、Firebase の認証機能を使えるようにします。Firebase のウェブサイトから、今回のプログラムに使用する Firebase のプロジェクトを開いて、"Authentication" をクリックします。

 

f:id:Akihiro_Kashiwagi:20210424175658j:plain

figure 01

 

そうすると、「始める」ボタンがあると思いますので、それをクリックして "Authentication" - "Sign-in method" のタブを開きます。

 

f:id:Akihiro_Kashiwagi:20210424180559j:plain

figure 02

 

今回は、「メール/パスワード」でのログインを作成するので、一番上のプロバイダ「メール/パスワード」を「有効」にします。

 

f:id:Akihiro_Kashiwagi:20210424181048j:plain

figure 03

 

有効にしたら、"Users" タブから新しい「ユーザーを追加」します。メールアドレスとパスワードの組み合わせは、今、ログインしているアカウントではなくても大丈夫です。今回のプログラムで使用するものです。

 

f:id:Akihiro_Kashiwagi:20210424181617j:plain

figure 04

 

これで Firebase のウェブサイト側の準備はできました。そうしたら、Flutter のパッケージ firebase_auth: ^1.1.1 を pubspec.yaml に設定して、Pub get して下さい。

 

dependencies:
flutter:
sdk: flutter


# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
firebase_core: ^1.0.3
firebase_auth: ^1.1.1

 

ここでも、パッケージのバージョン番号に気を付けてください。2021年4月24日現在では、上記バージョンが最新となっています。

 

 

 

3. Firebase にログインする

 AndroidStudio が自動的に作成している main.dart を下記の通り変更します。全文を載せているので、丸々入れ替えてしまっても構いません。

 

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';   // <== 追加
import 'package:firebase_auth/firebase_auth.dart';   // <== 追加

final FirebaseAuth auth = FirebaseAuth.instance; // <== 追加

void main() async{ // <== 変更

WidgetsFlutterBinding.ensureInitialized();      // <== 追加
await Firebase.initializeApp();             // <== 追加

runApp(MyApp());
}

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'login',     // <== 変更
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Login'),    // <== 変更
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.

// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

String USER_ID;                     // <== 追加
String PASS;                       // <== 追加
String ErrorMessage = "ログインして下さい。";     // <== 追加

void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}

@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(               // <== TextWidget を修正
width: 300.0, height: 50.0,
child: Text( ErrorMessage ),
),
/*        // <== この TextWidget はコメントアウト
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
*/
Container(          // <== この後の Widgets は追加
width: 300.0, height: 50.0,
child: new TextField(
enabled: true, maxLength: 50, style: TextStyle(color: Colors.black),
obscureText: false, maxLines:1 ,
onChanged: (String value){
USER_ID = value;
},
decoration: const InputDecoration(
icon: Icon(Icons.face),
hintText: 'メールアドレスを入力してください。',
labelText: 'e-mail address:',
),
),
),
Container(
width: 300.0, height: 50.0,
child: new TextField(
enabled: true, maxLength: 20, style: TextStyle(color: Colors.black),
obscureText: true, maxLines:1,
onChanged: (String value){
PASS = value;
},
decoration: const InputDecoration(
icon: Icon(Icons.lock_open),
hintText: 'パスワードを入力してください。',
labelText: 'passowrd:',
),
),
),
Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () async {
try {
final result = await auth.signInWithEmailAndPassword(email: USER_ID,password: PASS);

setState(() {
ErrorMessage = "ログイン成功";
});

} catch (e) {
setState(() {
ErrorMessage = "ログインできません。";
});
}
}
)
)
],
),
),
);
}
}

 

 

簡単に説明すると、まず、Firebase にログインするために必要な dart ファイル 2つを import します。import とは、英語の意味の通り、プログラムを読み込んでくると言う意味です。

 

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';

 

次に、FIrebase のインスタンスを作成しています。インスタンスとは、実体といった意味ですが、プログラムをメモリに読み込んできて、使える状態にすると言う意味です。

 

final FirebaseAuth auth = FirebaseAuth.instance;

 

そして、main() 関数から 2つのAPI を呼び出していますが、これは Firebase を使用する為の初期化(initialize)関数です。言い換えると、Firebase を使うための初期設定です。

 

void main() async{

WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();

runApp(MyApp());
}

 

main() 関数の最初の行に "async" と言う表記がありますが、これは、直ぐ下にある "await" と組み合わせて使う機能です。英語の asynchronize と await そのままの意味ですが、この大かっこの中は「非同期」処理であること、そして await のところは処理が終わるのを「待つ」という事になります。ですから、await の所の初期化処理が終わってから、runApp() つまり、プログラムを開始します。

 

次に、プログラムのタイトルを、ちょこちょこっと変更しています。

 

Widget build(BuildContext context) {
return MaterialApp(
title: 'login',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Login'),
);
}

 

_MyHomePageState Class の最初では、ログインに使用する文字列変数を 3つ宣言しています。これらも呼んで字のごとくです。

 

String USER_ID;
String PASS;
String ErrorMessage = "ログインして下さい。";

 

後は、エラーメッセージを表示する為に既存の Text Widget を修正・コメントアウトし、ID、Password 用に TextField Widget を 2つと、ログイン実行用に ElevatedButton Widget を追加しています。これらは、「第二回 Widget を試してみよう!」を参照しながら、上記 main.dartソースコードを直接参照してみて下さい。

 

ElevatedButton では、"onPressed" の処理(ボタンが押された時)に、ログイン用 API である signInWithEmailAndPassword() を呼び出しています。これは、冒頭で作成したインスタンス FirebaseAuth.instance の機能なので、auth.sign... と記述されています。

 

onPressed: () async {
try {
final result = await auth.signInWithEmailAndPassword(email: USER_ID,password: PASS);

setState(() {
ErrorMessage = "ログイン成功";
});

} catch (e) {
setState(() {
ErrorMessage = "ログインできません。";
});
}
}

 

ID と Password を引数に指定して呼び出しているだけですから、特に難しいことはありません。 "async" と "await" は、先に説明したのと同じく、処理が終わるのを待ってから、次の処理に移ることを意味しています。"try" と "catch" についてですが、これは例外処理と言って、エラーが発生した時にどうするかを指定しています。つまり、"try" の大かっこで囲まれた中の処理で、エラーが発生した時には、"catch" の大かっこで囲まれた処理を行います。ここでは、「ログインできません」というメッセージを ErrorMessage 変数にセットします。

 

setState() ですが、これは、Flutter でのフレームワーク(決められた書き方)の一つで、この中での処理が実行されたら、Widget などを実行後の新しい値で更新することを意味しています。setState() の中で更新しなければ、変更内容は直ぐに反映されません。

 

では、実行してみましょう。

 

 

f:id:Akihiro_Kashiwagi:20210424193105p:plain

figure 05

 

 

正しく動作しているようでしたら、最初に設定したメールアドレスとパスワードを入力して "login" ボタンを押すとログイン処理が実行されます。

 

 

f:id:Akihiro_Kashiwagi:20210424193446p:plain

figure 06

 

もしかすると、最初はなかなか上手く動かないかもしれません。何か間違いがないか根気強く確認してみて下さい。結構、ちょっとしたミスでエラーになっています。build.grade に設定したパッケージ名は、Firebase の設定と同じになっているでしょうか。google-service.json は、app レベルのディレクトリに保存してあるでしょうか。

 

 

以上