[Flutter] macOS 向け環境の構築

Flutter

Flutter 2.0 で macOS にも正式に対応したそうなので、環境構築してみました。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.3
  • Xcode 12.5
注意
ほぼ、初見で flutter を理解しようとしていますので、間違いがあるかもしれませんし、用語も安定してません。ご容赦ください。

macOS 向け Flutter

SwiftUI では、macOS 向けの DatePicker さえ用意されていない状況に驚いたので、2.0 になって macOS に対応した Flutter を調べてみようと 環境を構築してみました。

将来的には、macOS 以外の OS 向け環境との混在も調べたいと思いますが、まずは macOS での環境構築を行いました。

SwiftUI との比較をしながら説明していきます。(SwiftUI は、わかっている前提です)

Flutter のインストール

Flutter のインストールは簡単です。

  1. Flutter の Web からダウンロードします。
    Download
    Download
  2. ターミナルで、インストールしたい場所に移動後、unzip ~/Downloads/flutter_macos_2.X.X-stable.zip を実行します。(ファイル名はバージョン番号によって変わります。)
  3. シェルの設定ファイルに export PATH = $PATH:<インストールフォルダ>/flutter/bin を追加 (以下は、zsh の設定ファイル .zshrc に追記)
    .zshrc 設定
    .zshrc 設定
  4. 環境設定を有効にするために、shell をあらためて起動
  5. flutter doctor を実行すると、環境が正しく設定できたかを確認できます。
    FlutterDoctorResult
    FlutterDoctorResult

Chrome, VisualCode は別件で既インストールのため チェックが入っていますが、Android 向けの開発をしなければ、Android toolchain, Android Studio いずれも 不要だと思います。

flutter doctor 実行時のエラー発生の対応

flutter doctor を実行しようとすると、以下のようなワーニングが出て実行できません。

flutter doctor Warning
flutter doctor Warning

システム環境設定で、以下の設定を行うことで実行できるようになります。

システム環境設定
システム環境設定

ただし、リスクもありますので、理解の上で設定してください。

Flutter のサンプルを作成して、動かしてみる

以下のコマンドをターミナル上で実行すると、サンプルのコードが生成され、実行できます。


% mkdir sample
% cd sample
% flutter create .
% flutter run -d macOS

以下のようなウィンドウが表示されます。

Flutter example
Flutter example

右下のボタンを押すと、中央の数字が増加します。

Flutter のサンプルを読んでみる

先ほどの "flutter create ." コマンドで作成されたファイルの一部をみてみます。

macOS フォルダ

macOS で動作させるために追加で必要なファイルが含まれているようです。

ファイル名からもわかるように、SwiftUI lifecycle ではなく、AppKit/UIKit lifecycle のパターンで使われているようです。

AppDelegate.swift

いわゆる AppDelegate が普通に実装されてます。サンプルでは、applicationShouldTerminateAfterLastWindowClosed だけ実装されていました。

MainFlutterWindow.swift

FlutterViewController という ViewController をインスタンス化し NSWindow の contentViewController に設定しています。

FlutterViewController は、アプリ開発者が実装するクラスではなく、Flutter が提供する ViewController です。

あとからでてくる main.dart で定義される Widget を表示するためのコンテナになると思われます。

lib フォルダ

サンプルアプリのコードが入ってました。シンプルなアプリなので、main.dart ファイル1つです。

Flutter のアプリは、Dart という言語で記述されます。この main.dart ファイルも Dart 言語で記述されています。

main.dart を理解する

Dart 言語の理解もほとんどできていませんが、コメントが多く記述されているので、読んでみます。

main


void main() {
  runApp(MyApp());
}

エントリポイント向けの記述です。おまじない扱いで良いと思います。

class MyApp extends StatelessWidget


class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

Flutter での Widget は SwiftUI での View に近いもののようです。

SwiftUI との違いとしては、Stateless と Stateful の2種類が用意されています。

Stateful は、状態に応じて表示が変わるもので、Stateless はそのような変化がないものを指します。

MEMO
SwiftUI には、StatelessWidget 相当は、単独には存在しないと思います。(@State 等に依存しなければ実質 Stateless 相当です)

実際の表示要素としては、MaterialApp というクラスが使用され、title, theme, home という要素をそれぞれ指定することで表示要素が構築されます。

いわゆる content として MyHomePage という widget を指定しています。

class MyHomePage extends StatefulWidget


class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

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

MyHomePage は、StatefulWidget なので、属性によって、表示が更新されるビューということになります。

そのビューに影響を与える要素は、State と呼ばれるクラスで表されます。この MyHomePage というビューは、MyHomePageState という State を持ちます。(依存しているということです)

class _MyHomePageState extends State<MyHomePage>


class _MyHomePageState extends State {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            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.
    );
  }
}

ビューの表示に影響を与える情報を保持するクラスです。

実際には、ボタンが押された回数を int 型で保持しています。

以下は、SwiftUI 視点との目立った相違点です。

setState

SwiftUI では、プロパティラッパーが使用されることで、フレームワーク側で依存関係を把握し変更によるアップデートを自動で行いますが、flutter では、setState と明示的に使うことでアップデートがかかるようです。

SwiftUI で例えると objectWillChange.send() を明示的に呼んでいる感じです。

build

State に build というメソッドがあり、setState されるとこのメソッドを経由して更新されます。

SwiftUI では、View 側の body が呼ばれる形でしたが、flutter では、State 側にビュー構築のメソッドがあります。

SwiftUI で例えると、@State や @ObservedObject の内部に、ビュー構築のメソッドがある感じでしょうか。

その他フォルダ

よくチェックしてませんが、ios だったり android だったりするので、別プラットフォーム向けのコードに見えます。

SwiftUI との比較

SwiftUI と flutter の類似点

  • UI を declarative に定義する点は同じ
  • View/Widget と State の依存関係を明示して更新するところも同じ

SwiftUI と flutter の相違点

方向性としては、大きく違うところはないように見えました。

実際にコードを書く上では、状態がビューを構築するのか(flutter)、状態変化を検知してビューが再構築するのか(SwiftUI) の違いはアプリケーションのアーキテクチャに影響を与える差異に思えます。

結局、Swift は value-type を指向する言語で、Dart は、(おそらく) reference-type を指向する言語なので、その違いがさまざまなところに現れてきそうです。

SwiftUI と flutter の相違点: おまけ

別視点からの相違点では、ドキュメントの充実度が全然違います。

Apple の ドキュメントでは、"No overview available" なドキュメントが多発しているのですが、flutter のドキュメントは、充実してます。斜め読みした範囲では、"No overview available" はありません(当たり前)。

さらに、その場でサンプルが実行できたり、説明ビデオが入っていたり、充実しすぎです。

まとめ:macOS 向け Flutter

macOS 向け Flutter
  • アーカイブを展開するだけで簡単に環境構築可能
  • 方向性は、SwiftUI と似ている
  • 責務分担が、SwiftUI と少し異なるので、先入観なしにドキュメントを読んだ方が良さそう
  • ドキュメントが感動するほど充実している
  • Dart も理解が必須

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です