中安拓也のブログ

中安拓也がプログラミングについて書くブログ

【Angular】リダイレクトモードでFirebase認証を行う

f:id:l08084:20200930195042p:plain

はじめに

FirebaseのSNS認証には、ポップアップウィンドウを表示するポップアップモードとログインページにリダイレクトするリダイレクトモードの2種類の形式があります。

今回は、後者のリダイレクトモードの実装方法について説明します。

環境

ハイブリットモバイルアプリ用フレームワークであるIonic(Angular)を使用してアプリを作成しています。

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

リダイレクトモードでFirebase認証

FirebaseのTwitter認証をリダイレクトモードで実装していきます。なお、モバイルデバイスの認証ではポップアップモードではなくリダイレクトモードが推奨されています。

リダイレクトモードでは、signInWithRedirectを呼び出します。Twitter認証後(リダイレクト後)には、getRedirectResultが呼び出されます。

下記のコードはFirebase認証を呼び出すサービスクラスです。ログイン画面から呼び出されます。

authentication.service.ts

export class AuthenticationService {
  constructor(public afAuth: AngularFireAuth) {}

  /**
   * Twitter認証を呼び出す。
   * 認証成功時にリダイレクトする。
   *
   * @returns {Promise<void>}
   * @memberof AuthenticationService
   */
  public signInWithTwitter(): Promise<void> {
    return this.afAuth.signInWithRedirect(
      new firebase.auth.TwitterAuthProvider()
    );
  }

  /**
   * リダイレクト後の処理。
   *
   * @returns {Promise<firebase.auth.UserCredential>}
   * @memberof AuthenticationService
   */
  public getRedirectResult(): Promise<firebase.auth.UserCredential> {
    return this.afAuth.getRedirectResult();
  }
}

下記のコードはログイン画面のコンポーネントクラスです。

login.page.ts

export class LoginPage implements OnInit {
  constructor(
    private authenticationService: AuthenticationService,
    private router: Router
  ) {}

  ngOnInit() {
    this.getRedirectResult();
  }

  /**
   * Twitterで認証する。
   *
   * @memberof LoginPage
   */
  public async signInWithTwitter() {
    await this.authenticationService.signInWithTwitter();
  }

  /**
   * リダイレクト後に呼び出される処理。
   *
   * @private
   * @memberof LoginPage
   */
  private async getRedirectResult() {
    const result: firebase.auth.UserCredential = await this.authenticationService.getRedirectResult();
    try {
      if (result.user != null) {
        this.router.navigate(['/weight/tabs/tab1']);
      }
    } catch (error) {
      console.log(error);
    }
  }
}

上記のコードで行っているのは、下記の内容です。

  1. ユーザーがsignInWithTwitter()でログインします。
  2. Twitter でログインを行うため、signInWithRedirect()メソッドによってリダイレクトがトリガーされます。
  3. Twitterログインをすると、ユーザーはログイン画面のコンポーネントに戻されます。
  4. ユーザーのログインは、ログイン画面のngOnInit()内のgetRedirectResult()で返される Promise によって解決されます。
  5. navigate()メソッドで、ルーターがユーザーを/weight/tabs/tab1に移動させます。

ポップアップモードのFirebase認証

ポップアップモードによるFirebaseのTwitter認証は下記の記事を参照してください。

Angular + Firebase でTwitter認証 - 中安拓也のブログ

参考サイト

JavaScript で Google ログインを使用して認証する  |  Firebase

Google Developers Japan: Angular コンポーネントから Route 特有のコードを綺麗にする

【Ionic v5】Colorsに色を追加してボタンに適用する

f:id:l08084:20200927151329p:plain

はじめに

f:id:l08084:20200927151907p:plain
Ionicには9つのデフォルトカラーがある

Ionicにはprimary, secondary...といったように9つのデフォルトカラーがあり、color属性を使用することで任意の色をIonicのコンポーネントに適用することができます。

今回の記事では、デフォルトカラー以外の色を使用するために、色をcolorに追加する方法を説明します。

環境

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

配色の追加

今回は、Twitterのログインボタンを作るために、Twitterカラー(#00acee)をIonicの配色に追加します。

Step1: レイヤードスタイルの作成

まず、New Color Creatorを使用して、追加したい色のレイヤードスタイルを作成します。

f:id:l08084:20200927170241p:plain
New Color Creator

新色Twitter色を作成しました。

New Color Creatorで作成したレイヤードスタイル

:root {
  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0,172,238;
  --ion-color-twitter-contrast: #000000;
  --ion-color-twitter-contrast-rgb: 0,0,0;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;
}

.ion-color-twitter {
  --ion-color-base: var(--ion-color-twitter);
  --ion-color-base-rgb: var(--ion-color-twitter-rgb);
  --ion-color-contrast: var(--ion-color-twitter-contrast);
  --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
  --ion-color-shade: var(--ion-color-twitter-shade);
  --ion-color-tint: var(--ion-color-twitter-tint);
}

ボタンのラベルとアイコンを黒ではなく白にしたいので、--ion-color-twitter-contrast--ion-color-twitter-contrast-rgbを黒から白にします。

New Color Creatorで作成したレイヤードスタイル(修正版)

:root {
  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0, 172, 238;
  --ion-color-twitter-contrast: #ffffff;
  --ion-color-twitter-contrast-rgb: 255, 255, 255;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;
}

.ion-color-twitter {
  --ion-color-base: var(--ion-color-twitter);
  --ion-color-base-rgb: var(--ion-color-twitter-rgb);
  --ion-color-contrast: var(--ion-color-twitter-contrast);
  --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
  --ion-color-shade: var(--ion-color-twitter-shade);
  --ion-color-tint: var(--ion-color-twitter-tint);
}

Step2: SCSSファイルにレイヤードスタイルを追加

続いて、作成したレイヤードスタイルをSCSSファイルに転記していきます。

まず、variables.scssファイルの:root内部にレイヤードスタイルの上部を転記します。

src\theme\variables.scss

  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0, 172, 238;
  --ion-color-twitter-contrast: #ffffff;
  --ion-color-twitter-contrast-rgb: 255, 255, 255;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;

次に、global.scssにレイヤードスタイルの下部を転記します。

src/global.scss

.ion-color-twitter {
    --ion-color-base: var(--ion-color-twitter);
    --ion-color-base-rgb: var(--ion-color-twitter-rgb);
    --ion-color-contrast: var(--ion-color-twitter-contrast);
    --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
    --ion-color-shade: var(--ion-color-twitter-shade);
    --ion-color-tint: var(--ion-color-twitter-tint);
}

Step3: ボタン作成

Step1とStep2で配色の追加は完了したので、追加した色を使用して、Twitterのログインボタンを作成します。

      <ion-button color="twitter" class="login-button">
        <ion-icon class="sns-icon" name="logo-twitter"></ion-icon>Twitterでログイン
      </ion-button>

color="twitter"という風に指定してあげることで、Step1, 2で作成したTwitterカラーがボタンに反映されます。

f:id:l08084:20200927195348p:plain
作成したTwitterボタン

参考サイト

配色 - Ionic Framework 日本語ドキュメンテーション

Ionic 4: How to add more colors and use them as color in buttons and more? | by Paul Stelzer | Medium

【Ionic v5】テーブル内でラジオボタンを使用する

f:id:l08084:20200921011913p:plain
テーブル内でラジオボタンを使用する

はじめに

テーブルのセル内でIonicのラジオボタンを使用する方法がわからず、手間取ってしまったため、メモとして残した。

環境

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

通常のIonicのラジオボタン

f:id:l08084:20200921021014p:plain
普通のIonicのラジオボタン

普通にIonicのラジオボタンを使用する場合は、次のようにコードを書く。

  <!-- 通常のラジオボタン -->
  <ion-list>
    <ion-radio-group [(ngModel)]="selectedAnimal">
      <ion-item>
        <ion-label>Dog</ion-label>
        <ion-radio value="dog"></ion-radio>
      </ion-item>
      <ion-item>
        <ion-label>Cat</ion-label>
        <ion-radio value="cat"></ion-radio>
      </ion-item>
      <ion-item>
        <ion-label>Fish</ion-label>
        <ion-radio value="fish"></ion-radio>
      </ion-item>
    </ion-radio-group>
  </ion-list>

1組のion-list, ion-radio-group内に、ラジオボタンの数だけ、ion-radioを定義するとラジオボタンが作成される

テーブルのセル内でion-radioを使用する

f:id:l08084:20200921021614p:plain
テーブルのセル内にラジオボタンを作成した場合

テーブルのセル内にラジオボタンを定義したい場合は、次のようにコードを書く。

  <div class="radio-button-table">
    <!-- テーブル -->
    <table>
      <thead>
        <tr>
          <th>
            Apple
          </th>
          <th>
            Grape
          </th>
          <th>
            Cherry
          </th>
        </tr>
      </thead>
      <tbody>
        <!-- ラジオボタン -->
        <tr>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="apple"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="grape"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="cherry"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
        </tr>
      </tbody>
    </table>
  </div>
  <!-- 選択したラジオボタンの値が表示される -->
  <div>{{selectedFruit}}</div>

普通にラジオボタンを使用する場合と違って、ラジオボタン一つにつき、ion-list, ion-radio-groupもそれぞれ書いてあげる必要がある。

参考サイト

ion-radio - Ionic Documentation

【競プロ】[C++/TypeScript]B - Lucas Numberが解けない

はじめに

競技プログラミングする前にC++の勉強しよ〜〜と思ってC++入門 AtCoder Programming Guide for beginners (APG4b)をやっていたんですが、その中で出題された「AtCoder Beginner Contest 079」の「B - Lucas Number」が解けない。。。。何度やっても実行時間制限超過(TLE)になり、不正解になってしまう。

「B - Lucas Number」 とは

1 ~ 86番目のリュカ数を計算するソースコードを書きなさい。という問題になります。 問題の詳細については、下記リンクを参照

atcoder.jp

間違った回答

TypeScriptで回答して、実行時間制限超過(TLE)になったあとTypeScriptよりも早い言語(C++)なら大丈夫かな?と思ってC++でも解いてみたんですが、どのみち実行時間制限超過(TLE)になりました。

TypeScriptでの回答

import * as fs from "fs";

const input = fs.readFileSync("/dev/stdin", "utf8");
const a = +input;

console.log(lucasNumber(a));

function lucasNumber(n: number): number {
  if (n === 0) {
    return 2;
  } else if (n === 1) {
    return 1;
  } else {
    return lucasNumber(n - 1) + lucasNumber(n - 2);
  }
}

C++での回答

#include <iostream>
using namespace std;

int64_t lucas_number(int64_t n)
{
    if (n == 0)
    {
        return 2;
    }
    else if (n == 1)
    {
        return 1;
    }
    else
    {
        return lucas_number(n - 1) + lucas_number(n - 2);
    }
}

int main()
{
    // 整数の入力
    int64_t n;
    cin >> n;
    cout << lucas_number(n) << endl;
    return 0;
}

問題文を読んで、はは〜ん再帰だな?と思って再帰を使って解いてみたんですが、実行時間制限超過(TLE)で不正解に....

f:id:l08084:20200911215717p:plain
2200msかかってTLEで不正解に

C++での正しい回答

再帰を使うのをやめて、配列を使うと、実行時間制限内で計算が完了して、正解になりました。

C++での回答

#include <bits/stdc++.h>
using namespace std;

int main()
{
    // 整数の入力
    int N;
    cin >> N;
    vector<int64_t> L(N + 1);

    if (N == 1)
    {
        L.at(N) = 1;
    }
    else
    {
        L.at(0) = 2;
        L.at(1) = 1;
        for (int i = 2; i <= N; i++)
        {
            L.at(i) = L.at(i - 1) + L.at(i - 2);
        }
    }
    cout << L.at(N) << endl;
    return 0;
}

86番目のリュカ数は、939587134549734843になるため、int型で表せる2147483647 を超えてしまいます。そのため、int型ではなくint64_t型を使用する必要があります。

f:id:l08084:20200913183424p:plain
再帰をやめると実行時間が1/200くらいになって正解になった

TypeScriptでの正しい回答

TypeScriptでも再帰をやめると、実行時間制限以内に計算が終わるようになります。

import * as fs from "fs";

const input = fs.readFileSync("/dev/stdin", "utf8");
const N = +input;

const L: BigInt[] = Array(N + 1);

if (N === 1) {
  L[N] = BigInt(1);
} else {
  L[0] = BigInt(2);
  L[1] = BigInt(1);

  for (let i = 2; i <= N; i++) {
    L[i] = BigInt(L[i - 1]) + BigInt(L[i - 2]);
  }
}

console.log(String(L[N]));

TypeScript(JavaScript)のnumber型で表現できる最大値は1.8×10^308であるため、正確な値を計算するためには、BigIntを使用する必要があります。

参考サイト

JavaScript/TypeScriptで競技プログラミングをするには 後編 | わたしろぐ

TypeScriptのBigIntを触ってみる | mrsekutの備忘録

BigInt - JavaScript | MDN

【競プロ】TypeScriptでAtCoderをやる

はじめに

仕事でいつも使っているTypeScriptを競技プログラミング(AtCoder)でも使う方法について調べました。

AtCoderのTypeScriptの対応状況

AtCoderはTypeScriptに対応しているため、言語のセレクトボックスでTypeScript(3.8)を選択してあげればOKです。

f:id:l08084:20200906185552p:plain
AtCoderはTypeScriptに対応している

標準入力をどうやるか

競技プログラミングだと必須である標準入力をTypeScriptでどうやればいいのか調べました。 C++だとcinで済む標準入力ですが、TypeScriptだと下記のように書いてあげる必要があります。

import * as fs from 'fs';

const input = fs.readFileSync("/dev/stdin", "utf8");

標準入力の仕方がわかれば、あとは計算結果をおなじみのconsole.logで出力するだけになります。続いて、実際に簡単な問題を解いてみます。

実際に問題を解いてみる

提出の練習として出題される問題であるA - Welcome to AtCoderをTypeScriptで解いてみます。

f:id:l08084:20200906193209p:plain
該当問題の標準入力と標準出力の形式

C++でこの問題を解くと下記のようになります。

#include <iostream>

using namespace std;

int main()
{
  int a, b, c;
  string s;
  
  cin >> a;
  cin >> b >> c;
  cin >> s;
  
  cout << a + b + c << " " << s;
}

TypeScriptでこの問題を解くと次のようになります。

import * as fs from 'fs';

const input = fs.readFileSync("/dev/stdin", "utf8");

const a = +input.split('\n')[0];
const b = +input.split('\n')[1].split(' ')[0];
const c = +input.split('\n')[1].split(' ')[1];
const s = input.split('\n')[2];

console.log(`${a + b + c} ${s}`);

fs.readFileSync("/dev/stdin", "utf8")で取得した標準入力の文字列から、split('\n')split(' ')を使って整数と文字列を取り出しています。

参考サイト

Node.jsの標準入力と - Qiita

TypeScriptでAtCoderに挑戦してみる - Qiita

【Java】BigDecimal型を使う

はじめに

仕事で金利に関するロジックを書いた時に、JavaのBigDecimal型を使ったので、BigDecimal型の使い方についてメモすることにしました。

なぜBigDecimal型を使うのか

BigDecimalには下記のような特徴があるため、誤差・意図しない値の丸めが発生することが許されない金額の計算のようなケースでは、float, double型ではなくBigDecimalの使用が推奨されます。

1. float, double型と違って少数の計算で誤差が発生しない

BigDecimalと同様に少数を表現する型として、float型とdouble型があります。float型とdouble型は、値を2進数で保持する関係上、循環小数としてしか表現できない10進数の少数が存在するため、少数を計算する過程で値を丸める必要があり、その過程で計算結果に誤差が発生してしまいます。 その反面、BigDecimal型では、値を「整数 * 10の何乗」という形式で保存しているため、10進数の有限小数のすべてを誤差なく表現することができます。

2. 値の丸めかたを指定することができる

BigDecimal型では、値の計算をすべてメソッド経由で実施するため、値の丸めかたを指定することができます。

// 1. float, double型では、少数の計算で誤差が発生するケースがある
double doubleAnswer = 3.3 / 1.1;
System.out.println("double: " + doubleAnswer); // double: 2.9999999999999996

BigDecimal bigDecimalAnswer = BigDecimal.valueOf(3.3).divide(BigDecimal.valueOf(1.1));
System.out.println("bigDecimal: " + bigDecimalAnswer); // bigDecimal: 3

// 2. 値の丸めかたを指定することができる
double doubleAnswer2 = 10.0 / 3.0;
System.out.println("double: " + doubleAnswer2); // double: 3.3333333333333335

BigDecimal bigDecimalAnswer2 = BigDecimal.valueOf(10.0).divide(BigDecimal.valueOf(3.0), 2, RoundingMode.HALF_UP);
System.out.println("bigDecimal: " + bigDecimalAnswer2); // bigDecimal: 3.33

BigDecimalの生成

数値からBigDecimalを生成するときは、valueOfメソッドを使用し、文字列から生成する場合は、コンストラクタを使用します。

BigDecimal value1 = BigDecimal.valueOf(1234.56);// 数値からの生成
BigDecimal value2 = new BigDecimal("1234.56");// 文字列から生成

BigDecimalの定数

0, 1, 10は定数として定義されています。

BigDecimal.ZERO
BigDecimal.ONE
BigDecimal.TEN

値の比較

equalsメソッド使用した場合、値のscaleが一致していないと、値が同一でもfalseが出力されてしまいます。

System.out.println(BigDecimal.valueOf(0.0).equals(BigDecimal.valueOf(0))); // 値のscaleが異なるため、falseになってしまう
System.out.println(BigDecimal.valueOf(0).equals(BigDecimal.valueOf(0))); // true

そのため、BigDecimalで値の比較を行う場合は、compareToメソッドを使用しましょう。compareToメソッドでは値は等しいがscaleが異なるBigDecimalオブジェクトを等しいとみなします。(例えば、2.0と2.00は等しいとみなされる)

BigDecimal one = BigDecimal.valueOf(1.00);
System.out.println(one.compareTo(BigDecimal.ZERO)); // 1
System.out.println(one.compareTo(BigDecimal.ONE)); // 0
System.out.println(one.compareTo(BigDecimal.TEN)); // -1

compareToメソッドでは、数値がパラメーターの値よりも小さい場合は、-1、等しい場合は0、大きい場合は1を返します。

加算

BigDecimal three = BigDecimal.valueOf(3.00);
BigDecimal four = BigDecimal.valueOf(4.0);

System.out.println(three.add(four)); // 7.0

減算

BigDecimal three = BigDecimal.valueOf(3.00);
BigDecimal four = BigDecimal.valueOf(4.0);

System.out.println(three.subtract(four)); // -1.0

乗算

BigDecimal three = BigDecimal.valueOf(3.00);
BigDecimal four = BigDecimal.valueOf(4.0);

System.out.println(three.multiply(four)); // 12.00

除算

scaleを指定せずに、divideメソッドを使うこともできますが、割り切れないときにArithmeticExceptionが発生するのでscale指定版を使用するほうが無難です。

BigDecimal three = BigDecimal.valueOf(3.00);
BigDecimal four = BigDecimal.valueOf(4.0);

System.out.println(three.divide(four, 3, RoundingMode.HALF_UP)); // 0.750

参考サイト

BigDecimal (Java Platform SE 8)

【Java】BigDecimalをちゃんと使う~2018~ - Qiita

BigDecimalの使い方 | Java好き

なぜBigDecimalを使わなければならないのか | Java好き

Java9以降のBigDecimalの小数点切り上げ・切り捨て・四捨五入-スケ郎のお話

【Angular】ngClassで三項演算子を使う

はじめに

AngularのngClass(テンプレート内でclass属性を動的に変更する機能)で三項演算子を使う方法を説明します。

<div class="block" [ngClass]="isRed ? 'red' : 'blue'"></div>

環境

  • Angular: 8.2.14

実装

f:id:l08084:20200718172344p:plain
isRed が true の時は赤色になる

ngClassで三項演算子を使う方法について説明するために、isRedtrueの時に赤色になり、isRedがfalseの時に青色になるプログラムを実装します。

まず、三項演算子を使わない書き方の例です。

app.component.scss

.block {
  border: solid 3px black;
  height: 200px;
  margin: 20px;
  width: 400px;

  &.red {
    background-color: red;
  }

  &.blue {
    background-color: blue;
  }
}

app.component.html

<div class="block" [ngClass]="{ red: isRed, blue: !isRed }"></div>

f:id:l08084:20200718192254p:plain
isRed が false の時は青色になる

続いて、三項演算子を使った場合の書き方の例です。

app.component.html

<div class="block" [ngClass]="isRed ? 'red' : 'blue'"></div>

三項演算子を使っていない場合と比較すると、{}で囲っていない、class名を''で囲っているといった違いがあります。

三項演算子を使うと条件文を一度書くだけで済むため、条件文が長い場合などは、三項演算子を使ったほうがすっきりします。

参考サイト

AngularのngClassで条件に応じてスタイルを切り替える(三項演算子) - Qiita