FlutterでFirebaseのStorageを使う〜前編〜

先日Flutterアプリにカメラボタンをつけましたが、写真を撮ったその後の処理がなかったので、今日は撮った写真をFirebaseにつないでアップロードしてみます。

ちなみにFirebaseとは、Googleが開発・提供しているmobile Backend as as Service(mBaaS)のこと。
通常Webアプリケーションを作る際にはサーバを立てたりAWSのようなサービスを利用してバックエンドの処理を行いますが、それをサーバレスで実現する仕組みのこと(だと思う)。
認証、画像のストレージ、DB、プッシュ通知やSMSメッセージの送信など、様々なサービスが提供されています。

今回はこの画像ストレージに画像を送信します。

Firebaseにログイン、プロジェクトの作成

Firebaseにログインします。持っていない場合はgoogleアカウントを使って作成します。
「コンソール」をクリックすると、「Firebaseへようこそ」メッセージが表示されます。
「+ プロジェクトを追加」カードをクリック。
プロジェクト名を求められるので適当に入力、プロジェクトIDと地域を必要に応じて変更します。

アプリにFirebaseを追加

今作成したプロジェクトを選択します。
iOSAndroid、Webが選択できる。ひとまずAndroidを選択します。
パッケージ名が求められるので、android/app/build.gradleにあるapplicationIdを入力して「アプリを登録」ボタンを押下。
google-services.jsonをダウンロードボタンが表示されるのでクリックしてダウンロード。android/appディレクトリに配置します。 android/build.gradleandroid/app/build.gradleにそれぞれ表示されているコードをコピーします。

google-services.jsonにはAPIキーが記載されているので、そのままgit pushしないよう注意。
やってしまいGitGurdianからワーニングがきたうっかりさんはわたしです。

Storageの設定変更

左ペインの「Storage」をクリック、「ルール」タブをクリックします。
今回認証は行わないので、誰でもread&writeできるようにルールを変更します。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}

というところで、続きはまた明日やっていきます。

Dart言語の3つのコンストラクタ

少しずつFlutterに慣れてきたところで、Dart言語も少しお勉強します。
今日はコンストラクタについて。

Dartには三種類のコンストラクタがあるそうです。

  1. 生成的コンストラク
  2. ファクトリ
  3. 定数コンストラク

DartPadというブラウザ上で実行できるREPLがあるので、入力しながら読むと理解がはかどりました。
なお、今日の参考ページはこちら -> Dartのコンストラクタについて - DevelopersIO

クラスの構成

整理すると難しくないのですが、慣れるまで違和感があったので一度整理します。

class Apple {
  static final String nickname = 'Ann'; // staticをつけるとクラス変数になる
  String color;               // staticをつけないとインスタンス変数。クラス変数と同名は指定できない
  Person() {                  // class名()でコンストラクタを記述
    this.color = 'Red';       // this.変数名(メソッド名も同様)でメンバにアクセス
  }
}

変数の宣言にはvar, const, finalも使えるし、String, intといった型も指定できます。
ただどちらかは指定しないとコンパイルエラーになるみたい。ふむ。

生成的コンストラクタ(Generative Constructors)

いわゆるふつうのコンストラクタ。

class Person {
  String name;
  Person() {
    this.name = 'Bob';
  }
}

void main() {
  Person foo = new Person();
  print(foo.name); // => 'Bob'
}

もちろん引数も取れます。

class Person {
  String name;
  Person(String name) {
    this.name = name;
  }
}

void main() {
  Person foo = new Person('Alice');
  print(foo.name); // => 'Alice'
}

Automatic field initialization

冗長な感じがするので、代入だけなら簡略して記述できます。

class Person {
  String name;
  Person(this.name);
}

void main() {
  Person foo = new Person('Tom');
  print(foo.name); // => 'Tom'
}

複数の引数でもOK。

class Person {
  String name;
  int age;
  
  Person(this.name, this.age);
}

void main() {
  Person foo = new Person('Tommy', 20);
  print(foo.name); // => 'Tommy'
  print(foo.age);  // => 20
}

Named Constructors

コンストラクタのオーバーロードはできないそうです。
代わりに、コンストラクタに任意の名前を付けて通常のコンストラクタから呼び出す方法を取るみたい。

class Person {
  String name;
  String full_name;
  
  Person(){
    this.name = 'Tommy';
  }
  Person.full_name(){
    this.full_name = '${this.name} Smith';
  }
}

void main() {
  Person foo = new Person();
  print(foo.name);      // => 'Tommy'
  print(foo.full_name); // => null
  
  Person bar = new Person.full_name();
  bar.name = 'Jacob';
  print(bar.name);      // => 'Jacob'
  print(bar.full_name); // => 'null Smith'
}

うん…?コンパイルは通るけど、こういう使い方じゃない気がする🤔

Redirecting Constructors

オーバーライドしたい時はこれで書くのがよさそう。

class Person {
  String name;
  String full_name;

  Person() : this.name();
  Person.name() {
    this.name = 'William';
    this.full_name = '${this.name} Smith';
  }
}

void main(){
  Person foo = new Person();
  print(foo.name); // => 'William'
  print(foo.full_name); // => 'William Smith'
}

参考ページにはリダイレクト元(左辺)のコンストラクタのボディで処理もできる、とあるのだけど、実行すると「Redirecting constructors can't have a body.」と言われる。
仕様がかわったのかな…?

Initializer Lists

Named Constructorsで、コロンに続けてフィールドの初期化処理を記述できる。
複数フィールドの初期化時はカンマ区切りでリストアップできる。

class Person {
  String name;
  String full_name;
  
  Person() : this.full_name();
  Person.full_name() : this.name = 'Ethan',
                       this.full_name = 'Ethan Smith';
  // field initializerの中からthisにアクセスはできない
  // これはコンパイルエラー
  // Person.full_name() : this.name = 'Ethan',
  //                      this.full_name = '${this.name} Smith';
}

void main() {
  Person foo = new Person();
  print(foo.name);      // => 'Ethan'
  print(foo.full_name); // => 'Ethan Smith'
}

親のコンストラクタを呼び出す時はリダイレクトコンストラクタを使う。

class Person {
  String name;
  String address;

  Person();
  Person.name_address(name, address){
    this.name = name;
    this.address = address;
  }
}
 
class Customer extends Person {
  Customer() : super.name_address('Emily', 'Tokyo');
// コンストラクタのボディで親クラスのコンストラクタは呼び出せない
//   Customer(String name, String address){
//     super.name_address('Emily', 'Tokyo');
//   }
}

main() {
  Customer foo = new Customer();
  print(foo.name);    // 'Emily'
  print(foo.address); // 'Tokyo'
}

ファクトリ(factory)

factoryコンストラクタ。
初めて聞いたのですが、インスタンスを生成しないコンストラクタなので、自分でインスタンスを生成します。
singletonパターンの実装なんかに使うみたいです(あまりよくわかってない)。

class Person {
  String name;
  static var _instance; // 返すインスタンスはクラス変数にする
  
  factory Person(String name){
    if (_instance == null) {
      _instance = new Person._internal(name);
    }
    return _instance;
  }

  Person._internal(this.name);
}

void main(){
  Person foo = new Person('Alan');
  Person bar = new Person('Elen');
  print(foo.name);   // => 'Alan'
  print(bar.name);   // => 'Alan'
  print(foo == bar); // => true、同じインスタンスを返している
}

定数コンストラクタ(Constant Constructors)

コンパイル時に定数オブジェクトをインスタンス化したい場合に使うそう。
グローバルスコープに定数オブジェクトを定義したい時、みたいな感じ…?🤔
定数オブジェクトのフィールドはfinal、コンストラクタはconstで定義、インスタンスの作成もconstで行います。

ちなみにconstfinalはどちらも定数を宣言するキーワードっぽいのだけど、以下のような違いがあるそうです。

class Person {
  final name;
  const Person(this.name);
}

final foo = const Person('Colin');

void main(){
  final Person bar = new Person('Mike');
  print(foo.name); // => Colin
  print(bar.name); // => Mike
}

以上、コンストラクタのまとめでした!

Flutterアプリにimage_pickerを使ってカメラ呼び出しボタンをつける

心はRubyistなのですが、毎日Flutterを触っているので今日も今日とてFlutterネタ。
Flutterといえば今日小田急線のロゴを見かけて、「Flutterのアイコンと似てる!!」とテンションがあがったのですが、帰って確認してみたら共通点は三角形ということと水色ということだけでした。
そんな大雑把な記憶を留める為に今日も小ネタを記してゆきます!

Flutterアプリにカメラ呼び出しボタンをつける

image_pickerというパッケージを使います。
手順としてはこんな感じ。

  1. image_pickerパッケージの読み込み
  2. image読み込みメソッド記述
  3. カメラ呼び出しアイコンを記述

順番にやっていきます。

image_pickerパッケージの読み込み

pubspec.yamlに追記します。

dependencies:
  flutter:
    sdk: flutter
  image_picker: # => Added

バージョン指定はなくても構いません。
書いたらflutter packages getでインストール。

なお、インストールしてるのにうまく反映されないな?てときはキャッシュが悪さしてることが往々にしてあるようなので、flutter clean & flutter runを行うと解決する場合があるようです。

image読み込みメソッドを記述

exampleがすごく充実してるのでその通りやるだけ。
main.dartimportを記述します。

import 'package:image_picker/image_picker.dart';
import 'dart:io';

RandomWordsStateクラスにgetImageメソッドを追記。
(前回の続きに追記しているのでRandomWordsクラスですが、example通りだと_MyHomePageStateクラス)

class RandomWordsState extends State<RandomWords> {
  // (略)
  File _image;

  Future getImage() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);

    setState(() {
      _image = image;
    });
  }
  // (略)
}

カメラ呼び出しアイコンを記述

getImageを呼び出す為のアイコンを追記します。

class RandomWordsState extends State<RandomWords> {
  // (略)
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body:_buildSuggestions(),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add_a_photo),
        onPressed: getImage,
      ),
    );
  }
  // (略)
}

これで、ボタンを押すとカメラアプリが呼び出されるようになりました!

Flutter tutorialで簡単なアプリを作る

Flutterの環境構築が終わったので、早速Tutorialをやっていきます。
記事はVSCodeのていで書いているので、他のアプリの方は適宜読み替えをどうぞ😊

お題は'Startup Name Generator'。要件としてはこんな感じ。

  • スタートアップ企業の名前が並ぶ
  • ユーザは名前を選択/選択解除して一番いい名前を選べる
  • 非同期的に名前を生成
  • スクロールされるとさらに名前を生成&スクロールは限界なくできる

このパートで学べること

  • iOSAndroidで自然に見えるFlutter appの書き方
  • Flutter appの基本構造
  • パッケージを探して見つけて拡張する方法
  • ホットリロードの使い方
  • ステートを持ったウィジェットの作り方
  • 終わりなく非同期的に読み込まれるリストの作り方

Step1. Flutter appをつくる

これは環境構築の中でやったところ。

$ flutter create hello_flutter

lib/main.dartを主に編集していくよ、とのこと。ふむふむ。

  1. lib/main.dartをすべて消して下記のコードに書き換え
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  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'),
        ),
      ),
    );
  }
}

(Dartシンタックスハイライトきくんですね。はてなさんすごい…!)

Tipsとして、インデントがくずれた場合は「Alt+Shift+F」で整形できるよ、と紹介されています。至れり尽くせり…!

2. エミュレータを起動してホットリロードを確認 「F5」でエミュレータを起動、「Shift+Cmd+F5」でホットリロードが行えます。
(アプリ実行中は保存でもホットリロードできるとのことなのだけど、うまくいかない…これも後回し案件です📝)

さて、「Hello, World」の文字が表示されました。わーい!😊

Step2. 外部パッケージを使う

名前を生成する為にenglish_wordsという外部ライブラリを導入します!

外部ライブラリはPub siteからDLできるよ、とのこと。RubyでいうRubyGemsみたいな感じでしょうか。

  1. pubspec.yamlenglish_wordsを加える
    これがGemfile的なものっぽい。
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0

2. flutter packages getの実行 とあるのですが、保存したら勝手に走った…!!!
あまりに早すぎて(2.3s)本当にちゃんとgetできたの?と心配になりますが、pubspec.lock(Gemfile.lock的なものっぽい)を見るとenglish_wordsの項目が増えているので追加できているようです!

3. packageのimport
lib/main.dartenglish_wordsをimportします。

import 'package:english_words/english_words.dart'

4. English Wordsパッケージを使って文字列を表示する Hello, Worldの代わりに今importしたパッケージを使ってランダムに生成した文字列を表示します。

Widget build(BuildContext context) {
  final wordPair = WordPair.random();
  return MaterialApp(
    title: 'Welcome to Flutter',
    home: Scaffold(
      appBar: AppBar(
        title: Text('Welcome to Flutter'),
      ),
      body: Center(
        child: Text(wordPair.asPascalCase),
      ),
    ),
  );
}

ホットリロードすると、(保存しなくても画面更新するだけで)ランダムな文字列が表示されるようになりました!

Step3. Statefulなウィジェットを加える

statelessウィジェットは不変で、プロパティを変更できない。すべての値がfinalとなります。
statefulウィジェットウィジェットのライフタイム中変化する可能性のあるstateを保持します。

statefulなウィジェットの実装には最低でも2つのclassが必要になります:

  1. インスタンスを作るStatefulWidgetクラス
  2. Stateクラス

StatefuleWidgetクラスはimmutable(不変)ですが、Stateクラスはウィジェットのライフタイムの間中持続します(?)

RandomWordsというstatefulなウィジェットを加えると、RandomWordsStateというStateクラスが作られます。
すると今つくっているMyAppのstatelessウィジェットの子としてRandomWordsを使えるようになるよ、とのこと。
読むだけじゃよくわからないのでやってみます。

  1. lib/main.dartの最下部に下記を追記
class RandomWordsState extends State<RandomWords> {
  // TODO Add build() method
}

Stateクラスがビルドメソッドを持たなくてIDEに怒られるけど、いったん放置。

2. statefulなRandomWordsウィジェットlib/main.dartに追記
RandomWordsウィジェットはこのRandomWordsのStateクラスを作る以外はほとんど何もしないとのこと。ふむふむ。immutableだからですかね。

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

3. 1で放置していたStateクラスにビルドメソッドを追加

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();
    return Text(wordPair.asPascalCase);
  }
}

4. MyAppクラスからStep2で追加したコードを削除してRandomWords()を呼ぶ

Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Welcome to Flutter',
    home: Scaffold(
      appBar: AppBar(
        title: Text('Welcome to Flutter'),
      ),
      body: Center(
        child: RandomWords(),
      ),
    ),
  );
}

5. アプリをリロード(ホットリロード)するとStep2と同じ挙動をします!

Step4. ずーっとスクロールできるListViewをつくる

最終段階!
RandomWordsStateクラスを拡張して、ユーザがスクロールすると永遠にリストが表示されるListViewウィジェットを作ります。
ListViewのビルダーファクトリーconstructorを使うとListViewを非同期的に作成できるそうです。

  1. _suggestionsリスト、_biggerFont変数をRandomWordsStateクラスに追加
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18.0);
  //
}

Javaと違ってDart言語はpublicやprivateといった予約語を持たなくて、_をつけるとprivateになるそうです。A Tour of the Dart Language - Dart

2. _buildSuggestions()functionをRandomWordsStateクラスに追加

Widget _buildSuggestions() {
  return ListView.builder(
    padding: const EdgeInsets.all(16.0),
    itemBuilder: /*1*/ (context, i) {
      if (i.isOdd) return Divider(); /*2*/

      final index = i ~/2; /*3*/
      if (index >= _suggestions.length) {
        _suggestions.addAll(generateWordPairs().take(10)); /*4*/
      }
      return _buildRow(_suggestions[index]);
    }
  );
}

急にわけわからなくなった!!😨
ひとつひとつ見ていきましょう🤔

  • /*1*/
    itemBuilderコールバックは単語のペアを呼び出してListTile行に配置します。偶数行にはListTile行を、奇数列にはセパレータを配置します。

  • /*2*/
    /*1*/で触れたセパレータがこれですね。

  • /*3*/
    i ~/ 2Rubyでいうところのi/2 ((i/2).to_i)と同。
    単語のペア数からセパレータ数を引いた数を計算するのに使います。

  • /*4*/
    単語のペアが終端に達したら、さらに10個作ってsuggestions listに加えます。

3. _buildRowfunctionをRandomWordsStateに追加
これはwordPairとstyleを持ったListTileを作るだけ。

Widget _buildRow(WordPair wordPair) {
  return ListTile(
    title: Text(
      wordPair.asPascalCase,
      style: _biggerFont,
    ),
  );
}

4. _buildSuggestions()を呼ぶようbuild()を修正
RandomWordsStateクラスで直接english_wordsライブラリを呼ぶのではなく、build()内で_buildSuggestions()を呼ぶようにします。
ふむふむ。ちょっとわかってきました。

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Startup Name Generator'),
    ),
    body:_buildSuggestions(),
  );
}

5. MyAppクラスのtitle表示とhome(bodyみたいな感じ?)を修正してRandomWords()を呼び出すよう修正

Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Startup Name Generator',
    home: RandomWords(),
  );
}

6. ホットリロードすると、永遠にスクロールできるStartup Name Generatorの完成です!!!

セッションCookieのHTTP Only属性とSecure属性の違い

先日セッションにまつわる言葉についてまとめましたが、ふと表題の件も理解が曖昧だなと気になったので調べてみました。

HTTP Only属性とは

cookie のスコープ(参照・操作の権限)を HTTP リクエストに制限するもの
CookieのSecure属性/HttpOnly属性の指摘と修正方法と脆弱性の解説

この「HTTPリクエスト」という言葉が引っかかって「Secure属性と何が違うの?🤔」となっていたのですが、ここでいうHTTPリクエストスクリプトによるリクエスト(XMLHttpRequest)に対する言葉。http or httpsは関係ありません。

つまり、HTTP Only属性はスクリプトからCookieへのアクセスを禁止してくれる属性。
もっと具体的にはXSS脆弱性があった際もCookieの盗み出しを予防してくれるもの(ただし、リスクの低減には役立つものの、完全に防衛できるわけではないそう)。

Secure属性とは

指定されたCookiehttpsの通信の時のみCookieを送信する
CookieのSecure属性/HttpOnly属性の指摘と修正方法と脆弱性の解説

プロトコルhttpの時はCookieを送信しないよ、という属性。

なお、この説明文中によく見かける「通信経路上にいる第三者」について、先輩が例を教えてくれました。

  1. ファーストフードやカフェのようなフリーWifiの提供されている場所で、似た名前(SSID)の罠Wifiを提供
  2. 利用者がそれと気づかず罠Wifiを利用
  3. Wifiの提供者は通信内容を盗聴し放題!👿

この時http通信だと盗まれてそのまま情報が見られるので、暗号化通信であるhttpsを利用しましょう、http通信の場合はCookieを送信しないSecure属性を利用しましょう、ということだそうです😊

実装例は?

Railsでの実装方法

  • HTTP Only属性
    デフォルトで設定されています。
    もし外したいような事象が出てきた場合は下記で行えますが、かなりレアなケースではないでしょうか。
# config/initializers/session_store.rb
# ダメ、ゼッタイ
MyApp::Application.config.session_store :active_record_store, key: "_myapp_session", httponly: false
  • Secure属性
    同じくsession_store.rbでsecure属性を設定できますが、ローカルでhttpで開発している場合、developmentの時は付与しないように設定します。
# config/initializers/session_store.rb
MyApp::Application.config.session_store :active_record_store, key: "_myapp_session", secure: (Rails.env.production? || Rails.env.staging?)

参考サイト

はじめてのFlutter〜環境構築後編〜

Flutterの環境構築のお話、後編です。
前編はこちらからどうぞ。

Steps

  1. Flutter SDKのインストール
  2. Android Studioのインストール <-いまここ
  3. XCodeのインストール

DLに時間がかかるものリスト

※ おそらく以前のバージョンだとDart単独でインストールする必要があったのだと思いますが、今はFlutterのSDKに含まれています。

Android Studioセットアップ(昨日からの続き)

下記の二記事を参考に進めます。
* Flutter開発環境構築(Mac編) - Qiita
* FirebaseとFlutterでアプリ開発してみる【其ノ二:Flutter&Dart篇】 - Qiita

  • 「Install Type」
    -> セットアップ方法をスタンダード/カスタムで選べる。ひとまずスタンダード。

  • 「Select UI Theme」
    -> UIテーマを選べる。暗い方が好みなのでDarculaを選択。

  • 「Verify Settings」
    今選択したセットアップ方法を確認して「Finish」。

セットアップ方法の指定が完了すると走るComponentのDLはやや時間がかかります。
また最後、HAXMのインストールをする際、パスワードが求められます。
パスワード入力してOKしたのだけど、処理が止まってしまって「Cancel」を押すしかない状態に。。。
ただ、DLは完了していたみたいでAndroid Studioを再起動したら普通に開けました。 HAXMがインストールされているか確認したいときは下記のコマンドで確認できます。

$ find ~/ -name sdk
# => /Users/makicamel//Library/Android/sdk

$ cd /Users/makicamel//Library/Android/sdk
$ ./emulator/emulator -accel-check
# => accel:
# => 0
# => HAXM version 7.3.2 (4) is installed and usable.
# => accel

Android Studioでプロジェクト作成〜仮想端末(Virtual Device)ダウンロード

Android Studioを再起動して(たぶん本来はFinished押せばいいだけ)「Start a new Android Studio project」をクリック。

OS(Phone and TabletとかWear OSとか)、テンプレートを選べます。
便利ですが、エミュレータが使いたいだけなのでどれでもOK。
名前、言語を適当に選択・記入して「Finish」するとAndroid Studio プロジェクトが作成されます。

IDEの画面が開きますが、Gradleのダウンロードが走るのでしばし待ちます。
この間暇なのでGradleとは?を調べる。OSSのビルドシステムなんですね。

完了したらエミュレータを動かします。
「Tools」->「AVD Manager」->「+ Create Virtual Device」をクリック、デバイス一覧が表示されるので適当なデバイスを選択します。今回はNexus 5Xを選択。

SystemImageを選択してと言われるので、適当に選択(API Level28)。Androidのキャラクタかわいいですね。
「Download」をクリックするとLicense Agreementに同意を求められるので同意します。またダウンロードが発生😣(900MB)
ダウンロードが完了したら自動でリフレッシュしてくれるので、選択して「Next」。
Configurationを確認して問題なければ「Finish」。やっと仮想端末ができました…!
今ダウンロードしたばかりの仮想端末が表示されるので、右側「Actions」からプレイボタン(▶)をクリックするとエミュレータが起動します!👏

VS CodeにFlutterプラグインをインストール

Gradleや仮想端末のダウンロードを待っている間、エディタの準備をしておきます。
VS Codeを起動してExtensions(Shift+Cmd+X)をクリックして「Flutter」を入力、一番上に出てきたものをインストール。

VS Codeを再起動してコマンドパレット(Shift+Cmd+P)で「doctor」を入力、flutter doctorしてVS Codeでエラーが出ていなければVS Codeの準備は完了です。

Android StudioでHello, world!してみる

仮想端末の準備ができたら、早速Hello, world!してみます!
ターミナルで適当なディレクトリに移動してコマンドを入力します。

$ cd ~/dev/src
$ flutter create hello_flutter
# => In order to run your application, type:
# =>   $ cd hello_flutter
# =>   $ flutter run

$ ls
# =>   hello_flutter

アプリを実行するにはflutter runしてね、と言われるので、実行します。

$ flutter run
# => No connected devices.

さっきダウンロードした端末と接続できてない様子。flutterに聞いてみます。

$ flutter doctor
[!] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses

ライセンスに同意する必要があるそうなので、同意します。

$ flutter doctor --android-licenses

ライセンスを確認しますか?と聞かれるのでyで答えてそれぞれ同意します。
再度flutterを実行!

$ flutter run
# => No connected devices.

むむむ。

$ flutter emulators
# => 1 available emulator:
# => Nexus_5X_API_28 • Nexus 5X • Google • Nexus 5X API 28
# => To run an emulator, run 'flutter emulators --launch <emulator id>'.
# => To create a new emulator, run 'flutter emulators --create [--name xyz]'.

ならば、とflutter emulators --launch Nexus_5X_API_28を入力すると、今立ち上げてるから二重起動はできないよ、と怒られます。うーん?

エミュレータを終了させて、再度flutter emulators --launch Nexus_5X_API_28を入力するとエミュレータが起動します。が、やはりdeviceが接続されていないよ、とのこと。
参考記事のようにもうひとつAndroid Emulatorを作成し、再起動後にflutter runすると、(This is taking an unexpectedly long time.)のメッセージ。
うーん…。

あれこれ試してみて、androidエミュレータを起動しているのにdevice not foundが出る問題を参考に、2つめに作った仮想端末をGUI(Android Studio)から起動してflutter runすると実行できることがわかりました。
謎事象だけど、実行を先にしたいので後で調べることにします。

今回はHello, Flutter!と画面に表示するのが目的なので、lib/main.dartファイルを編集します。
ホットリロードはターミナルでrを押してね、とのことなので、rを入力してファイルを保存。

children: <Widget>[
  Text(
    'Hello, Flutter for this many times:',
  ),
  Text(
    '$_counter',
    style: Theme.of(context).textTheme.display1,
  ),
],

無事ホットリロードされることを確認しました!👏👏👏

iOS環境を構築

お次はiOS環境の構築。
XCodeはAppStoreからインストール。

XCodeはインストール済だったので、早速Simulatorを立ち上げます。 XCodeのインストール方法はこちらに詳しいです。

なお、GUIの場合は「XCode」->「Open Developer Tool」->「Simulator」で起動。
CUIの場合はopen -a Simulatorで起動できます。

Androidflutter emulatorsで仮想端末を確認できますが、iOSflutter devicesで確認する様子。
Simulatorが起動している状態でflutter runで実行できます。
Androidと同様に「ホットリロードはrを押してね」と表示されるのですが、ターミナルでrを毎回入力する必要があるっぽい?この辺も後々確認していきたいと思います。

iOS環境を整える

終了!と言いたいところなのですが、flutter doctorすると結構エラーが出てきたので、その解決を。

$ flutter doctor

[✓] Flutter (Channel stable, v1.2.1, on Mac OS X 10.14.3 18D109, locale en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[!] iOS toolchain - develop for iOS devices (Xcode 10.1)
    ✗ libimobiledevice and ideviceinstaller are not installed. To install with Brew, run:
        brew update
        brew install --HEAD usbmuxd
        brew link usbmuxd
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller
    ✗ ios-deploy not installed. To install:
        brew install ios-deploy
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
        Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
        For more info, see https://flutter.io/platform-plugins
      To install:
        brew install cocoapods
        pod setup
[!] Android Studio (version 3.3)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.32.1)
[✓] Connected device (1 available)

Android StudioではなくVS Codeで開発していくのでAndroid Stuidoのエラーは放置。
iOS周りの問題を解決してゆきます。

とはいえflutterがコマンドを教えてくれているので、それをひとつひとつ実行するだけ。

# libimobiledevice and ideviceinstaller are not installed.
$ brew update
$ brew install --HEAD usbmuxd
$ brew link usbmuxd
$ brew install --HEAD libimobiledevice
$ brew install ideviceinstaller

# ios-deploy not installed.
$ brew install ios-deploy

# CocoaPods not installed.
$ brew install cocoapods
$ pod setup

(CocoaPodsはダウンロードに時間かかります)

全部終わったら再度flutter doctorを実行。

$ flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.2.1, on Mac OS X 10.14.3 18D109, locale en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.1)
[!] Android Studio (version 3.3)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.32.1)
[✓] Connected device (1 available)

無事環境構築が完了しました!

はじめてのFlutter〜環境構築前編〜

新しくReactNativeを使う予定だったのですが、予定変更でFlutterでやるよ!ということになったので、Flutterはじめました。
ので、今日はその環境構築メモの前半戦を記載していきたいと思います。

DLに時間がかかるものリスト

先にDLをしかけながら読んでいるとスムーズかもしれません。

Flutterとは?

flutter.dev

Googleが開発している、OSSでモバイルアプリ開発FWでありSDK。開発言語もGoogle開発のDart
ひとつのコードでiOSAndroidの両方に対応した開発ができる。Google開発なのでFirebaseなどのGoogleアプリとの連携しやすさもポイント。
2018年12月に1.0がリリースされたばかりで、まだ新しいFWです!

Flutterの押しポイントは3つ。

  • Fast Development

    without losing state, on emulators, simulators, and hardware for iOS and Android.

保存すると自動でビルドしてくれる機能なのですが、いわゆるホットリロードと違って状態を保ったまま反映してくれる機能。すごい!!

  • Expressive and Flexible UI
    MaterialDesignとiOSのデザイン規範(Cupertino)を踏襲したfull-customizedなデザインを簡単に作れるし、レンダリングもめっちゃ早いよ!ということみたい。

  • Native Performance
    スクロールやナビゲーションなど、iOSAndroidの違いを吸収してくれるので、どちらのプラットフォームでもNativePerfomanceが実現できるよ!ということみたい。

ざっくりFlutterの概要を理解したところで環境構築をしていきます。

FlutterSDKのインストール

わたしの環境はmacOS Mojave 10.14.3です。
System Requirementsは公式サイトにある通り。

公式サイトからSDKをダウンロード。回線によってはやや時間がかかります。

DLが完了したらzipファイルを好きなディレクトリに移して解凍。
ターミナルでそのディレクトリに移動、パスを通します。

pwd
# => /Users/makicamel/dev
export PATH="$PATH:`pwd`/flutter/bin"

これだけだと再起動するとパスが消えるので、永続化するには.bash_profileに書き込みます。

touch ~/.bash_profile
vim ~/.bash_profile

export PATH=$PATH:flutterを置いたPATH
# 例えばこう
# export PATH=$PATH:/Users/makicamel/dev/flutter/bin

.bash_profileは変更しただけでは反映されないのでsourceコマンドで反映します。

source ~/.bash_profile

確認はprintenvでできます。
printenvだけだと環境変数全てが返ってきて読みにくいので、PATHを指定。

printenv PATH

flutter --verionを実行して、Welcome to Flutter!メッセージと共にバージョン情報が返ってきたらSDK設定が完了。

$ flutter --version

Flutter 1.2.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8661d8aecd (3 weeks ago) • 2019-02-14 19:19:53 -0800
Engine • revision 3757390fa4
Tools • Dart 2.1.2 (build 2.1.2-dev.0.0 0a7dcf17eb)

なお、もし必要なパッケージがインストールされていなくてエラーが出たときは、flutter doctorで確認できるそうです。

Android環境を構築

Android Studioにアクセス、「DOWNLOAD ANDROID STUDIO」から をダウンロードします。

900MBほどあるので時間がかかります。

ダウンロードが完了したらApplicationディレクトリにインストールし、設定を進めます。

  • Import Android Studio Settings from
    -> 初回インストールなので「Do not import settings」を選択して起動します。

  • Data Sharing
    -> Android Studioをよりよくする為に、どんな機能を使っているか送信してもいいですか、というやつ。お好みに合わせて。

が、時間切れなので、今日はここまで!

また明日、後編をアップいたします😊