歌舞伎座.tech#11「Swiftプログラミング勉強会」を視聴しました。

上記勉強会ををニコ生で視聴させて頂いたので、メモを残しておきます。

クリーンアーキテクチャしたい、クリーンアーキテクチャしたくない?

メモ

  • クリーンアーキテクチャ、メリットデメリットあるけど良かったよという話。

合わせて確認したいページ

初めて Phantom Type と遭遇して、闇雲に闘いを挑んでみた話

メモ

  • Phantom Typeの紹介。

合わせて確認したいページ

Swift 3.0 と値型

メモ

  • Swift3.0 で値型が増えた。NSが取れたものがいくつかある。
  • 迷ったら値型使おう。
  • パフォーマンスも悪くない。

Good Design Pattern of Networking Programming in Swift

メモ

  • Swift3.0 の機能を使ったネットワークライブラリの話。

君の参照は。

メモ

  • 循環参照になるパターン、ならないパターンの話。

合わせて確認したいページ

ATSと通信ライブラリの話

メモ

  • ATSとそれに対応した通信ライブラリの話。
  • 理由を述べたからといってAppleがHTTP通信しているアプリ審査通してくれるはずがない(かもしれない)。
  • APIでHTTPアクセスしたければSocket使えばいいじゃんということで、Socket使って簡易に書ける通信ライブラリを作ったそうです。

Minimal Cake Pattern in Swift

www.slideshare.net

メモ

  • ドワンゴがDIで採用しているMinimal Cake PatternのSwift実装について紹介。

合わせて確認したいページ

Validation Decoration

  • Validation処理をDecoratorパターンを使って実装した話。

The introduction to RxSwift you've been missing

  • Rxの話。

ハッカソンに使うSwift」か「Swift 3.0のGCD」

  • ハッカソン参加しよう。こういう点を心がけよう。という話。

その他まとめページ

git の Discard all changes を復元する(取り消す)方法

最近はGitKrakenを使ってgit操作をしています。
GitKrakenにはcommit前の変更を全て取り消す Discard all changes という機能があります。
内部的にはgit checkoutを実行しているのでしょうか。

詳しく調べていませんが、GitKraken以外のGUIアプリでも実装されてるものは多いと思います。

で、たまに(よく)単一ファイルを Discard change しようとした時に、誤って Discard all changes してしまい、全ての変更を取り消してしまうことがあります。

その時の復元方法について紹介します。

1. git reflog でログ確認

 $ git reflog
104352f HEAD@{0}: discard: [8d1024be820c042cef4fe17432dc438d59189526]
104352f HEAD@{1}: commit: fix bug.
c263df1 HEAD@{2}: Fast forward branch feature/menu to branch develop

discard のログが残っていることを確認します。

2. git reset で復元

 $ git reset --hard 8d1024be820c042cef4fe17432dc438d59189526
HEAD is now at 8d1024b saved UNDO information for reset changes and discarded files

これで復元されます。
git reset --hardの右側の値はgit reflogで確認した discard の右側の値です。
discard した内容のcommitが出来上がるので必要に応じてcommit logの修正します。

注意点1. git reset --hard HEAD@{0} では復元されない

上記 reglog の状態でgit reset --hard HEAD@{0}を実行すると、HEAD@{1}の位置にHEADが移動するだけで discard した内容は復元されません。

注意点2. git reset --soft 8d1024 でも commit は作られる

git reset --softでもやってみましたが、commitが作られました。 softの場合はcommitに加えて、そのcommitを取り消す変更がある状態になりました。

Androidオールスターズ2に参加しました。

Androidオールスターズ2に参加したので、簡単なレポートとメモを残しておきます。

AndroidのCIを始めよう

レポート

まとめ

  • CIそのものに関する話と、AndroidのCIに関する発表でした。
  • iQONを運営するVASILYさんの事例紹介もありました。
  • VASILYさんの取り組みはこちらに詳しく記載されています。

Flux de Relax

発表資料と発表で使ったコード

レポート

なぜFluxか

  • Viewの状態管理が難しい
    • Fragment問題など
  • アプリがスケールした時の状態管理がつらい

Fluxの各役割

  • Dispatcher
    • ActionとStoreを結ぶ
    • シングルトンで作る
    • EventBusを使っている
    • DispatcherのメンバにEventBusを持っている
    • Mockしやすいように作る
  • Action
    • 外部リソースからデータを取得し揃ったらDispatcherに渡す
    • Rxのメリット
      • 複数リソースの合成
      • リクエストのキャンセル
  • Store
    • Storeの更新はStore内部で行う
    • Storeの状態が変更したらViewへ通知
    • DispatherへCallbackを登録する
    • ObservableXXからRxへの移行
      • ReadOnlyを制御出来ない。Rxなら出来る
      • ただしListは難しい
      • ObservableListならいい感じにやってくれる
  • View
    • StoreのStateを合成するケースがあるので、ObservableXXよりもRxが良い
  • Flux
    • View間の依存関係がない
    • 実装を統一できる
    • 単一フローでおいやすい
    • シンプルなアプリだと冗長過ぎる
    • 開放ミスでメモリリーク
    • AndroidにFluxがないのでTryError

まとめ

  • AndroidでFluxの発表でした。
  • コードも公開されていますので、発表資料と合わせて読むと理解が進むと思います。

aptとKotlinでコードを(なるべく)書かないAndroid開発

発表資料

レポート

まとめ

  • 前半apt、後半Kotlinの発表でした。
  • aptはコードを自動生成する仕組みです。
  • いずれも なるべくコードを書かない という目的のツールとして紹介されていました。

はじめよう Localization - ツール/tips/運用ノウハウなど

発表資料

レポート

  • Localization(地域化)とInternationalization(国際化)がある。
  • Internationalizationにエンジニア要素がある。
  • Localizationにエンジニア要素は少ない。
  • Internationalizationに必要なこと
    • 多言語リソースの用意
    • Formatterを使った日付や数字
    • 柔軟性の高いレイアウトの設計
  • Internationalizationに必要なことの要素
    • Strings
    • 画像
    • マルチメディア、音声、動画
    • レイアウト
  • Analyze > Run Inspection by Name でハードコードされているstringを見つけることが出来る
  • システムの言語設定を English XA にしてアプリを起動してまともに表示されているとそれがハードコードされているとわかる。
    • EnglishXAは特殊な文字列リソースを使い、30%程の文字を追加する特殊な言語
  • strings.xmlプレースホルダーを設定できる。
    • <string name="buyitems">Buy %1$d %2$s</string>
    • 上記のように値を外から設定できる string を定義できる。
  • もっと使いやすいプレースホルダー Pharaseというライブラリがある。
  • 複数形は plurals を使う
  • 日付・時刻
    • DateFormat
    • API18からは getBestDateTimePatternという便利なメソッドがある
    • DateUtilsもある
      • getDateとgetDateRnage
  • 通過、数値
    • NumberFormat
  • 電話番号
    • PhoneNumberUtils
    • API21以前は、英語日本語以外はあてにならない
  • 画像
    • 文字つきの画像は避ける
  • 柔軟性の高いレイアウト
    • 英語の30%増しくらいで考える。日本語は文字数少ない。

翻訳管理

  • 翻訳に必要なコンテキスト(どういうケースで使われるStringなのか)をXMLのコメントで付与する。
  • 翻訳不要なStringをマーク。Androidが用意している。
  • 翻訳管理サービス
  • 翻訳作業も自動化。
  • エンジニアがStringリソースを追加する。
  • CIが翻訳管理サービスに投げる。
  • アプリに翻訳に協力するボタン用意すると結構協力してくれる。

まとめ

  • Internationalization(Localization)に関する発表でした。
  • AndroidではInternationalizationのためのフレームワークがたくさん用意されており、知らないこともたくさんあったので非常に勉強になりました。

設計のお話 MVPとDDDで

発表資料

レポート

  • 設計:イメージしたものを実現するための見通しを立てる行為
  • プログラミングの設計:上の定義に加え、システムには完了が変更への強さも求められる
  • VとCの責務を持った、Activity/FragmentからイベントをPresenterとして外出しするためにMVP
  • スレッド管理はアプリケーションレイア

まとめ

  • AndroidでMVP/DDDの発表でした。
  • DDDからはドメインモデルとレイヤードアーキテクチャを採用していました。
  • MVPですが、AndroidのDatabindingが便利だということで、ViewModelを用意し、Viewにバインドしていました。
  • Android + MVP + DDD の実践的な設計が紹介されていたと思います。

Android初心者を脱するための20のチップス

発表資料

レポート

Javaのチップス

  • アクセス修飾子を適切に
  • static修飾子
    • staticメソッドにしておけばテストコードを書きやすい
  • final修飾子
    • 変数、クラス、メソッド
  • コメント
  • アノテーション
  • Non-primitive は @Nullable を指定する
  • ジェネリクス

Androidのチップス

  • 適切なViewを使う
  • Viewの階層は浅く
    • FrameLayout
    • テーブルっぽいレイアウト
      • NG: LinearLayout
      • OK: GridLayout
  • 関係ないView属性を書かない
  • ViewもDRY
    • <include>
    • <merge>
  • toolsを使う
    • tools:layout_heightで指定してpreviewで確認できる
    • tools:contesxtでActivityを指定しThemeを適用する
    • tools:layoutでFragmentのレイアウトを指定する
  • lintを確認
  • proguardをかける
  • 依存ライブラリは外部モジュールで
  • ディスプレイサイズはResourcesから取得
  • TextUtilsを使う
  • タスクは積み直せる
    • Activityのタスク
  • static 生成メソッド
  • フォーマットかけよう
    • Alt + Command + l
    • フォーマット修正diffがあるとレビューつらい

まとめ

  • 今日から使えるチップスの発表でした。

全体を通して

Androidに関係する様々なジャンルの話を聞けて、非常に勉強になりました。

Kotlin sealed class パターンマッチの挙動

sealed class を when でパターンマッチした際に、想定外の挙動をしていたのでいろいろ検証してみました。

網羅チェック

網羅チェックが動きそうで動かなかったケース

  • ビルドエラーになりません。
sealed class Event {
    class Insert: Event()
    class Update: Event()
    class Delete: Event()
}

fun hoge(event: Event) {
    
    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
//        is Event.Delete -> { print("Delete") }
    }
    
}

網羅チェックが動くパターン1

  • ビルドエラーになります。
sealed class Event {
    class Insert: Event()
    class Update: Event()
    class Delete: Event()
}

fun hoge(event: Event) {

    val x = when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
//        is Event.Delete -> { print("Delete") }
    }

}

網羅チェックが動くパターン2

  • ビルドエラーになります。
sealed class Event {
    class Insert: Event()
    class Update: Event()
    class Delete: Event()
}

fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
//        is Event.Delete -> { print("Delete") }
    }.let {  }

}

参考にしたページ

ネストしたクラス

下記のようなページも有ったので検証してみました。

  • ビルドエラーになります。
sealed class Event {
    class Insert: Event() {
        class Single: Event()
        class Multiple: Event()
    }
    class Update: Event()
    class Delete: Event()
}

fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Insert.Multiple -> { print("Multiple Insert") }
//        is Event.Insert.Single -> { print("Single Insert") }
        is Event.Update -> { print("Update") }
        is Event.Delete -> { print("Delete") }
    }.let {  }

}

複雑なクラス継承

継承した時の挙動が気になったので検証してみました。

sealed class の inner class を外で継承

  • ForceDeleteをwhenの条件に加えなくてもビルドエラーになりません。
  • ForceDelete の場合 Delete にマッチします。
sealed class Event {
    class Insert: Event()
    class Update: Event()
    open class Delete: Event() // 継承できるように open を指定する。
}

class ForceDelete: Event.Delete()

fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
        is Event.Delete -> { print("Delete") }
    }.let {  }

}
hoge(ForceDelete()) // Delete

sealed class の inner class を sealed class 内で継承

  • Deleteをwhenの条件に加えなくてもビルドエラーになりません。
  • hoge(ForceDelete()) では Delete が実行されます。
  • 1つ前の外で継承したケースと同じ挙動です。
sealed class Event {
    class Insert: Event()
    class Update: Event()
    open class Delete: Event()
    class ForceDelete: Event.Delete()
}


fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
        is Event.Delete -> { print("Delete") }
    }.let {  }

}
hoge(ForceDelete()) // Delete

sealed class の inner class を継承した場合の when の評価を検証

  • 次のコードでは Delete は Update にマッチします。
sealed class Event {
    class Insert: Event()
    open class Update: Event()
    class Delete: Event.Update()
}


fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Update -> { print("Update") }
        is Event.Delete -> { print("Delete") }
    }.let {  }

}

hoge(Event.Delete()) // Update
  • 次のコードでは Delete は Delete にマッチします。
sealed class Event {
    class Insert: Event()
    open class Update: Event()
    class Delete: Event.Update()
}


fun hoge(event: Event) {

    when(event) {
        is Event.Insert -> { print("Insert") }
        is Event.Delete -> { print("Delete") }
        is Event.Update -> { print("Update") }
    }.let {  }

}
    

hoge(Event.Delete()) // Delete

この通り、When-isの順番により処理が変わってしまうため、 sealed class の inner class を open にして継承することは避けるべきです。

最後に

いくつか微妙だなと感じる仕様があったので以下の仕様変更があればなと思います。

  • whenで評価した値を使わなくてもsealed classの網羅チェックが働くようにする。
  • sealed class の inner class は open にできないようにする。

こういうのどこでリクエストすればいいのでしょうか。

Kotlin + DataBinding + RecyclerView のサンプル。

標題のサンプルが見つからなかったので作りました。 github.com

このコードを元に、第3回Kotlin勉強会 @ Sansan - connpassでLTしてきたのですが、

このようなフィードバックを頂きましたので、合わせて参考にして頂ければと思います。

またLTで使った資料はこちらです。 speakerdeck.com

Android の Fragment で出来るを余白を消す

Androidアプリ開発でFragmentを使用した際に画面の上下左右に余白が出来てしまいました。
こちらのページに記載されている通り、 res/values/dimens.xml の設定を下記の通り変更することで余白が消えました。

<dimen name="activity_horizontal_margin">0dp</dimen>
<dimen name="activity_vertical_margin">0dp</dimen>

デフォルトでは 0dp のところに 16dp が指定されていました。

Android の Minimum Required SDK / Target SDK / Compile With

Android ではSDKの指定がいろいろあり、それぞれについて紹介します。

Minimum Required SDK

アプリがサポートする最低レベルのSDKを選択します。
ここで選択したバージョンより低いOSではアプリがインストールできません。

バージョンの選び方。バージョンアップ。

バージョンが低いと開発工数が増えますが、高くするとアプリを提供できる範囲が狭くなるため、
工数と端末シェアのバランスを見てバージョンを選びます。
端末シェアが低くなったバージョンはサポート外にし、バージョンを上げていくと良いと思います。

Target SDK

アプリをテストするプラットフォームのSDKを選択します。
Android SDK の過去のバージョンの SDK のアプリを実行できる機能があるため、ここで選択したものよりも新しいSDKの端末でアプリを実行することが出来ます。

バージョンの選び方。バージョンアップ。

リリース前は最新バージョンを選択すれば良いと思います。
Google公式には最新にするように書かれていますが、SDKのバージョンアップによりバグが発生しないとも限らないので慎重に更新を行う必要があると思います。
こちらのバージョンを上げる際は一緒に Compile With のバージョンを上げると良いと思います。

SDKに新たな機能が実装されたら?

新たな機能が実装された場合は、バージョンを上げなければならないかもしれませんが、サポートライブラリの提供がある場合はバージョンを上げずにそちらを利用する選択肢もあります。
SDKバージョンを上げる場合はアプリ全体をテストし直すようにしましょう。

Compile With

アプリをビルドするコンパイラSDKを選択します。

バージョンの選び方。バージョンアップ。

こちらは Target SDK と同じで、リリース前は最新バージョンを選択すれば良いと思いますが、SDKのバージョンアップによりバグが発生しないとも限らないので、リリース後は慎重に更新を行う必要があると思います。
先程も記載しましたが、こちらのバージョンを上げる際は一緒に Target SDK のバージョンを上げると良いと思います。

番外編

SDKバージョン20から提供された機能をSDKバージョン10の端末でも使えるか?

サポートライブラリが提供される場合はそちらを使い、SDKバージョン10の端末でも使えます。
サポートライブラリがない場合は頑張って自作することになります。

参考にしたサイト