Flutterに入門してみて理解したところやハマったところ
前説
こんにちは、tkyです。
2018/10/08 に開催された技術書典5で購入(知人経由で・・・)したFlutterの入門本でいよいよ私もFlutter入門者になりました。 その時学習した内容とハマりポイントなどを知見として残しておこうと思います。
今回学習に際し、mBaaSとしてFirebase Cloud Firestoreを使用しています。
- Flutter
- Dart
- Firebase Cloud Firestore
なお、このときFlutter1.0がリリースしたてだったのでそれなりにハマるだろうな〜という想定はしていましたが、そこまでズボズボではなかったですw
本記事ではFlutterのインストール手順や各プラットフォームのセットアップ方法などは記載しません。
flutter doctor
しまくってエラーに記載されているコマンドをひたすら実行していったら環境整えられたので調べる手間が省けて楽でした。
何作ったの
『Flutte x firebaseで始めるモバイルアプリ開発』という本で解説している貸し借りアプリを書籍ともに作りました。
良かったところ
- hot reload機能
- Android/iOSどちらもandroid studio上で開発できたところ
iOSにはもちろんXCodeは必要ですし、エミュレータも同様ですが、デプロイがAndroid Studio上からできて、hot reloadもちゃんと効くのはまじで便利なのでは?
学習してみて
まだDartの言語仕様部分だったり、どう作る?みたいな深掘りはできていませんが、こんな感じでFlutterって動いてるんだ〜という入門程度には学習できたかと思われます。
今後はWidgetの学習とレイアウトの特訓、アーキテクチャBLoCを使ったアプリ開発あたりに触れて知識の深掘りをしていこうと思います。
今回の気付き
- Widget充実しすぎて便利
- publicとprivateの区分方法がアンダースコア(_)
- 直感的にレイアウトを置ける、onPressedなどのイベントが書きやすい
- 画面遷移がpush,pop形式でわかりやすい
- 画面は基本的にStatefulで良さそう?
Widget充実しすぎて便利
FlutterはWidgetと呼ばれるUIオブジェクトを配置することで描画しますが、かなり種類が豊富なのでは?と感じています。
AndroidのUIっぽくMaterial Componentsが、iOSのUIっぽくCupertinoという種別のUIがありました。 書籍ではMaterial Componentsで実装し、Android, iOSどちらも同じUIとなっています。
publicとprivateの区分方法がアンダースコア(_)
Dartにはpublicやprivateのような修飾子はなく、基本publicの扱いとなります。 アンダースコア(_)を変数、関数、クラスにの先頭につけることで同じような働きができるようになります。
Dart公式ドキュメントにはアンスコつけたらそのライブラリだけで見れるようになるよ、と書いてあります。(この"ライブラリ"のスコープがわからん・・・) www.dartlang.org
start with an underscore (_) are visible only inside the library.
直感的にレイアウトを置ける、onPressedなどのイベントが書きやすい
例えばボタンを配置するときに、以下のように記述します。
ログアウトボタンを押したとき、Topページに戻る
FlatButton( child: const Text("ログアウト"), onPressed: () { _auth.signOut(); Navigator.pushNamedAndRemoveUntil(context, "/", (_) => false); }, ),
FlatButtonのプロパティにイベントを記述する書き方は結構好きです。 またNavigatorを使用して画面遷移を実現しています。画面遷移はルーティングで行うため、画面設計は必要そうですね。
画面遷移がpush,pop形式でわかりやすい
先程の例で説明しましたがNavigator.push or popで画面遷移を実現しています。それなりにわかりやすいかなと思いました。
// Topページに遷移する Navigator.pushNamedAndRemoveUntil(context, "/", (_) => false); // /newというルートを作成して、InputForm画面に遷移する Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: "/new"), builder: (BuildContext context) => InputForm(null))); // 1つ前の画面に遷移する Navigator.pop(context);
画面は基本的にStatefulで良さそう?
Flutterにはスプラッシュ画面など動きがない固定画面で使用するStatelessと、
内部でデータを保持し動的に表示を行う画面などで使用するStatefulという考え方があるようです。
ん〜、基本内部で状態を保持して画面更新するパターンのほうが多い気がするのですがStafefulで良さそうな感じでしょうか?もう少し学びながら掴んでいきたいと思います。
ハマりポインツ
AndroidについてはAndroid Studioで開発できる点でほぼ何もハマることなく開発できました。
iOSがちょっとハマりました。
- iOSで
Could not build Objective-C module 'Firebase
問題 - iOSで
cloud_firestore/CloudFirestorePlugin.h' file not found
問題
iOSで Could not build Objective-C module 'Firebase
問題
最初の問題でした。エラーの内容通り、Ob-cのFirebaseモジュールがSwiftではビルドできない、と言われていますが、なぜ???という感じですね。
Ob-cのモジュールをinstallしてしまったのだろう、という感覚レベルで、対応法がぐぐったらすぐに出てきたので解決でした。
簡単に言うと、「Podfile
系でinstallしたやつ全部消してもっかいinstallし直す」です。
iOSで cloud_firestore/CloudFirestorePlugin.h' file not found
問題
こいつにずっと悩まされました。。
試行錯誤した内容は以下です。まぁ解決できませんでした。
- pod install しなおす
- flutter clean してみる
- コマンドでビルドしてみる「flutter build ios」
- flutter packages getしてみる
また、「flutter firestore」のチュートリアル等を確認して自分の手順と違うところを確認したり。 Firebase for Flutter
Podfileはこんな感じで書いてました。
target 'Runner' do use_frameworks! pod 'Firebase/Core' pod 'Firebase/Firestore' end
この問題2つの共通点
よくよくハマった2点を考えてみるとどちらもPodでした。実はFlutterをやるまでcocoapodsについては未導入だったので今回初めてインストールしたのですが、 setupコマンドを打っていないことに気が付きました。
$pod setup
本にも書いてあるので、ちゃんと本に書いてあることは読みましょう、というn度目の教訓を得ました・・・
setupしたあとにxcode起動したらエラーは消えて無事に動作確認できました。
podfileに書かれていることはまだ理解できていないので、「なぜエラーが消えたのか?」は微妙にわからないままなので要勉強です。
僕はそもそもiOSをやったことなかった
Cordovaを使ったアプリ開発の経験はあるので、XCodeでiOSプロジェクトをビルドするとかはできるのですが、 iOSネイティブをやったことなかったことで基礎的な問題の解決に少し時間がかかりました。
しかしながら、開発の効率が非常に高そうなFlutterは今後とも触れて行きたいと思います。
付録(バージョン情報など)
開発時点でのFlutter doctor内容を載せておきます。
$ flutter doctor -v [✓] Flutter (Channel beta, v1.0.0, on Mac OS X 10.13.6 17G2307, locale ja-JP) • Flutter version 1.0.0 at /Users/ticktakclock/flutter • Framework revision 5391447fae (2 weeks ago), 2018-11-29 19:41:26 -0800 • Engine revision 7375a0f414 • Dart version 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297) [✓] Android toolchain - develop for Android devices (Android SDK 28.0.3) • Android SDK at /Users/ticktakclock/Library/Android/sdk • Android NDK at /Users/ticktakclock/Library/Android/sdk/ndk-bundle • Platform android-28, build-tools 28.0.3 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06) • All Android licenses accepted. [✓] iOS toolchain - develop for iOS devices (Xcode 9.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 9.2, Build version 9C40b • ios-deploy 1.9.4 • CocoaPods version 1.5.3 [✓] Android Studio (version 3.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 31.1.1 • Dart plugin version 181.5656 • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06) [!] VS Code (version 1.29.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension not installed; install from https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [!] Connected device ! No devices available
pod setupしたときのPodfileの内容も載せておきます。
# Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def parse_KV_file(file, separator='=') file_abs_path = File.expand_path(file) if !File.exists? file_abs_path return []; end pods_ary = [] skip_line_start_symbols = ["#", "/"] File.foreach(file_abs_path) { |line| next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } plugin = line.split(pattern=separator) if plugin.length == 2 podname = plugin[0].strip() path = plugin[1].strip() podpath = File.expand_path("#{path}", file_abs_path) pods_ary.push({:name => podname, :path => podpath}); else puts "Invalid plugin specification: #{line}" end } return pods_ary end target 'Runner' do use_frameworks! # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') # Flutter Pods generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') if generated_xcode_build_settings.empty? puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." end generated_xcode_build_settings.map { |p| if p[:name] == 'FLUTTER_FRAMEWORK_DIR' symlink = File.join('.symlinks', 'flutter') File.symlink(File.dirname(p[:path]), symlink) pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) end } # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods.map { |p| symlink = File.join('.symlinks', 'plugins', p[:name]) File.symlink(p[:path], symlink) pod p[:name], :path => File.join(symlink, 'ios') } end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' end end end