くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterに入門して疑問に思ったことと、そのとき調べたこと

前々から気になってたFlutter
Flutter for Webが統合されたっぽいので、そろそろはじめたいなと(´ω`)

www.publickey1.jp

せっかくなので、
「なにを疑問に思って、なにを参照したか」
をまとめておこうと思ったので、整理してみた(´ω`)

疑問に思ったことを時系列にまとめてます。

注意

初期段階の理解度なので、正しくないこともあります。。
どう理解したかだけを書いていく予定です。

公式ドキュメントとか

このあたりをベースに、いろんな記事を調べていく感じ。

公式ドキュメント以外には本を2冊仕入れました。
2冊目は10/31に発売されたばかりなので、1冊目を読了している感じ。

Flutter モバイルアプリ開発バイブル

Flutter モバイルアプリ開発バイブル

ここからが本編。

疑問に思ったことと参照したこと。

なにができるの?Flutterってなに?

Google製のクラスプラットフォームフレームワーク
1つのソースで、iOSアプリ/Androidアプリが作れる。
Web Supportも統合されて、Webアプリもつくれそう。

言語はDart。UIもDartで書く宣言的UIフレームワーク
同じくGoogle製のFirebaseとも親和性が高い。

どこまでできるの?(いまだわからない)

ここらへんが気になる所。
既存のアプリで使ってる標準機能がベースになってる。

基本は「flutter 〇〇」でググってそれっぽい記事があるかを調べてみてる。
あと、Dart packagesで検索してみたり。

音声や動画の再生

パッケージがある

カメラの起動

パッケージがある

Push通知

FirebaseのCloud Messagingが使えるし、パッケージがある

定期実行

この記事を見るとできないことはなさそう?
公式はまだ未対応っぽい?

指定時刻の実行

定期実行と同じく、Androidはパッケージがあるけど、iOSは未対応っぽい?

定期実行系はまだ未対応っぽいけど、それ以外であれば、イケそうな感じ。
定期実行もFirebase Cloud Functionsの定期実行とFirebase Cloud Messagingを組み合わせれば、
実現できそうな気がしてるので、既存のアプリのリライトも大丈夫かなという印象。

環境構築方法は?

公式ドキュメントを見てみた。

flutter.dev

これに沿って進めればOK。

開発環境は、Android StudioVSCodeが選べるよう。

Dartってどんな言語?

そもそもDartがわからないので、どんな言語か見てみる。

このあたりを参照。以下、調べたときのメモ書き。

  • 推論できる強い型付言語。型注釈は特に不要
  • すべてのオブジェクトはObjectクラスから継承。nullもオブジェクト
  • Dartには、public/protected/privateがなく、変数名がアンダースコア(_)で始まる場合は、private
  • 型は数値(int, double), 文字列(String), 真偽(bool)に、データ構造でList/Set/Map。Enumもある
    • 文字列ではテンプレートも使える'Hello $name'
  • 変数には、finalとconstが使える。
  • 関数に、無名関数がある
  • オペレータは一般的な感じ。cascadeが特殊でkotlinのletっぽい。メソッドチェーン的に使える
  • 制御文は、if/else, for, while/do-while, break/continue, switch/case, assert
  • 例外ハンドリングはtry-catch-finally。throwで投げる
  • クラス: 継承や抽象クラス、インターフェスはある。superで親クラス呼び出し。オーバライド可
    • クラスの変数やメソッドにstaticがつかえる
    • mixinもあり、Genericsにも対応
    • プロパティのセッター/ゲッターは、setget
  • importは、import 'dart:html';import 'package:lib1/lib1.dart';。asも使える
  • async/awaitをサポート。Future/Streamを返す型のみ
  • コメントは、///* */。ドキュメントは///のみ

標準のディレクトリ構成はどんな感じ?

どこになにがあるか、どこになにを於けばいいかを知りたいので、
ディレクトリ構成を見てみた。

書籍「Android/iOSクロス開発フレームワーク Flutter入門」を参照。

android/    ... Android用のソース
ios/        ... iOS用のソース
build/      ... ビルド時の生成先
lib/        ... ※Dartのファイルを配置※
test/       ... テスト関連のファイル
.packages   ... 利用しているパッケージの情報
.metadata   ... Flutterツールが利用するファイル
pubspec.yaml ... Dartのパッケージマネージャ(Pub)が使うファイル

画像とかのassetsやリソースは、自分で名前を決めて、
pubspec.yamlに追加するっぽい?

どんな登場人物がいるの?

書籍「Android/iOSクロス開発フレームワーク Flutter入門」を読みながら、
主要なキーワードを見ていく。以下は読んだときにメモったこと。

公式の「Tutorials」や「Introduction to widgets」にも詳しく書いてある。

UIはどうやって作る?: WidgetとState

  • WidgetはUI部品
  • Stateは「状態」(=可変の変数のかたまり)
  • すべてはWigetを継承する
  • StatelessWidgetとStatefulWidgetがある
    • Stateless ... 状態を持たない静的なUI部品群
    • Stateful ... State+Widget。状態をもたせることができる
  • 起点はrunApp()の引数のWidget

  • Widgetの階層構造でDOMツリーのような木構造で画面を構成する

    • 構造と装飾は同時に記述する
    • Statefulであれば、値をStateから取得できる
    • Stateはcreateにより都度生成される

入力や値変更は?: StateやController

  • ウィジェットの値を管理する専用クラス
  • TextFieldなどの入力系で必要
    • AndroidEditText.Editorみたいなもの?
    • 入力された値はControllerから取得する: ex. _msg = controller.text;
    • TextFieldにはonChangeがあるので、それで取得もできる

ページ遷移は?: ナビゲーションはNavigatorで

  • スタック方式。pushで進んで、popで戻る
  • MaterialPageRouteでルート(次の画面)を指定

  • routesに「アドレス」と「対象のウィジェット」をまとめて書くこともできる

    • Navigator.pushNamed()で呼び出す

テストはどうやってやるの?

書籍「Android/iOSクロス開発フレームワーク Flutter入門」を読みながら。
テスト用のパッケージが公式である感じ。

ユニットテストやWigetのテスト、Integrationテストもできるらしい。

公式ドキュメント(Testing Flutter apps)にもいろいろ書いてある。

設計手法はなにがいい?

なんとなくどういうふうに書くかがわかってくると、
次はどういう構成がいいか気になる。

ネイティブアプリでも、MVP、MVVM、Fluxとかいろいろあるので、
Flutterだとどうするか調べてみた。

これも状態管理(State management)として公式に書かれてる。
参考: State management

Githubにそれぞれの手法で実装したサンプルもたくさんある。
参考: brianegan/flutter_architecture_samples: TodoMVC for Flutter

なにがいいだろと悩んでいたら、@_monoからすてきなアドバイス。。

こっちのフローチャートも良い感じ(´ω`)

とりあえず、初心者にオススメな「StatefulWiget/setStateでベタ書き」ではじめて、
フローチャート順に書き換えて理解していくのが良さそう。

パッケージ構成はどんなのがいい?

状態管理の次は、パッケージ構成をどうするのがいいかが気になる。。

これもいろいろアドバイスをもらったので、参考にしながら試していく。

情報収集おわり、さわってみる

ここまでで気になる情報はだいぶ集まったので、
ここから実際に手を動かしてみて進めてく。

StatelessWigetってどう階層化するの?

本のサンプルや雛形のソースだと、1ファイルにベタ書きされているので、
ファイル分割したくなる。。

まずは、1クラスになってるのを、複数クラスに分割したい

雛形を生成するとこんな感じのWidgetが出てくる。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    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.display1),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

とりあえず、bodyにある部分を分割してみた。
コンストラクタで値を受け取ればいけるっぽい

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: MyHomePageBody(counter: this._counter),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

class MyHomePageBody extends StatelessWidget {
  MyHomePageBody({Key key, this.counter}) : super(key: key);
  final int counter;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('You have pushed the button this many times:'),
          Text('$counter', style: Theme.of(context).textTheme.display1),
        ],
      ),
    );
  }
}

コンストラクタで関数も渡せるので、FABもこんな感じで分離できる。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: MyHomePageBody(counter: this._counter),
      floatingActionButton: MyHomePageFab(incrementCounter: this._incrementCounter),
    );
  }
}

class MyHomePageBody extends StatelessWidget {
// 略
}

class MyHomePageFab extends StatelessWidget {
  MyHomePageFab({Key key, this.incrementCounter}) : super(key: key);
  final Function incrementCounter;

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: this.incrementCounter,
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
  }
}

ファイル分割やimportはどうやるの?

クラスが分割できたので、各クラスを別ファイルに分けて、importできるようにしてみる。

作ったのは以下の2ファイル

libs/MyHomePageBody.dart

import 'package:flutter/material.dart';

class MyHomePageBody extends StatelessWidget {
  // 略
}

libs/MyHomePageFab.dart

import 'package:flutter/material.dart';

class MyHomePageFab extends StatelessWidget {
  // 略
}

ファイルを分けたので、main.dartにimportしてみる。

import 'package:flutter/material.dart';
// ※ファイルを分けたクラスをimport
import "MyHomePageBody.dart";
import "MyHomePageFab.dart";

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

// 略

class _MyHomePageState extends State<MyHomePage> {
  // 略
}

こんな感じで、package:みたいなプレフィックスを付けずに、
相対パスで指定すれば良い感じ

おわりに

これでなんとなく情報が揃った気がするので、
あとはWidgetを調べながら、UIを作っていき、
Stateの情報をコンソトラクタで渡していけばなんかつくれそう。

ただ、この方法は性能的によくなさそうなので、ある程度理解したら、
状態管理フローチャートの次のステップ「InheritedWidget」を使えるようにするのがいい感じ。

とりあえず、基本的なところは抑えられた気がするので、なんか作ってみる。

また進めていきながら、疑問点が出てきたら、まとめよう。

Flutter モバイルアプリ開発バイブル

Flutter モバイルアプリ開発バイブル

www.memory-lovers.blog

こんなのつくってます!!

積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!
積読ハウマッチは、Nuxt.js+Firebaseで開発してます!

tsundoku.site

もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ

要望・感想・アドバイスなどあれば、
公式アカウント(@MemoryLoverz)や開発者(@kira_puka)まで♪