Closed86

Flutter公式チュートリアルをやる

例のごとく何をやったらいいかを調査するところから

↑この記事は2018年の記事で、2020年にいくつか追記はされている。
記事執筆の当初と比べると、だいぶ日本語情報が増えている(特に入門は)印象なので、そっちから入ってもいいのよね

公式の表玄関から入って、そこで挫折したら、↑の本を頼る、にしたらいいかな。
Flutter公式はわかりやすそうだけど、英語オンリーではありそう。

チュートリアル終わったら、ビンゴゲームでも作ってみようかしらん。
ランダムで1〜99まで数字出して、過去の数字が見られる感じ。

Flutter入門のためのDart入門

Flutter環境構築

チュートリアル開始

という流れで

Dartの基礎をやっていきます

基本的には強い型付け言語だが、dynamicを使うことで動的っぽく型を使うことができる。

void main() {
  dynamic d = "hello";
  print(d);
  
  d = 1;
  print(d + 1);
}

強い型付け

強い型付けとは、2つの型に互換性があるかどうかを検出し、互換性がなければ、エラーを投げるか、型の強制変換を行う
という意味だ。表面的には、JavaとRubyはどちらも強く型付けされている。
一方、アセンブリ言語やCのコンパイラは、弱く型付けされている。これらのコンパイラは特定のメモリ位置にあるデータが
整数なのか、文字列なのかあるいは単なるデータなのかを必ずしも意識する必要はない。

コンパイラによる型チェックでエラーを投げるのが強い型付け、ということか。
確かにアセンブラはコンパイルして実行したらよくアライメント合わなくて死んだような記憶がある。
(IBM系アセンブラはなんか型あったような気がするが、そんなに厳密でもなかった気がする)

静的型付け

静的に型付けされた言語では、型の構造体に基づいてポリモーフィズムが行われる。
つまり、遺伝子的な青写真によってアヒルかどうかを判定するのが静的型付け。
鳴き声や歩き方によってアヒルかどうかを判定するのが動的型付けだ。

この定義はちょっとあんまりピンと来ない。

コードをコンパイルしたときに全ての型が決まっているのが静的型付けで、決まらないのが動的型付けかと思っていました(後略)

こっちの方がピンとくるかも。

たとえばSwiftは、静的型付け言語で、強い型付けだと思うけど、
Ruby/Swiftは動的型付け言語だが、強い型付けはしている。
コンパイル時に型エラーは吐くけれど、その型はundefinedの場合があるよ、という理解でいいのかな

finalconstの違いがよくわからなかったが、配列の定義で違いが出るみたい。

void main() {
  List array = const [1, 2, 3];
  print(array);
  array.add(4); // ここでダメなはず
  print(array);
}

DartPadでやってると↑でエラーメッセージが出ないけど、2回目のprint()は無視されて実行されるという結果になる……

List array = final [1, 2, 3];

この書き方はそもそもできない

void main() {
  final array = [1, 2, 3];
  print(array);
  array.add(4);
  print(array); 
}

これで出力がこれ

[1, 2, 3]
[1, 2, 3, 4]

セミコロン必須がちょっとうっとうしい

コンストラクタ、private変数がちょっと独特。
特にprivateっていうアクセス修飾子がなくて、_から始まるのが全部private扱いなのは驚き

class Person {
  String firstName;
  String lastName;

  Person(this.firstName, this.lastName);

  String _member;

  Person.origin() {
    this.firstName = '氏';
    this.lastName = '名';
  }

mixinがそもそも初見だった

mixin とはオブジェクト指向プログラミング言語において、サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラスである。言語によっては、その言語でクラスや継承と呼ぶものとは別のシステムとして mixin がある場合もある(#バリエーションの節で詳述)。

https://ja.wikipedia.org/wiki/Mixin

extends : 継承
implements : インターフェイスの実装。多重継承が可能
with : ミックスイン

カスケード記法という名前を知らなかった

特定のインスタンスに対して..で続けることでそのインスタンスに対する操作(メンバ関数呼び出し)を続けることが出来ます。

Cascadeという言葉自体は階段状に落ちる滝・水路を示すワード。
そこから派生して、連続で処理することを示すようになったとのこと。

うーんコード読んでみても結局ピンと来なかった。。。
モダンなJSの非同期処理をちゃんと理解するのが早道かも。一旦いいや

今日は時間的に環境構築したら終わりかな

flutter doctorの診断結果。
当然Androidの開発環境は入っていないが、Flutterをやるためには整えないといけないのか。

$ flutter doctor

  ╔════════════════════════════════════════════════════════════════════════════╗
  ║                 Welcome to Flutter! - https://flutter.dev                  ║
  ║                                                                            ║
  ║ The Flutter tool uses Google Analytics to anonymously report feature usage ║
  ║ statistics and basic crash reports. This data is used to help improve      ║
  ║ Flutter tools over time.                                                   ║
  ║                                                                            ║
  ║ Flutter tool analytics are not sent on the very first run. To disable      ║
  ║ reporting, type 'flutter config --no-analytics'. To display the current    ║
  ║ setting, type 'flutter config'. If you opt out of analytics, an opt-out    ║
  ║ event will be sent, and then no further information will be sent by the    ║
  ║ Flutter tool.                                                              ║
  ║                                                                            ║
  ║ By downloading the Flutter SDK, you agree to the Google Terms of Service.  ║
  ║ Note: The Google Privacy Policy describes how data is handled in this      ║
  ║ service.                                                                   ║
  ║                                                                            ║
  ║ Moreover, Flutter includes the Dart SDK, which may send usage metrics and  ║
  ║ crash reports to Google.                                                   ║
  ║                                                                            ║
  ║ Read about data we send with crash reports:                                ║
  ║ https://flutter.dev/docs/reference/crash-reporting                         ║
  ║                                                                            ║
  ║ See Google's privacy policy:                                               ║
  ║ https://policies.google.com/privacy                                        ║
  ╚════════════════════════════════════════════════════════════════════════════╝


Running "flutter pub get" in flutter_tools...                    1,954ms
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 2.2.0, on macOS 11.2.2 20D80 darwin-x64, locale ja)
[] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from:
      https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK
      components.
      (or visit https://flutter.dev/docs/get-started/install/macos#android-setup
      for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.

[] Xcode - develop for iOS and macOS
[] Chrome - develop for the web
[!] Android Studio (not installed)
[] VS Code (version 1.56.2)
[] Connected device (1 available)

! Doctor found issues in 2 categories.

そもそもAndroid向けにビルドするかもわからない(Webだけかも)ので、Android向けの環境構築は一旦スルー。

https://flutter.dev/docs/get-started/install/macos#update-your-path

パス通すところがちょっとドキドキしたが、できた。
俺の環境だとbashなので、bash_profileに下記を一行追記した。

export PATH="$PATH:[PATH_OF_FLUTTER_GIT_DIRECTORY]/bin"

[PATH_OF_FLUTTER_GIT_DIRECTORY]には、Flutterのzipファイルを解凍したものを置いてあるパスを指定した。

一度ターミナルを閉じて、開き直して、

 echo $PATH

↑で確認。

$ echo $PATH
/Users/me/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:~/.dotnet/tools:/Library/Apple/usr/bin:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Users/me/Dev/flutter/bin

出力結果はこんな感じ。
(ユーザー名をmeとしています)

Flutterだけのパスなら、下記で確認できる

$ which flutter
/Users/me/Dev/flutter/bin/flutter

起動成功!

VSCodeの設定もした

open -a simulator

これでiOS Simulatorを開き、VSCodeでF5を押すと、ホットリロードが効くようになった。
これはかなり感動。

VSCodeの勉強をする

VSCodeでファイルが上書きになるか別タブになるかの条件がわからなくてイライラしてたけど、下記が条件みたい

  • Make a change to a file.
  • Double-click a file's header.
  • Double-click a file in the Explorer.
  • Open a file that is not part of the current folder.

https://code.visualstudio.com/docs/getstarted/userinterface#_open-editors

⌘KZでZenモードに行ける。
抜けるときはEsc二回押し。
前に見たときはこんなん使わないと思っていたが、今は魅力的に見える

Center Layoutじゃない方が好みなので、設定を変えた

VSCodeなのかMacBookなのか、ダブルクリックの認識がシビアな気がする

ここからFlutterに戻ります

iOS, Webとビルドできたので、せっかくならAndroidも試したくなってきた。環境構築するかあ

Android Studioの初回起動で、「Import studio setting from: 」という選択が出て、一瞬戸惑った。
どうやら前のバージョンから設定を引き継ぎたい人向けのダイアログらしい。

https://www.javadrive.jp/android/install/index2.html

MacOSは力強くBetaと書かれていたので、思わずスルーしてしまったが、気になって戻った。

$ flutter config --enable-macos-desktop

これをやったが、

$ flutter run -d macos
Launching lib/main.dart on macOS in debug mode...
Exception: No macOS desktop project configured. See
https://flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-app to
learn about adding macOS support to a project.

と言われエラー。
プロジェクト作成時にMacOS環境をenableにしてなかったのが原因。

既存PJでも、

flutter create --platforms=windows,macos,linux .

でファイルを追加できる、と書いてあってやってみたのだが、

$ flutter create --platforms=macos
No option specified for the output directory.
Create a new Flutter project.

If run on a project that already exists, this will repair the project,
recreating any files that are missing.

とエラーが出た。
まだサンプルプロジェクトだったので、作り直した。

Flutter x VSCode、デバッグモードにすると、ここでビルド環境が選べるんだ。新鮮

Android Studio入れて、Android Studio Setup Wizardを完了させて、エミュレーター(AVD)を起動させられるようになった。
が、VSCodeから立ち上げようとするとエラー

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'
  To build this project, accept the SDK license agreements and install the missing components using the Android Studio SDK Manager.
  Alternatively, to transfer the license agreements from one workstation to another, see http://d.android.com/r/studio-ui/export-licenses.html

 Using Android SDK: /Users/xxx/Library/Android/sdk

と、思ったらコマンドでまたエラー

$ flutter doctor --android-licenses
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
	at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
	at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)
	at com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:81)
	at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73)
	at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	... 5 more

これでようやくライセンスにyesを入れることができた

来ましたねえ!

iOS/Android/MacOS/Webの4プラットフォームでの動作を確認。
感動。。。

やっていき

こんな感じでボイラーテンプレートつくってくれる。便利

VSCodeの⌥ + Shift + fでフォーマットしてくれるの、すごい便利だ

Flutterの構成要素はWidgetという単位で分割される。
(Reactで言うComponentに相当すると思われる)

Widgetには二種類ある。

StatelessWidget
StatefulWidget

状態なし/あり。
基本的には状態なしの方が実装が楽なので、チュートリアルもその順で説明される。

StatelessWidgetのHello Worldがこちら。

lib/main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

MaterialApp, Scaffold, AppBar, CenterWidgetの一種。
FlutterアプリはこのようにWidgetの組み合わせで作成する。

一方、StatefulWidgetは、こんな感じ。

lib/main.dart
class WidgetName extends StatefulWidget {
  
  _WidgetNameState createState() => _WidgetNameState();
}

class _WidgetNameState extends State<WidgetNameState> {
  
  Widget build(BuildContext context) {
    return Container();
  }
}

本体部分とState部分が分割される感じ。

コード読んでみたけど、contextやindexがどこから来ているのかよくわからなくなった

どうやらitemBuilderがそういう仕様みたい

The ListView class provides a builder property, itemBuilder, that’s a factory builder and callback function specified as an anonymous function. Two parameters are passed to the function—the BuildContext, and the row iterator, i. The iterator begins at 0 and increments each time the function is called. It increments twice for every suggested word pairing: once for the ListTile, and once for the Divider. This model allows the suggested list to continue growing as the user scrolls.

(拙訳)ListViewクラスにはビルダープロパティがあります。itemBuilderです。itemBuilderはファクトリービルダーで、匿名関数を受け取るコールバック関数です(※ここ、よくわからない)。2つのパラメーターをその関数に渡します。BuildContextと行番号のイテレーター(i)です。イテレーターは0はじまりで、関数が呼ばれるたびにインクリメントしていきます。サジェストされたワードペア1組に対して、2回インクリメントされます。まずListTileに対して、次にDividerに対して。このモデルはユーザースクロールに対して、サジェストリストを更新し続けます。

ListViewはデフォルトが無限スクロールで、itemCountで上限をつけるらしい。

デバッグしてみたら、初期のListViewは26までインデックスが来るらしい。
サンプルでは2の倍数でDividerを入れてる。
これはなんかそうするのがベストプラクティスなのかと思って色々調べてみたけど、そういう訳ではなさそう。
見栄え的にDividerは欲しいけど、別に入れ方は自由。

渡されるインデックスの仕様はよくわからないが、画面に表示されている要素+αで来ている気がする

setStateを呼ぶと、UIの更新が走るのはReactと同様。
ただ、setState(() { …… } );ってなんだ?

      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else { 
          _saved.add(pair); 
        } 
      });

Dardの文法をちゃんと理解できてなかったが、引数にクロージャー(というかコールバックと呼ぶ?)を渡すとき、
クロージャーに渡す引数がない場合、↑指定になるみたい。

https://api.flutter.dev/flutter/widgets/State/setState.html

コールバックがよくわからなくなってきた

コールバックを使った関数って、

Swiftなら
func temp(completion: ((String) -> Void)) {
    completion("temp")
}

temp { message in // (message) in でもよし
    print(message)
}

// 引数を無視することもできる
temp { _ in
    print("aaa")
}

頭の中混乱してきたので、図解してみた。

親メソッド、コールバックともに関数ではあるので、それぞれで引数・戻り値を持っている。

Dartに戻ると、

void setState(
    VoidCallback fn
)

という定義は、両方とも戻り値がvoidのメソッド。
だが、setState -> VoidCallbackへの値を渡すことは可能なので、それを渡してないですよ、という意味で、

setState(() {})

()が挟まっているみたい。
Swiftでいう_ inに相当すると考えて良いのかな。

チュートリアル完了!
あとは入門記事何個か見て終わるか

このスクラップは2021/05/21にクローズされました
ログインするとコメントできます