省エネ

Flutter、vue3修行中。

【iOS】SwiftUIチュートリアルやってみた

WWDC 2019 で SwiftUI が発表されましたね。

とりあえず使ってみたいですよね。

Xcode11

SwiftUIを使うにはXcode11 が必要みたいです。
ダウンロードはこちら。beta版をダウンロードできます。

https://developer.apple.com/download/

チュートリアルページはこちら。

developer.apple.com

実装

  • Single View Application -> Next
  • Use SwiftUIにチェック

f:id:qkuroneko:20190612161354p:plain:w420

import SwiftUI

struct ContentView : View {
    var body: some View {
        Text("Hello World!!!!!")
    }
}

f:id:qkuroneko:20190612163523p:plain:w320

  • テキストの色、フォントを変える
import SwiftUI

struct ContentView : View {
    var body: some View {
        Text("Hello World!!!!!")
        .font(.title)
        .color(.green)
    }
}

f:id:qkuroneko:20190612163822p:plain:w320

  • 横にプレビューが表示されるみたいですが、私のOSがまだMojave 10.14.5のままなので見てれないです

  • 縦方向に並べる

import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack {
            Text("Hello World!!!!!")
                .font(.title)
                .color(.green)
            Text("Hello World!!!!!")
                .font(.subheadline)
        }
    }
}

f:id:qkuroneko:20190612165117p:plain:w320

  • VStackにalignmentつけつつ横方向に並べる
  • Spacer()は空いてるスペース埋めちゃう感じ
import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack(alignment: .trailing) {
            Text("Hello World!!!!!")
                .font(.title)
                .color(.green)
            HStack {
                Text("Hello World!!!!!")
                    .font(.subheadline)
                Spacer()
                Text("foooo")
            }
        }
    }
}
  • padding
import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack(alignment: .trailing) {
            Text("Hello World!!!!!")
                .font(.title)
                .color(.green)
            HStack {
                Text("Hello World!!!!!")
                    .font(.subheadline)
                Spacer()
                Text("foooo")
            }
        }
        .padding(.leading)
    }
}
  • .padding()だけだと全方向にpaddingできるのかな。

  • 画像を丸くする

  • ページ上部のリンクからサンプルをDLするとチュートリアルで使用している画像を手に入れることができます。

  • Assetsに画像をD&Dします
  • 新しいSwiftUIファイルを作って「CircleImage」って名前をつけます
CircleImage.swift

import SwiftUI

struct CircleImage : View {
    var body: some View {
        Image("turtlerock")
        .clipShape(Circle())
        .overlay(Circle().stroke(Color.white, lineWidth: 4))
        .shadow(radius: 10)
    }
}
  • .clipShape(Circle())で画像を丸くできる
  • .overlay()で画像の周りにstrokeをつけることができる
  • .shadow()で画像の周りに影をつけられる
  • RootのViewでCircleImage()を呼ぶと丸く切り取られた画像のViewを呼び出すことができる

f:id:qkuroneko:20190612175804p:plain:w320

  • 地図を使う
  • MapView.swiftファイルを作る
MapView.swift


import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ view: MKMapView, context: Context) {
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.011286, longitude: -116.166868)
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}
  • UIViewRepresentableを継承する
  • このクラスのことよく分からないのですがmakeUIView()updateUIView()をオーバーライドして地図の大きさやどこを指すかとか設定って感じ

  • これまで学んだことを組み合わせするとこんなのができる

f:id:qkuroneko:20190612180353p:plain:w320

import SwiftUI

struct ContentView : View {
    var body: some View {
        
        VStack {
            MapView()
                .frame(height: 300)
            
            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)
            
            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
            .padding()
            
            Spacer()
        }
    }
}
  • 真ん中に鎮座している画像の位置をoffsetで上のViewに乗せてるんですね。

  • チュートリアルを最後まで進めたら一番下に問題があるので答えてみます。

こんな感じでチュートリアルの「Creating and Combining Views」は終了です。
後日、続きをやっていきたいと思います。

【Web】PWAにしてみた

こんにちは。
先日、Flutter for webでwebアプリをリリースしたのですが、そのページをPWAにしてみました。

qkuronekop.hatenablog.jp

やり方は簡単。

manifest.jsonというファイルを作成します。

{
    "name": "qkuronekop.dev",
    "short_name": "qkuronekop",
    "background_color": "#ffffff",
    "icons": [{
        "src": "./icon-256.png",
        "sizes": "256x256",
        "type": "image/png"
      },{
        "src": "./icon-192.png",
        "sizes": "192x192",
        "type": "image/png"
      }],
    "start_url": "./?utm_source=homescreen",
    "display": "standalone"
  } 

name,short_name,background_color,icons,start_urlは自分のサイトに合わせて変更してください。

アイコンは最低、256×256と192×192の2種類のサイズが必要みたいです。

service-worker.jsというファイルを用意します。

self.addEventListener('install', function(e) {
    console.log('[ServiceWorker] Install');
  });
  
  self.addEventListener('activate', function(e) {
    console.log('[ServiceWorker] Activate');
  });
  
  self.addEventListener('fetch', function(event) {});

こんな感じ。

index.html

  <link rel="manifest" href="./manifest.json">
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('./service-worker.js').then(function() { console.log('Service Worker Registered'); });
   }
  </script>

これを追加。

諸々パスに気をつけてくださいね。
私は全てindex.htmlがある階層においてます。

https://qkuronekop.dev/#/

PCブラウザで開いてもなんの変哲もないのですが、スマホで開くと

f:id:qkuroneko:20190607095636p:plain:w320

こんな感じになります。
ただし、アプリを追加しますか?と出てくるのは2回目以降の訪問の時で前回の訪問から5分以上経過している場合だそうです。

f:id:qkuroneko:20190607095904p:plain:w320

「追加」をタップすると、

f:id:qkuroneko:20190607100026p:plain:w320

こんな感じでホームにアイコンが追加されます。
アイコンをロングタップすると「アプリの情報」というモーダルが出てこのアイコンがただのブックマークではなくアプリであることがわかります。

f:id:qkuroneko:20190607100335p:plain:w320

さらに、ホームのアイコンからアプリを開くと上部にあったURLが消えていてネイティブアプリっぽい表示になります。

また、画像はないのですが、manifest.jsonにname、background_color、iconsを設定すると画面が開く直前にスプラッシュスクリーンを表示してくれるようになります。
qkuronekop.devもスプラッシュスクリーンが表示されているので、ぜひお手元のスマホでみてみてください。

iOSだとうまく表示されてないかもしれないです。

iOSでの対処法はこちらの方が紹介してくれていますのでご参考にどうぞ。

qiita.com

【Flutter】Flutter for Web使ってみた

こんにちは。
Google IO 2019にて発表されたFlutetr for webが発表されましたね。
どんなもんかと思ってさっそく使ってみました。

github.com

私が試した方法です。
上のリポジトリをCloneします。

hello_woldディレクトリをコピーしてディレクトリ名を変更します。

pubspec.ymlを以下のように書き換えます。

name: qkuronekop.dev

environment:
  # You must be using Flutter >=1.5.0 or Dart >=2.3.0
  sdk: '>=2.3.0-dev.0.1 <3.0.0'

dependencies:
  flutter_web: any
  flutter_web_ui: any

dev_dependencies:
  build_runner: ^1.4.0
  build_web_compilers: ^2.0.0

dependency_overrides:
  flutter_web:
    git:
      url: https://github.com/flutter/flutter_web
      path: packages/flutter_web
  flutter_web_ui:
    git:
      url: https://github.com/flutter/flutter_web
      path: packages/flutter_web_ui

サンプルのままだとflutter_webflutter_web_ui相対パスになっているので直しただけですね。

あと、nameのところを変更しました。

ここまでできればあとはlib/main.dartを自由に書き換えればOKですね。

動かし方はflutter_webのリポジトリのREADMEに詳しくあるのですが、flutter pub global activate webdevでwevdevをインストールしておきます。
先ほど作ったプロジェクトのルートでflutter pub upgradeします。
webdev serveしビルドに成功するとhttp://127.0.0.1:8080で接続できます。

開発中はwebdev serve --auto restartで起動するとホットリロードしてくれます。

せっかくなので、Firbase hostingでページを公開してみました。

flutter for webで作ったページをfirebaseで公開する方法はこちらの記事を参考にさせていただきました。

qiita.com

作ったページはこちら。

https://qkuronekop.dev/#/

すごく簡素な感じなのでおいおい色々試して行きたいです。 あと、スマホで見ると文字が切れてるかも。

main.dart

// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter_web/material.dart';
import 'dart:js' as js;

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

class MyApp extends StatelessWidget {
  @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.deepPurple,
            primaryColorDark: Colors.deepPurpleAccent),
        home: Scaffold(
            appBar: AppBar(
              title: Text(
                'qkuronekop.dev',
                textDirection: TextDirection.ltr,
              ),
            ),
            body: SingleChildScrollView(
              child: Stack(
                children: <Widget>[
                  Column(
                    children: <Widget>[
                      Container(
                        margin: EdgeInsets.all(16.0),
                        child: Column(
                          children: <Widget>[
                            SizedBox(
                              height: 16.0,
                            ),
                            Center(
                              child: Text(
                                'Profile',
                                textDirection: TextDirection.ltr,
                                style: TextStyle(
                                    fontSize: 28.0,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                            SizedBox(
                              height: 24.0,
                            ),
                            Center(
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: <Widget>[
                                  Container(
                                    height: 48.0,
                                    width: 48.0,
                                    decoration: BoxDecoration(
                                      image: DecorationImage(
                                          image: NetworkImage(
                                              '画像URL'),
                                          fit: BoxFit.fill),
                                      borderRadius: BorderRadius.circular(24.0),
                                      boxShadow: [
                                        BoxShadow(
                                            color: Colors.black38,
                                            offset: Offset(2.0, 2.0))
                                      ],
                                    ),
                                  ),
                                  SizedBox(
                                    width: 16.0,
                                  ),
                                  Container(
                                    width: 400.0,
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: <Widget>[
                                        Text('こんにちは。\n辻村里美(@qkuronekop)と申します。'),
                                        Text(
                                            'Androidアプリ開発が得意です。\n現在は、KotlinでのAndroid開発やFlutterでのAndroid、iOS開発をしています。'),
                                        Text('上記以外に興味があるのはGo言語でのWebアプリ開発です。'),
                                      ],
                                    ),
                                  )
                                ],
                              ),
                            ),
                            Container(
                              margin: EdgeInsets.symmetric(vertical: 32.0),
                              width: 300.0,
                              height: 2.0,
                              color: Colors.purpleAccent,
                            ),
                            Center(
                              child: Text(
                                'Github',
                                textDirection: TextDirection.ltr,
                                style: TextStyle(
                                    fontSize: 28.0,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                            SizedBox(
                              height: 24.0,
                            ),
                            GestureDetector(
                              child: Text(
                                'https://github.com/qkuronekop',
                                style: TextStyle(
                                  color: Colors.deepPurpleAccent,
                                  decoration: TextDecoration.underline,
                                  decorationColor: Colors.deepPurpleAccent,
                                  decorationStyle: TextDecorationStyle.solid,
                                ),
                              ),
                              onTap: () {
                                js.context.callMethod(
                                    "open", ["https://github.com/qkuronekop"]);
                              },
                            ),
                            Container(
                              margin: EdgeInsets.symmetric(vertical: 32.0),
                              width: 300.0,
                              height: 2.0,
                              color: Colors.purpleAccent,
                            ),
                            Center(
                              child: Text(
                                'Blog',
                                textDirection: TextDirection.ltr,
                                style: TextStyle(
                                    fontSize: 28.0,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                            SizedBox(
                              height: 24.0,
                            ),
                            GestureDetector(
                              child: Text('http://qkuronekop.hatenablog.jp/',
                                  style: TextStyle(
                                    color: Colors.deepPurpleAccent,
                                    decoration: TextDecoration.underline,
                                    decorationColor: Colors.deepPurpleAccent,
                                    decorationStyle: TextDecorationStyle.solid,
                                  )),
                              onTap: () {
                                js.context.callMethod("open",
                                    ["http://qkuronekop.hatenablog.jp/"]);
                              },
                            ),
                            Container(
                              margin: EdgeInsets.symmetric(vertical: 32.0),
                              width: 300.0,
                              height: 2.0,
                              color: Colors.purpleAccent,
                            ),
                            SizedBox(
                              height: 24.0,
                            ),
                            Center(
                              child: Text(
                                'App',
                                textDirection: TextDirection.ltr,
                                style: TextStyle(
                                    fontSize: 28.0,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                            SizedBox(
                              height: 24.0,
                            ),
                            GestureDetector(
                              child: Container(
                                width: 320.0,
                                child: Image.network(
                                    '画像URL'),
                              ),
                              onTap: () {
                                js.context.callMethod("open", [
                                  "https://play.google.com/store/apps/developer?id=qkuronekop&hl=ja"
                                ]);
                              },
                            )
                          ],
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            )));
  }
}

現状感じていること

  • 外部リンクができないのでdart:jsで外部リンクを開くスクリプトを書かねばならない。リンクではなくonTapを使っているのでPCでみた時にカーソルが変化しないのがちょっと微妙かな。
  • 当たり前だけど今あるNativeアプリに依存するようなpackageは使えないのでweb用のライブラリとかが充実してくるといいな。
  • Image.assetが使えないんだけど私だけ???
  • CSS書かなくてもいい感じのUIが作れるのは最高。   

2019/6/26追記

Image.assetについて勘違いしていたことがあって、Nativeの時みたいにpubspec.ymlにパスかいて使うのではなくweb/assets/にリソースを置いて、そこのパスを書けばいいんですね。
しかし、ローカルでは表示されるけどfirebase hostingにアップすると表示されなくなる……なぜだ。。。

ちょっと色々デザインいじってみました。 gitにも公開したので興味ある方はみてください!

github.com

【bot】Cogbot作ってきました

cogbot.connpass.com

こんにちは。
先日、こんな勉強会へ参加してきました。

AzureとLine Message Apiを使ってラインbotを作るハンズオンです。

私は「食べれるかニャン?BOT」を作りました。

f:id:qkuroneko:20190422174245p:plain

「食べれるかニャン?BOT」は食べ物の名前を送信すると、猫が食べられるかどうかを教えてくれるbotです。
作り方は超簡単で、ノーコーディングで1時間くらいで作れました。

興味がある方は上のコンパスのリンクからページを開くと、作り方を詳細に記載したqiitaのリンクが貼ってあります。
AzureとLineのアカウントとパソコンがあればすぐに作れます。

「食べれるかニャン?BOT」でも使ったのですが、QnA Maker を使うと、サービスのQ&Aみたいに簡単な応答のbotならすぐ作れちゃいます。
便利ですね。

もっと複雑なことがしたければ、ノーコーディングでは難しそうですが、Cloud functionなどと組み合わせると色々できそうな感じでしたね。

とりあえず作ったみたいだけなら全て無料でできますので、色々作ってみると楽しそうですね。

【Flutter】Firebase Storageに画像を保存する

こんにちは。
前回、写真を撮る機能をアプリに追加したのですが。

qkuronekop.hatenablog.jp

この撮った写真をFirebase Storageに保存したいと思います。

pubspec.yml

dependencies:
  flutter:
    sdk: flutter
  firebase_storage: 1.0.4

AndroidX関係でエラーになるので、下記のリンク先のバージョンに合わせます。

flutter.dev

import

import 'package:firebase_storage/firebase_storage.dart';

code

  Future<Null> uploadFile(String filePath) async {
    final ByteData bytes = await rootBundle.load(filePath);
    final Directory tempDir = Directory.systemTemp;
    final String fileName = "${Random().nextInt(10000)}.jpg";
    final File file = File('${tempDir.path}/$fileName');
    file.writeAsBytes(bytes.buffer.asInt8List(), mode: FileMode.write);

    final StorageReference ref = FirebaseStorage.instance.ref().child(fileName);
    final StorageUploadTask task = ref.putFile(file);
  }

こんな感じでファイルを作り、Firebase Strageにuploadします。

 RaisedButton(
   color: Colors.grey,
   shape: RoundedRectangleBorder(
     borderRadius: BorderRadius.all(Radius.circular(10.0)),
   ),
   child: Icon(Icons.camera),
   onPressed: () async {
     var filePath = await takePicture();
     if (mounted) {
       setState(() {
         imagePath = filePath;
       });
     }
     await uploadFile(filePath);
   },
 ),

前回作ったカメラ画面のシャッターボタンのonPressed()で、画像のパス取得後にuploadFile()を呼び出しています。

私のアプリの場合には、匿名ログインの機能を入れいているので、この状態で動作させることが可能です。

qkuronekop.hatenablog.jp

が、FirebaseAuthを入れていない場合にはデフォルトでwrite権限がないのでFirebase consoleのStorageメニューを選択し、ルールタブを開きアクセス権の変更をします。

f:id:qkuroneko:20190323074416p:plain

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

ルールをこんな感じに変更します。
これでトークンがなくてもread、writeすることができます。
ちょっと機能を試してみたい場合にはこれでいいかと思います。

こちらを参考にしました。

www.youtube.com

【Flutter】写真撮影をする

Flutterで写真を撮るアプリを作りたいと思ったのでカメラ機能を入れてみました。

使ったプラグインはこちら。

pub.dartlang.org

まだAndroidでしか試してないけどもiOS/Androidで使えます。
Androidの場合minSdkVersion 21です。

使い方

pubspec.yml

dependencies:
  flutter:
    sdk: flutter
  camera: ^0.4.2

これを追加してPackages getします。

camera.dart

カメラ機能を追加する画面をcamera.dartとして作ります。

class CameraView extends StatefulWidget {

  @override
  State createState() => CameraState();

}

class CameraState extends State<CameraView> {
 @override
  Widget build(BuildContext context) {
  }
}

まずはStatefulWidgetで画面を作ります。

class CameraState extends State<CameraView> {

  CameraController controller;

  Future<void> getCameras() async {
    cameras = await availableCameras();
    controller = CameraController(cameras[0], ResolutionPreset.medium);
  }

  @override
  void initState() {
    super.initState();
    getCameras().then((_) {
      controller.initialize().then((_) {
        if (!mounted) {
          return;
        }
        setState(() {});
      });
    });
  }
}

initState()で使えるカメラを取得して、カメラコントローラーの初期化をします。
私が作りたいアプリの場合背面カメラの静止画だけでいいかなと思ったので1種類だけ使ってます。
機種に乗っていればフロントカメラやムービー撮影も使えるようです。

class CameraState extends State<CameraView> {
 @override
  Widget build(BuildContext context) {
    if (controller == null || !controller.value.isInitialized) {
      return Container();
    } else {
    }
  }
}

画面が起動した直後、またはカメラへのアクセス許可がされていない状態ではカメラプレビューを表示できないので、
コントローラーがnullまたはisInitializedfalseの場合にはカメラプレビューではないWidgetを返します。

class CameraState extends State<CameraView> {
 @override
  Widget build(BuildContext context) {
    if (controller == null || !controller.value.isInitialized) {
      return Container();
    } else {
      return AspectRatio(
                  aspectRatio: controller.value.aspectRatio,
                  child: CameraPreview(controller)
                 );
    }
  }
}

AspectRatioは縦横比を維持したまま、最大の大きさに広がってくれるWidgetです。
カメラcontrollerが縦横比を持っているのでそれを設定してあげるといい感じの大きさのプレビューが作れます。

CameraPreview(controller)のところが実際のカメラプレビューになります。

class CameraState extends State<CameraView> {

  Future<String> takePicture() async {
    final Directory extDir = await getApplicationDocumentsDirectory();
    final String dirPath = '${extDir.path}/Pictures/own_note';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.jpg';

    if (controller.value.isTakingPicture) {
      // A capture is already pending, do nothing.
      return null;
    }

    try {
      await controller.takePicture(filePath);
    } on CameraException catch (e) {
      // エラー時の処理
      return null;
    }
    return filePath;
  }

 @override
  Widget build(BuildContext context) {
    if (controller == null || !controller.value.isInitialized) {
      return Container();
    } else {
      return Column(
            children: <Widget>[
               AspectRatio(
                  aspectRatio: controller.value.aspectRatio,
                  child: CameraPreview(controller)),
               RaisedButton(
                  child: Icon(Icons.camera),
                  onPressed: () async {
                      var filePath = await takePicture();
                  },
               ),
            ]
      );
    }
  }
}

シャッターボタンを追加します。
ボタンWidgetonPressed()takePicture()を呼び出します。
takePicture()では、保存先のパスとファイル名を作り、画像を保存しています。

私が作りたいアプリの場合には、写真を撮った直後に同じ画面内にサムネイルを表示しつつ、
画像をサーバーへアップロードしているのでtakePicture()が成功した場合にはファイルパスを返すようにしました。

f:id:qkuroneko:20190323071353p:plain:w320

色々装飾してますが、こんな感じになります。

ここを参考にしました。

github.com

【Flutter】Flutter + Firebase Authenticationで匿名ログイン

ここ最近はFlutetrで何かアプリを作ってGooglePlayへリリースするところまでやってみたいなと思い、アプリを作り始めています。

ただ、サーバーを用意するのが手間なので、ネイティブ以外の作業は全てFirebaseで賄おうと思っています。

とりあえずはFirebaseをFlutterアプリに入れてみます。
今回はAndroidだけですが、いずれiOSも対応したいと思います。

FirebaseをFlutterアプリへ

Flutter アプリに Firebase を追加する  |  Firebase

結構丁寧にここにやり方が書かれています。
AndroidもしくはiOSアプリにFirebaseを入れたことがある人なら簡単にできると思います。
ただ、途中やり方が変わってきます。

Firebase ConsoleからAndroidアプリを追加します。
google-services.jsonをダウンロードし、android/appに置きます。

gradle

トップレベルのgradleに

buildscript {
    ext.kotlin_version = '1.3.21'
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:3.2.1'
    }
}

のように

classpath 'com.google.gms:google-services:3.2.1'

を追加します。

現時点で、もっと大きいバージョンがあるみたいですが、上にリンクしたFirebaseのページには、

注: Flutter は現在、バージョン 3.2.1 の Google サービス プラグインと互換性があります。

とあるので、バージョンは動かさないほうがよさそうです。

app下のgradleには一番下に

apply plugin: 'com.google.gms.google-services'

を追加するだけでOKです。

pubspec.yml

flutter用のfirebase pluginがあるようなので、package getで取得します。

pubspec.ymlに下記を追加。

dependencies:
  flutter:
    sdk: flutter

  firebase_core: 0.2.5+1
  firebase_analytics: 1.1.0
  firebase_auth: 0.7.0

ここでハマったのですが、

firebaseopensource.com

ここを見て最新のバージョンを使おうとすると、package getは問題なく通るのですが、ビルドが通らなくなります。

AndroidX関係でエラーになるので、下記のリンク先のバージョンに合わせます。

flutter.dev

ここに書かれているバージョンに直したところ、問題なくビルドし、機能が使える様になりました。

f:id:qkuroneko:20190315134318p:plain:w320

アプリをインストールすると、Androidアプリが認識されました。

匿名ログイン

Firebase Consoleからプロジェクトを開き、開発→Authenticationページを開きます。

f:id:qkuroneko:20190315134757p:plain:w320

一番下の「匿名」を有効にします。

あとは匿名ログインしたいタイミングで、

FirebaseAuth#signInAnonymously();

を呼び出せはOKです。

私の場合はスプラッシュ画面でしれっとログインしてしまおうと思ったので、こんな風にしてます。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:own_note/views/todays_photo_list.dart';
import 'package:splashscreen/splashscreen.dart';

class SplashView extends StatelessWidget {

  final FirebaseAuth firebaseAuth = FirebaseAuth.instance;

  Future<FirebaseUser> signInAnon() async {
    FirebaseUser user = await firebaseAuth.signInAnonymously();
    return user;
  }


  SplashView() {
    signInAnon().then((FirebaseUser user) {
      print('User ${user.uid}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return SplashScreen(
      seconds: 2,
      navigateAfterSeconds: TodaysPhotoListView(),
      image: new Image.network(
          'https://flutter.io/images/catalog-widget-placeholder.png'),
      photoSize: 100.0,
      gradientBackground: LinearGradient(colors: [Colors.cyan],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight),
    );
  }

}

雑に実装した感じだとこんな感じ。

www.youtube.com

こちらの動画を参考にしました!