ページ

2018年3月21日水曜日

重い腰を上げてCloud Firestoreを触ってみた

Cloud Firestore

:computer:環境構築


とりあえずSampleプロジェクトを立てて、Cloud Firestoreを使って見る
image.png (17.1 kB)
セキュリティルール
image.png (76.8 kB)
とりあえず「テストモード」で開始する

セキュリティルール

  • テストモードで作られたrules
    service cloud.firestore {
      match /databases/{database}/documents {
            match /{document=**} {
              allow read, write;
            }
      }
    }
    
  • ログインしているユーザー全員にデータを読ませる場合
    service cloud.firestore {
      match /databases/{database}/documents {
            match /{document=**}{
                allow read, write:if request.auth != null;
            }
      }
    }
    

コレクション/ドキュメント

  • コレクション
    • ドキュメントを含んだ単なるコンテナ
  • ドキュメント
    • フィールド(例、名字、名前、年齢、性別、、、)を含んだ一意の名前のレコード

:pencil: 実装


簡単なサンプル

まずは試せる最小の構成を作成
html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
    <title>Google Firestore Sample</title>
  </head>
  <body>
    <div class="container"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js"></script>
    <script src="https://www.gstatic.com/firebasejs/4.10.1/firebase.js"></script>
    <script src="https://www.gstatic.com/firebasejs/4.10.1/firebase-firestore.js"></script>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXX,
        authDomain: "xxxxxx.firebaseapp.com",
        databaseURL: "https://xxxxxx.firebaseio.com",
        projectId: "xxxxxxx",
        storageBucket: "xxxxxx.appspot.com",
        messagingSenderId: "xxxxxxxxxxxx"
      };
      firebase.initializeApp(config);
    </script>
  </body>
</html>
configの所は適時置き換え

データの読み込み

特定のパスを直接指定して値を読み込んで見たいと思います。
html
    <div class="container">
      <p class="text"></p>
    </div>
jsの部分 (今回は誰でも触れるrulesでやってます)
js
      const db = firebase.firestore();
      // データ読み込み
      const ref = db.doc('/xxxxx/xxxxxx');
      ref.get().then((doc) => {
        if (doc && doc.exists) {
          console.log(doc.data());
          $('.container .text').text(doc.data().value);
        }
      }).catch((error) => console.error(error));

データの書き込み

js
      // データの書き込み
      $('.container > .form > .send').click(() => {
        ref.set({
          name: $('.container > .form > .name').val()
        }).then(() => {
          console.log('write success!');
        }).catch((error) => console.error(error))
      });
※ updateと違ってドキュメント全てを置き換えるので注意!

データの監視

js
      // データの監視
      ref.onSnapshot({includeMetadataChanges: true}, (doc) => {
        if (doc && doc.exists) {
          $('.container .text').text(doc.data().name);
        }
      });

2018年3月19日月曜日

Google公式GASのCLIツールclaspを試してみる

clasp

GASのCLIツールがGoogle公式のが出てたので使ってみる。

:computer:環境構築


まずは対象アカウントでこちらにアクセスし、
Google Apps Script APIをオンにする。
image.png (13.2 kB)
次にclaspをインストール
sh
$ npm i @google/clasp -g
claspでログイン
sh
$ clasp login
ブラウザが開クノで対象のアカウントでログイン
この時、~/.clasprc.jsonが作成される様子。

:pencil: 実装


簡単なサンプル

  1. Googleドライブ上でGASのテストプロジェクトを作成
  2. 作成したプロジェクトをローカルにクローン
    • $ clasp clone [プロジェクトID]
    • プロジェクトIDはファイル→プロジェクトのプロパティで表示された以下赤枠の部分
    • image.png (86.7 kB)
    • クローンされるとappsscript.json.clasp.jsonも作成される
  3. ファイルを修正してアップロードしてみる。
    • $ clasp push で無事アップロードされていればOK

Webpackを導入しES6で書けるようにする

  1. yarn initしてpackage.json作成
  2. 以下パッケージ追加
    json
      "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-es2015": "^6.24.1",
        "dotenv": "^5.0.0",
        "gas-webpack-plugin": "^0.2.1",
        "webpack": "^3.10.0"
      }
    
  3. lib/entry.jsを以下の内容で作成
    js
    global.execute = function() {
      Logger.log('---- execute start ----');
      Logger.log('---- execute end ----');
    }
    
  4. webpack.config.jsを以下の内容で作成
    js
    var path = require('path');
    var GasPlugin = require("gas-webpack-plugin");
    
    require('dotenv').config();
    
    module.exports = {
      context: __dirname + '/lib',
      entry: './entry.js',
      output: {
        path: __dirname + '/src',
        filename: 'main.js'
      },
      plugins: [
        new GasPlugin()
      ],
      module: {
        loaders: [
          {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: /(node_modules|bower_components)/,
            query: {
              presets: 'es2015'
            }
          }
        ]
      },
    };
    
  5. ビルドしてみる npm run build
    • src/main.jsが作成されとけばOK
  6. この時点でclasp pushしちゃうとnode_modules配下もあげようとするので
    以下のような .claspignoreを作る
    **/**
    !src/main.js
    !appsscript.json
    
  7. またはルートディレクトリを変更してもOK .clasp,jsonを修正する
    {"scriptId":"x-x-x-x-x", "rootDir": "src"}

2018年3月17日土曜日

RxKotlinとRxBindingでフォームの入力チェック


RxBinding

AndroidのUIコンポーネントをRx~でよろしく触れるようにしたもの

:computer:環境構築


今回はKotlinを使うのでRxBindingもKotlin用をインストール
app/build.gradle
    // RxKotlin
    compile "io.reactivex.rxjava2:rxkotlin:$rxkotlin_version"

    // RxBinding
    compile "com.jakewharton.rxbinding2:rxbinding-kotlin:$rxbinding_version"

:pencil: 実装


簡単なサンプル

よくある以下のようなフォーム画面を作成してみる。
  • 入力フィールドに正しい値が設定されている時のみSubmitボタンが押せるようにする
レイアウトはこんな感じ
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/profileToolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        app:theme="@style/ToolbarTheme"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            app:errorEnabled="true"
            app:hintEnabled="true"
            app:hintAnimationEnabled="true">

            <EditText
                android:id="@+id/profileName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:maxLength="20"
                android:inputType="text"
                android:hint="@string/hint_name" />

        </android.support.design.widget.TextInputLayout>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hint_area"/>

        <Spinner
            android:id="@+id/spinnerArea"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:entries="@array/areas" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:paddingStart="50dp"
            android:paddingEnd="50dp"
            android:gravity="center">

            <Button
                android:id="@+id/profileSubmit"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/round_shape_disabled"
                android:enabled="false"
                android:text="@string/save"
                android:textColor="@android:color/white"/>

        </LinearLayout>

    </LinearLayout>
</LinearLayout>
image.png (43.9 kB)
名前とエリアを選択するフォーム画面
名前やエリアが未選択の場合Submitボタンが押せない状態にする
kotlin
        val nameChanges = profileName.textChanges()
        val areaChanges = spinnerArea.itemSelections()

        disposable += Observables.combineLatest(nameChanges, areaChanges)
                    {name, area -> name.isNotEmpty() && area != AdapterView.INVALID_POSITION}
                    .subscribe { isValid ->
                        profileSubmit.isEnabled = isValid
                        profileSubmit.setBackgroundResource(
                                if (isValid)
                                    R.drawable.round_shape_enabled
                                else
                                    R.drawable.round_shape_disabled)
                    }
名前とエリアのObservableをcombineLatestで結合し、
両方の値チェックでOKだった場合のみボタンを有効化しています。
name.isNotEmpty() && area != AdapterView.INVALID_POSITION
-> 名前が空でなく、spinnerの選択位置が空でない

オペレータ

  • distinctUntilChanged
    連続して重複したデータを通知しない
    kotlin
        disposable += Observable.fromArray(1, 2, 2, 3, 4, 5, 5, 6)
                .distinctUntilChanged()
                .subscribe(::println) // 1,2,3,4,5,6
    
  • combineLatest
    どちらか1つの送信される時、指定された関数によって、各Observableから送信される最新のアイテムを結合し、この関数の評価に基づいたアイテムを送信する
    kotlin
            val o1 = PublishSubject.create<Int>()
            val o2 = PublishSubject.create<String>()
    
            disposable += Observables.combineLatest(
                                    o1, o2
                                    ) { n, s -> "${n}/${s}"}
                                    .subscribe(::println)
            o1.onNext(1)
            o2.onNext("a")
            o1.onNext(2)
            o1.onNext(3)
            o2.onNext("b")
            // 1/a
            // 2/a
            // 3/a
            // 3/b
    

:bomb: バッドノウハウ


自分だけかもしれませんが、Observables.combineLatestObservable.combineLatest
(Observable(s)の違い)と書くとエラーになります。。