Kotlinのデータクラスが便利なのでRubyでそれっぽいものを作ってみたときに勉強になったこと

いまの職場に入社した頃はPython大好きRuby大嫌いだったのだけど、3年半もRubyばっかり触っていたら流石に少し克服しつつあるので、初めてGemを作ってみることにした。

ネタはKotlinで便利なdata class

data class Point(val x: Double = 0.0, val y: Double = 0.0) {
  val norm get() = Math.sqrt(x * x + y * y)
}

つくってみると、「Rubyのこの仕組みって、こうやって実装されてるのか!」ってところがいくつか有ったので、書いておこうかなと思う。

そもそもなんでデータクラスを作ろうと思ったのか?

まずは前提の共有から。

いま検索画面を仕事でつくっていて、「検索条件」のように6つくらいの値(キーワード、ソート順、カテゴリ、etc)を保持する箱がほしいと常日頃おもっていた。

qiita.com

↑ではかなり控えめに書いたんだけど、現状だと6つくらいの値がすべてコントローラのインスタンス変数で保持されていて、無秩序にそれらをパーシャルビューやヘルパーメソッドから読んでいるような実装になっている。イメージこんなかんじ↓

<% if @keyword.present? %>
<h1><%= @keyword %>の検索結果</h1>
<% else %>
  <% if professional_search? %>
  <h1>プロに仕事を依頼する</h1>
  <% elsif ... %>
     ...
  <% end %>
<% end %>
def professional_search?
  @keyword.blank? && @only_professional_flag.present?
end

これを、

SearchQueryViewModel = ViewModel.create(:keyword, :sort_order, category_ids, ...)
@search_query_view_model = SearchQueryViewModel.new(keyword: params[:q], sort_order=params[:order], ...)

のように、コントローラの変数は1つにして整理したり、

search_result = User.search(...)

UserPartialViewModel = ViewModel.create(:display_name, :avarage_feedback_score, :num_contracts)
@users = User.where(id: search_result.user_ids).includes(:feedback_summary, :profile).map{|user|
                  UserPartialViewModel.new(
                    display_name: user.profile.display_name,
                    avarage_feedback_score: user.feedback_summary.avarage_score,
                    num_contracts: user.feedback_summary.num_contracts)
               }
<% @users.each do |user| %>
<%= render 'shared_partials/user', locals: { user: user } %>
<% end %>

のように、パーシャルビューに必要な変数たちをひとまとめにしたり、あとは、ヘルパーに生やしてる変数をビューモデル側で定義したり、

まぁそれなりにひとかたまりに値をまとめる箱がほしいと思うケースがあった。  

とくにライブラリとか使わなくてもシンプルに

class HogeViewModel
  def initialize(keyword:, hoge_flag:)
    @keyword = keyword
    @hoge_flag = hoge_flag
  end

  attr_reader :keyword, :hoge_flag
end

のようなボイラープレートコードを書きまくればいい話なんだけど、できればこれを先に書いたような HogeViewModel = ViewModel.create(:keyword, :hoge_flag) みたいにサクッと済ませられる仕組みが欲しかった。

Ruby標準のStructでいいんじゃないか?」とも思ったのだけど、こいつは多機能すぎた。そもそもImmutableではないし、#members のように属性一覧をイテレートできるような仕組みまであるのはビューモデルでは全く必要ない。

ということで、自分が欲しいものをそのまま作ってみることにした。

 

bundle gem

Nodeでいうところの npm init みたいな感じの物があるだろうと思ったら、やっぱりあった。

bundle gem kt_data_class --test=rspec

github.com

RSpecの下準備をやってくれるのは結構ありがたみある。

CIの設定

bundle gem を叩いただけで、 .travis.yml が作られるし、rakeのデフォルト動作で単体試験実行をしてくれるようになっている。なので、そのままTravis CIをONにするだけで、masterプッシュ時に自動でテストがまわるCIっぽいことは十分にできる。

ただ「CIで実行したときだけspecが落ちる」という事象が起きたときに、Travis CIだとどうにもこうにもデバッグができず、かなり格闘をすることになった。 やっぱり使い慣れてるCircle CIでSSHデバッグしたいなーーと思い、速攻で乗り換えた。

github.com

結局は require 'spec _helper' を書き忘れていただけ、というショボショボの原因だったんだけど、それでもなぜかローカルではspecが通ってしまうので結構ハマった。

大抵の場合にはTravis CIで十分なんだろうなーとは思うけど、とはいえRubyのバージョンによってはTravis CIはRubyのダウンロードとか毎度やって無駄に時間がかかるし、Circle CIのコンフィグはコピペすれば動く系なので、Circle CIを最初から使ってしまうのが楽かなぁと個人的には思った。

eql?, equal?, ==, === は何を返すべきか

これはPython信者からするとかなり意味不明とキレたくなるところだ。Rubyにはequalっぽいメソッドが多く、名前もややこしい。 same_object? same_value? とかそういうメソッド名にしなかったのはなんで??ってのがものすごく謎い。

という愚痴はここまでにして、

mickey24.hatenablog.com

を参考に実装した。あとは、HashとかStructがそれぞれどういう eql? equal? == になっているかも参考にした。

このあたりは、毎度コンソールで動作確認するのがしんどいので、

  describe 'equality' do
    let(:klass1) { KtDataClass.create(:x) }
    let(:klass2) { KtDataClass.create(:x) }

    describe '同一のクラスの2つの同値インスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass1.new(x: 1) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(true) }
      it { expect(instance1 <=> instance2).to eq(0) }
       it { expect(instance1.eql?(instance2)).to eq(true) }
      it { expect(instance1 === instance2).to eq(true) }
    end

    describe '同一のクラスの2つの異なる値のインスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass1.new(x: 2) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(false) }
      it { expect(instance1 <=> instance2).not_to eq(0) }
      it { expect(instance1.eql?(instance2)).to eq(false) }
      it { expect(instance1 === instance2).to eq(false) }
    end

    describe '定義が同じで、異なるクラスの2つのインスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass2.new(x: 1) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(true) }
      it { expect(instance1 <=> instance2).to eq(0) }
      it { expect(instance1.eql?(instance2)).to eq(false) }
      it { expect(instance1 === instance2).to eq(true) }
    end

のようにspecをササッと書いて、こいつが通るまで実装を繰り返すっていうTDDっぽい感じで進めると捗った。

分割代入はどうやったら実現できるのか

これは必要のない機能なんだけど、どうやって実現されてるのか知りたかったので実装した。

github.com

Structだと

Point = Struct.new(:x, :y)
p1 = Point.new(3, 4)

x1, y1 = p1

x1
# => 3

y1
# => 4

のような挙動になるやつは、何を実装すればできるようになるのか?

to_a あたりを生やせばいいのかな?と思って調べ始めたところ

Rubyの多重代入におけるto_aとto_aryの挙動 - maeharinの日記

この記事に行き着いた。 to_ary っていうメソッドを生やせばいいんだとか。(これまたわかりにくい名前だw)

初期化ブロックはどうやって実装するのか?

Structだと

Point = Struct.new(:x, :y) do
  def norm
    Math.sqrt(x * x + y * y)
  end
end

のように拡張することができる。これはどうしても欲しい。

で、何を調べていたか忘れたけど、このあたりで「あれ、既に似たようなライブラリあるじゃん??」ってのを発見して、

github.com

この実装を結構参考にさせてもらった。クラスの拡張は class_eval を使ってるんだなー、と。

github.com

少し悩んだ末に型安全を捨てた

実はこのGemを作り始めたときは

Point = KtDataClass.create(x: Fixnum, y: Fixnum)

のように、型を明示的に指定して、

p1 = Point.new(x: 3, y: 4)
# => #<Point:0x000000000233c5f0 @x=3, @y=4>

p2 = Point.new(x: 3, y: "4")
# ArgumentError: type mismatch: y must be a Fixnum, String given

のように、型に合わないものは入れられないようにするつもりだった。

特に理由はないが、なんとなく「入れられるものを制限しておいたほうが、無用な条件分岐とか is_a? とかしなくて済むかなー」くらいの軽いノリで。

ところが、実際に使うとなると、これだと結構不便だということがわかった。

一番「あれれ」と思ったのが、true/falseを入れるときの定義。 TrueClass or FalseClass みたいな謎の定義をしないといけない。それはあまり狙ったところではない。むしろ邪魔くさい。

さらに、

qiita.com

このあたりの議論を見るに、Rubyはダックタイピングなので、型で制限するのは筋がよくないということだ。

それはそのとおりだと思った。

github.com

Gemの公開

何を間違ったか、最初の方でRakefileを消してしまっていたので rake build ができなかった。

とはいえ、specをrake経由で実行したいとは思わないので、

require "bundler/gem_tasks"

の1行だけを書いたRakefileを定義して、無事にrake buildでパッケージが作れた。

github.com

あとは、 rake release でリリースしようとしたら、Gitに何かがpushされるっぽく邪魔くさかったので、

rake build
gem push pkg/kt_data_class-0.1.0.gem 

のように、明示的にプッシュ対象を指定して、シンプルにリリースした。

このあたりは手作業でやるのがだるいので、そのうちCIのgit tagトリガーのbuildに任せたいと思う。

まとめ

Gemを作ると、結構勉強になることが多かった。

ひっそりと福岡に住み始めた2018年を振り返る

2018/2/17 (偶然にも32歳の誕生日だった)、福岡市に住み始めた。 理由はとても個人的なものなので、ここには書かないけど、突発的なものではなく割と準備をした上での移住だった。

半年経ったら何かしら書こうと思ってたんだけど、8月は微妙に忙しい日々が続いて後回しになってしまっていた。とりあえず年末だし何か書いておこうかなと思う。

とくに誰かに向けてメッセージ性があるものではなく、単純に自分が現時点で書いておきたいことを書いているだけである。

なぜ福岡・・?

多くは語れない「個人的な理由」が大きいのだけど、それでも候補地として

  • 札幌・・・過去に3年間住んでいたことがある。ぜひまた住んでみたいと思う場所ではあった。
  • 大阪・・・大学のときに京都に住んでいて何度か行ったことはあって、住めば都なのかなー くらいの場所だった。
  • 福岡・・・旅行で行ったことすらない。自分の中では完全に未知の場所。

の3つがあった。  

福岡は本当に知らなかった。一方で、福岡の会社事情がテレビでちょくちょく取り上げられているのをみて「これは札幌より面白い場所なのでは?」という思いはあった。

実は移住までに2回福岡に格安長期旅行していた

「これは一度住んでみないと、ちゃんとした判断はできなさそう」という直感が自分の中で働いた。ということで、とりあえず行くことにしたのだ。

仕事はもともとフルリモートでさせてもらっていたので、川崎に居ようと福岡にいようと仕事ができるという確信はあった。 仕事にはなるべく迷惑がかからないよう、Wifi完備のAirbnb宿を2週間くらい取って、"派手な観光はしない生活"を実際にやってみた。

これは本当に有意義だったと今でも思う。

1回めは、桜坂っていうちょっと高級な住宅街に宿はあったんだけど

  • 「天神まで電車で4駅」って遠いと思っていたら、めっちゃ近かった
  • スタートアップカフェというコワーキングスペースが無料で快適だった
  • 一蘭のラーメンを初めて食べて、ドハマリしてしまったw
  • スーパーで普通に売っている豆腐が(平均点てきに)めちゃくちゃおいしかった
  • マキイっていう体に優しい系の食品/惣菜を24時間で売っている(いい意味で)狂った店がある
    • こんな店は全国探し回ってもここくらいしかないのでは・・・?w
  • 魚が(平均点的に)とてつもなくおいしかった。
    • 富山県の海のほう出身だし札幌に3年くらい住んでいたし、それなりに魚の味には厳しい方だと思っているけど、それでも福岡の魚はめちゃくちゃおいしかった。
      • スーパーで売っている魚の平均点でいうと札幌よりも高い
  • 人口密度が適正
    • 通勤ラッシュは無くはないが、関東の半分くらいの密度で、時間も長くて10分くらい
    • 武蔵小杉の隣駅に住んでいたのだけど、休日の武蔵小杉の人混みは生理的に無理だった
  • のこのしまアイランドパークが最高すぎた
  • 海の中道海浜公園も最高すぎた

などなど、福岡の"生活"を知ることができた2週間だった。

 

2回めは、警固(けご)という天神に近い場所に宿をとって、住みたい場所を決めるのをメインで行ったんだけど

  • 都会でも住める
    • 博多は雰囲気的に東京みたいで近寄りたくないけど、天神は本当に庶民的な生活ができる都会
  • やっぱり豆腐がおいしいし魚もおいしい
  • 農薬が少なめの野菜を売っている店が札幌に比べると多く、関東に比べると安い
  • のこのしまアイランドパークがやっぱり最高すぎた
  • 賃貸の家賃は札幌の1.5〜2倍くらいするんだけど、分譲マンションを買ってしまえばそんなに高くない
    • 分譲マンションの値段は札幌とそんなに変わらない
      • とかいいつつそんな財力はなくて未だに購入できていないが...w

という感じで、総合的に見て札幌よりも良いということが自分の中では確実になった。

 

繰り返すが、多くは語れない「個人的な理由」が大きい。でも、仕方なく移住をしたわけではなく、自分なりに心底納得をした上での移住判断だった。(現に、いまでも後悔はしていない)

自分は"生活"に何を求めているか

心底納得ができた背景にはおそらく以下のような要素がある

体に良い食べ物

なぜ食べ物にこだわるのか?ということを少し書いておかないといけない。

長生きしたいなら、パンは自分で焼くのがいいかも - YusukeIwakiのブログ でも謎の健康意識を見せていたのだけど、本当の理由は皮膚病(痒疹?)だ。

自分は昔からしつこい皮膚病(痒疹?)もちで、時々ステロイドに頼らないと皮膚が大荒れになる状態だった。

ところが、ある日、会社の先輩と一緒に鍋パをしたときに「それは食生活の問題では?」と言われ、それからしばらくは砂糖とマーガリン/ショートニングトランス脂肪酸)を一切排除する生活をしてみた。すると、不思議なことに皮膚病が少しずつ改善していった。それ以来、多少の不健康な生活はしつつ「砂糖とマーガリン/ショートニングは極力とらないようにすれば皮膚病は軽減される」というのは確信に至り、とりあえずヤマザキパンは4年くらい食べていない。甘いものも、砂糖の甘さは偽物(自然由来の甘さではないもの)だと思うようになった。

本当に美味しい野菜

  丁寧な野菜づくりをする農家の存在についても少し書いておかないといけない。

スーパーで売っている野菜の大半は、大量生産することに重きをおいた野菜たちである。北海道にいるときにスーパーで買って食べていたのはこういう野菜たちである。

もちろんまずくはないんだけど特段美味しいわけでもない。

一方、「ちゃんと野菜を作ってくれている農家を守る」っていうことを謳っているスーパーや八百屋が時々ある。道の駅とか直売所などでもたまにそういうのがある。

そういう野菜は、どこか自然の甘み・旨味があって美味しい。

正直最初は、「高い野菜なんだから美味しくて当然じゃないか」「意識高いこと言ってるだけじゃないのか」とか懐疑的だった。しかし、ある時点から、美味しいものが食べられることはとても幸せで何事にも代えがたいことなのでは?と思うようになった。そんな美味しいものを今後も食べていくには、もっとちゃんと野菜を作ってくれている農家には感謝しないといけない、って思うようになった。(悪く言えば"舌が肥えているだけ"かもしれないw)

先に書いたように皮膚病対策で砂糖をとらないとなると、そのぶん自然のもので甘いものを食べないと自分の心は満たされない。となると、一番いいのは野菜と果実の甘さだ。

かりにパンを食べるにしても 長生きしたいなら、パンは自分で焼くのがいいかも - YusukeIwakiのブログ で書いたように原料の小麦のクオリティが高いだけで、砂糖がなくても十分に満足ができる。

これらは結局、丁寧に作ってくれる農家があってこそのものだ。

いろいろ書いたけど、要するになるべく砂糖には依存したくないので、身近にちゃんとした食べ物が買える環境があるというのは生活面ではかなり重視したいのだ。

人混みの少ない場所

もともと富山県の田舎で育ったので、人混みに対する耐性はかなり低い。

街で休憩するにもカフェに入らないと座る場所がないという時点で関東は結構つらかった。

会社に通勤するにも、渋谷や恵比寿までの満員電車は「なんでこんな人権侵害な空間に30分も居ないといけないんだ」と心底苦痛を感じていた。

休日に商業施設(グランツリーとかラゾーナ川崎とかはまぁまぁ行ってた)に行くと、人混みで息苦しさを覚えていた。


ということで、他にもいろいろ判断軸はあったけど、福岡移住の選択には自分なりの必然性が伴っていた。

実際に福岡に住んでみて

博多は都会

博多は都会なんだけど都会すぎるので、ヨドバシ以外にはあまり行かない。

福岡に住んでいて、生活の中心は博多ではなく天神だ。 地下鉄沿線に住んでいるという要因は大きいかもしれないけど、博多よりは天神のほうが買い物しやすいし、店の種類が多くて楽しい。

豆腐がおいしい

福岡にずっと住んでいる人に「福岡ってスーパーに売っている豆腐でも本当においしいですよね」って言うと、大抵は「はっ・・?」って反応をされるんだけど、間違いなく美味しい。

フクユタカっていう九州の大豆が原料になっているやつが特に美味しい。関東だとさとの雪っていうのがスーパーには並んでるはず。それを食べて美味しいと思った人はきっと福岡の豆腐の美味しさがわかるはず。

「豆腐が美味しいので移住しました!」ぐらいの仲間というか理解者が身近に居て欲しい・・・(切実w

鯛が安くておいしい

福岡に住むまでは鯛は一度もスーパーで買ったことはなかったけど、福岡に来てからは1週間に1回は買っていると思う。

鯛は高級魚ではなく、身近な魚なのだ。北海道の人が鮭やサンマを買うくらいの感覚で、自分は毎週どこかで鯛を買ってる気がする。

ちなみに、(個人商店を除く)スーパーで魚を買うなら

ボンラパス > ハローデイ > 西鉄ストア >>(越えられない壁)>> イオン

だ。イオンの近くに住むくらいなら絶対ボンラパスの近くに住むべきだと断言していいレベル。

意外とエンジニアリングの仕事がない

福岡といえば、スタートアップ企業がワンチャンあって、イイ感じのIT企業の求人もたくさん・・・

は無い。

関東の給与水準に慣らされていると、「え、この給与でやっていくの、みなさん?」ってところがかなり多い。

「お給料よりも、とにかくいい仕事がしたいんだ!」って人にはスタートアップ天国なのでいいかもしれないけど、自分には多分ないかな...。

お給料がいい企業も無くはないけど、多くは福岡"支社"なので、楽しい開発はみんな東京の方でやっているので福岡で楽しい開発ができることは無さそうだなという感触。

ふつうに今の会社で仕事をやらせてもらいつつ、副業で得意分野を増やしてメインの業務に還元するというスタイルを続けていくほうがいいなと思っている。(今の会社で福岡にエンジニアリング拠点つくればいいんじゃないかとも思っている)

京都よりも自転車天国

メルチャリの実証実験都市に選ばれているように、福岡はとても自転車での移動が便利。

住んでみて改めて思ったのが「自転車が最も効率的に移動できる手段」であるということ。

  • 東京や大阪だと、最も効率的な移動手段は電車。
  • 札幌とかその他多くの地方都市だと、最も効率的な移動手段は車。
  • 京都だと、バス・・・は渋滞がひどすぎるので、自転車が最も効率的な移動手段。

大学のときにサイクリング部に所属していたので、自転車に乗ること自体は好きなんだけど、日常的に乗るにはそれなりの理由がないと乗らないのもまた事実。

福岡は、住む地域によっては車が必須になるものの、メルチャリが展開されている地域くらいであればほぼ間違いなく自転車が便利である。福岡は京都に比べると、幹線道路がよく整備されているので、車道を窮屈な思いをしながら走るようなことも少ない。

自分の場合は、天神から電車で10分かからないところに住んでいるんだけど、それでも時々自転車で天神の方に行くことがある。なぜかというと、 地下鉄の料金が高い からだw 地下鉄で天神までいくと片道260円もかかるが、天神には無料の駐輪場があるので自転車だと無料で行き来ができる!天神までは4kmほどしかないので、自転車で行っても15〜20分だ。往復でモスバーガーが1つ食べれるくらい浮くなら自転車で行くわ、ってなるw

まとめ・・・られてないけど

とても個人的な理由で移住することになったけど

  • いい食べ物が身近に手に入る生活にはとても満足している
  • 人口密度の適正さは本当によい
  • 自然公園(のこのしまアイランドパーク、海の中道海浜公園)が身近にあるのも、自分の肌にはあっている感じがする
  • 仕事を楽しくやりたいならやっぱり関東かなー...

FlutterはStatelessWidgetでWebサイトのトレースから始めると楽しい

Flutter、最近よく聞くようになってきました。マテリアルデザインっぽいアプリならサクッと作れると評判のやつです。

Flutter、サンプルコードがまぁまぁムズい

New Project... でFlutterプロジェクト作って最初の、FABクリックでカウントアップするやつ、まぁまぁムズいです。

https://flutter.io/images/intellij/hot-reload.gif

読んでも動きがわからないとかそういうムズさではなく、どうにもこうにも次の一歩が踏み出せない感がある"ムズい"です。

なにがムズいのか

StatefulWidget。こいつがいきなり出てくるところかなと思っています。

HTML知らないのにJSいきなり教えられた(・o・)ポカーン な感じw

HTML画面の作り方を知った上で、動的なものを作りたくてJSを学ぶのがスーッと入っていけるのと同じで、FlutterもまずはStatelessWidgetで画面の作り方を知った上で、動的なものを作るところでStatefulWidgetを使えばいいんじゃないの?って感じました。

ということで、、作りたい画面があるなら作ればいいのだ!

静的な画面をつくるだけなら、StatefulWidgetの理解なく進められます。

「最近、よく読んでる https://home.livingfk.com/ ページ、もうちょっとスマホUIで読めたら最高なのになー」からの、Flutterトレースを始めてみました。

f:id:YusukeIwaki:20181021223304p:plain

StatelessWidgetでガンガン作っていくと結構楽しいです。

flutter.io

qiita.com

この2つのページと、あとはぐぐりながらやると、だいたいの画面は作れます。まだまだ途中だけどいったんリポジトリに上げてみました。

github.com

ネイティブ実装に比べてFlutterがラクなこと

マテリアルデザイン

まずスタイル定義が超ラクです。

あとは、ドロワーやアクションバー、ボタンやカードやDividerなど、一通りウィジェットが揃ってるのも本当にラクです。

最近は https://github.com/material-components/material-components はFlutterが一番先行してますよね。Androidですら未実装なのにFlutterは実装済み、とか結構あります。StepperとかBackdropとか。

ドロワーとかアクションバーのメニューとか

ネイティブだとJavaでコードを書かないとDrawerToggleが出ないとかオプションメニューが出ないとかありますよね。

FlutterだとScaffold使えば、

  • Drawerを定義するだけで自動でDrawerToggleつけてくれたり
  • AppBarにactionsって定義するだけでアクションバーのメニューをつけてくれたり

結構ボイラープレートコードっぽいものが省力化できてる感あります。

レイアウトの反映が一瞬

ネイティブだと、レイアウトXML変えたらビルドし直してまぁまぁ待たされますが、Flutterだとホットリロードで2秒くらいで結果が見れる。レイアウトの勉強には最高の利点です。

NetworkImageがライブラリなく使える

ネイティブだとGlideとかPicassoに頼ってContext渡して・・・とかいろいろ面倒なところ。

Flutterだと Image.network("https://xxxx.xx.xx/hogehoge.png") みたいなのがサラッと書けて、画像もWebページ表示するみたいに難なく非同期表示されるのはめちゃくちゃ便利です。

Webでは簡単だけどFlutterだと難しそうなこと

widthは画面の横幅100%、縦横比4:3のImage

Webだと簡単にできるんだと思いますが、Flutterだと幅を計算した後に高さを指定ってのがどうもうまくできませんでした。

PageView など事前に高さがわかってないと使えないウィジェットもあったりするけど、みんなどうやって解決してるんだろう・・・

NetworkImageを表示するカルーセル

PageViewの高さが事前にわかってないといけない制約です。はい。

画面にあわせて画像の方を加工せよ(あんまり縦横比が違いすぎる画像をまぜるな)ってことなんでしょうけど・・・

(ちなみにこれはネイティブでも同じことが言えるのでFlutterだから悪いとかそういうことではない)

 

まとめ

counterのサンプルアプリをみて「で、次なにからやればいいんだ・・・」ってなってる人は、ぜひWebページのトレースをやるといいです。楽しいよ。

f:id:YusukeIwaki:20181021225316g:plain