Open17

Flutter紀行

harapekoharapeko

,でフォーマット制御可能

個人的には,を全部付与していきたい。
左端揃っている方が上下見やすくなることが多い為。

return Scaffold(appBar: AppBar());
return Scaffold(
  appBar: AppBar(),
);
harapekoharapeko

セットアップ

インストール

  • $ brew install flutter
    以後、都度$ flutter doctorを確認しながら進めると良い

iOS

XCode

  • $ xcode-select --install
    // cocoapodsはiOSネイティブコードを触るflutterプラグインを使わない場合は不要かも?(公式より)
    // でも今回flutter doctorに言及された気がするので追加した
    // 公式はgemでインストールしている
  • $ brew install cocoapods
  • $ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
  • $ sudo xcodebuild -runFirstLaunch
    XCodeを起動してWelcome画面まで進める(初起動は許可を求められるかも)

Android

  • $ brew install --cask android-studio (JetBrainsの人はJetBrains toolboxでOK。私です)
  • Android Studioで起動してWelcome画面まで進める。
  • Customize -> All Settings -> Android SDK -> SDK Toolsで、
    Android SDK Command-line Tools(latest)をチェックする。
  • $ flutter doctor --android-licenses

確認

$ flutter doctorがすべてチェックされていることを確認する

エディタ

  • Android Studioで Plugins -> flutter、Dart インストール
  • AppCodeでDartプラグインをインストール

新規プロジェクト作成

  • $ flutter create my_app
  • $ cd my_app
  • $ flutter run
    これでブラウザで立ち上がる
    シミュレータも立ち上げたい場合は起動してから実行する

確認

$ flutter doctor -v
[] Connected device (3 available)
    • sdk gphone64 arm64 (mobile) • emulator-5554                        • android-arm64  • Android 12 (API 31)
      (emulator)
    • iPhone 13 (mobile)          • ECD4C6D8-7BD4-4FC8-BA1F-78A769C823AD • ios            •
      com.apple.CoreSimulator.SimRuntime.iOS-15-0 (simulator)
    • Chrome (web)                • chrome                               • web-javascript • Google Chrome 95.0.4638.69

備考

  • $ flutter runの起動デバイスは一つだけ
  • $ flutter run -d allで全デバイス
harapekoharapeko

コマンド

Global Options

command desc
flutter --version SDKバージョン確認

SDK

command desc
flutter channel 現在利用しているSDKのチャンネル確認
flutter config ~/.flutter_settingsを更新する。直接編集可能
flutter doctor -v Flutter環境診断
flutter downgrade 現チャンネルの旧バージョンにダウングレード
flutter upgrade 現チャンネルの新バージョンにアップグレード

Project

command desc
flutter analyze プロジェクトのDartコードを解析
flutter create プロジェクト新規作成。引数覚えるのが面倒なので、IDEから作成でOK
flutter clean ビルドファイルを削除する

Package

command desc
flutter pub add xxx パッケージをpubspec.ymlのdependenciesに追加
flutter pub add xxx --dev パッケージをpubspec.ymlのdependenciesに追加
flutter pub get パッケージの取得、更新。pubspec.ymlを更新した時に使う
flutter pub deps 依存関係ツリー表示

run, build

command desc
flutter run devicesのターゲット(Android/iOS優先)にpub get、ビルド、インストールを実行
flutter run -d [頭文字] 対象デバイスで実行
flutter run --release リリースモードで実行
flutter run --debug デバッグモードで実行
flutter run --verbose 詳細実行
flutter build xxx xxxの部分はapk, iosだけ覚えておけばOK
--analyze-sizeでビルドサイズ確認可能

test

command desc
flutter drive --target=xxx.dart インテグレーションテスト
flutter test ユニットテスト実行

Tools & Devices

command desc
flutter devices 接続されたデバイス一覧。エミュレータは起動後に表示される
flutter install ビルドしたパッケージをデバイスにインストール
flutter install -d [頭文字] 対象デバイスにインストール
flutter screenshot 接続されたデバイスをスクリーンショット。--outで出力先を指定可能

チャンネル

チャンネル名 desc
master 最新版。不安定な可能性あり
dev α版相当。masterでテストパスしたビルド※削除予定
beta beta版相当。月初めの月曜日にmasterから切り出される
stable 正式リリース安全版。基本これ。四半期に一度安定したbetaから切り出される
harapekoharapeko

デバッグ時のみ実行するコードを書く

import 'package:flutter/foundation.dart';

if (kDebugMode) {
  print('debug code');
}

経緯

次のように記述すると、printに警告波線が表示され、IDEのヒントで「Make conditional on 'kDebugMode'」と表示された
printメソッドを使って、これが出たのは今回始めてな気がするが、確かにケアしたほうが良い。

onTap: () {
  print('押されたよ');
},

kDebugModeを使う根拠(assertではなく)

公式にassertを使ったコードがあり、依存関係もないしassert使おう派もいるらしい。
が、どうやらkDebugModeが後から出来たようなので、単純に古いassertコードが残っているだけではという意見あり。
個人的には、適切な役割であるkDebugModeを使っていく方が気持ちいいように思う。

https://mobile.twitter.com/mkobuolys/status/1371558472131751941

Did you know that you can execute some code that will only be compiled and executed on debug mode, with an assert?

I am using the if(kDebugMode) {...} for the same thing, later the tree shaking does it magic to remove this code in release build. Do you see any advantage of using assert?

Probably because those asserts pre-date the addition of kDebugMode
kDebugMode is significantly more readable and flexible

harapekoharapeko

ページ遷移

決り文句だと思ってOK

onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const OtherPage(),
    ),
  );
},
harapekoharapeko

別ページに値を渡す/受け取る

渡す

onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const OtherPage('Foo'),
    ),
  );
},

受け取る

class TalkRoom extends StatefulWidget {
+  final String name;
-  const TalkRoom({Key? key}) : super(key: key);
+ const TalkRoom({Key? key, required this.name}) : super(key: key);

  @override
  _TalkRoomState createState() => _TalkRoomState();
}
harapekoharapeko

標準ライブラリ

ライブラリ名 desc
dart:core Dartの基本型、日付など基本的なAPIを提供する。このライブラリはimportせずとも暗黙的に利用可能
dart:math 指数関数、対数関数、n、乱数を扱うRandomクラスなど数学に関連するAPIを提供する
dart:io ファイル、通信などI/Oに関連するAPIを提供する
dart:async 非同期処理に対応するためのライブラリ
harapekoharapeko

プロジェクトの構成要素

ライブラリ名 desc
.dart_tool pub等のツールで使用されるフォルダ
.idea IntelliJ IDEA系の設定情報が格納されるフォルダ
android Androidアプリ生成に必要なファイルが格納されるフォルダ
ios iOS 〃
lib dartファイルを格納するフォルダ
test ユニットテストを格納するフォルダ
.gitignore Git管理対象外を設定するファイル
.metadata Flutterツールが機能の評価、アップグレード等で使用するファイル
.packages 使用パッケージ情報が定義されているファイル
アプリ名.iml モジュール定義ファイル
pubspeck.lock 依存パッケージのバージョンをロックする設定ファイル
pubspeck.yml 依存パッケージのバージョン情報などの設定ファイル
README.md プロジェクト説明
harapekoharapeko

Containerの最大サイズ調整

Container(
  constraints: BoxConstraints(
      maxWidth: MediaQuery.of(context).size.width * 0.6),
  ),
  //
)
harapekoharapeko

ListViewスクロール逆転

ListView.builder(
  reverse: true,
  //
)

要素が少ない時は上部配置したいとき

高さをコンテンツ由来にすればよい

ListView.builder(
+ shrinkWrap: true,
  reverse: true,
  //
)

さらにスクロールできるのはコンテンツが画面を超えた時だけでいいので
(コンテンツ由来の高さの中でスクロールしたときに、その高さの中でコンテンツが動いて、見切れたりするので、スクロールできない方が美しい)

ListView.builder(
+ physics: const RangeMaintainingScrollPhysics(),
  shrinkWrap: true,
  reverse: true,
  //
)
harapekoharapeko

Firebaseプロジェクト作成

Firebase Consoleでプロジェクトを作成
いつもどおりなので、細かいところは割愛

Android追加

  • 取得
    • AndroidManifest.xmlの2行目の値をコピー
  • アプリ追加からAndroidを選択
    指示に従って作業するだけでよい
    Authを使うならこの段階でSHA-1を登録しておいて良さそう

iOS追加

  • Bundle Identifier取得
    • open ios/Runner.xcworkspace
    • 左サイドバーRunner -> TARGETSのRunner -> Generalタブ -> Bundle Identifierをコピー
  • アプリ追加からiOSを選択
    • GoogleService-info.plistをダウンロード
    • RunnerのRunnerにD&Dで設置する
      • この時、Copy items if neededにチェックを入れる
        • これがないとコピーされているように見えるが、実際は参照しているだけになる
    • Firebase SDK、初期化コードの追加(今回は必要なさそうなのでスキップ。Authとか使うなら必要になる?TODO: 今後確認)
harapekoharapeko

Firestore作成(お試し)

  • テストモードにて作成(今回は勉強用なので)
  • asia-northeast1(東京)を選択(2は関西)
harapekoharapeko

FlutterとFirebase(Firestore)連携

  • $ flutter pub add cloud_firestore
  • $ flutter pub add firebase_core
  • $ flutter pub get
  • $ npm install -g firebase-tools (The CLI depends on the underlying Firebase CLI.とあったので)
  • $ dart pub global activate flutterfire_cli
  • $ flutterfire configure
    • プロジェクト選択、ターゲット選択、iOSのbundle IDを入力(コピペ使えなかった)
    • lib/firebase_options.dart が作成される
  • lib/main.dartにFlutterFire開始の為の初期化コードを追加
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}
harapekoharapeko

Preparing the list of your Firebase projectsの解決

firebase-toolsを再インストール(アップグレード)した関係か、時間的に認証トークンが無効になったせいか、flutterfire configureをしたときにこんなエラーが出た
試しに、firebase projects:listをしても同様のエラーが出た
(flutterfire configueは、firebase projects:list --jsonでコケているようだったので試した)
(firebase loginをすると、ログイン済みにはなっていたが)

$ firebase projects:list
✖ Preparing the list of your Firebase projects

Error: Failed to list Firebase projects. See firebase-debug.log for more info.

https://stackoverflow.com/questions/57941289/how-do-i-solve-error-failed-to-list-firebase-projects-see-firebase-debug-log

これはfirebase-toolsをアップグレードすると、認証トークンが無効になる可能性がある為のようである。

$ firebase login --reauth

ログイン、ログアウトでも可能

$ firebase logout
$ firebase login
harapekoharapeko

iOSビルド

Error running pod install

デバッグモードで起動を試すとこのようなメッセージが出た

[!] CocoaPods could not find compatible versions for pod "cloud_firestore":

[!] Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.

CocoaPodsのiosターゲットバージョンを上げると解決する

デフォルト値である10.6にすれば良いが、試しに14系を指定しておく
https://guides.cocoapods.org/syntax/podfile.html#platform

ios/Podfile
# Uncomment this line to define a globalf platform for your project
-  // platform :ios, '9.0'
+  platform :ios, '14.0'

再度デバッグモードで起動を試すと成功した

harapekoharapeko

Androidビルド

minSdkVersionの設定

デバッグモードで起動を試すと、次のエラーが表示された

Launching lib/main.dart on sdk gphone arm64 in debug mode...
Running Gradle task 'assembleDebug'...
Warning: Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Warning: Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
/Users/harapeko/hobby/flutter_udemy_chat/android/app/src/debug/AndroidManifest.xml Error:
	uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:cloud_firestore] /Users/harapeko/hobby/flutter_udemy_chat/build/cloud_firestore/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 16
	Suggestion: use a compatible library with a minSdk of at most 16,
		or increase this project's minSdk version to at least 19,
		or use tools:overrideLibrary="io.flutter.plugins.firebase.firestore" to force usage (may lead to runtime failures)

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:cloud_firestore] /Users/harapeko/hobby/flutter_udemy_chat/build/cloud_firestore/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 16
  	Suggestion: use a compatible library with a minSdk of at most 16,
  		or increase this project's minSdk version to at least 19,
  		or use tools:overrideLibrary="io.flutter.plugins.firebase.firestore" to force usage (may lead to runtime failures)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s

The plugin cloud_firestore requires a higher Android SDK version.
Fix this issue by adding the following to the file /Users/harapeko/hobby/flutter_udemy_chat/android/app/build.gradle:
android {
  defaultConfig {
    minSdkVersion 19
  }
}

Note that your app won't be available to users running Android SDKs below 19.
Alternatively, try to find a version of this plugin that supports these lower versions of the Android SDK.
Exception: Gradle task assembleDebug failed with exit code 1
android/app/build.gradle
defaultConfig {
    // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
    applicationId "dev.harapeko.flutter_udemy_chat"
-   minSdkVersion flutter.minSdkVersion
+   minSdkVersion math.Max(flutter.minSdkVersion, 30)
    targetSdkVersion flutter.targetSdkVersion
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

ext.kotlin_versionを最新バージョンにする

続けて次のようなエラーが出た

BUILD FAILED in 24s
[!] Your project requires a newer version of the Kotlin Gradle plugin.
    Find the latest version on https://kotlinlang.org/docs/gradle.html#plugin-and-versions, then update /Users/harapeko/hobby/flutter_udemy_chat/android/build.gradle:
    ext.kotlin_version = '<latest-version>'
Exception: Gradle task assembleDebug failed with exit code 1

https://kotlinlang.org/docs/gradle.html#plugin-and-versionsを見て最新バージョンを指定してとあるのでその通りにする

android/build.gradle
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.6.10'
    repositories {
        google()
        mavenCentral()
    }

備考

minSdkVersionを対象デバイスのバージョンよりも高くしたときのエラー

Error: ADB exited with exit code 1
Performing Streamed Install

adb: failed to install /Users/harapeko/hobby/flutter_udemy_chat/build/app/outputs/flutter-apk/app.apk: Failure [INSTALL_FAILED_OLDER_SDK: Failed parse during installPackageLI: /data/app/vmdl141932564.tmp/base.apk (at Binary XML file line #7): Requires newer sdk version #31 (current version is #30)]
Error launching application on sdk gphone arm64.

また、Error: ADB exited with exit code 1はエミュレータのデータが一杯になったときにも表示されることがある(以前、遭遇した)
このときは、AVD MangerからWipe Dataを選択して解決した