torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

Haskellの門を叩いてみた

こんにちは、とりかつです。

最近、私の中で関数型パラダイムのブームが来ています。 その流れでHaskellを触りました。 そこで、今回の記事はHaskellを触る経緯と、学習していて感動したところについて話したいと思います。

Haskellを触る経緯

冒頭でもお伝えしたように、私の中では関数型パラダイムがブームです。 最近、普段JSとか触ってる自分の耳にも、ReactのFunctionalComopnent最高だぜとかテストしやすいぜとかいう声が聞こえるようになりました。

私は、一度気になったものは、気になって気になってしょうがない性なので、空いてる時間に細々と、関数型パラダイムに入門することにしました。

関数型の入門にあたって、初めはJSとかで関数型の雰囲気を掴もうとしてました。 でも関数型やるならゴリゴリの関数型言語やればいいやんってなって、Haskellの門を叩いてみることにしました。

普段、新しい言語とか触るときは、公式docとかでさらっとやって「完全に理解した」状態にしてました。 Haskellもそうしようと思っていましたが、あえて入門書を買うことにしました。

あえて入門書を買うことにした理由として、関数型パラダイムを基礎から固めたいという思いがあります。 私が今まで触ってきた言語は、java, kotlin, dart,jsあたりで、どれもオブジェクト指向の考え方を持ち越すことができました。 しかし、Haskellは純粋な関数型言語ということでパラダイムが異なります。 思想を基礎から固めるには、しっかりと構造化された情報でインプットを行う必要があると考えられます。 それゆえ、入門書を買うという選択をしました。

幸い、オブジェクト指向とかを触ってきた人向けの「すごい Haskell 楽しく学ぼう!」という本があったのでこの本にしました。

www.amazon.co.jp

まだ全部読み切ってはいないですが、解説がとても丁寧な点と、オブジェクト指向の思想と混乱しそうな箇所がしっかりとフォローされていた点がとても最高な本です。

Haskellで感動したこと

Haskellの感動したところは、数学っぽいことが自然な形でコードに落とし込めるところです。

例えば、10以下の偶数の集合を表現したいときは、以下のようになるかと思います。

{0, 2, 4, 6, 8, 10}

さらに、次のようにも書くことができます。

{ x | x≤10, xは偶数 }

ここで、10以下の偶数のリストをHaskellで作ると、次のようになります。

[ x | x <-[0..11], x `mod` 2 == 0 ]

実際に実行してみると、確かに[0, 2, 4, 6, 8, 10]が出力されます。

f:id:torikatsu923:20210127223508p:plain

集合とリストは異なりますが、集合の表記とコードが非常に似ています。

このように非常に自然な表現でコードに落とし込むことができるところがHaskellのいいところだなと思います。

おわりに

まだ学習し始めたばかりで、Haskellのいいところを全て吸収できたわけではありません。 ですが、これは見方を変えると、入門したてにもかかわらず、いい!って思えるところが見つかるプログラミング言語だといえます。

まだHaskell触ったことないって人は、ぜひHaskellを触ってみてはいかがでしょうか。

Logicool mx ergoを導入してみて

お久しぶりです。 とりかつです。

先日、Logicool mx ergoというトラックボールを導入ました。 今回の記事は、その使用感とデスク環境のアップデート状況について話をしたいと思います。

f:id:torikatsu923:20210124231934j:plain

また、以前、以下の記事で私のデスク環境を紹介させていただきました。 もしよければご覧になってください。 torikatsu923.hatenablog.com

目次

  • logicool mx ergo導入の経緯
  • 実際に使ってみて
  • 置いてみて(見た目)
  • おわりに

logicool mx ergo導入の経緯

私は、ITエンジニアとして働いており、その職の性質上机に向かい続ける時間が長くなる傾向があります。 そこで、少しでもQOLを向上させるために、デスクまわりの環境が快適になるよう様々なアイテムを導入してきました。

私のデスク環境を整えていく方針として、自身の生産性に対して投資をするというものがありました。 また、別の軸として、PC操作をキーボードで完結させるというものもありました。

それゆえ、chromeにvimiumという拡張機能を導入し、マウスレスブラウジングを実現したり、 vimを導入して黒い画面からなんでも操作できるようにするみたいな工夫をしてきました。 chrome.google.com

f:id:torikatsu923:20210124232305p:plain
黒い画面の見た目もいろいろいじりました

それゆえ、トラックボールの導入はPC操作をキーボードで完結させるというポリシー登坂するため、導入について非常に悩みました。 これについて、会社の先輩の先輩に相談したところ、「マウスとキーボードのいいとこ取りして共存するのもいいかも」という話をいただきました。 たしかに、真に求めるべきものは効率であり、先入観や自身の価値観で便利なものを導入しないのことは、かえって作業の効率を落としかねないです。

例えばvimでインサートモードの時に、カーソル以降の文字を全て消したい時は、esc→$→xなんてせずに、インサートモードのままctrl+kした方が効率が...(ry おっと、だれかがきたようだ...

こんな経緯で、トラックボールの導入を決意しました。

一口にトラックボールには様々な種類があります。 その種類の多さゆえに、アイテム選びには非常に迷いました。

そこで、以下の条件で絞り込みをしました。 - 所持しているデスク周りのアイテムと親和性の高いデザイン - Bluetoothで接続先を複数設定できる - 机に対するグリップ力

この条件でいろいろ見ていった結果Logicool mx ergoに辿り着きました。 www.logicool.co.jp お値段は1.4万円ほどで非常にリーズナブルな価格帯です(?)

私にとっての決め手は以下の4つです。

  • 色が黒色で、所持しているHHKBと相性がよかったこと
  • 角度調整が可能なこと
  • Bluetoothで充電回数が極端に少ないこと
  • Logicoolというブランドの信頼性

実際につかってみて

最初は、トラックボール自体、使うのが初めてだったので、新しい操作にかなり戸惑いました。 しかし、2、3日触っていたらかなり手に馴染みました。

トラックボールの設定で、スクロール速度を最高にしました。 さらに、Mac本体の設定でポインタの移動速度を最速にしました。

f:id:torikatsu923:20210124234604p:plain
スクロール速度の設定

その結果爆速ポインタ移動が実現しました! ディスプレイの移動も親指で少し回すだけで端から端まで移動できちゃいます! 最初は早すぎて「使えるか!」とか思ってましたが、今ではMacトラックパッドのポインタ移動がとても遅く感じてしまう体になってしまいました。 また、トラックボールの滑り具合やボタンのクリックした時のフィードバックはとても快適です。

mx ergoを購入する前の懸念点として、机に対するグリップ力がありました。 折角性能のいいトラックボールでも、固定しずらければ、デスクのフォームが崩れたりかえって疲れてしまったりする原因になります。 ですが、この心配は取り越し苦労でした。 mx ergoの底面にはゴムがついており、このグリップ力が高いおかげで、ズレとは無関係の、快適な操作を提供してくれました!

mx ergoのすごいところは他にもあります。 それは角度調整が可能だというところです。 mx ergoは本体の角度を、0度と20度の2種類に調整できます。

f:id:torikatsu923:20210124235656p:plain
https://www.logicool.co.jp/ja-jp/products/mice/mx-ergo-wireless-trackball-mouse.910-005183.html?crid=7より
f:id:torikatsu923:20210124235704p:plain
https://www.logicool.co.jp/ja-jp/products/mice/mx-ergo-wireless-trackball-mouse.910-005183.html?crid=7より

この20度という角度がとても絶妙な角度なのです。 絶妙であると考えられる理由として、鉛筆の持ち方との共通点が考えられます。

トンボ鉛筆によると正しい鉛筆の持ち方の定義について以下のようにあります。

「5本の指の関節(15箇所)と、手首の関節を自由に屈伸、屈折することができ、手と腕に余計な力が動くことなく、筆記具を自在に動かしてあらゆる基本線を思うように書ける持ち方」と定義されています。 tombow-ippo.jp https://tombow-ippo.jp/assets/img/howto/point03.png

つまり、私たちの手が最もリラックスした状態が20度という角度なのです。 これよりmx ergoは、手が疲れないよう、非常に考えられた設計がされたトラックボールであるといえます。

実際に使ってみて、ペンを握るような感覚で非常にリラックスして作業をすることができました。

デスクにおいてみて(見た目)

このmx ergoですが、Bluetoothのおかげで机の上がとてもシンプルになりました。

f:id:torikatsu923:20210124231934j:plain

HHKBのコンパクトさと相俟って、デスク上の雑念が存在しない空間作りに貢献してくれているのがわかると思います。 落ち着いたマットな黒色も、高級感を演出していて気分がいいです。

おわりに

今回の記事では、logicool mx ergoというトラックボールを導入した経緯とその使用感についてお話をさせていただきました。 それでは快適なデスクライフを!

JSの+について考える

こんにちは、とりかつです。 最近JSのコードを読むことが多いです。 とあるリポジトリのJSのコードを読んでいたら以下のような文がありました。

var a = +new Date();

+new Date()...??? なんじゃこりゃ...

と言うことで今回の記事ではjsの+について軽く調べてわかったことをまとめていきます。

+の種類

+ 記号を使用する演算子はいくつかの種類があります。

演算子 名前
+ 単項正値演算子
++ インクリメント
+ 加算
+= 加算代入

それぞれ見ていきます。

単項正値演算子

構文は以下のようになります。

+ ○

この記事の冒頭で出てきた+ は単項正値演算子です。 オペランドが数値以外の場合、数値に変換する役割を持っています。

+ 10 // -> 10

new Date()は数値変換するとミリ秒を取得できます。

f:id:torikatsu923:20201204005839p:plain

なので、冒頭のコードはミリ秒を取得するコードになります。

f:id:torikatsu923:20201204010021p:plain

インクリメント

インクリメントはオペランドに1を加算して返します。 インクリメントはオペランドの前後どちらにもつけることができます。 ですが、動きは異なります。

前置した場合は、インクリメントしてから値を返していることがわかります。

f:id:torikatsu923:20201204010511p:plain

後置した場合は、値を返してからインクリメントしていることがわかります。 f:id:torikatsu923:20201204010657p:plain

加算

オペランドを足し合わせた値または文字列連結した結果を返却します。

f:id:torikatsu923:20201204011213p:plain

オペランドの型が違う時、JSによって暗黙の型変換が行われます。

f:id:torikatsu923:20201204011317p:plain

暗黙の型変換には優先順位?みたいなものがあります。

f:id:torikatsu923:20201204011608p:plain
対応表

引用:https://jsprimer.net/basic/implicit-coercion/

また、加算演算子は左から右に評価されます。 なので、式の項の順序(これは正確な言い回しではないかもしれないです。)によって結果が変わります。 これ覚えとくと、いつか何かに使える気がします。

f:id:torikatsu923:20201204011838p:plain

加算代入

右辺のオペランドを変数の値と加算し、結果を変数に代入します。

f:id:torikatsu923:20201204012142p:plain

加算代入は代入系の演算子で優先度が3です。 対して、加算、減算の優先度は14、剰余、乗算、除算の優先度は15です。

なので+= ( 10 - 3 ) みたいにかかなくても良さそうです。

f:id:torikatsu923:20201204015351p:plain

おわりに

ここまでJSの+についてまとめてきました。 +に限らずJSの演算子による表現は幅が広いです。

f:id:torikatsu923:20201204012856p:plain

(どこでこれ使うかわかりませんがこんなこともできちゃいます)

また、新しい発見があったらまとめてみたいなと思います。

Vueで直接DOM生成してクラス割り当てようとしたらできなかった話

こんにちは、とりかつです。 今回は、VueでDOMを生成してクラスを割り当てようとしたら、スタイルが適用されなくてハマりました。 そこで、ハマリから得られた知見を共有したいと思います。

やろうとしていたこと

SFCで以下のようなことをしようとしていました。 付与するクラスinner_elementのスタイルは、SFC内の<css scoped>で定義してあります。

  1. mounted()のタイミングで動的に<span></span>を生成 1 生成した要素にnewElementというクラスを付与
  2. $refsの子要素として追加

コードにすると以下のようになります。

mounted() {
  const el = this.$refs.hoge as HTMLElement
  const newElement = document.createElement('span')
  newElement.className = "inner_element"
  el.append(newElement)
}

いざ実行してみると<span>はしっかり追加されており、class="inner_elementも確認することができました。 しかし、スタイルは適用される気配がありません。 うまくいかない原因はなんでしょうか。

なんでVueでVanilaJS使ってDOMを追加しようとしとるねんってことはあえて考えないようにしましょう。

VueのCSSが読み込まれるタイミング

スタイルが適用されなくて悩んでる時にscoped cssの仕様についてふと思い出しました。

scoped 属性をもつ <style> タグを利用するとき、その CSS は現在のコンポーネントの要素にのみ適用されます。これは Shadow DOM のスタイルのカプセル化に似ています。いくつか注意点がありますが、polyfill は必要ありません。PostCSS を使用して以下を変換することによって実現しています[^1]

同ページには以下のようなスニペットも掲載されていました。(一部抜粋)

example[data-v-f3f3eg9] {
  color: red;
}

要約すると、SFCのスコープ付きCSSのクラス名はコンパイルされて、.inner_element.inner_element[data-v-jfoieh]のようになるといった感じです。

では、CSSコンパイルされるタイミングはいつでしょうか。 以下はVue.js公式ドキュメントに掲載されていたライフサイクルの図です。[^2]

f:id:torikatsu923:20201127210526p:plain
Vueライフサイクル

この図からはSFCテンプレートがコンパイルされるタイミングがcreated()beforeMount()の間であることが読み取れます。 ですが、CSSコンパイルされるタイミングは読み取ることができませんでした。 推測ではありますが、この間でCSSコンパイルされている可能性がありそうです。

ここで気づいたのですが、CSSコンパイルされるのと同時にコンパイルによって付与されたID的なものがVueによって生成されるテンプレート内の要素にも付与されるようです。 以下のようなSFCを考えます。

<templete>
  <button class="button">
</templete>
<style scoped>
  .button {
    /* some style */
  }
</style>

Vueによってマウントされたとき、.button.button[data-v-horekjkd]となるとします。 このとき、Vueによって生成されるbuttonは次にようになります。

<...>
  <button data-v-horekjkd class="button"></button>
</...>

このようにVueによって生成されたIDはHTMLのタグに反映?されるようです。

実際にインスペクタを見てみるとv-data-.....buttonに付与されていることがわかります。

f:id:torikatsu923:20201127203001p:plain
buttonタグ内にdata-v-24921c38が存在していることが確認できます。


少し冗長になってしまいました。 ここまでわかったことをまとめます。

  • スコープ付きCSSはVueによってコンパイルされ、一意なIDが付与される
  • IDはテンプレート内のタグにも付与される
  • CSSコンパイルされるタイミングははっきりとわからない
  • テンプレートはcreated()beforeMounted()の間でコンパイルされ、このタイミングでIDが付与される

CSSコンパイルされるタイミングをはっきりと掴むことができませんでした。 しかし、CSSコンパイルで付与されるIDがテンプレート内のタグに付与されることと、テンプレートがコンパイルされるタイミングは明らかになりました。 これによりmounted()で生成した要素にスタイルが付与されなかったことの説明がつきそうです。

CSSコンパイルされるタイミングは追々調べようと思います。

まとめ

当初、私はmounted()<span class="inner_element"></span>を生成していました。 しかし、この時点でSFCCSSクラス名はコンパイルされ.inner_element[v-data-hgoejdklgha]のように変化していました。 また、テンプレートのコンパイルも終了していました。 スタイルを適用するには、クラスに付与されたIDをテンプレートの要素にも付与する必要がありましたが、私の生成したspanはIDが付与されていませんでした。 これによってスタイルが適用されなかったと考えられます。

おわりに

ここまで、Vueのmounted()でVanilaJSによって生成した要素にスタイルが適用されない原因について考えてきました。

思い返してみると... Vueでクラス名をバインドする際は、

:class="{classA: true, classB: false}"

みたいに書く必要がありました。 いままで、以下のように書けたらいいのにな。なんだか気持ち悪いなとか思ってました。

$refs.class = "classC"

ですが、テンプレートがコンパイルされるタイミングでIDが付与されることを考慮すると、 あらかじめクラス名をバインドしておいて、vueのdata等の状態の切り替えで、クラスの付与のアクティブ/パッシブを切り替えに納得が行きました。


[^1] https://vue-loader-v14.vuejs.org/ja/features/scoped-css.html [^2] https://jp.vuejs.org/v2/guide/instance.html

JavascriptのIIFEについて考える

こんにちは、とりかつです。 最近、Javascriptで以下のようなコードを見かけました。

(function() {
  console.log("hogehoge");
})();

調べていくとこれはどうやら即時実行関数式(IIFE: Immediately Invoked Function Expression)[^1]というものだそうです。 私はぱっと見でIIFEの書き方が理解できませんでした。 なので、今回の記事ではこのIIFEを完全に理解したいと思います。

原点回帰

最初に関数の定義について振り返ります。 JSで関数式は以下のように定義, 実行できます。

// 関数の定義
var a = function() {
  return "done";
}

// 関数の実行
a();

aの後ろの()は関数呼び出し演算子です。 関数呼びだし演算子の構文は...(/* 引数 */)です。[^2]

関数の実行の流れは以下のような感じかと考えました。

  1. aが関数の実態と置き換わる
  2. 関数が実行される
  3. 関数の返り値の"done"に置き換わる

関数実行のイメージ
関数実行のイメージ
(もしかしたらこの説明は少し語弊があるかもしれません。あくまでイメージ程度で捉えていただければと思います。)

関数の実行時に変数が関数の実態と置き換わるイメージから、関数の定義と同時に関数の呼び出しをおこなうことができそうな感じがします。 やってみます。

関数を即時事項してみる
関数を即時実行してみる

... だめでした。 エラーにはUncaught SyntaxError: Function statements require a function nameとありました。 どうやら現在の書き方では関数式ではなく関数定義文として認識されていて、関数名が必要だと怒られているようです。

関数の定義を関数式として認識させる

ではどうしたら関数式として認識してもらえるようになるのでしょうか。 私は関数式という名前から関数を式として認識させればいいと考えました。 具体的には+, -, !のような演算子を関数の定義の前につければいいといった感じです。

プログラミング言語において式はオペレータとオペランドから成り立っています。 たとえば以下のような式を考えます。

1 + 6

この式では16オペランドで、+がオペレータです。 JSで関数定義を式として認識させるためには、JSエンジンに関数の定義がオペランドだよと教えてあげられれば良さそうです。

なのでオペレータを使って関数定義をオペランドとして認識できるようにします。

// 例
+ function() {
  return "done";
}

実行してみると...

関数をオペランドとして認識させる
関数をオペランドとして認識させる

うまくいきました。 現時点では関数呼び出しはしていないのでNaNが返ってきていることがわかります。 次に関数呼び出し演算子を使って関数を呼び出してみます。

関数の即時実行
関数の即時実行
うまくいきました! これで関数を定義して即時実行することができるようになりました。

グループ化演算子を使用する

関数の定義を式として認識させるためには、関数の定義がオペランドであることを明示すればよさそうでした。 JSのグループ化演算子(...)は「式での評価の優先順位を制御」[^3]する役割をもっています。

グループ化演算子は中に式が入ることが想定されています。 これよりグループ化演算子の中で関数を定義すれば、その関数は関数式として扱われると考えられます。 実際にやってみます。

グループ化演算子を使って関数を定義する
グループ化演算子を使って関数を定義する
グループ化演算子を使って関数を式として定義することができました。

もちろん、即時実行も可能です。

グループ化演算子を用いて関数を即時実行する

グループ化演算子を使う理由

ここまで、演算子を用いた関数式の定義と関数の即時実行について考えてきました。 私がIIFEを理解できなかったポイントは、なぜグループ化演算子を用いるのかという点です。 なので最後になぜグループ化演算子を用いるのか考えていきます。

グループ化演算子は静的なスコープをもっているようです。 そのため、以下のケースではエラーを吐かれます。

グループ化演算子ありの場合

このようにグループ化演算子内で宣言する前の変数にはアクセスできないようです。 これは名前空間を切り分けることに役立ちそうです。

実際の開発では複数人で開発を進めていきます。さらにさまざまなパッケージを使用することが考えられます。 なので、名前空間は非常に重要となってきます。

以上より、JSファイルが読み込まれた際に名前空間を切り分ける目的でグループ化演算子が用いられていると考えられます。 逆に名前空間を切り分ける必要がないばあいに関数を即時実行したければ使用する演算子はなんでもいいかもしれません。

おわりに

今回はJSのIIFEを切り口にJSの関数について考えました。

個人的に、演算子を用いることで関数を式として認識させるという部分は大きな再発見でした。

参考

vuex-module-decorators使用時のエラーについて

お久しぶりです。 最近、Vuexをクラススタイルで記述することを可能にするvuex-module-decoratorsを使う機会があったのですが、ActionDecoratorParamsについて気になったので記事を書きました。

vuex-module-decoratorsとは

VueをTypescriptで書こうとすると、絶妙なところでTSの恩恵を受けられないといったケースがあると思います。 例えばVueのPropstypestringの配列を指定しようとするケースを考えます。

私ははじめ以下のように指定しました。

props: {
  items: string[]
}

しかし、この書き方はどうやらできないようです。 これはpropstypeは以下のものに制限されているためです。[^1]

  • String
  • Number
  • Boolean
  • Aarray
  • Object
  • Date
  • Function
  • Symbol

そこでArrayを指定しようと考えました。

props: {
  items: Array
}

しかしこれではany[]となってしまい、いまいちです。 どうやらPropTypeを使用することでstring[]を認識させることができるようですが、さくっと型を指定できないのはどこかもどかしく感じました。[^2]


少し話が逸れてしまいました。 先ほどの例のように、素のVueではTypescriptの恩恵にあやかるために一手間加える必要があります。 このような経験はVuex+Typescriptでも経験しました。

そこで、VuexでゴリゴリにTypescriptの恩恵にあやかれるように使用したのがvuex-module-decoratorsです。

github.com

情報を漁っていくと以下のような記述もありました。[^3]

最も人気のあるアプローチの1つは vuex-module-decorators です。- ガイドを参照してください。

モジュール自体も軽量で、コード行数もそこまで多くないです。 そのため内部実装の把握にそこまでコストがかからないと感じたので使ってみることにしました。

@Actionで謎のエラーが大量に出る

使ってみるなかで、私は以下のようなコードを書きました。

@Action
public getItemFromLocalStorage(): string {
  const item = localStorage.getItem("item")  as string
  if(!item) {
    throw new Error("item is not exist")
  }
  return item
}

このコードはlocalStorageから"item"というキーでアイテムを取得し、もし存在しなければエラーを投げるというコードです。

このコードが正常に動作するか確認するためにテストをしていたのですが、エラーを投げるか確認するテストを組んでいるときに以下のようなエラーに遭遇しました。

ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access 
                this.someMutation() or this.someGetter inside an @Action?
                That works only in dynamic modules.
                If not dynamic use this.context.commit("mutationName", payload)

先ほどのコードでは単にError("item is not exist")を投げるだけだったため、急に発生した予期しないエラーに思わず声が出てしまいました。

調べてみるとvuex-module-decoratorsは、エラーが発生した場合に独自のエラ〜メッセ〜ジを全力で投げつけてくるそうです。 ですがActionデコレータにrowError: trueを渡せば[^4]、ちゃんと元のエラーメッセージを優しく(個人調べ)投げつけてくれるそうです。

@Action({ rowError: true })

ここで、個人的になぜrowError: trueで元のエラーを投げてくれるのか気になったのでコードをよんでみることにしました。

Actionのコードリーディング

まず、@Actionの定義元にジャンプしてみます

/**
 * Parameters that can be passed to the @Action decorator
 */
export interface ActionDecoratorParams {
    commit?: string;
    rawError?: boolean;
    root?: boolean;
}
export declare function Action<T, R>(target: T, key: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]) => R>): void;
export declare function Action<T>(params: ActionDecoratorParams): MethodDecorator

どうやらActionにはActionDecoratorParamsなるものを渡せるそうです。

ですが、これ以上定義元ジャンプで遡るのが厳しかったので大人しくGithubリポジトリを見てみることにします。 コードを見ていくと/src/action.tsが怪しそうです。

コードは以下のようになっていました。ビンゴっぽいです。 github.com

こちらのコードのインターフェースの宣言のところには以下のようにコメントしてあるだけだったので、actionDecoratorFactoryrowErrorがどのように使われているか確認していきます。

/**
 * Parameters that can be passed to the @Action decorator
 */

コードを読んでいくと以下の部分に行き着きました。

throw rawError
          ? e
          : new Error(
              'ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access ' +
                'this.someMutation() or this.someGetter inside an @Action? \n' +
                'That works only in dynamic modules. \n' +
                'If not dynamic use this.context.commit("mutationName", payload) ' +
                'and this.context.getters["getterName"]' +
                '\n' +
                new Error(`Could not perform action ${key.toString()}`).stack +
                '\n' +
                e.stack
            )

https://github.com/championswimmer/vuex-module-decorators/blob/08249c58ab60dd26a993c995d8a00c15c7222a48/src/action.ts#L47

先ほどの章でコメントした謎のエラーに似た文字列を発見することができました。 どうやらrawErrorの値によって独自のエラーを投げるか、素のエラーを投げるか切り替えているそうです。

おわりに

今回の記事では@Action({ rawError: true })とする方法を紹介しました。 READMEによると一括で設定を指定できるようです。

import { config } from 'vuex-module-decorators'
// Set rawError to true by default on all @Action decorators
config.rawError = true

一個一個rawErrorをしていくよりも一箇所で指定できた方が何かと便利そうな感じがします。

リンク

Riverpodのドキュメントを意訳してみた2

お久しぶりです。今回は前回に引き続きriverpodを読んでいたので、メモ代わりに意訳をまとめました。

今回まとめたのは、以下の二つです。

riverpod.dev

riverpod.dev

Providerをreadする

このガイドでは、どうやってProviderを使用するのかを知ることができる

providerをreadする方法はいくつかあって、基準・規格・仕様によって少し違う 手短に知るには以下の図にそってどのproviderをreadするかみてね

どこでProviderをreadするか? - テストやDart-Only-PackageでreadするならProviderContainer.read

  • Widgetの中

    • Dialog表示のためにlisteningするならProviderListener
    • onPressedを覗くbuildメソッドの中でreadしなければContext.read
    • flutter-hooksを使うならuseProvider
    • flutter-hooksを使わないならConsumer
  • 他のProviderの中

    • providerの更新に伴ってproviderによって公開された値が、再生成されるべきか
      • べきならProviderReference.watch
      • ちがうならproviderReference.read

つぎにそれぞれ個別の場合をみながらどうやって動作するのか紹介します

このガイドでは以下のproviderで考えます。

final counterProvider = StateProvider((ref) => 0);

readの方法を決定する

listenしたいProviderによって、値をlistenする方法がいくつかあります

たとえば、以下のStreamProviderについてだと、

final userProvider = Streamprovider<User>(...);

userProviderをreadするとき、以下のような方法が可能である。

  • userProvider自身で同期的に現在の状態をreadする
Widget build(BuildContext context, ScopeReader watch) {
  AsyncValue<User> user = watch(userProvider);

  return user.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stack) => const Text('Oops'),
    data: (user) => Text(user.name),
  );
}
  • Streamの連携に伴って変更される値をreadするならuserProvider.stream
Widget build(BuildContext context, ScopeReader watch) {
  Stream<User> user = watch(userProvider.stream);
}
  • 最新の値で発火されるFutureで解決される値を取得するには、userProvider.lastを使用する
Widget build(BuildContext context, Scopedreader watch) {
  Future<User> user = watch(userProvider.last);
}

さらなる情報はAPIリファレンスみてね

ProviderをWidgetの中で使う

この章では、ProviderをWidgetの中で使う方法について知ることができる

ConsumerWidget

ConsumerWidgetStatelessWidgetのような基底クラスだ。そして、providerをlistenすることができる

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, Scopedreader watch) {
    int count = watch(counterProvider).state;

    return Scaffold(
      appBar: AppBar(title: const Text('Counter example'));
      body: Center(
        child: Text('$count'),
      ),
    );
  }
}

この例では、counterProviderとcounterが変更される旅にTextがリビルドされるだろう。

counterProviderの値によって気づくことは、関数がwatchを呼ぶことだ。ConsumerWidgetで作られた関数は私たちのproviderをlistenし、値の変更が公開されるとリビルドする。

Caution

ConsumerWidgetの引数を渡すwatch()は```onPressedのような関数では非同期で呼ばないべきだ。

もしユーザのイベントのレスポンスをproviderをreadする必要があるなr、myProvider.read(BuildContext)を使用してください

Consumer

Consumerはアプリケーション内でデータを扱う特定のウィジェットの再描画のパフォーマンスを最適化することができるConsumerWidget

たとえば、ConsumerWidgetのスニペットを更新以下のコードでは、まえもってTextのみが再描画されることを認識できる。

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Consumer(
          builder: (context, watch, child) {
            int count = watch(counterProvider).state;
            return Text('$count');
          },
        ),
      ),
    );
  }
}

context.read(myProvider)

ときには、listenする値が存在しないこともある。例えば、オブジェクトはonPressedでのみ必要な場合。

私たちはConsumerを使うことができる

Consumer(builder: (context, watch, _) {
  StateController<int> counter = watch(counterProvider);
  return RaisedButton(
    onPressed: () => counter.state++,
    child: Text('increment');
  )
});

しかし、これは効果的ではない。providerのlistenによって、counterが変更される旅にRaisedButtonが再描画される。counterが実際にbuildに使われない場合でさえ。

解決方法としてcontext.read(myProvider)が存在する。

これを使うことで前述のコードを以下のようにリファクターできる。

@override
Widget build(BuildContext context) {
  retur RaisedButton(
    onPressed: () => context.read(counterProvider).state++,
    child: Text('increment'),
  );
}

こうすることで、依然として、クリックすることで、ボタンはcounterをインクリメントする。しかし、もはやproviderをlistenしておらず、不必要な再描画を避けている。

context.readがみつからない。どうしたらいい?

もしcontext.readが見つからないなら、それはおそらく正しいパッケージをインポートしていないからだ。このメソッドを使うにはpackage:flutter_riverpodか、package:hooks_riverpodをインポートしなければならない。

Info

listenしているProviderによっては、これは必要ないかもしれない。 たとえば、StateNotifierProviderはそれ自身の状態のlistenを除く、StateNotifierを取得する方法が用意されている。

class Counter extends StateNotifier<int> {
  Counter(): super(0);

  void increment() => state++;
}

final counterProvider = StateNotifierProvider((ref) => Counter());

// ...
@override
Widget build(BuildContext context, ScopedReader watch) {
  final Counter counter = watch(counterProvider);

  return RaisedButton(
    onPressed: counter.increment,
    child: Text('increment'),
  );
}

Caution

context.readbuild()の中で呼ぶことを回避してください。もし、再描画を最適化したいなら、値の変更のlistenをProviderの中に変更してください。

ProviderListener

ときには、Providerの変更のあとでダイアログの表示やウィジェットツリーへのルートの挿入を行いたい場合もある。

ProviderListenerウィジェットを利用して以下のように実装してください

Widget build(BuildContext context) {
  return ProviderListener<StateController<int>>(
    provider: counterProvider,
    onChange: (counter) {
      if(counter.state == 5) {
        show Dialog(...);
      }
    },
    child: Whatever(),
  )
}

これできっとカウントが5になるとダイアログが表示されます。

Providerの中で他のProviderをreadする

一般的なProviderの作成方法として他のオブジェクトからオブジェクトを生成することがある。 例えば、UserRepositoryからUserControllerを生成したい時、それらは違ったProviderによって公開されているとする。

このシナリオはproviderがパラメータを受け取るためのrefを使うことによって可能になる

final userRepositoryProvider = Provider((ref) => UserRepository());

final userControllerProvider = StateNotifierProvider((ref) {
  return UserController(
    repository: ref.watch(userRepositoryProvider),
  );
});

CombineProvidersガイドでより多くの情報を得られる。例えば以下のように - 時間と共に変化する値からオブジェクトを作る時、どんなことがおこるか - 良い方法について

Riverpodのドキュメントを意訳してみた1

こんにちは。 私は、現在FlutterアプリにRiverpod導入を考えています。導入に先立ってRiverpodの公式ドキュメントを読みました。 いつかドキュメント全体をざっくり見返すときにあまり頭を使いたくないため、メモの意味も兼ねてドキュメントを意訳して記事にまとめておくことにしました。

今回、意訳したページは以下のリンク先です。

意訳は正確でないこともあるのであらかじめご了承ください。

riverpod.dev

Providerをなぜ使うのか

  • 様々な場所から状態へのアクセスを簡単にする
  • 状態の組み合わせを単純にする
  • パフォーマンスを最適化する
  • テスタビリティを向上させる
  • 簡単に統合する

Providerの生成

Providerは異なった方法で生成されるが動作は一緒 一般的に以下のように、グローバル定数として宣言される

final myProvider = Provider((ref) {
  return MyValue();
});

Note

Providerをグローバル変数で宣言することを恐れる必要はない
Providerは完全に変更不可能だ。Providerを宣言することは関数を宣言することと変わらない、そしてテストやメンテナンスをしやすい

このスニペットは3要素から成り立っている - final myProviderという変数宣言 将来的にProviderから状態を読み込むための変数で、つねに変更不可能だ。

  • Provider、使用するProviderのp種類 Providerは一番基本的なProviderだ。二度と変更されないオブジェクトを公開する。状態をインタラクティブに変更するためにProviderStreamProviderStateNotifierProviderに変更することもできる。

  • 状態を共有する関数 関数は常にrefとよばれるオブジェクトを引数で受け取る。このオブジェクトはほかのProviderを読み取ることや、Providerが破棄されるときになんらかの処理を実行することを可能にする(フック的なやつ)。

関数によって作られたオブジェクトの型は、使用されるProviderに依存したProviderに渡される。 例えば、Providerの関数はなんらかのオブジェクトを生成できる。一方、StreamProviderのコールバックはStreamを返すだろう。

Info

宣言可能なProviderはどれも制限を必要としない。package:providerを使用した場合とは逆に、Riverpodでは同じ型のProviderを公開できる。 実際、ふたつのProviderはどちらもStringを生成しているが、問題はない。

Caution

Providerを使用する時、ProviderScopeをFlutterApplicationsのルートに追加してください。

Stateが破棄される前の処理の実行について

時には、状態についてのProviderは破棄・再生成される。一般的な使用例は、providerが破棄される前に状態を破棄する。たとえばStreamControllerをcloseするように。

これはref.onDispose()を使用することで実現する。

final example = StreamProvider.autoDispose((ref) {
  final streamController = StreamController<int>();
  ref.onDispose(() {
    streamController.close();
  });
  return streamController.stream;
}

Note

使用するProviderによっては、事前にProviderを破棄する方法が用意されているかもしれない。 たとえば、StateNotifierProviderStateNotifierdisposeメソッドを呼ぶ

ProviderのModifierについて

すべてのProviderには、さらなる機能をついかする方法が用意されている。 refに新しい特徴を追加するか、ProviderをConsumeする方法を少し変更している。

final myAutoDisposeProvider = StateProvider.autoDispose<String>((ref) => 0);
final myFamilyProvider = Provider.family<String, int>((ref, id) => '$id');

ときには、二種類のModifiersを使用することが可能だ

  • .autoDispose, 参照されなくなった時にProviderの状態を自動で破棄する
  • .family, 外部のパラメータからProviderの生成を可能にする。

Note

はじめに複数のmodifiersを使う時

final userProvider = FeatureProvider.autoDispose.family<User, int>(ref, userId) async {
  return fetchUser(userId);
});

Providerをreadする

このガイドを読む前にread about Providersを読んね

このガイドでは、どうやってProviderを使用するのかを知ることができる

providerをreadする方法はいくつかあって、基準・規格・仕様によって少し違う 手短に知るには以下の図にそってどのproviderをreadするかみてね

どこでProviderをreadするか? - テストやDart-Only-PackageでreadするならProviderContainer.read

  • Widgetの中

    • Dialog表示のためにlisteningするならProviderListener
    • onPressedを覗くbuildメソッドの中でreadしなければContext.read
    • flutter-hooksを使うならuseProvider
    • flutter-hooksを使わないならConsumer
  • 他のProviderの中

    • providerの更新に伴ってproviderによって公開された値が、再生成されるべきか
      • べきならProviderReference.watch
      • ちがうならproviderReference.read

つぎにそれぞれ個別の場合をみながらどうやって動作するのか紹介します

このガイドでは以下のproviderで考えます。

final counterProvider = StateProvider((ref) => 0);

readの方法を決定する

listenしたいProviderによって、値をlistenする方法がいくつかあります

たとえば、以下のStreamProviderについてだと、

final userProvider = Streamprovider<User>(...);

userProviderをreadするとき、以下のような方法が可能である。

  • userProvider自身で同期的に現在の状態をreadする
Widget build(BuildContext context, ScopeReader watch) {
  AsyncValue<User> user = watch(userProvider);

  return user.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stack) => const Text('Oops'),
    data: (user) => Text(user.name),
  );
}
  • Streamの連携に伴って変更される値をreadするならuserProvider.stream
Widget build(BuildContext context, ScopeReader watch) {
  Stream<User> user = watch(userProvider.stream);
}
  • 最新の値で発火されるFutureで解決される値を取得するには、userProvider.lastを使用する
Widget build(BuildContext context, Scopedreader watch) {
  Future<User> user = watch(userProvider.last);
}

さらなる情報はAPIリファレンスみてね

ProviderをWidgetの中で使う

この章では、ProviderをWidgetの中で使う方法について知ることができる

ConsumerWidget

ConsumerWidgetStatelessWidgetのような基底クラスだ。そして、providerをlistenすることができる

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, Scopedreader watch) {
    int count = watch(counterProvider).state;

    return Scaffold(
      appBar: AppBar(title: const Text('Counter example'));
      body: Center(
        child: Text('$count'),
      ),
    );
  }
}

この例では、counterProviderとcounterが変更される旅にTextがリビルドされるだろう。

counterProviderの値によって気づくことは、関数がwatchを呼ぶことだ。ConsumerWidgetで作られた関数は私たちのproviderをlistenし、値の変更が公開されるとリビルドする。

Caution

ConsumerWidgetの引数を渡すwatch()は```onPressedのような関数では非同期で呼ばないべきだ。

もしユーザのイベントのレスポンスをproviderをreadする必要があるなr、myProvider.read(BuildContext)を使用してください

Consumer

Consumerはアプリケーション内でデータを扱う特定のウィジェットの再描画のパフォーマンスを最適化することができるConsumerWidget

たとえば、ConsumerWidgetのスニペットを更新以下のコードでは、まえもってTextのみが再描画されることを認識できる。

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Consumer(
          builder: (context, watch, child) {
            int count = watch(counterProvider).state;
            return Text('$count');
          },
        ),
      ),
    );
  }
}

context.read(myProvider)

ときには、listenする値が存在しないこともある。例えば、オブジェクトはonPressedでのみ必要な場合。

私たちはConsumerを使うことができる

Consumer(builder: (context, watch, _) {
  StateController<int> counter = watch(counterProvider);
  return RaisedButton(
    onPressed: () => counter.state++,
    child: Text('increment');
  )
});

しかし、これは効果的ではない。providerのlistenによって、counterが変更される旅にRaisedButtonが再描画される。counterが実際にbuildに使われない場合でさえ。

解決方法としてcontext.read(myProvider)が存在する。

これを使うことで前述のコードを以下のようにリファクターできる。

@override
Widget build(BuildContext context) {
  retur RaisedButton(
    onPressed: () => context.read(counterProvider).state++,
    child: Text('increment'),
  );
}

こうすることで、依然として、クリックすることで、ボタンはcounterをインクリメントする。しかし、もはやproviderをlistenしておらず、不必要な再描画を避けている。

context.readがみつからない。どうしたらいい?

もしcontext.readが見つからないなら、それはおそらく正しいパッケージをインポートしていないからだ。このメソッドを使うにはpackage:flutter_riverpodか、package:hooks_riverpodをインポートしなければならない。

Info

listenしているProviderによっては、これは必要ないかもしれない。 たとえば、StateNotifierProviderはそれ自身の状態のlistenを除く、StateNotifierを取得する方法が用意されている。

class Counter extends StateNotifier<int> {
  Counter(): super(0);

  void increment() => state++;
}

final counterProvider = StateNotifierProvider((ref) => Counter());

// ...
@override
Widget build(BuildContext context, ScopedReader watch) {
  final Counter counter = watch(counterProvider);

  return RaisedButton(
    onPressed: counter.increment,
    child: Text('increment'),
  );
}

Caution

context.readbuild()の中で呼ぶことを回避してください。もし、再描画を最適化したいなら、値の変更のlistenをProviderの中に変更してください。

ProviderListener

ときには、Providerの変更のあとでダイアログの表示やウィジェットツリーへのルートの挿入を行いたい場合もある。

ProviderListenerウィジェットを利用して以下のように実装してください

Widget build(BuildContext context) {
  return ProviderListener<StateController<int>>(
    provider: counterProvider,
    onChange: (counter) {
      if(counter.state == 5) {
        show Dialog(...);
      }
    },
    child: Whatever(),
  )
}

これできっとカウントが5になるとダイアログが表示されます。

Providerの中で他のProviderをreadする

一般的なProviderの作成方法として他のオブジェクトからオブジェクトを生成することがある。 例えば、UserRepositoryからUserControllerを生成したい時、それらは違ったProviderによって公開されているとする。

このシナリオはproviderがパラメータを受け取るためのrefを使うことによって可能になる

final userRepositoryProvider = Provider((ref) => UserRepository());

final userControllerProvider = StateNotifierProvider((ref) {
  return UserController(
    repository: ref.watch(userRepositoryProvider),
  );
});

CombineProvidersガイドでより多くの情報を得られる。例えば以下のように - 時間と共に変化する値からオブジェクトを作る時、どんなことがおこるか - 良い方法について

Dartのコンストラクタ紹介2

こんにちは、とりかつです。

今回は、前回の記事で紹介しきれなかったファクトリコンストラクタについて紹介したいと思います。

ファクトリコンストラク

Dartにはgenerative constructor, constant constructorの他にfactory constructorというものがあります。

factoryコンストラクタはコンストラクタ内でインスタンスを生成しません。なので、自分でインスタンスを生成する必要があります。

コンストラクタ内でインスタンスを生成しない特性を生かし、シングルトンパターンで使われたりします。

以下のスニペットのように宣言します。

class Sample {
  factory Sample() {
    _instance ??= Sample._();
    return _instance;
  }

  Sample._();

  static Sample _instance;
}

このスニペットでは、_instancenullのとき、プライベートなネームドコンストラクタを呼び出して_instanceに代入することでシングルトンパターンを実現しています。

ですが、こんな書き方をせずとも、以下のように実装することもできそうです。

class Sample {
  Sample._();

  static Sample _intance;

  static Sample getInstance() {
    _instance ??= Sample._();
    return _instance;
  }
}

コードの振る舞いは変わりませんが、コードの印象は大きく変わります。

factoryを使った時

final sample = Sample();

従来のシングルトンパターン

final sample = Smaple.getInstance();

考えは様々ですが、使い手にシングルトンパターンを意識させたいなら従来のシングルトンパターンを使い、使い手にシングルトンであることを意識させたくないならfactoryを使うと混乱を避けらると思いました。


少し話は変わりますが、インスタンスの生成方法を隠蔽する実装もできそうです。(むしろこっちがファクトリコンストラクタの本命だとおもってる)

class Sample {
    factory Sample(SampleType type) {
      switch () {
        case SampleType.A:
          Sample._a();
          return;
        case SampleType.B:
          Sample._b();
          return;
      }
    }

    Sample._a() {
      /// todo somthing
    }

    Sample._b() {
      /// todo somthing
    }
}

enum SampleType {
  A,
  B,
}

おわりに

ファクトリコンストラクタを使わなくても実装できてしまうケースがほとんどだと思うので、ファクトリコンストラクタは使いどころが難しいと私は感じました。ですがこれを使いこなすことでよりDartライクなコードになるとおもうので、積極的に使っていきましょう。

Dartのコンストラクタ紹介

こんばんは。最近Flutterでアプリ開発をしているとりかつです。

Dartにはコンストラクタが数種類あるのですが、Flutterをはじめたてのころ、けっこうつまづきました。

そこで今回の記事ではDartのコンストラクタの種類と使い方について紹介したいと思います。

Generative Constructor

ここでは生成的コンストラクタ(通常のコンストラクタ)を紹介していきます。

通常のコンストラク

Dartのコンストラクタはクラス名で宣言します。Javaを触ったことのある人には馴染みがあるかと思います。

class Sample {
  Sample(String text) {
    someField = text;
  }
  
  String someField;
}

フィールド代入の省略

これで動作はするのですが、もうひと工夫加えたいと思います。

Dartにはコンストラクタでのフィールド代入を省略する機能があります。

以下のようにコンストラクタの引数でthis.hogehogeとしてやるだけです。さらに、コンストラクタの処理がない時は{・・・}を省略することができます。

class Sample {
  Sample(this.someField);
  
  String someField;
}

簡単ですね。 こうするとコードの行数が減るだけでなく、変数名を考える手間も省けるので生産性が上がりそうです。

積極的につかっていきましょう。

ネームドコンストラク

Dartではコンストラクタのオーバーロードがサポートされていません。 その代わり、ネームドコンストラクがあります。

以下は色を表すクラスです。

class Color {
  Color.fromRGB(this.r, this.g, this.b) {
    alpha = 1.0;
  }

  Color.fromRGBA(this.r, this.g, this.b, this.a);

  /// red
  int r;

  /// green
  int g;

  /// blue
  int b;

  /// alpha
  double alpha;
}

このようにクラス名.コンストラクタ名で宣言できます。 これも簡単ですね。

ネームドコンストラクタをつかうことで、どうやってインスタンスを生成しているかが明確になります。

ネームドコンストラクタの命名にはfromキーワードを使うとインスタンス生成時のコードが自然言語ライクになるので個人的にオススメです。

/// fromを使わない場合
final color = Color.RGB(100, 20, 49);

/// fromを使った場合
final color = Color.fromRGB(100, 20, 49);

オプショナルパラメータを使えばもっとスマートに実装できますが、それについてはここでは触れないどきます。

定数コンストラク

Dartには定数コンストラクというものがあります。 以下は定数コンストラクタの例です。

class Sample{
  const Sample(this.text);
  
  final String text;
}

コンストラクタの先頭にconstがついているのがわかると思います。 これが定数コンストラクタです。

定数コンストラクタを使うことで、インスタンスの生成時にconstキーワードをつけて、定数オブジェクトをインスタンス化することができます。

 const Sample('サンプル');

定数コンストラクタを宣言する条件は以下の2つです。

  • クラスのフィールドが全てfinal
  • コンストラクタのボディがない(InitializerListはOK)

少し難しく感じますが、慣れてくるとなんてことはなくなります。

ユースケースとしてはパフォーマンスの向上が考えられます。

FlutterのStreamなんかがわかりやすいと思います。

以下のコードはStereamBuilderの例です。

StreamBuilder<String>(
  stream: textStream,
  builder: (context, snapshot) => Column(
    children: <Widget>[
      /// text widget A
      Text(snapshot.data),
      
      /// text widget B
      Text('サンプル'),
    ],
  ),
);

この場合、steramに値が流れてくるたびにtext widget Atext widget Bが更新されます。

text widget Bのコンストラクタの引数は'サンプル'という定数の文字列のため、constキーワードをつけて定数化することができます。

StreamBuilder<String>(
  stream: textStream,
  builder: (context, snapshot) => Column(
    children: <Widget>[
      /// text widget A
      Text(snapshot.data),
      
      /// text widget B
      const Text('サンプル'),
    ],
  ),
);

text widget Bは定数のため、streamに値が流れてきても再描画されるのはtext widget Aのみで済みます。

今回の例では、パフォーマンスへの影響はほとんどありませんが、「チリも積もれば山となる」ので、積極的に使っていきましょう。

終わりに

本当はファクトリコンストラクタまで紹介しようと思ったのですが、寝るのが遅くなってしまいそうなので、次回の記事で紹介したいと思います。

Dartでコンストラクタをつかいこなすことは可読性とパフォーマンスの向上に大きく貢献すると思うので、ぜひみなさんもコンストラクタとお友達になりましょう。

WidgetやProviderのライフサイクルについて調べてみた

現在Flutterでアプリ開発をしているのですが、ウィジェットやProviderのライフサイクル周りで少しハマりました。

そこで今回の記事ではWidgetやProviderのライフサイクルについて紹介したいと思います。

ハマったケース1

問題

CustomScrollView内にProviderを複数配置した時にBlocA、BlocBは生成されますが、BlocCは生成されない

ウィジェットの構成
ウィジェットの構成

解決方法

この問題の原因はウィジェットのライフサイクルにあります。 スクロール可能な画面において、ウィジェットは画面外に出てしまうと自動で破棄され、再び画面内に入るときに生成されるようです。

今回のケースではBlocA、BlocBを注入するProvider<BlocA>Provider<BlocB>はすでに画面内に存在するため生成済みとなりますが、BlocCを注入するProvider<BlocC>は画面外のため未生成となります。

これより、画面の生成時にBlocA・locB・BlocCを注入するProviderをすべて生成するにはCustomScrollViewの位置でMultiProviderをつかうなどして各Providerを生成すれば良さそうです。

ただし、この方法でProviderを生成してしまうとCustomScrollView以下全てのウィジェットから全てのBlocにアクセスできてしまうので注意が必要です。

私はBlocの肥大化を防ぐためにBlocを分割したかったので、今回のケースではどこからでもBlocへアクセスできることには目を瞑ることにしました。

ハマったケース2

問題

先ほどのケース1を踏まえウィジェットの構成を修正してみました。

CustomScrollViewrより親でMultiProviderを使ってBlocを注入するよう変更しました。

しかし、ここでまた問題が発生しました。

Providerは全て生成済みですが、BlocCは未生成のままです。

修正したウィジェットの構成
修正したウィジェットの構成

解決方法

今回のケースの問題はProvider.of<Bloc>(context)のタイミングにあります。

Providercreateを呼ぶタイミングはProvider.of()が初めて呼ばれるタイミングであって、Providerを生成するタイミングではないようです。

今回のケースではBlocABlocBProvider.of()するウィジェットは画面内に存在していますが、BlocCProvider.of()するウィジェットは画面外にあります。

問題1よりウィジェットは画面内に入ってきて初めて生成されるようなので、BlocCProvider.of()するウィジェットは生成されておらず、そのためProvider.of<BlocC>(context)が呼ばれていないためBlocCが未生成となるようです。

この問題を解決する安直な方法として予めCustomScrollViewの外でProvider.of<BlocC>(context)をしておくことが考えられます。

ですが、Blocを必要としない場所でProvider.of()をすることはあまり良くないように思えます。

幸いProviderにはこの問題を解決してくれるlazyというパラメータが用意してありました。

lazyは注入したいインスタンスを遅延初期化するかどうかについてのパラメータで、何も指定しなければ遅延初期化するようです。

lazeにはboolを渡すことができ、laze = falseとすることで遅延初期化を無効化できます。

これにより全てのBlocを全て生成することができました。

おわりに

今回はウィジェットとProviderのライフサイクルについてまとめました。

今回のハマりを通し、ライフサイクルの理解度がフレームワークを使いこなす鍵になると改めて感じました

Blocパターンでのフォームバリデーション

最近、Flutterでアプリ開発をしているのですが、Blocパターンでフォームのバリデーションのスマートな実装方法についてまとめられた記事があまりなく、どハマりしました。

そこで今回の記事では、BlocパターンでRxDartを用いたスマートなフォームのバリデーションの実装方法をサインインページを例として紹介したいと思います。

記事の対象:

Rx・flutter初心者

得られる情報

Blocを使ったフォームの実装方法

サインインページの要件は以下の通りとします。

  • メールアドレス

    @を含む1文字以上の文字列

  • パスワード

    8文字以上の英数字からなる文字列

はてなシンタックスハイライトが弱いので、今回はあえてスクリーンショットを用いてコードを掲載します。

1. UIを用意する

まずUIを用意します。今回はパスワードとメールの入力欄+送信ボタンのシンプルな構成とします。

UI
用意したUI

2. Blocを用意する

今回は以下のようなBlocクラスを用意します。

ポイントはBehaviorSubjectを使うところです。 BehaviorSubjectはイベントをキャッシュしておいてくれるので、過去のイベントに対し、BehaviorSubject.valueでアクセスできます。 

そのため入力情報を保持しておくフィールドを用意しなくてすみます。

Blocクラス
Bloc

Blocでは全ての入出力をStreamにするべきだとありますが、ここではあえてそのルールを破りsubmitをパブリックなメソッドとして定義しています。

なぜならば、ストリームでsubmitを定義すると送信ボタンのonPressedが冗長になってしまうからです。

ストリームで実装すると以下のように冗長な書き方になりますが、

onPressed: () => bloc.submit(null)

メソッドで定義しておくと

onPressed: bloc.submit

と定義でき、UI側のコードがすっきりします。

UIにBlocを注入する

Providerを使ってUIにBlocを注入します。
ついでにTextFieldとRaisedButtonにコールバックを渡します。

BlocをUIに注入
BlocをUIに注入

Providerはウィジェットの生成と破棄に合わせた処理を渡しておけるので便利です。

ここでProviderの直下にBuilderを配置していますが、これはこのクラス内からProvider.of(context)Blocにアクセスするためです。

なぜBuilderを挟む必要があるのかについてはまたいずれ記事にしたいと思います。

バリデーションの実装

いよいよバリデーションを実装していきます。 UI側にバリデーションの結果を伝えるためにStream.addErrorRx.combineStreamを使います

以下はバリデーションを追加したBlocクラスです。

バリデーション追加後のBloc
バリデーション追加後のBloc

ポイントはRx.combineLatestを使用することです。 これを使わずに送信ボタンにバリデーションの結果を通知するストリームを用意してもいいのですが、新しくストリームを生成しなければならないうえ、closeもしなくてはなりません。

今回のケースでは気になりませんが、Streamの数が増えるほどStreamの管理がしんどくなってくるので、combineLatestを使った方がスマートだと思います。

UIでバリデーションの結果を受け取る

UIでバリデーションの結果を受け取るにはInputDecorationクラスのerrorTextを利用します。

StreamBuilderで入力情報のストリームをリッスンしておき、エラーが流れてきたらsnapshot.errorがエラーテキストとして表示されると言った感じです。

また送信ボタンもバリデーションの結果を受け取れるようにしておきます。

UIでバリデーションの結果を受け取る
UIでバリデーションの結果を受け取る

コード全体像

コードの全体像は以下の通りです。 UI

UI全体
UI全体
Bloc全体
UI全体

おわりに

今回フォームの実装方法を探る中で、Rxdartのコードを読みました。英語が苦手で過ごし面食らいましたが、じっくり読んでみるとかなり丁寧にコメントがされていて、ネットの情報見るよりわかりやすかったので、もし困ったらコードのコメントを読むのをお勧めします。

Flutterで菱形を描画する

こんばんは。最近はFlutterを使ってアプリ開発をしているのですが、Flutterで図形を描画する方法に少しハマりました。

そこで今回の記事では個人的に得られた知見を共有したいと思います。

まだFlutterの経験が浅いため、誤った解釈をしている部分があればご指摘いただければ幸いです。

手順

任意の図形を描画するときは以下の手順に従います。

Flutterで図形を描画する時のイメージは、図形を描くペンと、図形の頂点の座標一覧の二つを予め用意しておき、その二つを元にFlutterが図形を描画してくれる感じでいいと思います。

Flutterが図形を描画するイメージ
描画のイメージ

今回は例として菱形を描画してみたいと思います。

1.CustomPaintを継承したクラスを作成する

Flutterで図形の描画を担うのはCustomPainterクラスです。
CustomPainterクラスはFlutterの標準ライブラリに含まれているのでパッケージを追加する必要はありません。

図形を描画する際はCustomPainterクラスを継承したクラスを自作して、paint()shouldRepaint()をオーバーライドする必要があります。

それぞれの役割は次の通りです。

  • paint(Canvas canvas, Size size)

描画する図形を指定する

canvasは言葉の通り図形を描く座標平面みたいなもので、sizeはWidgetツリーの親要素のサイズと考えて良さそうです。

  • shouldRepaint(CustomPainter oldDelegate)

再描画に関する設定をする

以下のスニペットはCustomPainterクラスを継承したクラスです。 この状態ではまだなんの図形も描画しません。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
  }
}

2.Paintインスタンスを生成する

図形を描画するペンの役割を担うPaintインスタンスを生成します。

Paintインスタンスは様々な値を設定することができます。 詳細は以下のリファレンスが参考になります。

api.flutter.dev

ここでは黒色で太さが1のペンを指定します。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
  }
}

3.Pathインスタンスを生成し、図形の頂点の座標をせっていする

つぎにPathインスタンスを生成し、図形の頂点を与えます。 ここでは菱形を描くので(1, 0), (1, 2), (2, 1), (0, 1)の4つの点を与えます。

ここで気をつけなければならないのは図形を座標を負の数にしないことです。 もちろん負の数でも描画はできるのですが、負の数で座標を指定するとCenterなどでWrapした時に図形の中心がずれてしまいます。

CustomPainterは図形の描画のみに集中し、センタリングや大きさなどはContainer()Center()などでラップしてあげる方が図形に関する関心ごとを分離できて再利用性が高まるので、個人的には全て正の数で描画するのがおすすめです。

またpaint()の引数で渡ってくるSizeを利用して座標を指定するとより柔軟な図形を描くことができます。

Path().moveTo(x, y)でペンの位置を任意の場所に移動することができ、Path().lineTo(x, y)で現在のペンの位置から引数で指定した座標まで線を引くことができます。 線を引き終わったらPath().close()で描画を終了します。

ここではdartのカスケードが使えるので是非使いましょう。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    /// ペンの設定
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;

    /// 座標の設定
    ..moveTo(size.width / 2, 0)
        ..lineTo(size.width, size.height / 2)
        ..lineTo(size.width / 2, size.height)
        ..lineTo(0, size.height / 2)
        ..lineTo(size.width / 2, 0)
        ..close();
  }
}

これでペンと座標の設定が終わりました。あと一息です。

4.canvasにPathインスタンスを渡す

さいごに今まで準備したPath()Paint()paintの第一引数に渡ってくるcanvasに渡して完成です。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    /// ペンの設定
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;

    /// 座標の設定
    ..moveTo(size.width / 2, 0)
        ..lineTo(size.width, size.height / 2)
        ..lineTo(size.width / 2, size.height)
        ..lineTo(0, size.height / 2)
        ..lineTo(size.width / 2, 0)
        ..close();

    /// キャンバスにペンと座標を渡す
    canvas.drawPath(path, paint);
  }
}

以上で菱形を描画することができました!お疲れ様です!

おわりに

Flutterは公式パッケージに用意されているWidgetがとても充実しているのでいざオリジナルの図形を描画するとなると面食らってしまいますが、実際にコードを書いてみると意外と単純で、とっつきやすいと思います。

それでは、よいflutterライフを!

生産性爆上がり間違いなしのデスク周りのアイテム紹介!

腰が痛い...
目が疲れた...
手が痛い......

長時間PCで作業していると体の不調を感じることってかなりあると思います。

コンディションが整ってないと作業に集中できず、生産性が下がってしまいます。

そこで今回は私が実際に使ってみて「絶対に使ったほうがいい!」と思ったアイテムたちを紹介していきたいと思います!

全体像

こちらが今の私のデスク環境です。

デスク環境
デスク環境

なかなかシンプルでクリアな環境だと思います。

いますぐにでも座ってコードを描き始めたくなる環境です笑

デスク周りのアイテムを揃えるにあたって

自分の生産性に投資

というワードを強く意識しました。

生産性を少しでも上げるためにアイテム選びには一切の妥協をしていません。

以下では、生産性こだわりお化けな私が選び抜いたアイテムについて書いていきます。

PC

PCは絶対に必要ですよね笑

私はMacBook Proをチョイスしました。

www.amazon.co.jp

私は普段アプリ開発をしているので、メモリを16GBにしました。

おかげでビデオミーテイングをしながらでもキビキビと動いてくれています。

コード書いたり動画編集したりしなければMacBook Airでもスペックは十分だと思います。

いろいろな場所へ持ち運ぶことを想定し、あえて13インチをチョイスしました。

MacBookスタンド

MacBookの画面に合わせると首に負担がかかるのでスタンドを使います。

さまざまなスタンドがありますが個人的にMajextandが一番お勧めです。

www.amazon.co.jp

Majextandのオススメポイントは「とても薄い(1mm)のに安定感抜群で丈夫」です

はじめは薄さが1mmということで、

壊れやすそう... ぐらつきそう...

とネガティブな印象を持っていました。

しかし、使ってみると、「こいつ壊れることあんの?」ってぐらい丈夫で安定感抜群でした。

しかも付属の滑り止めのゴムのグリップ力が抜群で、安定感マシマシでした。

横からみるとこんな感じです。

MacBookスタンド
横から見たMajextand

この隙間がPCの排熱をよくしたり、収納スペースの役割もになってくれるので、一石三鳥なスタンドです。

モニター・アーム

モニターはHPのP224を購入しました

www.amazon.co.jp

そこまで大きなモニターを必要としていなかったのでこれをチョイスしました。

それでもMacBookよりは大きい画面で作業ができるので、生産性に貢献してる気がします。


モニターにはモニターアームが欠かせません

せっかく広い画面で作業ができても、首が痛くなってしまったら非常にもったいないです。

私はColebrookBossonSaunders CBS Flo Blackをチョイスしました。

item.rakuten.co.jp

このモニターアームは3次元で細かく調整できるので、
首が疲れないベストな位置にモニターを調整することができました。

値段は35000と少ししますが、
自分の首の疲れを軽減して生産性を向上させることを考えたら十分アリな値段だと思います

モニターアーム裏側
モニターアームの裏側 コードがすっきり収納できる

写真のようにこのモニターアームはコードをすっきり収納できる構造になっているので、 配線が視界にチラつくのも防いでくれます。

オフィスチェア

イスはとても大事です

1日8時間、、、つまり1日の1/3はイスに座ることになります。

こんなにも長い時間、コンスタントにお世話になるイスを中途半端に選んでしまうと、首、肩、腰が簡単に崩壊してしまいます。

コンディションの乱れは生産性が低下するサインです。

いいイスにすわりましょう!

私はアーロンチェアマスタードを選びました。

ZOZOでも導入されているなど、実績があるオフィスチェアです。

こいつに座って世界が変わりました笑

Flexispot E3
スタンドデスクとしての運用

そのため、普段は普通の机として使い、姿勢変えたい時はスタンドデスクとして運用することができます。

自分の最適な高さに調整して、体を守ることができ、スタンドデスクとしても運用可能と、いいことづくしの机です。

キーボード・パームレスト

イス、机、モニターアーム...で生産性を向上させてきました。

あとひとつ、生産性を向上させるのにマストなアイテムがあります。

そう、キーボードです!

私はHHKB TypeS HYBRIDをチョイスしました。

www.amazon.co.jp

キーボードとしては少し値段が張ってしまいますが、値段以上のベネフィットがあります。

HHKBはキー数が60程度と通常のキーボードに比べキー数が少ないのが特徴です。
そのため、ホームポジションから手を動かさなくていいため、高速なタイピングが可能です。

また、キー数の少なさのおかげで机の占有率も抑えられ、すっきりとしたデスクを実現できます。

HHKB
すっきりとした印象のHHKB
HHKBはしばしば「馬の鞍」に喩えられます。

以下はHHKB公式サイトからの引用です。

アメリカ西部のカウボーイたちは、馬が死ぬと馬はそこに残していくが、どんなに砂漠を歩こうとも、鞍は自分で担いで往く。 馬は消耗品であり、鞍は自分の体に馴染んだインタフェースだからだ。 いまやパソコンは消耗品であり、キーボードは大切な、生涯使えるインタフェースであることを忘れてはいけない。 東京大学 名誉教授 和田英一

私はこの生涯使えるインターフェースという言葉に惚れ込み、購入を決意しました。

実際にこのHHKBは静電容量無接点方式という仕組みを採用しており、理論上は無限に使えるため、まさに生涯使えるインターフェースです。

実用面についてもキートップの角度押下圧など何から何まで考え尽くされており、打鍵感はこの上ないぐらい気持ちがいいです。

このキーボードを使い始めてからレポートを各スピードが体感1.5倍上がりました。

しっかりと生産性の向上に貢献し、とにかく文字を打つことが楽しくなるキーボードです。

このキーボードの魅力をさらに引き出すのにパームレストがオススメです。 筆者がチョイスしたのはFilco ウッドパームレスト(小)です

このパームレストの小サイズはHHKBの幅とぴったりなので相性が抜群です。

パームレストが木製と聞いて、ウレタンのように柔らかい材質の方がいいんじゃないかと疑問を感じる人もいると思います。

私ははじめは木よりも柔らかい材質の方がいいと考えていました。しかし、長時間使う場合、柔らかい素材だと手首を安定させることができなかったり、蒸れて不快に感じることを考え、木製をチョイスしました。

購入してから半年ほどたちますが、木製を選んで正解だと感じています。 安定感や蒸れにくさはもちろんですが、木の温もりがどこか心地よく、快適に作業をすることができています。

www.amazon.co.jp

Filcoウッドパームレストはその高さもHHKBと相性がよく、写真のようにHHKBと机の間にできる段差をカバーすることができ、より快適にキーボードを使うことができます。

パームレスト
パームレストがHHKBと机の段差をカバーしている

このパームレストをよくみると真ん中ぐらいから手前に向かって傾斜があるのがわかるでしょうか。

この傾斜があることによって手首の角度が自然な形になり、より長く快適に作業をすることができます。 HHKB公式のウッドパームレストととても迷ったのですが、このわずかな傾斜が決めてとなってFilcoウッドパームレストを購入しました。

www.pfu.fujitsu.com

さいごに

ここまで私が生産性を向上させるためにチョイスしてきたアイテムについて紹介してきました。

それなりに値が張るアイテムが多いですが、日々の生産性の向上に投資すると考えれば実はそれほど高くなかったりするのかもしれません。

生産性の向上に貢献してくれるアイテムはほかにもたくさんあると思うので、アップグレードした際にまた紹介したいと考えています。

デスク周りのアイテムについてはそれなりに調べたつもりなので、もしかしたら何か有益なアドバイスをできるかもしれません。もし聞きたいことがあれば気軽に聞いてください!

それではいいデスクライフを!

ダックタイピングの紹介

はじめに

お久しぶりです。とりかつです。
最近は設計とかアーキテクチャとかOOPとかを学習しているのですが、そのなかで「ダックタイピング」というものが出てきました。今回の記事ではその紹介とそれについて思ったことを簡単に書いていこうと思います。
一応なんですけど、自分は静的型付け言語触ってきたこともあり、若干偏見が混じってるかもしれません。ご了承ください。

ダックタピングとは

うまく説明できる自信がないので具体例をあげながら説明します。

犬を表すDogクラスと、猫を表すCatクラスがあります。
犬と猫はそれぞれ吠えたり鳴いたりするメソッドを持っています。

class Cat
  def meow
    "にゃー"
  end
end

class Dog
  def bow
    "わん"
  end
end

class Main
  dog = Dog.new
  cat = Cat.new

  # なかせる

  # わん
  dog.bow

  #にゃー 
  cat.meow
end

で、ここからがダックタイピングなんですけども、犬も猫も鳴き声は違えど、とりあえず鳴きますよね。
犬も猫も鳴き声ごとに呼び出すメソッドを変えるんじゃなくて、どっちにも「鳴いて!」って言えば鳴いてくれた方がクラス間の依存を疎結合に保つことができそうですよね。 ということで上のコードをダックタイピングで書き換えてみました。

class Cat
  # パブリックインターフェース
  def cry
    meow
  end

  def meow
    "にゃー"
  end
end

class Dog
  def cry
    bow
  end

  def bow
    "わん"
  end
end

class Main
  dog = Dog.new
  cat = Cat.new

  # なかせる
  # わん
  dog.cry
  #にゃー 
  cat.cry
end

DogクラスもCatクラスも固有の鳴き声メソッドをcryメソッドでラップしてあげることでどちらも同じメソッドを呼び出すだけで鳴かせることができるようになりました。これがダックタイピングです。

ダックタイピングの利点

ダックタイピングの利点として以下が挙げられると思います。

  • 型を判別して呼び出すメソッドを分岐させる必要がないということは依存性を減らせる
  • 明示的にインターフェースを実装する必要がない

もちろん他にも利点はあると思いますが自分の認識では上の2つが大きい利点だと考えています。

静的型付け言語のinterfaceつかえばよくない?

javaとか静的型付け言語触ってきた人はここであれ?と思っていると思います。
わざわざ暗黙的にインターフェースを実装しなくてもinterface使えば良さそうですよね。インターフェースの実装が強制されていること知らないチームメンバーがいたら...なんてケースも考えられます。
もちろんその考え方も間違ってないと思います。でもそれは静的型付け言語に慣れてきたからこその感覚だと思います。

そもそも静的型付け言語は「こんなインターフェース用意してるから使って!」ってイメージで、動的型付け言語は「とりあえずこのインターフェース呼べば良さそう!なかったらその時考えよう」ってイメージです(偏見)。
なのでどっちが優れてるとかじゃなくてどっちにもメリットとデメリットはあるよってことだと思います。
毒だって使い方によっては薬になりますよね。今回紹介しているテクニックもそれと同じで適しているところに使えば大きなメリットを生みますし、変な使い方をすれば大きくコストをかけることになったりもします。
ちなみに先ほどの

インターフェースの実装が強制されていること知らないチームメンバーがいたら...

の解決策としてテストで実装を強制することが挙げられます。

まとめ

今回はダックタイピングについて紹介しました。個人的には動的型付け言語ならではの面白いテクニックだなと感じました。Railsとかで開発する際に積極的に使ってみたいです。