Spica*

プログラミングの話。

Kotlin Flow完全に理解したので、LiveDataで困っていたことを書く

むおおおお! 久しぶりに個人ブログを更新したけど、 個人的に脳内を整理するために書き散らしたエントリなので役に立たないかも。

Kotlin Flowを勉強する場合、例えば以下のサイトを流し読む方が役には立つハズ…

勉強するときに見たサイト

このエントリを書いた背景とか

  • ViewModelやModelといったビジネスロジックレイヤーやデータレイヤーからデータをもらう時の手段としてAndroid LiveDataを使っている。頭が古くなっていて、LiveDataから抜け出せずにいる
  • LiveDataでは困ってるところがわりと出てきていて、そろそろKotlin Flow移行したい気持ちが湧いて出ている

LiveDataで困ってるところ

LiveDataはobserveしないとintermediaryが実行できない

Kotlin Flow

上記はKotlin Flowの最初に出てくる図だけど、 上記の図の各要素は、LiveDataでは以下のように例えられる。

class MyView : LifecycleOwner {
    val dataSource = MyDataSource()

    fun main() {
        // ③
        dataSource.displayUpdatedAt.observe(this) { value ->
            // TODO display value
        }
    }

    override fun getLifecycle() = TODO("Not yet implemented")
}

class MyDataSource {
    private val _updatedAt = MutableLiveData<OffsetDateTime>() // ①
    val displayUpdatedAt = _updatedAt.map {
        it.format(DateTimeFormatter.ISO_DATE_TIME) // ②
    }

}
  • ①がProducer
  • ②がIntermediary
  • ③がConsumer

このとき、LiveDataは、③のコードを書いていないと、②が実行されないという特徴がある。(Kotlin Flowは使い方次第)

これはLiveDataの仕様である。LiveDataはあくまでUIに値を表示するために作られたもの。 ②はUIのために値を加工するプロセスであり、(LiveData的には)③がなければ②は実行する必要がないのだ。 これはLiveDataのいいところであり、③のコードが実行されるまでは、①が実行されても②を実行しないので、②がコストのかかる処理だった場合、無駄な処理をしなくて済む。

しかしこの挙動が困ることがある。例えば①の値が更新されたことを契機に、MyDataSource内で何らかの処理(データベースのアクセスやネットワークアクセスなど)をしたいことが時々ある。 それを実現するために、③をわざわざ書かざるを得ないことがある。(当然その目的の場合、observe()の中身は空実装になる) LiveData「だけ」だと、実現できないことがあるのが問題。

Kotlin Flowの方が細かいことができること、 Kotlin Flow(というよりFlow Builderかな?)はLiveDataに変換することもできることを考えると、  基本的にはKotlin FlowのAPIを使って、LiveDataとして扱いたいときは .asLiveData() ( androidx.lifecycle  |  Android Developers )を使ってLiveDataとして振る舞うようにするのがよいのではないかと考えている。

(後半は推測) Eventの扱いが面倒

LiveDataは、前述の①を変更すると③の関数が実行される。 しかし、①を変更したタイミングだけでなく、 ①で値を持っている状態で③を実行する(=observeを行う)と、そのタイミングでも③のobserverの関数が実行される。

これはネットワーク通信を行った結果を一度だけ受信する時に、問題になる。 例えばネットワーク通信後のデータをLiveDataで保持し、画面回転すると、実際にはネットワーク通信をしてないのにネットワーク通信後のデータが③のobserverの関数に流れる。

これをLiveDataを使って避ける方法については、既に答えが出ていて、Eventラッパーを使うのが正解なのだが、いかんせん記述が長くなるのが気になっている… 例えばネットワーク通信部分では、以下のような定義を行う。

class MyDataSource(
    private val api: RestApiService
) {
    private val _myList = MutableLiveData<Event<Resource<List<MyEntity>>>>()
    // ....(以下省略)

    suspend fun fetchData() {
        _myList.value = Event(Resource.Loading(_myList.value?.peekContent()?.data))
        try {
            val result = api.myRestApi().response()// TODO ネットワークからREST APIのデータ取得
            _myList.value = Event(Resource.Success(result))
        } catch (e: IOException) {
            _myList.value = Event(Resource.Error(e, _myList.value?.peekContent()?.data))
        }
    }
}

(ここからは推測) Kotlin Flowは、

他の中間演算子で指定しない限り、Flow は「コールド」かつ「遅延」となります。 https://developer.android.com/kotlin/flow?hl=ja

ということなので、observeした時に命令を実行する挙動は避けることが出来そう。 つまりEventラッパーはなくすことができて、シンプルに記述することができるようになりそう。

Resourceラッパーは https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ja を見る限り

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(val exception: Throwable): LatestNewsUiState()
}

のような記述があるのでどうだろうなーと思いつつ、 Kotlin FlowではexceptionをFlowで流せるので、Resourceもいらなくなるかもなーと感じている。 シンプル・イズ・ベスト。余計なグルーコードなどはなくしていきたい所存。

(これは推測) LiveDataはAndroidのための仕組みなので、Kotlin Multiplatform(KMP)などで障害になる

LiveDataはLifecycleOwnerを指定することで、AndroidのLifecycleを監視して、 onStart()〜onResume()の間でのみobserve()の中身を実行するようになっている。 (これはDialogの表示などで例外が発生しないようにする時なんかで特に便利に使っている)

LiveDataはAndroidのための仕組みであり、AndroidAPIに依存してしまう。 Kotlin MultiplatformといったAndroid非依存向けの環境も(将来含め)ターゲットに入れる場合、 ロジックが結局Androidに依存した処理となってしまい、障害になってしまうのではないかと思っている。

対してKotlin FlowはAndroidではなくKotlinという言語自体に含まれる仕組みであるため、 例えばiOS(Kotlin Native)やWeb(Kotlin/JS)などでも扱えるのではないかと思っている。 (ただ、Kotlin MultiplatformもKotlin NativeもKotlin/JSも、実際に扱ったわけではなく、コンセプトだけ見て想像でものを言ってるので、正しくない可能性はある)

まあ、それを言い出すと例えばMVVMでいうところのViewModelやModelレイヤーでは ContextやJava 8 Date/Time APIなどにも依存しないコードを書く必要があるはずなので 視野に入れるかどうかの判断のほうが先に重要にはなりそうではあるが…。

ということで

Kotlin Flowには徐々に移行していきたいお気持ち。

一気に変更することってなかなかできないのよね。 チームに納得してもらう必要があるし、テストしなおしだし。 事前知識が大量にいる上に、お金と時間の調整もしないといけないから、 積極的にマイグレーションしているブログなんかを見るとすげえなって思う。 がんばってこ。

ピアノの練習を始めてしばらく経ったので感想とか

最近誰か身内以外と話したり、ブログを書いて自分の考えを整理したりする機会というのを設けていないなぁと思い、ここ最近趣味でやってるピアノのことを書いてみる。 (自分向けのおっさんの戯言なので、面白いことは特にないです)

ピアノを触っていた経歴とか

  • 幼稚園の時、「鍵盤楽器」を初めて触った
  • 小学4年の時、数ヶ月程度電子キーボードを短い期間、音楽の先生に「やらされていた」ことがあった
  • 中学3年の時、小学4年のときに練習で使っていた電子キーボードを引っ張り出してきて数ヶ月遊んだ。それをきっかけに電子ピアノを親に買ってもらった
  • 高校1年の時、吹奏楽部にも入り、音楽にどっぷり浸っていた
    • さらにクラスにDTMが趣味である友達の影響も受け、間を空けつつ22歳くらいまでは作曲をして遊んでいた
    • ピアノはMIDI端子があったので、その間ピアノは作曲道具となっていた。

…といった経歴がある。(こう書き出してみると、鍵盤楽器に触っていた期間はすごく長いなぁ…)

とはいえ、どの期間においても「誰かに教えてもらった」期間が全くなく、ピアノのレベルでいえば初心者レベルのままだった。

その後、20代の間はほぼひたすらエンジニアとして仕事をしていた。音楽にふれることも全くなくなり、電子ピアノはただ場所を取る置物になっていた。「もうピアノはやらない」と思ったので、ピアノは立て掛けて邪魔にならないクローゼットの中にそっと仕舞った。

まだピアノに戻ってきた

ふと、31歳になったころ、エンジニアとしてもイマイチで「僕はどうなっていきたいかなー」ということ考えるようになった。バリバリ活躍してるとかなら良かったかもしれないけど、そういうわけでもなく。絵を書くことも、趣味でプログラム書くことも、曲作ることも、ピアノ触ることもなくなった。 そうなったときに、自分の人生に色をつけるものってなんだろう。ずっと楽しく続けれるものってなんだろうと考えたとき、思いついたのがピアノだった。

いつだってピアノの音ならずっと聞いてられていた。死ぬまでピアノの音を聞いていることは想像ができた。 といったところから、32歳のおっさんにもなってピアノを再開した。

ピアノは、以前から使っていた電子ピアノではなく、新しくCASIOの電子ピアノを購入した。 理由は、以前から使っていた電子ピアノはすでに古くなっていたこと、もともと使っていたのがカシオのピアノでありタッチの感覚が好きだったこと、角野隼人さんに影響されたこと。 今はピアノ1年半経って34歳なのだけど、今も続けられている。

ピアノの練習(開始編)

どうやって練習していけばいいのかが全くわからない。。これが最初の感想。 経緯にも書いたけれど、「誰かにピアノを教わった」という経緯がないので、どの順番で進むかがわからない。

吹奏楽部に居た頃、ピアノを練習していた人が居たので、「ハノンとかやるよね」「最初はバイエルからかな」「これやったらいいよ(と、バーナムの楽譜を数枚コピーしてもらった(※やっちゃダメです))」「ピアノやってて、次ソナチネなの」とかそういう話を聞いた程度。

おっさんなので、基礎が重要なことはわかる。 とりあえず簡単なものから「できないことをなくしていこう」という精神で、ネットで調べつつバーナム一巻を購入。(どうも最近はバイエルよりもバーナムでピアノに入門する人が多いようなので)

並行して、好きな曲が弾けるようにもなりたいので、

などなどした。 当然どれもちゃんと弾けるはずもなく。あれこれ試行錯誤を続けていた。

ピアノの練習(現在)

今使っているピアノを購入してから1年4ヶ月。 よくわからずとも続けてみるもので、色んなものを触っていると自分のレベルというのもがよくわかってくる。

  • 楽譜は初級〜初中級と銘打ってるのものを選ぶと、大きなストレスなく演奏が行える
    • 細かい難易度はアテにならない(黒鍵がおおい調などによって大きく難易度が変わる)
  • バーナムはすごく効果が高い
    • 各曲が短く、サクサクと習得できる
    • 1巻終わったあと、2巻の途中、2巻終わったあとで比べると、随分楽に曲が弾けることを感じる
      • (終わった…というのも判断する人がいないので個人の判断になっているのが不安ではあるのだけど)
  • ハノンは意外と難易度が高い
    • 筋トレのようなものなので、毎日続けるべきだがなかなか難しい
    • 20番まで覚えはしたが、ちゃんと指を独立してテンポに合わせ弾こうと思うと、未だハノンNo.2でテンポ74がギリギリ
    • 曲になっておらず音楽的な解釈を行いにくいので、力の入れ方がわかりにくい
      • メトロノーム入れつつテンポキープは絶対
      • 中指、薬指あたりでたいてい遅れるのだけど、遅れたときに後で帳尻をあわせるようなことをなるべくしない意識が必要
      • 音量はpくらいの強さで、テヌートで弾くのを基本として、時折アーティキュレーションを変更して弾くのがいい
  • ツェルニーがすごく楽しい
    • リストやショパンのお師匠さんっぽい?
    • まだ7番までしか弾いてないけど、それぞれがちゃんと曲になっていてすごく楽しい
      • こうしたほうが楽しいなぁとか考えながらできる
    • それぞれの曲が短く、繰り返しが多いので反復練習になる
      • 繰り返した後はクレシェンドをそこまでしないように弾いたりとか
  • ブルグミュラーは基礎総集編
    • 曲ごとに求められるスキルがはっきりしている
      • 例えば小さな集会だと3度の和音がはっきり上手に基礎で引けてないと手をつけられない
      • 例えば牧歌だとバーナムの「懸垂の練習」とかがちゃんとできてないとテンポキープが難しい
    • 自分が何ができて何ができないのかがよく分かる
  • バッハは難関だが通るべき道
    • ツェルニーを代表として、多くの曲は左手が支えになるので、あまり動かない
      • バッハは左手も主旋律になるので、苦手が露呈する
      • 逆にバッハができれば左手の苦手意識が大きく減りそう
    • 左手と右手の掛け合いがあるので、強弱の付け方も色々学べる
    • 作曲としてのテクニックも学べる(らしい。和音、和声、弱起、とか?よくわかってない)
    • インベンションと小プレリュードと小フーガは買ったが、プレインベンションを買うべきか悩ましい。。

まとまらないまとめ

自分の頭を整理するために、今練習している範囲で思いつく限り書いたけど、 結構考えてることあったんだな。。とか思った。

完成度を高めるため、自分の考えの間違いがある・ないを確認するためにも これを誰かと共有しつつ進めたいなぁ…と思うなどした。 誰かにピアノを教えて貰いに行きたいなぁ。

アシダ音響 ヘッドフォン ST-90-05 を買った

ST-90-05 側面

アシダ音響のヘッドフォン届いたので早速使ってみた。6,600円(税込)+送料。 https://togetter.com/li/1839213 を見て知っていて、 友達も買ってたので、僕も電子ピアノ用にいいかもって思って買ってみた。

久しぶりにヘッドフォンを買って、触ったり聞いたりしてるうちにいろいろ気持ちが溢れてきたので書き留めておく。 他の人の感想とか見ずに書いてるし、音楽からもう結構離れてる人なので結構トンチンカンなこと言ってると思う…

外観・重さ・つけ心地

ST-90-05 俯瞰

まず開けた第一印象は「し、視聴覚室にあるやつ!」 小学校の時に放送室とかにもあったかも? 飾り気とかなくてシンプル。ザ・業務用感。

全体的に小ぶり。イヤーパッドも小さい。 ヘッドフォンは結構大きくていかついやつも多いので、かなりコンパクトな印象。

持ってみた感じはかなーり軽い。 ヘッドフォンをつけたときの、耳へのフィット感はかなりよい。丁度耳にピトッとくっつく感じ。 耳への圧迫感も少なめで、耳が痛くなりにくいかもという印象。 実際に長時間つけたわけではないので、ちょっと意見は変わるかも。

以前Rolandのヘッドフォン(12,000円くらいのやつ)を持ってたのだけど 耳がめがねのつるとヘッドフォンに押しつぶされて、短時間でもじわじわ痛くなっていた。 僕は普段眼鏡をしているので、軽くて長時間つけれるだったら嬉しいな。

ST-90-05 端子

ヘッドフォンにしてはケーブルの長さは短めという印象。(コード長1.5m) 床にひこずらない長さで嬉しい。

プラグはステレオ3極。 最近のイヤフォン、たいていマイクもついててだいたい4極の物が多いのだけど、 僕が使ってる電子ピアノは3極しか対応してないものなので嬉しかった。

つけた時の遮音性はそこそこ。 ヘッドフォンつけて音楽鳴らしていない状態で誰かに話しかけられた時は、 相手が何か言ってることは分かるけど、何言ってるのかは分からないくらいの感じ。 僕が妻に話しかけられた時、妻が何言ってるか分からなかったので、ヘッドフォンを外して、「ごめんごめん、なんて?」って聞き返したことがあった。 密閉型ヘッドフォンだしこんなもんかなーとは思う。

ST-90-05 イヤーパッド

実際の音の方は「コスパすごい。買ってよかった。音楽というよりも音を聞くのに向いている。オススメする相手は選ぶ」という印象。

音量バランスについて

音楽鳴らしてみてまず気づくのは低音の強さ。 普段は

を使ってるのだけど、これらと比べてもかなり低音が強い。 どれくらい強いかと言うと、ポップスの曲を聞くとボーカルの声よりもドラムやベースの音の方が目立つくらいは強い。 なので、人によっては「なんか音がくぐもって聞こえる気がする?」と思うのではないかなーと。 一般的なイヤフォンと比べてしまうと癖があるので、オススメする相手は選ぶなと感じる。

ただ、僕はドンシャリな音が好みなのと、密閉型ヘッドフォン持ってないので「一般的なイヤフォン」に結構バイアスかかってる可能性は高い。 低音が強めに鳴るのは密閉型ヘッドフォンの特徴でもあるので、他の密閉型ヘッドフォンと聴き比べるとまた違う感想を持つかも。

音の輪郭がはっきりしていることについて

音量バランスについてあれこれ言ってしまったところだけど、このヘッドフォンのすごいところは他にある。

このヘッドフォンがすごいのは、音の輪郭がはっきりしていることと、音の立体感があること。 この2つがすごい。Twitterでお値段以上って言われてたの、多分これが理由じゃないかな。

音の輪郭がはっきりしているというのは、平たく言うと 音楽を聞いてる時にそれぞれの楽器の音がそれぞれ分かれて聞こえる(識別しやすい)ということ。 (適切な言い回しではないかもしれない。僕はこう解釈してるという話)

ヘッドフォンほしいなと思ったら僕はヨドバシに言って聴き比べをするのだけど この音の輪郭がはっきりしているという特徴は安くても1万を超えるイヤフォンにしかない特徴と思ってる。 3000-6000円くらいのイヤフォンにはこの特徴はない。6600円でこの特徴を持ったヘッドフォンに出会えるのはすごい。 2-3万円する高級イヤフォン/ヘッドフォンまでになると、この特徴はもっとはっきりする。 以前Jabra(だったかな)のイヤフォンをした時に、めっちゃうらやま…って思ったことがある。 (音楽への情熱が足りないので手は出せなかった…)

音の立体感がある、というのは音の輪郭がはっきり聞こえるというのにも関連しているのだけど 鳴っている音の方向がはっきり聞こえるというイメージ。

音について総合的に見てみて

上記の特徴から、音楽というよりより細かい「音」を聞くという用途に向いたヘッドフォンだなと感じてる。 それこそ視聴覚室とか放送室で「人の声がどう入っているかを聞く」とか、複数の音のバランスを確認するとかいった用途には向いてるんじゃないかと思う。

僕は趣味でピアノをやっている(下手くそだけど…)ので家に電子ピアノがあるのだけど、 音色がはっきり聞こえるので電子ピアノには向いてるなーと思った。 togetterでもASMRに向いてるのでは?という意見あるけど、本当にそう思う。 マイクついてないけどビデオ会議なんかにも向くんじゃないかな。

逆に一般的な音楽鑑賞という用途には、あまり向いていないかもなーと。 低音の目立つ曲を聞いたり、クラシックのように繊細な音の性格を聞いたりする場合ならいいかも。

実際に音楽されてる方の意見も聴きたい。レビュー記事探してみようかな。

余談

あとこれはふと思っただけだけど、ヘッドフォンをつけている状態で ・ヘッドフォンから音楽を流した時 ・他の人から話しかけられた時 の2つの音の低音〜高音の音量バランスがほぼ同じになってるかも?と思った。 僕がたまたまそう思っただけなのかもだけど、現場での音の確認用途なんかも意図して設計している可能性はありそう。

Daggerに入門した

GDG DevFest Okayama 2019に参加した際、STAR_ZEROさんに色々Daggerに入門するときのリソースを教えてもらったので、Android Daggerに入門してみました。(やっと個人的な宿題を一段落させた気持ち…)

具体的にはCodeLabsのUsing Dagger in your Android appを教えてもらいました。yanzmさんのMaster of Daggerも教えてもらったんですけど、英語を勉強中なのと、公式のリソースだけで進めたい気持ちもあり、どれを読むかを悩んだ結果CodeLabsを選びました。 今回はそのときに学んだ内容のメモを書きます。ほぼ超訳です。主にDaggerで使用するAnnotations周りのメモとなります。

僕がDaggerを勉強したいなと思ったのは、ユニットテストをやりたかったからです。 僕は今DIを使っていないプロジェクトを触っていて「ユニットテストを書きたい」気持ちがわりとあります。しかし、どうしてもユニットテストをするために書かなければならないコードが多い、かつmockにしないといけないオブジェクトが多いために、テストが書き始めれない…となっているところです。DIを使うと、クラスをインスタンス化する時に渡す必要のあるインスタンスを必要なものだけにできるので、モックにするものを限定的にすることが可能です。そこに魅力を感じています。

Daggerについて

DaggerはDependency Injection(DI)のためのライブラリです。Androidだけでなく、Javaプロジェクトでも利用できます。 「Daggerを使用することで、コードの再利用性を高め、リファクタリングやテストを容易にします」とのこと。 「DIって何?」という箇所に関しては、Dependency injection in Androidやその他リソースを読むのがよさそうです。

基本的なAnnotations

@Inject

二種類あります。

  • コンストラクタにつける@Inject
    • Daggerが、どうやってインスタンスを作るかを知るために必要なアノテーション
    • これをクラスのコンストラクタにつけることで、@Injectをつけたコンストラクタの引数は、そのクラスの依存であることをDaggerが知ることができる
    • これはDaggerが生成できるインスタンスに限ってつけることが可能。例えばAndroidではActivity/Fragmentなどはシステムが生成する。Dagger自身が生成することはできない。Activity/Fragmentなどは、代わりにfield injectionを使用する。
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
    ...
}
  • field injectionでの@Inject
    • Androidフレームワークは、システムによってActivityやFragmentが生成される。そのため、Daggerによってインスタンスを生成できない。そのため、Field Injectionを使う。
    • 注入したいフィールド変数の宣言に@Injectをつける
      • @Inject lateinit var registrationViewModel: RegistrationViewModel のような感じ
      • この時、宣言にprivate修飾子をつけないこと
class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // ① #inject(LoginActivity) で、LoginActivity内の@Injectのついたフィールド変数にDaggerによってインスタンス化された値が注入される
        (applicationContext as MyApplication).appComponent.inject(this)
        // ②この時点で、 this.loginViewModel にはインスタンスが注入されている

        super.onCreate(savedInstanceState)
    }
}

@Component

@Component
interface AppComponent {
    ...
    fun inject(activity: LoginActivity)
}
  • 上記の例であれば、依存を注入して欲しい
  • インターフェースメソッドのパラメータは、Daggerに何を注入して欲しいか伝えるためのもの
    • LoginActivityには、var loginViewModel: LoginViewModelの宣言に、@Injectをつけた。この場合、Daggerは、LoginActivityをDaggerコンポーネントであるAppComponent#inject()メソッドにLoginActivityを渡すことで、DaggerにLoginViewModelを注入して欲しい事がわかる
  • AppComponent#inject(this)をActivityで呼び出す場合、super.onCreateよりも前に書く必要がある。Fragmentの再生成時の問題を回避するため。

@Module

  • classに対して@ModuleをつけたものをDaggerモジュールと呼ぶ
  • Dagger Moduleは、Daggerがどうやってインスタンスを用意するかを、Daggerに教えるもの
    • @Provides, @Binds, @BindsInstanceを使ってインスタンスを提供する方法を定義できる
  • Dagger ModuleはDaggerがアプリケーショングラフを構成するのために、@Componentを付与しているインターフェースに対して@Component(modules = [StorageModule::class])のようにDaggerに教える必要がある
  • モジュールは、オブジェクトを提供する方法を隠す手段である

@Binds

  • インターフェースを用意する手段をDaggerに教えるためのもの
    • もっと複雑な教え方をすることも可能(CodeLabsでは触れていない)
  • 必ずabstractメソッドにする必要がある
  • 返却の型を、用意したい型とすること
@Module
abstract class StorageModule {

    // Storageクラスを要求した場合、DaggerはSharedPreferencesStorageを用意させる
    @Binds
    abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}

@Provides

@BindsInstance

スコープ

  • Daggerでは、@Injectの対象のインスタンスは、毎回Daggerによって生成される。これがデフォルトの動き。
  • しかし、毎回newするのではなく、同じインスタンスを複数の画面で使いまわしたい場合がある。この場合に使うのがスコープ。

    @Singleton

  • これをクラスに着けると、必ずアプリケーショングラフにつき一つのインスタンスとなる
@Singleton
class UserManager @Inject constructor(private val storage: Storage) {
    ...
}

@Subcomponent

所感

DaggerのDocumentationを読んでて思ったのは、「なんか便利そうだから入れてみる」みたいなノリで入れると、「DI使うのかい?使わないのかい?どっちなんだい?」って感じになって地獄を見るかもしれないので、基礎を勉強した上で(チームでの開発をしている場合はチームを説得するなりして)入れるのがよいかなって思っています。 あと、もし既存プロジェクトにDaggerを導入する場合は、まずはリファクタリングから始めなければならないかも知れません。どのようにリファクタリングするかは、Daggerの基礎を読み、ゴールとなるapplication graph(アプリで使用しているクラスの依存関係)を考え確立したうえで、導入した方がよさそうです。

Daggerは情報量が多く、ドキュメントが色んな所に散らばっている上に、古い情報も残っていたりして、かなりいろいろなところを読まないといけない状態になっています。これは学習コストをさらに引き上げている一つの要因のような気がします…。

おまけ:dagger-androidについて

DaggerをAndroidプロジェクトに導入するには、下記をbuild.gradleに入力すればOKです。

dependencies {
    ...
    implementation "com.google.dagger:dagger:2.24"
    kapt "com.google.dagger:dagger-compiler:2.24"
}

これはCodeLabsのapp/build.gradleを見ると分かります。

ただ、僕は終わってから気づいたんですが、CodeLabsの内容は、Daggerの基本のみです…。このままでもプロジェクトへの導入は可能ですが、もっと効率的にAndroidのプロジェクトでDaggerを使うには、dagger.dev/androidにある、dagger-androidやdagger-android-supportといったライブラリの使い方も覚えたほうが良いです。Android Architecture Component - GitHubBrowserSampleには、実際にこれらのライブラリを使ったサンプルがあります(基礎がわかってないとソースみてもちんぷんかんぷんです)。

dagger-androidを含めたDaggerライブラリ全体を入れるには、下記のコードをbuild.gradleに入れる必要があります。

dependencies {
    ...
    implementation "com.google.dagger:dagger:2.24"
    implementation "com.google.dagger:dagger-android:2.24"
    implementation "com.google.dagger:dagger-android-support:2.24"
    kapt "com.google.dagger:dagger-compiler:2.24"
    kapt "com.google.dagger:dagger-android-processor:2.24"
}

↑のようなdaggerの基本ライブラリからdagger-android-supportまでを含めたすべてのdependenciesの導入方法は、GitHubBrowserSampleの中身を見て調べました。公式に書いてそうなんですが、僕自身がその導入方法を書いてるところを見つけれていません。。 (CodeLabsは基本だけなのでdagger-android-supportに関する記述がないし、Dagger公式のHow do I get it?セクションは古いし…。)

Catalinaへアップグレードした

会社のPCをアップデートする前に、自分のPCをCatalinaにアップグレードしといた。

インストール

  • ダウンロードで20分程度、インストールは1時間程度だった
    • 酷使しているPCではないので、よく使っているPCとかだともっと時間かかるのかも

気になったこと

  • Catalinaインストール後、通知を許可するかどうかのNotificationが沢山表示された。
    • アプリ毎に表示されるっぽい
  • 確かにWebページとか見てるとフォントが太くなった
  • Google日本語入力の学習データが無くなったような気がする
    • 以前は普通に一番欲しい変換候補が一番上に出てきていたが、でなくなった
    • 学習機能が動いていないわけではないので、使ってたらすぐに直っていく感じ
  • Googleのバックアップと同期アプリが、「パスが見つからない」というダイアログを出してきた
    • ダイアログが出た後、ディレクトリを選択する画面が出てきた
    • ~/Google ドライブディレクトリを選択すると、通常通り使えるようになった
  • Mac付属の辞書アプリ(dictionary.app)を開くと「辞書が構成されていません」という状態になっていた
    • しかし、辞書を構成する画面を開くと、以前選択していた辞書にチェックは入っていた
  • Xcodeをインストールするために容量を開けるのが大変だった
    • ストレージ自体は23.01GB空いていたが、それでも足りないと言われた

所管

  • アップデートして少し使ってみたけど、大きな問題は起きてない
    • Steamを手動でアップデートしなければならなかったくらい。
    • あと、一つだけゲーム入れてたんだけど、32-bitのみ対応だったので動かなくなった…
      • 古いゲームなので仕方がない。おそらく今後64-bit対応されることもないだろう…
  • Xcodeは現在インストール中
    • 3-4時間くらいかかってる気がする…
    • Catalinaのアップグレードよりも時間がかかってる
  • SidecarはiPad持ってないので試してない。けどちょっとiPadが欲しくなってきた。
  • Twitter for Macはインストールした
    • バッチリ動いている
    • iOSアプリ開発者は多いので、iOSアプリが作れればmacOSアプリが少ない手順で作れる、という認識
    • 結構これはインパクトでかいのでは。

SwiftUIとMac Catalystがすげー気になる。わくわく。

ルータ変更記: Aterm WG1200HS -> Buffalo WSR-2533DHP2

経緯

  • Aterm WG1200HSを使っていた
  • 比較的新しい機器、具体的にはPixel 3や、MacBook Pro(2018) with TouchbarでWi-Fiがブチブチ切れる問題が発生していた
  • 「これはあかんなー」みたいな感じ
  • なのでルーターを買い換えた
  • ちなみにWSR-A2533DHP2 っていうのもある。型番にAがついてる。Amazonではこれが人気。
    • WSR-2533DHP2との違いは「バンドステアリングLite」という機能がついているかどうか
    • これは、Wi-Fi SSIDを一つにして、そこにつないでおくと、電波状況に応じて5GHzと2.4GHzを切り替えてくれる機能
    • この機能が切り替わるときに瞬断が発生する可能性があるのではないかと思い、買わなかった
      • 家せまいし必要なさそうっていう理由もあった
    • ちなみにこの機能がない、僕が購入したやつは、価格も3,000円ほど安い

速度

Googleのインターネット速度計測を使用。

  • Aterm WG1200HS
    • #1: download: 49.6, upload: 91.7
    • #2: download: 45.3, upload: 92.1
    • #3: download: 36.1, upload: 91.8
  • Buffalo WSR-2533DHP2
    • #1: download: 81.0, upload: 91.7
    • #2: download: 90.7, upload: 91.6
    • #3: download: 86.9, upload: 91.6

まぁ、発売時期ぜんぜん違うし、価格も倍違うので…って感じはある。

機能的な部分

  • あんまり使い倒さないので、Atermでも十分だった
  • AtermよりもBuffaloのほうが、専門用語みたいなのは少なくて印象は良かった
    • 例えばAtermプロテクション機能とだけ書かれた、Wi-Fi 2.4GHzのみの設定なんか全然なんなのかよくわからなかった
    • ヘルプついてないし
  • マニアックなところでいうとsyslog機能はBuffaloの方にだけあった
    • macのsyslog有効にして、ルータの情報とって障害追求して遊ぶのもアリ?

Hello, Flutter! (各種フレームワーク間比較)

Getting Startedの記事ではありません。各種フレームワーク間のアーキテクチャを元に良し悪しを考えた記事です。

Flutter 1.0 is out!!

Flutterがついに1.0になったようです!

とはいえ、僕はFlutterを追いかけてはいなかったので、「お前が言うな」とか言われそうですが。

僕がFlutterを気になっている理由

僕がFlutterが気になっているのは、クロスプラットフォームアプリ開発ツール、という点だけではありません。それ以上に UIの描画を自前で行う というコンセプトがあるのが僕的に重要な点になっています。(ちなみに僕はあまりTwitterを見たりしていないので、流行りには疎めです)

クロスプラットフォーム開発のフレームワークというのは、今までにもたくさんありました。僕はAndroid/iOSのネイティブアプリ開発者ですが、ionic(当時は2.0でした)やvue.jsといったフレームワークも扱ったことがあります。それゆえに、OSのデザインとしての制約に関しては無視できない部分があり、ずっと"ちゃんと"クロスプラットフォームで良いアプリを作ることのできないむず痒い気持ちを抱えています。

ionicの事例

例えばionic。ionicはCordovaを基礎としたフレームワークで、Cordova上でAngularを動作させることでクロスプラットフォーム開発を行うフレームワークです。各プラットフォームに合わせた美しいコンポーネントが用意されており、少しのHTMLを書くだけで簡単にAndroid/iOSに合わせたデザインが適用できます。そしてLive Reload機能もあるため、ソースコードを変更するとすぐに画面に結果が反映される。まさに高速に開発できるフレームワークです。

僕はこのフレームワークを採用するのを過去に渋ったことがあります。ionicが心配だったのではありません。問題は、Cordovaがベースとなっている点でした。

Cordovaはご存知でしょうか。ネイティブアプリ上にWebViewを一枚表示し、そのWebViewとネイティブアプリ(AndroidだとJava, iOSだとObjective-Cソースコードを書く部分)間の共通インターフェイスを提供することでクロスプラットフォームを実現するフレームワークです。

このアーキテクチャには、決定的に問題のある点が一点ありました。ネイティブAnroidアプリ開発者はおそらくご存知でしょう。Androidは現在フォアグラウンドにあるアプリを快適に動かすために、「アプリをバックグラウンドに移した後、端末のメモリが不足した場合、現在フォアグラウンドにあるアプリ以外のアプリのActivityやプロセスを状況に応じて殺す」という仕組みがあります。これはOSのデザインの一部であり、設定によってなんとかViewを殺さないようにする、といったことは不可能です。

ionicはCordovaの仕組みに加えてAngularをWebView上で動作させるため、大量のメモリを食います。ここで、ionicアプリからIntentで端末にプリインストールされているカメラアプリを起動してみましょう。どうなるでしょうか。カメラはご存知のように大量にメモリを食います。Nexus 5Xのようなメモリの少ない端末や、Pixel 3のように大量のCPU/メモリを使用するカメラアプリの場合は、バックグラウンドプロセスをすぐに殺しにかかるでしょう。そして元のionicアプリに戻ったときには、Angularの動くWebViewは既に殺されており、アプリは最初からになります。当然、WebView上の画面のスタックはクリアされ、それを復元する術はフレームワーク側にはありません(僕の触った時点では少なくともそうだった)。

また、Cordovaには罠が多く、プラグイン制作に関しては多くの人がStackoverflowにお世話になったことでしょう。configがわかりにくく、プラグインのアンインストールに関しては多く人が一度はハマるところと思います。gitignoreがフレームワーク的に意識されていないのも、なにか心の中にひっかかったものを感じ続ける要因です。

また、UIのタッチレスポンスが遅い問題もあります。WebViewなので仕方がないでしょう。

こういった理由より、採用を渋っていました。結局のところ採用はしたのですが、上記の問題は杞憂ではありませんでした。

React Nativeの事例

React Native at Airbnbの記事を覚えているでしょうか。もし見ていなかった場合、ぜひ確認してみて下さい。

ちなみに僕はReactは良いものと思っています。disるつもりは毛頭ありません(ついでに髪の話でもありません!)。WebサイドではReactを少し使った経験があり、とても使いやすかったことを覚えています(React Native自体は使ったことがありません)。伝えたいのはionicと同じく、仕組みの話です。

React NativeはJavaScript Coreをバンドルして動作します(iOSはそうではないようですが)。それを使うことでJavaScriptでロジックを書くことを実現しています。JavaScriptはWebの進化によって多くの便利な仕組み(AltJSやテストツールなど)があり、高速に動作するので、とても良い言語と今でも思っています。

問題はUIの作り方です。React NativeはJavaScriptを経由して書くプラットフォームに存在するViewを利用してUIを描画します。つまり、同じコードでもプラットフォーム独自のUIの制約に縛られてしまうのです。例えば見た目を変えようとしても全く一緒にするには多くのコードを書くことになり、深いことをしようとするとネイティブプラグインを導入せざるを得なくなることもあるでしょう。この問題は、Airbnbの記事にも書いてあります("共通のデザイン言語システム(DLS)"の項目や"ジェスチャー"の項目)。

この問題はどうしても完全に解決はできないでしょう。Webの開発者がネイティブアプリを作るためのフレームワークなのに、結局JavaObjective-Cを覚えなければならなくなることが発生してしまう。これはアーキテクチャの問題です。

Xamarin

Xamarinもアーキテクチャとして、React Nativeと同じ問題を抱えていると考えています。C#よりXamarinの用意したインターフェイスの仕組みを通して、ネイティブのViewを生成して操作します。使用している言語やツールの問題ではありません。

Unity

React NativeやXamarinの問題がUIと言いました。UIを独自に作成する機能を持つ、Unityだったら問題ないのでは、と考えたこともあります。2Dだけでなく3Dも表現できる。表現力ではピカイチです。

しかしこの場合標準のUIコンポーネントが用意されていない、というのが問題です。全く同じ画面をAndroid/iOSで用意してあげる(まさにゲームのような)のであれば良いかもしれません。しかし、各OSを使用しているユーザは、ゲーム以外でその挙動を期待することはおそらく無いでしょう。また、dot-per-dotの美しいUIにしたい場合、viewportの設定など考えることが増えるのではないかという懸念もあります。

Flutter

話を戻します。僕はFlutterはここまでの各種問題をほとんど解決しているのではないかと思っています。 各問題に関して列挙しながら書きます。

  • Cordovaの懸念
    • Activityの復帰の問題
      • この記事を書きながら調べてみましたが、これは解決していませんでしたGoogle製なのに、このissueがfixになっていないのは残念でした。
      • ただし問題ではないかも、と思っています。このコメントで依頼されているように、常に今の状態を永続化しておけば、問題にはならないかもしれません。この問題は、僕は真っ先に調べるつもりです。
    • プラグインについて
      • Cordovaのプラグイン管理の仕組みは対応は半ば無理やりのようなものでした。まだ確認していませんが、問題がないことを祈っています。
    • タッチの遅さ
      • こちらも確認していませんが、そんな致命的な問題を残すとは考えにくいので、心配していません。
  • React Native, Xamarinの懸念
    • FlutterにはSkiaが入っており、UIを独自に描画する仕組みになっています。最悪、Widgetsを自分で作る場合でも、複数解像度を意識だけしておけば、DartだけできれいなUIが作れることでしょう。それぞれのOS向けに何かをしなければならない、といったことは起きないと確信しています。
  • Unity
    • FlutterにはUIコンポーネントが存在しているので、最初から独自にUIを作り上げる必要はありません。Material/Cupertinoが自動で振り分けられるという認識でいます。

Flutterについても、どうしても懸念として残ってしまう問題もあります。ionic以外に共通していますが、VMを内包しているのでフレームワーク側のバグでクラッシュしてしまうことがありえます。その場合、アプリ制作者である僕らは、issueに助けを求めるしか方法がありません。自分で直すのは、多くの場合人や時間的な都合で困難でしょう。

まとめ

いろいろ書きましたが、実はほとんどアーキテクチャしか意識しておらず、コードを書いていません。なので、面倒な部分も実は多いかもしれないな、と感じているところもあります。

もっとちゃんとFlutterのDocumentationを読んだ上で、業務でのこのフレームワークの使用を慎重に判断していこうと考えています!