つばくろぐ @takamii228

知は力なり

FlutterでID/Password入力画面を作ってみる

FlutterでID/Pass入力画面を作るときに設定するのパラメータをちょっと調べたのてまとめてみます。(※入力のバリデーションやオートフォーカス等のアクションは含みません)

よくあるID/Pass入力画面の要件

よくあるID/Passの入力画面の要件といえばこんな感じでしょうか。

  • ID入力

    • 半角英数が入力できる
  • Password

    • 半角英数が入力できる
    • 入力内容はマスクしたい
    • 入力内容を確認したいので、マスクの表示非表示を切り替えられるようにしたい
    • 予測変換はさせたくないしヒストリーに記録なんてさせたくない
    • 入力欄のコピペはブロックしたい

入力フォームはTextInputFiledを使えば実現できます。中でTextFieldをラップしているようです。

TextFormField class - material library - Dart API

TextField constructor - TextField class - material library - Dart API

上記の要件に絡みそうなパラメータは以下の通りです。

  • obscureText
    • 入力内容のマスク表示の切り替え
  • autocorrect
    • オートコレクト・予測変換の有効・無効化
  • enableInteractiveSelection
    • 入力エリアのセレクトアクション(コピペ、選択、削除など)の有効・無効化
  • TextInputType

今回の要件では、以下のような設定にするのがよさそうです。

ID入力欄

パラメータ 理由
obscureText false IDは常に見せておくため
autocrrect false/true どちらでもよさそう。メールアドレスとかだとサジェストがあると嬉しいかもしれない。
enableInteractiveSelection true コピペしたいケースがありそうなので。
TextInputType emailAddress IDがメールアドレスのケースも多く、とりあえず半角英数で立ち上がってほしいので。

Password入力欄

パラメータ 理由
obscureText true/false 右端に目のアイコンを用意し、クリックで切り替えられるようにする。
autocrrect false パスワードは守りたいので。
enableInteractiveSelection false コピペはブロックしたいので。
TextInputType visiblePassword パスワードを見せる専用のキーボードタイプ。v1.9.1+hotfix-6から使える模様。

Widgetに上記の設定をしてiOS/Androidそれぞれエミュレータで動かしてみます。

今回試したコードサンプルはこちら。

github.com

なおPassword入力画面のパーツについては本家のflutter garallyにもあり、今回の実装の参考にしました。

github.com

実行環境は以下の通りです。

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel unknown, v1.9.1+hotfix.6, on Mac OS X 10.14.6 18G87, locale en-JP)
 
[] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[] Xcode - develop for iOS and macOS (Xcode 11.3)
[] Android Studio (version 3.5)
[!] IntelliJ IDEA Ultimate Edition (version 2019.3)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.41.0)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[] Connected device (2 available)

! Doctor found issues in 2 categories.

$ flutter devices
2 connected devices:

AOSP on IA Emulator • emulator-5554                        • android-x86 • Android 9 (API 28) (emulator)
iPhone 11 Pro Max   • 424952E6-D634-4B19-AA9A-8552B62DE670 • ios         •com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator)

挙動確認

iOS

f:id:takamii228:20191214225141p:plainf:id:takamii228:20191214225400p:plainf:id:takamii228:20191214225410p:plain

想定通り動いてそうです。

Android

f:id:takamii228:20191214225437p:plainf:id:takamii228:20191214225447p:plainf:id:takamii228:20191214225459p:plain

Androidも大丈夫そうです。

余談:TextInputTypeがvisiblePasswordじゃないとAndroidでautocorrectが効かないらしい。

そもそもFlutterにはautocorrectのバグがあったようです。

github.com

このバグの対処のために、最新のstableであるv1.12.13+hotfix.5ではenableSuggestionsというパラメータが追加されたようです。 このパラメータ追加で予測変換を一律制御できるのかな〜と思ったのですがそもそもAndroidのみとのこと。またIssueのコメントにあるようにTextInputType.textだと予測変換の無効化は効かない模様。まじかー。

入力のI/O周りはAndroidiOSの両OSのモジュールをうまくラップして制御するのが難しいのでしょうかね。そういえばWebやDesktopもあるんだった・・・。

あと今回他のサービスはどうなってるのかな〜と思っていろいろ調べたのですが、ほとんどがWebViewでの入力画面が立ち上がるパターンでした。。。

GitLab APIを使ってtag名とtagのタイムスタンプをcsv形式で出力する

リリース頻度のデータを分析するために、GitLabのtag名とtagを作成した時のタイムスタンプの一覧が欲しくなったのでGitLab API経由で取得するシェルを書きました。

#!/bin/sh

BASE_URL="xxxx"
API_TOKEN="xxxx"
PROJECT_ID="xxxxx"

# 全tag一覧を取得するための最大ページ数を取得する
TOTAL_PAGES=`curl -D - -s -o /dev/null -X GET -H "PRIVATE-TOKEN: ${API_TOKEN}" "${BASE_URL}/api/v4/projects/${PROJECT_ID}/repository/tags?per_page=100" | grep "X-Total-Pages" | tr -d "X-Total-Pages: " | tr -d "\r"` 

# タグとタグのタイムスタンプをカンマ区切りで取得する
RESULT=""

for i in `seq 1 ${TOTAL_PAGES}`
do
  RESULT=${RESULT}`curl -X GET -H "PRIVATE-TOKEN: ${API_TOKEN}" "${BASE_URL}/api/v4/projects/${PROJECT_ID}/repository/tags?per_page=100&page=${i}" | jq -r '.[] | (.name|tostring) + "," + (.commit.authored_date|tostring)'`"\n"
done

# UTCで返却されるので、JSTに変換し出力する
for line in ${RESULT}
do
  TAG_NAME=`echo ${line} | cut -d ',' -f1`
  TIMESTAMP_UTC=`echo ${line} | cut -d ',' -f2`
  TIMESTAMP_JST=`date -v+9H -j -f "%Y-%m-%dT%T.000Z" ${TIMESTAMP_UTC} +%Y-%m-%dT%T.000Z`
  echo ${TAG_NAME},${TIMESTAMP_JST}
done

tag一覧の取得は/api/v4/projects/${PROJECT_ID}/repository/tagsのエンドポイントを叩くと取れます。

https://docs.gitlab.com/ce/api/

tag一覧の取得はページングのハンドリングが必要なので、レスポンスヘッダーのX-Total-Pagesからページ数を取得し、最終的にはfor文で回してjqで加工したものを連結します。

タイムゾーンの設定によってはUTCで結果が帰ってくるので、最後にdateコマンドでJSTに変換しています。

上記のシェルを実行すると以下のようなcsvが取得できます。

1.5.0,2019-09-30T10:59:49.000Z
1.4.0,2019-09-26T15:35:21.000Z
1.3.2,2019-09-20T19:00:11.000Z
1.3.1,2019-09-17T18:21:30.000Z
1.3.0,2019-09-11T21:01:37.000Z
1.2.0,2019-09-03T22:08:34.000Z
1.1.0,2019-08-27T22:41:38.000Z
1.0.2,2019-08-20T22:37:28.000Z
1.0.1,2019-08-13T22:13:56.000Z
1.0.0,2019-08-06T21:38:05.000Z
0.3.0,2019-07-30T21:16:30.000Z
0.2.0,2019-07-23T22:38:51.000Z
0.1.0,2019-07-17T15:30:01.000Z

あとはこれをSpreadSheetなりExcelなりPythonなりで集計すればリリース頻度の分析の集計ができます。

master中心のブランチ戦略で開発している話

ここ数年、CI/CDパイプラインを構築したりブランチ戦略を考えるロールで仕事をすることが多いのですが、ブランチ戦略はいつも頭を悩ませていました。

当初はGit FlowGitLab Flowの採用を考えたりしたのですが、ブランチやマージの戦略がちょっと重厚でめんどうなだという印象を持っていました。

「releaseブランチとかpre-productionとか管理面倒だからぶっちゃけmasterだけでよくね?」とふと思い、master中心のシンプルなブランチ戦略を考え、実践してみました。するとそれなりにうまく立ち回ることができるようになっていたので、整理もかねてまとめておきます。

対象とするプロジェクト

本ブランチ戦略は、以下のような開発チームに対してうまく機能できると考えています(※もちろんこの方法が最適な解とは限りません)

  • バックエンド・フロントエンド・スマホアプリなど開発領域ごとにチームが別れている
  • サービスを成立させるためには各チームが独立にリリースすることはできず、最終的には資材の同期を取る必要がある
  • 先に開発を終えたチームは次の案件の開発に着手しており、追加開発とリリース用の2つの資材を管理する必要がある
  • 開発チームメンバーは数人から20人程度
  • masterをcleanに保つためのCIが整っている

基本的な考え方

master中心のブランチ戦略のベースとなる考え方は以下の通りです。

  • masterが最新の安定版となるようにコミットしていく(※ リリース版ではなく機能開発の最新)
  • ブランチは本開発、追加開発、リリース版などの開発の流れ(= Flow)を表す
  • ブランチはどこかのブランチにマージするか、マージしない場合は独立に更新を続け最終的には捨てる
  • この機能を入れる資材と入れない資材が必要、というときにブランチを切って開発の流れを分岐させる
  • リリースバージョンはタグに紐づけるようにし、ブランチと実行環境は紐付けない

これら原則に従うことで、以下のようなメリットが得られます。

  • 管理するブランチが少なくなるため、ブランチの管理やマージ先の手間をへらすことができる
  • 「masterブランチを最新の安定版」とするため、Pull Reuquest / Merge Requestのマージ先はhotfix以外はすべてmasterになり、追加開発時のマージ先誤りの事故を防ぐことができる
  • ブランチと実行環境が分離されるため、環境管理とは独立して資材のバージョン管理ができる

以下、実際にリリースの前・リリース後のブランチ戦略を説明していきます。

リリース前のブランチ戦略

リリース前のブランチ戦略は非常にシンプルです。masterブランチに対して、featureブランチ・bugfixブランチをマージすることで機能追加を反映していきます。

f:id:takamii228:20191126204756p:plain

初期開発時に利用するブランチは以下の通りです。

ブランチ名 ブランチの切り元・マージ先 用途
master - 最新の安定版ブランチ
feature/* master 機能追加用ブランチ。CIが通ったもののみマージする。チケット番号と紐付ける。masterにマージしたら削除する。
bugfix/* master テスト・QA等で発覚したバグの修正用のブランチ。CIが通ったもののみマージする。チケット番号と紐付ける。masterにマージしたら削除する。

このとき、成長するブランチはmasterブランチのみであり、チームメンバはmasterブランチのみを追いかけていればよいです。

検証環境へのリリース資材を作成する場合はtagでバージョンとコミットハッシュを紐付けることでバージョン管理ができます(図のv0.1.0、v0.1.1)。

リリース後のブランチ戦略

ここでのリリース後、とは自分のチームの開発範囲のリリースを指します。すなわちバックエンドチームであればバックエンドのAPIのみのリリースを指します。

バックエンドは順調に開発が進み先に環境へリリースできた一方で、フロントエンドは画面の仕様調整に難航しスケジュールが遅れてしまった、というケースはよくあると思います(逆もしかり)。

このときバックエンドチームはフロントエンドチームの待ちの状態になるため、開発チームの余ったリソースを使って先に追加開発に着手し始めるケースがよくあります。このとき、masterブランチのみの運用だと本来のリリースには含めてはいけない資材が混じってしまう可能性があるため、フロントエンドの改修がバックエンドに波及したときのhotfixの対応を受け入れるために、masterとは別のproductionブランチを作成し2つの流れを作って運用します。

productionブランチを作っておくことで、フロントエンドチームと同期をとったときの不具合のフローと追加開発のフローを分けて管理することができます(下図)。

f:id:takamii228:20191126211825p:plain

利用するブランチは以下の通りです。 リリース後のブランチはリリース前にあったブランチに加えてproductionブランチとhotfixブランチが追加されています。

ブランチ名 ブランチの切り元・マージ先 用途
master - 最新の安定版ブランチ
production master 最新のリリース版、リリースごとに作成する。リリース版にのみ緊急に反映させたいパッチがあればあてる。追加開発版のリリースが行われたら今まで使っていたproductionブランチは削除し、リリース対象のタグから新しいproductionブランチを作成する。
feature/* master 機能追加用ブランチ。CIが通ったもののみマージする。チケット番号と紐付ける。masterにマージしたら削除する。
bugfix/* master テスト・QA等で発覚したバグの修正用のブランチ。CIが通ったもののみマージする。チケット番号と紐付ける。masterにマージしたら削除する。
hotfix/* production / master リリース版バグ修正用のブランチ。CIが通ったもののみマージする。チケット番号と紐付ける。productionにのみパッチをあてる場合はproductionから切ってproductionにマージし削除する。production / master両方に必要なパッチの場合はそれぞれの根本から2本パッチを充ててそれぞれにマージし削除する。

productionブランチは外部チームへの提供可能なバージョンのタグを切ったときに作成します。productionブランチにはリリースに必要なパッチや緊急のhotfixのみの変更が当てられます。hotfixのパッチは場合によってはデグレを防ぐためにmasterとproduction両方に当てる必要があります(※後述)。追加開発向けの機能追加はリリース前と同様です。

リリース版で発覚したバグ修正のhotfixは、追加開発を行っている最新版のmasterに入れる場合と入れない場合とで以下のように対応を分けます。

  • リリース版および最新版にもパッチを入れる場合(例:恒久的なバグ対応)
    • hotfixブランチをproductionから切って、productionにマージする
    • hotfixブランチをmasterからも切って、productionブランチへのhotfixと同様の変更を加えmasterにマージする(cherry-pickなどを使う)
  • リリース版のみに入れるパッチの場合(例:ライブラリの緊急パッチ等の暫定対処)
    • hotfixブランチをproductionから切って、productionにのみマージする

hotfixの対応は一見複雑そうに見えますが、「masterが常に追加開発の最新版である」こと、「productionブランチがリリース版の最新版である」ことを考えると意外とシンプルにまとまっているのではないでしょうか。

なお、productionブランチはmasterには不要な変更を含んでいる場合があるため、masterにはマージしません。次のリリース版のtagが打たれたらまた新しいproductionブランチを作成します。

まとめ

Gitのブランチ戦略はGiit FlowやGitLab Flow等がありますが、どちらも扱うブランチの種類が多かったりデプロイ先の環境と密結合に語られていて扱いにくい印象を持っていました。

「masterを最新の安定版ブランチ」とする、というこのmaster中心のブランチ戦略を使うことで、シンプルで運用しやすい資材管理ができるようになったと個人的には考えています。興味を持たれた方はぜひ実践してみてください。