くらげになりたい。

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

CircleCIでFirebaseへ自動デプロイ(Hosting+Funcsions)

Nuxt.jsでアプリをSSRなアプリを作っているけど、
Gitでmasterにpushしたら、CircleCIで自動でデプロイしてほしいので、いろいろ調べたときの備忘録

Hostingだけの設定はあるけど、HostingとCloud Functionsを同時にする記事がなかったので、結構ハマった。。

ディレクトリ構成

ディレクトリ構成はこんな感じ

  • srcDirも変更しているし、
  • functionsのディレクトリにも、package.jsonがある
.
├── app            ... NuxtのsrcDir
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── pages
│   ├── plugins
│   ├── static
│   └── store
├── functions      ... Firebase FunctionのsrcDir
│   ├── index.js
│   └── package.json
├── nuxt.config.js
└── package.json

CI用のトークンを取得しとく

CIなどでfirebaseコマンドを使う場合には、CI用のトークンを利用する必要がある。
なので、まずは、ログインして、トークンを払い出す

$ firebase login:ci

circleci/config.ymlの中身

Firebase HostingとCloud Funtionsを同時にfirebase deployする設定は、こんな感じ。

version: 2
jobs:
  build:
    # 自分の環境のNode.jsと同じバージョンのDockerイメージを使う
    docker:
      - image: circleci/node:11.9.0
    # 対象のブランチはmasterのみ
    branches:
      only:
        - master

    working_directory: ~/repo

    # 環境変数の設定。WebUI上でも設定できるけど、YMLでもできる。
    environment:
      FIREBASE_PROJECT: <firebaseのプロジェクトID>
      FIREBASE_TOKEN: <firebase login:ciで払い出したトークン>

    # ここからが処理の始まり
    steps:
      # リポジトリをチェックアウトする
      - checkout

      #####################################################
      ### BUILD
      #####################################################
      # キャッシュのリストア処理。node_modulesを毎回installするのは重いので、キャッシュしとく
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "functions/package.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      
      # packageのインストール
      - run: npm install
      # functionsのほうもnpm installする
      - run: cd funcsions && npm install
      # デプロイのときにfirebaseコマンドが使えるようにinstall
      - run: npm install -D firebase-tools

      # キャッシュの保存処理。funstionsにもあるので、pathsに両方記載する
      - save_cache:
          paths:
            - node_modules
            - functions/node_modules
          key: v1-dependencies-{{ checksum "package.json" }}-{{ checksum "functions/package.json" }}
      
      # いざ、ビルド
      - run: npm run build
      
      #####################################################
      ### DEPLOY
      #####################################################
      
      # インストールしたfirebase-toolsは、node_modules/.bin/firebaseにあるので、それを使う
      # .firebasercにaliasを利用している場合は、firebase useする
      - run: node_modules/.bin/firebase use release --project "$FIREBASE_PJ" --token "$FIREBASE_TOKEN"
      - run: node_modules/.bin/firebase deploy --project "$FIREBASE_PJ" --token "$FIREBASE_TOKEN"

ハマったポイント

  1. run: cd functionsで移動してたけど、次のステップでは無効になっていて、functionsでnpm installできていないかった
  2. キャッシュのパスをhostingのほうしか指定していなくて、リストアがおかしい感じになってた
  3. .firebasercでaliasを使ってたのに、firebase useを使ってなかった

以下のような感じにしていたけど、なかなかうまくいかず。。 ハマったポイント満載だった。。

だめな例。ハマる例。

steps:
      - checkout

      #####################################################
      ### BUILD for Hosting
      #####################################################
      # キャッシュのリストア処理
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      
      - run: npm install
      - run: cd funcsions && npm install
      - run: npm install -D firebase-tools

      # キャッシュの保存処理
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}
      
      # いざ、ビルド
      - run: npm run build

      #####################################################
      ### BUILD for Function
      #####################################################
      
      # functions配下に移動   ・・・ ⇐ この時点では、「~/repo/functions」移動した
      - run: cd functions
      
      # キャッシュのリストア処理 ・・・ ⇐ この時点では、「~/repo」に戻ってる!!
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      
      - run: npm install
      - run: npm install -D firebase-tools

      # キャッシュの保存処理
      # !! cdが戻っているので、pathsは「~/repo/node_modules」だけ!!
      # !! さらに、keyがHostingの方と同じにっているので、キャッシュも意味なし!!
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }} 
      
      # デプロイのために、元の場所にcdした(つもりになってた。。)
      - run: cd ../
      
      #####################################################
      ### DEPLOY
      #####################################################
      
      - run: node_modules/.bin/firebase deploy --project "$FIREBASE_PJ" --token "$FIREBASE_TOKEN"

以上!!

参考にしたサイト様

AndroidのListViewで先頭に要素を自然に追加する

ツイッターみたいに、上方向の自動読み込みを実装したいなと思い、
ListViewの先頭に追加しても、スムーズにスクロールできる方法を、いろいろ調べたときの備忘録。

ほぼこの記事のまま、すごい。。
以下は、コメントとかを追加したくらい

// 要素を一番上に追加
int addItemSize = 1;
adapter.insert("added item", 0);

// 画面上で最上部に表示されているビューのポジションとTopを記録しておく
int pos = listview.getFirstVisiblePosition();
int top = listview.getChildAt(0).getTop();

adapter.notifyDataSetChanged();

// 追加した要素分したに移動して、要素追加前の状態になるようセットする
listview.setSelectionFromTop(pos + addItemSize, top);

// リストのトップにスクロールされている場合は新しい要素のところまでスクロールする
if (pos == 0 && top == 0) {
    listview.smoothScrollToPositionFromTop(pos, 0);
}

ポイント・気をつけること

  • setSelection系の前に、notifyDataSetChangedをしないといけない
    • ListViewがデータが変わったことを通知しないと、setSelection系が意図通り動かない
  • setSelectionではなく、setSelectionFromTopをつかう
    • setSelectionだと要素の先頭じゃないときに、カクっとなってしまう

他に試したこと

  • android:transcriptMode="disabled"は、効果がなかった
  • setNotifyOnChange(false)/setNotifyOnChange(true)とかを使ったけど、効果なかった

参考にしたサイト様

TypeScriptでNavitagorクラスのclipboardがエラーになる。。(回避方法)

Navigaterのclipboardでクリップボードコピーしたかったけど、
TypeScriptでエラーになったときに調べた備忘録。ただし、workaround..

ブラウザのサポートがまだなので、まだ対応していないっぽい。。

回避方法は型定義を追加してあげるだけ。

// navigator.clipboard.d.ts

// Type declarations for Clipboard API
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
interface Clipboard {
  writeText(newClipText: string): Promise<void>;
  // Add any other methods you need here.
}

interface NavigatorClipboard {
  // Only available in a secure context.
  readonly clipboard?: Clipboard;
}

interface Navigator extends NavigatorClipboard {}

以上!!

参考にしたサイト様

Android EmulatorでDBのバージョンを手動で戻す

正攻法は、バックアップ&リストアだけれど、開発中だとちょっとだけ戻して、
スキーマの更新の処理を確認したいときがある。

Androidで使うデータベースのバージョンは、SQLiteuser_versionを使っているので、
無理やり手動で変更すれば、OK。やり直しが簡単に試せる。

### 変更前
sqlite> .dbinfo
...
user version:        4
...

### versionを3に変更する
sqlite> PRAGMA user_version = 3;

### 変更後
sqlite> .dbinfo
...
user version:        3
...

以上!!

参考にしたサイト様

AndroidのEmulatorに入ってSQLite3でDBの中をみてみる

AndroidのEmulatorに入ってSQLite3でDBの中身をみるときの備忘録。
ひさびさにDBのマイグレーションをするときに、今どうだっけ?を見たかったが、
やり方忘れてたので、その時のメモ

Emulatorにログイン

$ adb shell

アプリの権限に変更

$ run-as <package-name>

dbの中を見る

$ sqlite3 database/<database-file-name>.db
# テーブル名の一覧を表示
sqlite> .table

# データベースの情報を表示
sqlite> .dbinfo
...
text encoding:       1 (utf8)
user version:        4
...

# テーブルのスキーマ情報の表示
sqlite> .schema --indent <table-name>
CREATE TABLE `<table-name>`(
  `_id` INTEGER PRIMARY KEY AUTOINCREMENT ,
  ...
);

# SQL実行結果のヘッダ表示のON/OFF
sqlite> .header on
sqlite> .header off

# SQLの実行
sqlite> select * from `<table-name>`;

# SQLiteの終了
sqlite> .exit

以上!!

nuxt-i18nでNuxt.jsの国際化して、英語版と日本語版を用意する

Nuxt.jsでアプリ作るけど、やっぱり国際化大事。英語版と日本語版は作っておきたい。。
nuxt-i18nを使うと簡単に多言語対応できたので、その時の備忘録。

インストール

$ npm install --save nuxt-i18n

設定

modules: [
  ['nuxt-i18n', {
    strategy: "prefix_and_default", // デフォルトのlangも言語指定のパスを生成する設定
    locales: [
      { code: 'ja', iso: 'ja_JP' },
      { code: 'en', iso: 'en_US' },
    ],
    defaultLocale: 'ja',
    vueI18n: {
      fallbackLocale: 'ja',
      messages: {                        // メッセージはここで設定
        "en": {
          "hello": "HELLO"
        },
        "ja": {
          "hello": "ハロー"
        }
      }
    }
  }]
],

用意したメッセージを表示する

テンプレート上

<template>
  <div>{{ $t("hello") }}</div>
</template>

SFC

export default {
  methods: {
    get_message() {
      return this.$t.bind(this)("hello");
    }
  }
}

localeを考慮したルーティング

テンプレート上
<nuxt-link :to="localePath('index')" />
SFC上で$router.push()
export default {
  methods: {
    go_to_index() {
      this.$router.push(this.localePath("index"));
    }
  }
}
midlleware上でredirect()
export default function({ app, redirect }) {
  redirect(app.localePath("index"));
}

小ネタ集

Vuetifyと一緒に使うときは、seoオプションを無効にして、自分でマージする

VuetifyでThemeを設定しているときは、seoオプションを無効にしないといけないぽい。
VuetifyはThemeを考慮したcssがheadに追加するが、nuxt-i18nがそれを消してしまう。

なので、nuxt.config.jsでseoオプションをOFFにして、自分でマージすればOK

// nuxt.config.js
modules: [
  [ "nuxt-i18n", {
    seo: false,
  }]
],
// layouts/default.vue

export default {
  head () {
    const i18nSeo = this.$nuxtI18nSeo()
    return {
      htmlAttrs: {
        // My HtmlAttrs
        ...i18nSeo.htmlAttrs
      },
      meta: [
        // My Metas
        ...i18nSeo.meta
      ],
      link: [
        // My Links
        ...i18nSeo.link
     ]
    }
  }
}

ブラウザの言語を検出する

nuxt-i18nは、デフォルトで、ブラウザの言語を検出して、いい感じにリダイレクトしてくれる。
これを制御するオプションがdetectBrowserLanguage

// nuxt.config.js

['nuxt-i18n', {
  detectBrowserLanguage: {
    useCookie: true, // 検出・設定した言語をクッキーに保存する設定
    cookieKey: 'i18n_redirected' // クッキーのキー名
  }
}]

クッキーを使わずに、毎回検出してほしい場合は、useCookiefalseにする。

// nuxt.config.js

['nuxt-i18n', {
  detectBrowserLanguage: {
    useCookie: false
  }
}]

そもそもブラウザの言語を検出してほしくなければ、detectBrowserLanguage自体をfalseにする

// nuxt.config.js

['nuxt-i18n', {
  detectBrowserLanguage: false
}]

メッセージをSCFに書けるようにする

// nuxt.config.js

modules: [
  ['nuxt-i18n', {
    // ...
    vueI18nLoader: true // vue-i18n-loaderを有効にする
  }]
],
<i18n>
{
  "en": {
    "hello": "HELLO"
  },
  "ja": {
    "hello": "ハロー"
  },
}
</i18n>
<template>
  <div>{{ $t("hello") }}</div>
</template>

メッセージを別ファイルにする

// message.json
{
  "en": {
    "hello": "HELLO"
  },
  "ja": {
    "hello": "ハロー"
  },
}
<i18n src="./message.json"></i18n>
<template>
  <div>{{ $t("hello") }}</div>
</template>

サイトマップi18n対応する

@nuxtjs/sitemapを使うだけ。ただ、nuxt-i18nのあとに設定する必要がある。
'nuxt-i18n'がrouteを生成したあとに、sitemapを作らないといけない。

modules: [
  ['nuxt-i18n', {
    locales: [
      { code: 'ja', iso: 'ja_JP' },
      { code: 'en', iso: 'en-US' },
    ],
  }],
  "@nuxtjs/sitemap"
],

以上!!

参考にしたサイト様

NuxtをTypeScript化するときのやりかた

Vue.jsとNuxt.jsでいろいろ作っているけれど、TypeScriptが素敵すぎる。。
create-nuxt-appだとTypeScriptがないので、TypeScript化するときにやることを整理したときのメモ

TypeScriptが使えるように設定する

0. ディレクトリ構成

ディレクトリ構成はこんな感じ。

  • プロジェクトはcreate-nuxt-appで作成
  • Firebase Functionsも使うので、app配下をsrcDirに変更している
.
├── app            ... NuxtのsrcDir
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── pages
│   ├── plugins
│   ├── static
│   └── store
├── functions      ... Firebase FunctionのsrcDir
│   ├── src
│   ├── package.json
│   └── tsconfig.json
├── nuxt.config.js
└── package.json

1. npmでパッケージのインストール

$ npm install --save nuxt-property-decorator vuex-class
$ npm install --save -D @types/node ts-loader typescript

2. index.d.tsの追加

TypeScriptで.vueファイルを利用できるようにapp/index.d.tsを追加する

declare module "*.vue" {
  import Vue from "vue";
  const _default: Vue;
  export default _default;
}

3. typesを配置するディレクトリを用意

nuxt-community/typescript-templateを参考に、 app/typesディレクトリと、以下のファイルを作成

  1. app/types/index.ts ... 空ファイル
  2. app/types/state.ts
export * from "./state";

4. modules/typescript.jsを追加

nuxt-community/typescript-templateのものをコピーしてくる。

export default function() {
  // Add .ts & .tsx extension to Nuxt
  this.nuxt.options.extensions.push("ts", "tsx");

  // Extend webpack build
  this.extendBuild(config => {
    // Add TypeScript
    config.module.rules.push({
      test: /\.tsx?$/,
      loader: "ts-loader",
      options: { appendTsSuffixTo: [/\.vue$/] }
    });

    // Add .ts extension in webpack resolve
    if (!config.resolve.extensions.includes(".ts")) {
      config.resolve.extensions.push(".ts");
    }

    // Add .tsx extension in webpack resolve
    if (!config.resolve.extensions.includes(".tsx")) {
      config.resolve.extensions.push(".tsx");
    }
  });
}

追加したモジュールをnuxt.config.jsに追加

module.exports = {
  modules: [
    "~/modules/typescript.js"
  ]
}

5. tsconfig.jsonを追加

これもnuxt-community/typescript-templateのものをコピーしてくる。

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "module": "es2015",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "noImplicitAny": false,
    "noImplicitThis": false,
    "strictNullChecks": true,
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": false,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./app/*"]
    }
  }
}

6. 設定後のディレクトリ構成

設定はこれで完了。変更後のディレクトリ構成はこんな感じ。

.
├── app            ... NuxtのsrcDir
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── modules
│   │   └── ★typescript.js
│   ├── pages
│   ├── plugins
│   ├── static
│   ├── store
│   ├── ★types
│   │   ├── ★index.ts
│   │   └── ★state.ts
│   └── ★index.d.ts
├── functions      ... Firebase FunctionのsrcDir
│   ├── src
│   ├── package.json
│   └── tsconfig.json
├── ★nuxt.config.js
├── package.json
└── ★tsconfig.json

.vueファイルの内容をTypeScriptに変更する

あとは、各.vueファイルを変更していく。

変更前

<script>
export default {
  head: function() {
    return { title: "タイトル" }
  },
  
  props: {
    name: { type: string, default: "なまえ" }
  },
  
  data(): {
    return {
      flag: false
    };
  },
  
  computed: {
    isEmptyName: function() {
      return this.name === "";
    }
  },
  
  methods: {
    toggleFlag: function() {
      this.flag = !this.flag;
    }
  }
}
</script>

変更後

  • Componentは、@Componentに設定する
  • data/propsは、fieldとして定義。props@Prop()が必要
  • computedは、getアクセサとして定義
  • methodやライフサイクルの関数は、methodとして設定する
<script lang="ts">
import { Component, Vue } from "nuxt-property-decorator";

@Component({}) // ※必須. コンポーネントを使ってなくても必要
export default class  extends Vue {
  /** head() */
  private head() {
    return { title: "タイトル" });
  }
  
  /** prop() */
  @Prop() name: string = "なまえ";

  /** data() */
  private flag: boolean = false;

  /** computed() */
  public get isEmptyName(): boolean {
    return this.name === ""; 
  }
  
  /** methods() */
  private toggleFlag(): void {
    this.flag = !this.flag;
  }
}
</script>

以上!! 型のある開発はやっぱり素敵

参考にしたサイト様