Things of interesting

プログラミングに関する技術ネタの備忘録

Flutterでfirebase+Blocなアプリケーションを作る(その2)

やること

Flutter入門としてサンプルを使ったデモアプリを作成します。

はじめに

前回からの変更点

前回flutter_blocというライブラリを使ってデモアプリを動かしてみたのですが、かなりReduxよりのアーキテクチャになっていて、 例えばFirestoreのSnapshotを参照したりすると結構面倒な実装になりました。。。

blocクラスとのIFがEvent/Stateになっていて直接Widget側にはStreamを渡せない) hnak.hatenablog.jp

複雑な状態管理を行うのであればいいのかも知れませんが、簡単なアプリを作るにはやりすぎな気もするので シンプルなbloc_providerのライブラリをベースに作り直すことにしました。

こちらのライブラリを使わせていただきました、今回のデモアプリもexample/complexをベースしています。 pub.dev

作ったもの

Githubにあげてます。 github.com

Firebase ログインの実装フロー

Step1. ログインボタンの呼び出し

lib/screens/login/login_form.dart
このクラスでログイン画面を構成しています。
下の方でGoogleLoginButtonのWidgetを作っています。
※通常のログインは適当実装なので動くかわかりません、Googleログインを使ってください。

lib/screens/login/google_login_button.dart このクラスでGoogleログインボタンを作り、ボタンを押すことでloginBlocのSinkにデータが流れます。

      onPressed: () {
        loginBloc.googleLoginSink.add(null);
      },

Step2. ログインのビジネスロジック処理

lib/bloc/login/login_bloc.dart
ここではログインの状態管理とGoogleログインの実行を行なっています。
signInWithGoogleが成功するとログイン状態(LoginState)がsuccessになります。

    _googleLoginController.stream.listen((onData) async {
      try {
        _loginState.add(LoginState.loading());
        await userRepository.signOut();
        await userRepository.signInWithGoogle();
        _loginState.add(LoginState.success());
      } catch (_) {
        _loginState.add(LoginState.failure());
      }
    });

Step3. ログインの状態管理と画面遷移

lib/bloc/login/login_state.dart LoginStateという状態管理用のクラスを定義していますが、flutter_blocライブラリのStateクラスではなくてStreamを流れるただのImmutableなオブジェクトです。
LoginFormではこのLoginStateの状態を監視していて状態が変わることで処理が動くようになっています。
初期化処理の中でLoginStateがSuccessになると別の画面へ遷移するようにしています。

  @override
  void initState() {
    super.initState();
    loginBloc = LoginProvider.of(context);
    loginBloc.loginStateStream.listen((onData) {
      if (onData.isSuccess) {
        Navigator.of(context).pushNamed(ProductScreen.routeName);
      }
    });
    _emailController.addListener(_onEmailChanged);
    _passwordController.addListener(_onPasswordChanged);
  }

このLoginStateは画面コンポーネントのバリデーションにも使用しており、例えばパスワードのInputでは以下のように処理しています。
少しわかりにくいですが、bulderに記述されたsnapshotの中身は、streamで記載されたloginStateStreamから渡されるオブジェクトになります。
なので snapshot.data.isPasswordValid は LoginStateのisPasswordValidを呼び出すことになります。

StreamBuilder<LoginState>(
                    stream: loginBloc.loginStateStream,
                    initialData: loginBloc.loginStateStream.value,
                    builder: (context, snapshot) => TextFormField(
                          controller: _passwordController,
                          decoration: const InputDecoration(
                            icon: Icon(Icons.lock),
                            labelText: 'Password',
                          ),
                          obscureText: true,
                          autovalidate: true,
                          autocorrect: false,
                          validator: (_) {
                            return !snapshot.data.isPasswordValid
                                ? 'Invalid Password'
                                : null;
                          },
                        ),
                  ),

さいごに

こんな感じでBlocなアプリケーションにもFirebaseを組み込むことができました。
あんまりFlutterの勉強時間が取れていませんが、これからFlutterを使って色々なアプリを作ってみたいと思います!

Flutterでfirebase+Blocなアプリケーションを作る(その1)

やること

Flutter入門としてサンプルを使ったデモアプリを作成します。

はじめに

Flutterとは

Googleが開発するクロスプラットフォームなモバイルアプリケーションSDK です。
iOSAndroidに対応していましたが、2019年5月にウェブ版のFlutter for Web(旧 hummingbird)が正式発表されました。

www.publickey1.jp

他のクロスプラットフォームとの大きな違いとして、独自のレンダリングエンジンを使用してUIを構築することが挙げられます。
このためiOSAndroid間でも同じようなUIを構成することができます。

これまでのクラスプラットフォームの経験としてはCordova、期待しているのはNativeScript or Flutterですが、それぞれ比較してみると

  • vs Cordova: WebViewを挟まず独自レンダリングできるのでパフォーマンスが良い。 というかもうCordovaは使いたくない :P
  • vs NativeScript: UIを揃えられる分、プラグイン周りはObjective-C, Javaでのコーディングが必要になる(自作する場合)。あとまだプレビュー版ですがFlutterではウェブ版も作れる。

Blocとは

Bloc(Business Logic Component)の意味でGoogle Adwardsチームが発表したデザインパターンです。
ビジネスロジックをプレゼンテーションから分離し、以下のようなルールを定めるというものです。 (現状はDart自体がFlutter開発のための言語のようなものなのでBlocの実装ライブラリはFluttter用に開発されてるような感じです。)
- Inputs and outputs are simple Streams/Sinks only
- Dependencies must be injectable and platform agnostic
- No platform branching allowed
- Implementation can be whatever you want if you follow the previous rules

https://raw.githubusercontent.com/felangel/bloc/master/docs/assets/bloc_architecture.png

www.didierboelens.com

この資料が概要を理解するのに分かりやすかったのでリンクを貼っておきます。 slides.com

注意

その2ではライブラリを今回使用しているflutter_blocから変更しています。flutter_blocが不要であればこの記事は飛ばしてください。 hnak.hatenablog.jp

手順

0. 開発環境のセットアップ

FlutterSDK自体のセットアップについては割愛します。公式のチュートリアル通りに進めて問題ないはずです。

Install - Flutter

1. サンプルのチェックアウト

今回はBlocライブラリのレポジトリにあるFlutterのfirebaseサンプルを起動させてみたいと思います。
以下レポジトリをクローンします。 github.com

bloc/examples/flutter_firebase_login/ 下にfirebase loginを使ったサンプルソースがあります、作業を続ける前にFirebaseのセットアップをしておきます。

2. Firebaseのセットアップ

Firebaseで適当なプロジェクトを作成してiOSアプリの追加をします。 f:id:hnakanoya:20190514151156p:plain

情報を埋めて新規iOSアプリの追加を行います、この時画面の指示に合わせてGoogleService-Info.plistをダウンロードします。 f:id:hnakanoya:20190514151407p:plain

firebase authentificationの設定からGoogleログインをONにしてください。

3. サンプルの編集

bloc/examples/flutter_firebase_login/ios/Runner/GoogleService-Info.plist のファイルをダウンロードしたファイルで上書きしてください。

同じディレクトリにあるInfo.plistを開いて「paste-the-reversed-client-id-from-googleservice-info.plist-here」と記載されている場所にGoogleService-Info.plist内に記載されたREVERSED_CLIENT_IDの内容を上書きしてください。

4.サンプルの起動

以下コマンドでサンプルが起動するはずです。

# サンプルディレクトリに移動
$ cd bloc/examples/flutter_firebase_login
# アプリを起動
$ flutter run

このソースを参考に独自にアプリを構築する場合はbloc/examples/flutter_firebase_loginを切り出すような形になると思うので、その場合はBlocライブラリのパスをローカルの相対パスからライブラリ参照に変更してください。

bloc/examples/flutter_firebase_login/pubspec.yaml

  flutter_bloc:
    path: ../../packages/flutter_bloc

↓以下に変更

flutter_bloc: ^0.13.0

【Vue.js】Buefyでvuelidateを使ってバリデーション

やること

Vueのアプリ開発にBuefyというBulmaベースのライブラリを使おうかと考えていますが、 フォームごとや複雑なバリデーションを想定するとデフォルト機能のバリデーションだと心もとないなので Vuelidateというバリデーション用のライブラリと組み合わせて使ってみます。

事前準備

vue-cli 3.0 の環境で作りました。

vue create my-app

Buefyのインストール

yarn add buefy

Vuelidateのインストール

yarn add vuelidate

main.js

import Vue from 'vue';
import Buefy from 'buefy';
import 'buefy/dist/buefy.css';
import Vuelidate from 'vuelidate';

Vue.use(Buefy);
Vue.use(Vuelidate);

バリデーションを掛けるフォーム画面の作成

ログインページの想定で作ってみます。 (全量は下にあります。パスワードコンポーネントはbuefyデフォルトのものになっています)

Buefyのb-inputタグのカスタマイズ

ここでは必須(required)属性をサンプルとして実装してみます。

  • デフォルトのバリデーション属性は使わないので、requiredは外します。
  • 代わりに@blurblur時のイベント発火(vuelidateのバリデーション起動)をセットします。
  • b-fieldにはvuelidateのerror条件判定でtype,messageが表示されるように設定します。
  • スクリプトの方はloginIdのバリデーション定義をいれています。
  • vue-property-decoratorというライブラリを使っているのでそのまま使う場合はyarn等でインストールしてください。
            <b-field 
              :type="$v.loginId.$error ? 'is-danger': ''" 
              :message="$v.loginId.$error ? 'this is validation message' : ''" 
            >
              <b-input
                ref="loginId"
                v-model="loginId"
                placeholder="ログインID"
                @blur="$v.loginId.$touch"
              />
            </b-field>
〜省略〜
<script>
import { Component, Vue } from 'vue-property-decorator';
import { validationMixin } from 'vuelidate';
import { required, maxLength } from 'vuelidate/lib/validators';

@Component({
  mixins: [validationMixin],
  validations: {
    loginId: { required, minLength: maxLength(30) },
    password: { required, minLength: maxLength(30) }
  }
})
export default class Login extends Vue {
  loginId = '';
  password = '';

  error = false;
〜省略〜

まとめ

こんな感じでVuelidateと連携できるはずです!
Vuelidateだとthis.$v.$touch()を使ってのフォーム全体のバリデーション発火やthis.$v.$errorでの状態チェックが便利ですね!

Login.vue

<template>
  <section class="hero is-fullheight is-primary is-bold">
    <div class="hero-body">
      <div class="container has-text-centered">
        <h1 class="title">TITLE</h1>
        <p class="subtitle">SUB</p>
        <div class="column is-4 is-offset-4">
          <div class="box">
            <b-field 
              :type="$v.loginId.$error ? 'is-danger': ''" 
              :message="$v.loginId.$error ? 'this is validation message' : ''" 
            >
              <b-input
                ref="loginId"
                v-model="loginId"
                placeholder="ログインID"
                maxlength="30"
                @blur="$v.loginId.$touch"
              />
            </b-field>
            <b-field>
              <b-input
                ref="password"
                v-model="password"
                placeholder="パスワード"
                required
                type="password"
                maxlength="30" 
                password-reveal/>
            </b-field>
            <a
              :class="{ 'is-loading': loading }"
              class="button is-fullwidth is-primary"
              type="submit"
              @click="hoge()">LOGIN</a>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import { Component, Vue } from 'vue-property-decorator';
import { validationMixin } from 'vuelidate';
import { required, maxLength } from 'vuelidate/lib/validators';

@Component({
  mixins: [validationMixin],
  validations: {
    loginId: { required, minLength: maxLength(30) },
    password: { required, minLength: maxLength(30) }
  }
})
export default class Login extends Vue {
  loginId = '';
  password = '';

  error = false;
  loading = false;

  hoge() {
    this.$v.$touch();
  }
}
</script>

Vue + Spring SecurityでJWT認証を使う

やること

フロントエンドをVue、バックエンドをSpringで構成したアプリケーションで、JWTによる認証を行う

やりかた

バックエンド側の設定

以下の記事によくまとまっていたので参考にしました。

qiita.com

VueでAPIを呼び出した際にCORSで弾かれてしまったので、WebSecurityConfigに以下設定を追加しています。
フロントエンドをdev serverで起動しているので動作確認しているのでそのせいかも?
フロントエンド側でヘッダーに格納されるAuthorization情報が取得できなかったので expose 設定に追加しています。
それ以外は一般的なCORS設定です。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // 省略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().configurationSource(this.corsConfigurationSource())
        // 省略
    }
    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
        corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
        corsConfiguration.addExposedHeader("Authorization");
        corsConfiguration.addAllowedOrigin("http://hogehoge.com"); 
        corsConfiguration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        corsSource.registerCorsConfiguration("/**", corsConfiguration);

        return corsSource;
    }
}

フロントエンドの設定

ログイン処理の中でヘッダー情報のトークンをvuexのストアに保存します。
axiosの実装は下のJavascriptファイルを参考にしてください。
vuexへの保存は一般的な実装なので省略します。

// ログイン処理を行うメソッド内
const res = await HttpClient.getAxios().post('/api/login', 
      {
        "userName" : this.loginId,
        "password" : this.password
      }
    );
const jwt: string = res.headers.authorization;
this.$store.commit('saveJwt', jwt);
// 省略

HTTP通信をするのにaxiosというモジュールを使っているので、これを介してHTTPクライアントを作成するときに ヘッダー情報がセットされるようにしています。(ログインで保存しておいたvuexのストアから取得)
※初回リクエストとなるloginではjwtは空になりますが、サーバー側でloginAPIのみトークン認証がかからないようにしています。

import axios from 'axios'
import store from 'store/index';

const URL = 'http://localhost:8081';
const instance = axios.create({
    baseURL: URL,
    timeout: 10000,
    headers: {
        'Content-Type': 'application/json'
    }
  });

export default class HttpClient {
    public static getAxios() {
        instance.defaults.headers.common['Authorization'] = 'Bearer ' + store.getters.jwt;
        return instance;
    }
}

残作業

セッションのような状態を管理しないので、多重ログイン制御がこれ単体ではできない。
セッションの場合はRedisを使っていたので同じようにRedisに有効なJWTを保存してバリデーションをかける。

MacでDockerコンテナからホストにつなぐ方法

やること

MacでDockerコンテナからホストにつなぐ方法がわからないので調べる

やりかた

結論

ホスト名に host.docker.internal を指定する。

調べたメモ

バージョンによって使用できる方法が変わるようですが、バージョン18.03からは上記の方法が使えるようです。

「I WANT TO CONNECT FROM A CONTAINER TO A SERVICE ON THE HOST」のところをみてください。

docs.docker.com

仮想通貨をマイニングする その3

やること

マイニング1ヶ月間の結果発表

マイニング結果

約7000円の利益でした!

詳細

掘れたZCASHは0.5ZECくらいでした。 f:id:hnakanoya:20180421143730p:plain

f:id:hnakanoya:20180421144616j:plain 電気代は560Wくらいで落ち着いていたので19円/kWで計算すると、
0.56 * 19 * 24 * 30 = 7660.8円

今日のレート(29,511ZEC/JPY)で以下の感じなので、
0.5 ZEC = 14755.71 円

14755.71 - 7660.8 = 7094.91円 となりました。

仮想通貨をマイニングする その2

やること

追加分のGPUが届いたので増設する

マイニングリグのGPU増設

GTX1080を2つ追加で購入しました。 f:id:hnakanoya:20180321174020j:plain

マイニングリグに追加しました。

基本的にGPU追加時もなんの設定もいらず、正しく接続できていれば勝手にマイニングが始まります。 f:id:hnakanoya:20180321174222j:plain

ハッシュレートの確認

予定通り1枚のおおよそ3倍で1.5KH/sをいったりきたりする感じです。 f:id:hnakanoya:20180321174506p:plain

しばらく稼働させたらまた結果を報告します!