賢くなりたいトイプードルの日記

データサイエンス系の話をメインにしていきます

Flutterに入門

Flutterをとりあえず環境構築したり触ってみた。今の理解はこんな感じ

  • Dartという言語が強くて、iOSAndroidに対応
  • ゲームにはあまり使われてないっぽい
  • IDEとしてAndroid Studioがよく使われる
  • cocoapodsというrubygemを使ったりする
  • flutterはSDK(開発フレームワーク
  • Googleのやつ
  • flutterとgo言語は連携すると強くなるらしい
  • firebaseがよく使われるっぽい

知りたいこと

参考文献

ホットリロードとは

https://flutter.dev/docs/get-started/test-drive)

Flutter offers a fast development cycle with Stateful Hot Reload, the ability to reload the code of a live running app without restarting or losing app state. Make a change to app source, tell your IDE or command-line tool that you want to hot reload, and see the change in your simulator, emulator, or device.

Flutterは、ステートフルホットリロードを備えた高速開発サイクルを提供します。これは、アプリの状態を再起動したり失ったりすることなく、実行中のアプリのコードをリロードする機能です。アプリのソースに変更を加え、IDEまたはコマンドラインツールにホットリロードすることを伝え、シミュレーター、エミュレーター、またはデバイスで変更を確認します。

要するに、一回雷マークを押せば、毎回デバッグボタンをクリックしなくてもシミュレータやエミュレータに変更が反映される機能のこと。

ネイティブのARMコードにコンパイル

https://forest.watch.impress.co.jp/docs/news/1334821.html

ARMアーキテクチャは消費電力を抑える特徴を持ち、低消費電力を目標に設計されるモバイル機器において支配的となっている。本アーキテクチャ命令セットは「(基本的に)固定長の命令」「簡素な命令セット」というRISC風の特徴を有しつつ、「条件実行、定数シフト/ローテート付きオペランド、比較的豊富なアドレッシングモード」といったCISC風の特徴を併せ持つのが特徴的だが、これは初期のARMがパソコン向けに設計された際、当時の同程度の性能のチップとしてはかなり少ないゲート数(約25,000トランジスタ)で実装されたチップの多くの部分を常に活用する設計として工夫されたもので、回路の複雑さを増さないという方向性だというように見れば、CISC風の特徴というよりむしろRISC風の特徴とも言える。このような設計が、初期の世代の実装において、(性能の割に)低消費電力、小さなコア、(RISCとしては)高いコード密度といった優れた特性に結びつき、広く普及する原動力となった。

要するに、Flutterはネイティブアプリに対してエネルギー効率が良いARMアーキテクチャを使うということ。

2D-GPU-accelerated-APIs ?

Android 3.0(API レベル 11)から、Android 2D レンダリング パイプラインはハードウェア アクセラレーションをサポートしています。つまり、View のキャンバス上で行われる描画オペレーションはすべて GPU を使用します。ハードウェア アクセラレーションを有効にするために必要なリソースが増えるため、アプリの RAM 使用量が増えます。

https://developer.android.com/guide/topics/graphics/hardware-accel?hl=ja

IntelliJプラグイン & IDEデバッグ

AppCodeもAndroid Studioも根っこはIntelliJです。

IntelliJ IDEA(インテリジェイ アイディア)とは、2001年にチェコにあるJetBrainsが開発したIDEのことです。

IDEとは、プログラミングするときに必要な機能を1つにパッケージングしたソフトウェアのことです。

IntelliJは、Javaの開発をサポートするために作られたIDEです。

Javaで最も人気なIDEは2016年までEclipseでしたが、現在はIntelliJがシェアを抜いています。

IDEがintegratedというのは、エディタ上でデバッグができるから。

IntelliJがもともとJAVAの開発をサポートすることが目的で、Android Studioの根幹はIntelliJだから、Android StudioにはJAVAが必要なんだろうと、とりあえず理解

JAVAC++ ? JavaScript ?

Flutterは主にC++Dartとの2つの言語によって記述されている。

エンジンとしてC言語とかC++を使っていて、DartJavaScriptの後継言語を目標として他の言語の良い点を参考に開発された言語だから、書き方はJavaScriptに似ている。void main()とか@overrideとかが出てくるから、ちょっとC言語とかJAVAっぽくもある。AndroidStudioを使うから環境構築にJAVAのセットアップしとかないといけない、みたいな感じととりあえず理解。

Flutterの構成

スタック

flutter.io

Flutterは大きく分けると、OSに近い方から

  • Embedder
  • Engine
  • Framework

の3つの層になっています。

アプリ開発者が触るのは基本的に最上層のDartのみです。EngineやFrameworkなどOSに近い方は、画面に何をどのように表示させるかというところなど、パフォーマンスに関係しています。

ちなみにFlutter Webについては、Dart言語をJavaScript言語に変換してブラウザ上で実行しているため、Flutter Engineは利用しておらず、全く仕組みが異なります。

Flutter Engineの役割は簡単に説明すると以下の通りです。

ちなみに、Embedder部分の役割は各種プラットフォーム上で動作させるための繋ぎ込みのAPIが定義され提供されています。この部分をプラットフォーム毎にポーティングします。

Flutter Engineは4スレッドで動作します。Flutterの公式ドキュメントの中では、Flutter Engineのレイヤではスレッドという表現を使わずに、Task Runnerという表現を使っています。実際のスレッド自体の作成や管理は下のレイヤのEmbedder (後述のプラットフォーム依存部分) で対応しており、そこから提供される枠組みの中でTask Runnerを動かしています。

そして、Engineには以下の4つのTask Runnerが存在し、Flutterアプリケーションが動作する土台の機能をshell(Linuxなどのコマンドのshellではなく、グラフィック機能を提供するシェル)として提供します。

Flutterは、各プラットフォーム上で基本的に以下の4スレッドで動作します。

スレッド (タスクランナー) 名用途
Platform Task Runnerいわゆるメインスレッドで、ユーザ操作やネイティブからのメッセージをハンドリングし、その他のTask Runnerとの受け渡しをするスレッド
UI Task RunnerDart VMが実行されるスレッド。Flutter Engineに渡すLayer Treeを生成するまでを担当する、Dartのコードが動くスレッド
GPU Task RunnerGPU処理に関わるつまり、Skiaを利用してOpenGLやVulkanなどでGPUを利用して最終的に描画する処理に関わるスレッド
IO Task Runner画像ファイルのデコードなど、I/Oアクセスを伴う時間がかかる処理を行う専用のスレッド

図示すると以下の様になります。

Flutterアプリ開発の全体像

ネイティブアプリ開発には

を使います。クロスプラットフォームフレームワーク

などがあります。

Flutterは、dartという言語で書いていきますが、dart

であり、独自のレンダリングエンジンを使っています。(C++

Flutterには多くのWidgetが用意されており、大きく分けて二つあり、StatelessWidgetとStatefulWidgetです。

アーキテクチャは四つあり、

画面ごとにモデルがあるというのは、画面とモデルがセットになっているということ。

たとえばこんな感じ

クリーンなアーキテクチャとは、プロジェクトの成長に合わせて理解しやすく、変更しやすいようにプロジェクトを編成することを指します。これには意図的な計画が必要です。大規模プロジェクトを保守性を保ちながら構築する秘訣は、ファイルまたはクラスを他のコンポーネントとは独立して変更できるコンポーネントに分割することです。

https://pusher.com/tutorials/clean-architecture-introduction/

ドメイン駆動型 + オニオンアーキテクチャなどが知られていますよね。

Providerは、完全な依存関係インジェクションソリューションです。一方、ScopedModelは、ModelクラスとnotifyListeners()を使用して、反応性という点では同じ機能を提供します。Providerは依存関係インジェクションソリューションであり、非常に強力なプロバイダタイプを備えており、例えばストリームの管理のような定型的な処理を行うことができます。

スコープモデルは、特に依存性注入のためのget_itと相まって、依然として強力です。私がやったように、古いアプリではScopedModelを使い続け、今後はプロバイダを使うということもできます。特にScopedModelに欠けていたものを知っていれば、それはとても素晴らしいことです。

https://www.reddit.com/r/FlutterDev/comments/brz0nu/scoped_model_vs_provider/

環境構築

https://www.youtube.com/watch?v=kpvVENfDCRc&ab_channel=Flutter%E5%A4%A7%E5%AD%A6

環境構築の流れとしては、

  1. Flutter SDKをダウンロード
  2. パスを通す
  3. Android Studioをダウンロード&プラグイン導入
  4. AndroidエミュレータAndroidの仮想デバイス)をダウンロード
  5. Xcodeをダウンロード
  6. iOSのシミュレータをダウンロード

そして、AndroidエミュレータiOSシミュレータにビルド、デバッグ、開発。

❯ flutter --version
Flutter 2.2.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f4abaa0735 (6 weeks ago) • 2021-07-01 12:46:11 -0700
Engine • revision 241c87ad80
Tools • Dart 2.13.4

とりあえずFlutterプロジェクトを立ち上げたらこんな感じのツリー構造になっている。

アプリ名フォルダ直下のlibフォルダにmain.dartがあり、これを見てみると、

import 'package:flutter/material.dart';

// Flutterアプリが起動すると、下記のMyAppクラスをもとにそのWidgetツリーを解釈してUIが表示される
// voidはただ処理をするだけで「何も値を渡すことができない」ことを明示している
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
 // overrideアノテーションでStatelessWidgetクラスのbuildメソッドを上書き
  @override
  Widget build(BuildContext context) {
   // MaterialAppはこのアプリ全体の根本(Scaffoldは一つのぺージの根本)
   // 根本部分でMaterialAppを返してマテリアルデザインのアプリを作る
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // ①
        primarySwatch: Colors.blue,
      ),
    // home にStatefulWidgetを継承したMyHomePageを指定。これがアプリ起動直後に表示されるページ
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
 // コンストラクタ(superで親クラスのコンストラクタを呼び出す)
  MyHomePage({Key? key, required this.title}) : super(key: key);

 // ②

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

// Stateは状態を保持・更新する機能と、
// StatelessWidgetと同様にbuildメソッドでWidgetツリーを返す機能がある
// buildメソッドを持つのがStatefulWidgetではなくStateとなっている理由は、
// StatefulWidgetを継承したWidgetを扱いやすくするため
class _MyHomePageState extends State<MyHomePage> {
  // このようにクラスのフィールドとして状態を保持する
  int _counter = 0;
 // 更新

 // _counter++; を実行して値の更新し、それをUIに伝えるためにsetStateで囲む
 // Flutterフレームワークに対する「次の画面更新タイミングで今のStateの状態を元にUI更新せよ」という命令
  void _incrementCounter() {
    setState(() {
      // ③
      _counter++;
    });
  }

 // このbuildメソッドの結果はUI全体に相当する
  @override
  Widget build(BuildContext context) {
    // ④
  // Scaffoldはこの全体の根本(MaterialAppはこのアプリ全体の根本)
    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),
      ), // ⑧
    );
  }
}

コメントアウトした①〜⑤のところには英文が書いてあったので、訳してみました。

① アプリのテーマ部分。'flutter run'コマンドでアプリを起動されて、ブルーのツールバーがあるアプリが表示される。それから、アプリをシャットダウンすることなく、下に書いてあるprimarySwatchをColors.greenに変えて”ホットリロード”すると、カウンターが0に戻ってない、つまりアプリが再起動していないことがわかる。

②このウィジェットはアプリのホームのページ。ステートフルとは、見た目に影響を与えるフィールドを含むStateオブジェクト(以下に定義)を持つことを意味する。このクラスは親(ここではAppウィジェット)から提供される値(ここではtitle)を持ち、これはStateのbuildメソッドで使用される。Widgetサブクラスのフィールドは常に "final "を前につける

③このsetStateを呼び出すと、Flutterフレームワークに、このStateに何か変更があったことを伝え、更新された値を表示に反映させるために、以下のビルドメソッドを再実行させます。setState()を呼び出さずに_counterを変更した場合、ビルドメソッドは再び呼び出されないので、何も起こらないように見えます。

④このメソッドは、例えば上記の_incrementCounterメソッドのように、setStateが呼ばれるたびに再実行される。Flutterフレームワークはビルドメソッドの再実行を高速化するように最適化されており、ウィジェットインスタンスを個別に変更するのではなく、更新が必要なものを再構築すれば良いようになっている。

⑤ここでは、App.buildメソッドで作成したMyHomePageオブジェクトから値を取得します。App.buildメソッドで作成したMyHomePageオブジェクトの値を取得し、それを使ってappbarのタイトルを設定しています。

⑥Centerは、レイアウトウィジェットです。1つの子を受け取り、親の中央に配置します。

⑦Columnは、レイアウトウィジェットでもあります。子のリストを受け取り,それを縦に並べます.デフォルトでは,子の水平方向の大きさに合わせて自身の大きさを調整し,親と同じ高さになるようにします.
デバッグペイント」を実行すると(コンソールで「p」を押すか、Android StudioのFlutterインスペクタで「Toggle Debug Paint」アクションを選択するか、Visual Studio Codeの「Toggle Debug Paint」コマンド)、各ウィジェットのワイヤフレームが表示されます。
カラムには、自身のサイズや子の配置を制御する様々なプロパティがあります。ここでは、mainAxisAlignment を使用して子を垂直方向にセンタリングしています。Column は垂直方向に配置されるので、ここでの主軸は垂直軸になります(交差軸は水平方向になります)。

⑧この末尾のコンマは、ビルドメソッドの自動フォーマットを整えるためのものです。

環境構築でのエラー

エラー1: AVD Managerの▶︎ボタン(Launch this AVD in emulater)を押したら「Unabel to locate avd」

(解決策)

「File」→「Project Structure」→出てきたウィンドウ左側の「Project」で、「Project SDK」の欄を確認。「<No SDK」となっていて、SDKが設定されていないので、プルダウンからSDKを選択し、決定します。Android API ##(番号) Platform・・・」を選択してapplyしてOKすると、エラーが表示されなくなる

エラー2: エミュレータを選択したら「emulator error running multiple emulators」

(解決策)

.android/avd/端末名.avd/直下の3つのファイルを削除(cache.img、hardware-qemu.ini.lock、multiinstance.lock)