今更ながら2020年前半を振り返る
【目次】
振り返り
こんばんは、Tamappeです。
もう7月も中盤に差し掛かっていますが、まだ2020年の前半を振り返っていませんでしたので今日は2020年前半の振り返りを行います。
1月に行ったこと
ふと、何を思ったのかいきなりFlutterを触ってみたくなった。
僕の生活スタイルは1年間で1つのテーマを学習するスタイルです。その1つのテーマをFlutterに決めたのが1月でした。
ただFlutterを学習するだけなのはもったいないのでなんかテーマを決めたのが
「iOSエンジニア視点でFlutterでアプリ開発を学習してみたら?」
ということで割と面倒くさいFlutterでiOSアプリを開発する学習記録を残していくことにした。
これなら近い将来FlutterをするiOSアプリエンジニアが増えてきたら僕のブログが読まれるじゃん!と勝手に思ってた。
ちなみにブログを続ける自信がなかったので1ヶ月ぐらい隠れて記事を投稿してた。
それからブログを投稿する習慣を付けたかったのでとりあえず2日に1回更新するように心掛けていた。
このときに参考にしてた本はこちらの本です。
Android/iOSクロス開発フレームワーク Flutter入門
- 作者:掌田津耶乃
- 発売日: 2018/09/14
- メディア: 単行本
この本は昨年、会社の福利厚生で購入した本だった。
去年(2019年)はそろそろFlutterがiOSアプリエンジニアでも流行り出すでしょということで予め購入していました。
その後積ん読になった・・・。
この本で学習した知識を通してさらにUIKitだったらこう作るよね?
という視点で記事を書いていた。
1月は7記事ぐらい投稿していた記憶があります。
2月に行ったこと
2月はまだコロナウイルスが騒がれていなかったような記憶がある。
ダイヤモンド・プリンセス号でコロナウイルスの集団培養が起きてコロナウイルスの感染力を目の当たりにしたような印象だった。
まだまだマスクなしで外出していた時期だった。
2月のブログの更新は2日に1回ペースでこっそり続けていた。
中旬ぐらいにコンパスイベントpotatotipsのイベントがあったのでそれに参加したのを覚えている。
potatotips #68
初のpotatotipsでの登壇でした。
iOS Tips発表枠で参加しました。確かUIAlertControllerの制御をテーマにして登壇しました。
Androidの登壇者はFlutterネタが多かったです。
あまりに多かったので、このタイミングでノリでツイッターで実はFlutterを学習していることをツイートしてしまいました。
これで逃げられなくなったのを覚えている。
2月の終わり頃にはFlutterの本が終盤でRSSアプリを作ってた。
3月に行ったこと
この頃になると日本でもコロナウイルスの流行が見逃せなくなったからか世間が騒がしくなってましたね。
どっちかというとパニックになっていた印象があります。
マスクは2月上旬で既に売り切れで仕入れることができなくなってたので引き続きコンビニや薬局で品切れになってました。
アプリ開発はFlutterでの操作に慣れてきた頃でSketchで4択クイズアプリを作る企画をしていた。
Flutter本が完了してUdemyで手頃な動画を探してその動画に沿ってコードを写経していた。
参考にしていた動画はこちらです。
動画を見ながらデザインをアレンジして4択クイズアプリを開発しながら記事を作っていた。
書き慣れてきたので土日に記事を作り溜めして2日に1回ペースで記事を投稿するようになった。
そういえば、2月まで1日平均3時間以上Switchのスプラトゥーン2をプレイしていたゲーム中毒問題は 会社の人にSwitch本体ごと譲渡したことで解消されたな。
プレイ時間はたしか3年間でトータル2000時間には達すると思うので、
この時間を別のことに集中してたらまた違った人生を歩んでいたに違いないと思っている。
今振り返ればブログに投資していたら何記事出来たことやらと思っている。
4月に行ったこと
4月に東京都で緊急事態宣言が発令されたので会社も突如リモートワークが導入されて在宅ワークになった。
世間的にはリモートワークとテレワークという言葉が流行りだした時期です。
気分の切り替えとして朝イチにレッドブルを飲む習慣を付けた。
この時は会社も世間も状況の入れ替わりが激しなったので業務後にしていた事を覚えていなかった。
人生でそうそうない在宅ワークが始まったのでYouTubeで動画を見るようになった。(これが一番今でも影響を受けている)
印象に残っているのはYouTubeにFlutterのライブコーディングの動画が沢山あったので、 これからFlutterを触る方はYouTubeの動画だけで十分な気がしていた。
Flutterでのアプリ開発はネタ切れ感があったので上司に相談したところ、 AppleMusicのクローンアプリを作ってみればということでSketchでデザインを作ってクローンアプリ開発を始めました。
5月に行ったこと
5月のゴールデンウィークまではひたすら色々なブログを書きまくっていた。
スプラトゥーン2のプレイ時間をブログ記事執筆に当てていれば・・・
とこのときほど後悔したことはなかった。。。
5月中旬はiOSアプリエンジニアの堤さんがキャンプファイヤーでなんかするということだったので、
日頃お世話になったような気持ちになったので寄付しました。
寄付してからオンラインサロンだったことに気づいてとりあえず登録作業だけ済まして放置してた。
Flutterでのアプリ開発はAppleMusicクローンアプリ開発が飽きてきた頃で オリジナルアプリとしてナンバーズアプリを作ることに決めた。
今振り返れば、AppleMusicアプリの延長でYouTubeのAPIを使って動画のまとめアプリに改変すればよかったと反省している。
この時が一番Flutterでのアプリ開発で楽しかった時期だったかもしれない・・・。
6月に行ったこと
(編集中)
全体の振り返り
思い返せば、2020年の前半は
- Flutterでのアプリ開発のスタート
- ブログ活動の本格開始
- オンラインサロンでの活動
- ゲーム中毒の解消
となかなか人生の分岐点になりそうなイベントばかりが起こっている。
半年前はまだスプラトゥーン2でゲーム中毒になっていたんだな。。。
あのゲームは極めれば極めるほどストレスが溜まるというなかなかの鬼畜ゲームだったのでそれを辞められたのは凄い大きい。
ブログ活動にFlutterなんで2020年は生活習慣がガラッと変わった年になったと思う。
ということで締まりが悪いですが色々振り返れたんでこういうのも悪くないと思いました。
それでは、バイバイ!
65. FlutterでListの要素をランダムにシャッフルするには
開発しているアプリでListの要素をランダムシャッフルして格納し直す必要が出てきたので ランダムにシャッフル出来ないのかを調べてみました。
List _shuffle(List items) { var random = new Random(); for (var i = items.length - 1; i > 0; i--) { var n = random.nextInt(i + 1); var temp = items[i]; items[i] = items[n]; items[n] = temp; } return items; }
これでListに入っている要素をランダムに並び替えることができる。
final list = List<int>.generate(25, (i) => i + 1); print("list: $list" ); final randomList = _shuffle(list); print("randomList: $randomList" ); # 結果 # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] # [12, 3, 18, 8, 23, 20, 7, 10, 9, 1, 19, 5, 6, 4, 17, 24, 21, 11, 16, 13, 22, 15, 25, 14, 2]
参考にしたページを貼ります。
Flutter for Web でWebプロジェクトを新規作成する
前提
Flutter コマンドを叩ける状態にしておく
コマンド
Flutterパッケージのあるディレクトリで次のコマンドを叩く
$ flutter channel master
flutter をアップグレードします。
$ flutter upgrade
Web サポートを有効にします。
$ flutter config --enable-web
chrome などのブラウザが使えるかを念の為確認します。
$ flutter devices
モバイルプロジェクトと同様にプロジェクトファイルを作成します。
$ flutter create the_basics
Chrome で開く場合
Chrome でローカルで開発する場合に使う感じです。
$ flutter run -d chrome ## 成功時 Downloading Web SDK... 8.9s Launching lib/main.dart on Chrome in debug mode... Syncing files to device Chrome... 17,359ms (!) Debug service listening on ws://127.0.0.1:62972/QwJpY-485sk= Warning: Flutter's support for web development is not stable yet and hasn't been thoroughly tested in production environments. For more information see https://flutter.dev/web 🔥 To hot restart changes while running, press "r" or "R". For a more detailed help message, press "h". To quit, press "q".
Chrome が開いて開発できます。
任意のサーバーで立ち上げる場合
laravel artisan serve みたいな感じ localhost で立ち上げたい場合に使う。
$ flutter run -d web-server ## 成功時 Launching lib/main.dart on Web Server in debug mode... Syncing files to device Web Server... 11,031ms (!) lib/main.dart is being served at http://localhost:63225
最後に書いている http://localhost:63225 を開くと表示されます。
Flutter コマンドを叩けるようにする
Flutter パッケージのダウンロードページ
Flutter パッケージのダウンロードページ
FlutterパッケージをDLしてパスを通す。
Flutter パッケージのあるディレクトリーで次のコマンドを叩く
export PATH="$PATH:`pwd`/flutter/bin"
これで flutter のパスを叩けるようになる。
64. FlutterのProviderパターンを3分で理解する
FlutterのProviderパターンを3分で理解する
Flutter 初心者にとって Provider の扱いはとても難しく思うはず。
僕も例外なく Flutter 学習初めの頃は Provider を見てもあまり魅力を感じなかった。
今回はその Provider について理解できるようにすることが主目的である。
【目次】
事前知識
Provider の書き方を理解する上で、前提知識といいますか経験値が必要な気がします。
- Container, Column, Row, ListView を使ってレイアウトを組み立てられる
- StatefulWidget の setState の使い方を理解している
- Widget の分割のメリットを理解している
多分、これらまで理解していたら Provider で今よりもいい感じの設計ができる(はず)。
Providerとは
Provider の概念・理念の理解は itome さんのブログが一番分かりやすいと思う。
この記事を読めば Provider については理解できるはずだった。はずだったというのは僕が例外だったから。
多分、本当に初心者の方にとっては Provider の理解は難しいと思う。
Provider の特徴
完全に Providerについて理解していないので、勿論間違った解釈をしているかもしれない。
そのレベルですが、 Provider を使うことで得られるメリットは
これらだと思っている。
それではこれを前提に Provider のついて学習していきたい。
サンプルコード
Provider のサンプルコードについては上記のページと同じコードを載せることになります。
最初は Flutter の初期コードをそのままカスタマイズするためです。
それでは Flutter の初期コードについてコメント文を削除した状態を載せます。
main.dart
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
こちらが初期コードになります。
特徴は
- setState で値を更新している (StatefulWidget のため)
- Widget の生成 と Model が同じクラス内で行われている
です。
これをProviderパターンに書き換えます。
Provider の手順について
それでは Provider を使う手順について紹介します。
- Provider をインストールする
- ChangeNotifier を継承した Model クラスを定義する
- 発火したいイベントを持っている widget にConsumer で包む
- Provider で値を更新させたい widget に ChangeNotifierProvider で包む
- 値を更新したい箇所 に Provider で包む
1. Provider をインストールする
Provider パッケージのページはこちらになります。
yaml ファイルに宣言してインストールします。
pubspec.yaml
dependencies: flutter: sdk: flutter provider: ^4.1.2
使うファイルで import します。
main.dart
import 'package:provider/provider.dart';
これで準備が整いました。
2. ChangeNotifier を継承した Model クラスを定義する
次に ChangeNotifier を継承した Model クラスを定義します。
main.dart
import 'package:provider/provider.dart'; class CountModel extends ChangeNotifier { /// 初期値 int count = 0; /// count の更新メソッド void increment() { count ++; notifyListeners(); } }
こんな感じでOKです。
3. 発火したいイベントを持っている widget に Consumer で包む
次に値を更新するイベントを持っているwidget を ChangeNotifierProvider で包んであげます。
ですが、最初のコードは StatefulWidget で書かれているので StatelessWidget に書き直して
Widget build(BuildContext context) の中身を移動させます。
main.dart
class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
この時点では counter や incrementCounter が存在しないのでエラーになります。
まずは Scaffold の部分を次のように変更します。
child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), /* * 以下省略 */ ) )
Consumer
4. Provider で値を通知したい widget に ChangeNotifierProvider で包む
次に値を更新させたい widget を ChangeNotifierProvider で包みます。
今回は MyHomePage クラスの Scaffold に対して値を通知したいです。
main.dart
return ChangeNotifierProvider<CountModel>( create: (context) => CountModel(), child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), /* * 以下省略 */ ) ), );
5. 値を更新したい箇所 に Provider で包む
最後に更新された値を受け取ってそれを widget に反映させます。
値を提供するということで Provider と名付けられているかもしれません。
main.dart
Text( /// Provider を使う '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, )
これで Consumer で包んだ FloatingActionButton をタップした時にProviderで受け取った Text の数値が変更されます。
またこれはwidget で切り離して使うこともできます。
class CountText extends StatelessWidget { @override Widget build(BuildContext context) { return Text( /// context からModelの値が使える '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, ); } }
なんと、model の count が Provider.of からシングルトンのように取得できるようになりました。
わざわざ count のプロパティを受け取る必要がないことがわかります。
これでビルドすると Flutter プロジェクトが作成された初期画面と同じ挙動になります。
全体のソースコード
最後に全体のソースコードを載せておきます。
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class CountModel extends ChangeNotifier { /// 初期値 int count = 0; /// count の更新メソッド void increment() { count++; notifyListeners(); } } void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider<CountModel>( create: (context) => CountModel(), child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), CountText(), ], ), ), floatingActionButton: FloatingActionButton( onPressed: model.increment, tooltip: 'Increment', child: Icon(Icons.add), ) ) ), ); } } class CountText extends StatelessWidget { @override Widget build(BuildContext context) { return Text( /// context からModelの値が使える '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, ); } }
ということで長かったですが、最後にこのパターンを暗記します。
一回テンプレート的に書き方を覚えたら後は応用になるからです。
ちなみに、この Provider パターンは、
RxSwift に例えるとするなら、
Consumer は BehavorRelay の accept で値を渡すところ
Provider は Observable で値を通知するところ
iOS でいうならば、
Consumer は NotificationCenter をpostするところ
Provider は NotificationCenter でadd して値を通知するところ
みたいなイメージです。
そんな印象でした。今日はここまで。
それでは、バイバイ!
63. Flutterでボタンにアニメーションを追加する
ボタンにアニメーションを追加する
本当はボタンに1回転のアニメーションを入れたかったのですが、Animation の回転が思わぬ位置に止まったので諦めました。
なので前回紹介したフリップのアニメーションを使ってボタンにアニメーションをかけます。
前回の続きはこちらになります。
使うアニメーションのパッケージはこちらです。
NumberButtonのウィジェットクラスを改造します。
改造前
class NumberButton extends StatelessWidget { final int number; final Function onPressed; NumberButton(this.number, this.onPressed); @override Widget build(BuildContext context) { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(10), color: Constants.orangeColor, ), child: FlatButton( child: Text( '$number', style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, ), ), onPressed: onPressed), ); } }
改造後
class NumberButton extends StatelessWidget { /// ボタンの番号 final int _number; /// タップした時の処理 final Function _onPressed; final bool _isOnTouch; NumberButton(this._number, this._onPressed, this._isOnTouch); @override Widget build(BuildContext context) { return FlipCard( direction: FlipDirection.HORIZONTAL, speed: 500, // タップイベント onFlip: _onPressed, flipOnTouch: _isOnTouch, front: _frontNumberButton(), back: _backNumberButton(), ); } Widget _frontNumberButton() { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(10), color: Constants.orangeColor, ), child: Center( child: Text( '$_number', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, ), ), ), ); } Widget _backNumberButton() { return Stack( children: <Widget>[ Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(10), color: Constants.orangeColor, ), child: Center( child: Text( '$_number', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, ), ), ), ), Opacity( opacity: .8, child: Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.black, ))) ], ); } }
注目するところはこちらになります。
FlipCard( direction: FlipDirection.HORIZONTAL, speed: 500, // タップイベント onFlip: _onPressed, flipOnTouch: _isOnTouch, front: _frontNumberButton(), back: _backNumberButton(), );
FlipCard はフリップ速度をコントロールできるので500 に指定します。
フリップの報告は縦は違和感しかないので横(HORIZONTAL) に指定します。
そして、前面(front) と背面(back) にそれぞれ乗せたいウィジェットを乗せるといった感じになります。
_frontNumberButton() のウィジェットはこちらになります。
Widget _frontNumberButton() { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(10), color: Constants.orangeColor, ), child: Center( child: Text( '$_number', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, ), ), ), ); }
_backNumberButton() のデザインはタップ後にボタンを押せなくして、 さらに少し非アクティブに見せたいので黒色のカバーを乗せます。
Widget _backNumberButton() { return Stack( children: <Widget>[ Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(10), color: Constants.orangeColor, ), child: Center( child: Text( '$_number', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, ), ), ), ), Opacity( opacity: .8, child: Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.black, ))) ], ); }
こちらをビルドして確認すると下のようなアニメーションが出来ているのが分かります。
だんだんゲームぽく見えてきましたね。
これでゲーム画面での最後の機能は右上のタイマーのみになります。
62. Flutterライブラリflip_cardでフリップアニメーションを作ってみる
フリップアニメーションを簡単に作れる flip_card を使ってみる
今作っているナンバーズアプリでカードにアニメーションを入れたいのでアニメーションの入れ方を調査しました。 調査しているうちにシンプルなパッケージがありましたので簡単に紹介してみます。
フリップカードアニメーションを作れるflip_cardパッケージ
flip_card はフリップカードアニメーションを簡単に再現できる component です。
Horizontal | Vertical |
---|---|
簡単に使い方を説明すると
FlipCard( direction: FlipDirection.HORIZONTAL, // default front: Container( child: Text('Front'), ), back: Container( child: Text('Back'), ), );
このように デザインは Container で作った widget を front(全面) と back (背面) に乗せるだけです。 Card と FlatButton が合体した widget みたいな印象を受けました。 ボタンタップ時の処理は onFlip のプロパティがありますのでタップイベントもハンドリングできます。
サンプルソースコード
flip_card を入れる
yaml ファイルを編集して flip_card が使える状態にします。
pubspec.yaml
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 flip_card: ^0.4.4
yaml ファイルでインストールに失敗するときはインデントを見直します。結構あるあるです。
パッケージをインポートする
ファイルにインポートします。
import 'package:flip_card/flip_card.dart';
今回は簡単に StatelessWidget に FlipCard を乗せました。
class FlipCardButton extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: FlipCard( direction: FlipDirection.HORIZONTAL, speed: 500, // タップイベント front: Container( width: 100, height: 100, color: Colors.redAccent, child: Center(child: Text('前面')), ), back: Container( width: 100, height: 100, color: Colors.blueAccent, child: Center(child: Text('背面')), ), ), ), ); } }
main.dart にはただ FlipCardButton ウィジェットを乗せました。
main.dart
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Numbers', theme: new ThemeData( fontFamily: 'Arial', ), home: FlipCardButton(), ); } }
これをビルドすると次のような画面が表示されます。
(はてなブログではめちゃくちゃ容量小さい gif でないとアップロードできないので動作が鈍いですが、実際は滑らかな動きです。)
これを使ってナンバーズアプリのボタンにアニメーションを入れていきます。
今日はここまでです。
それではバイバイ!