未熟学生エンジニアのブログ

プログラミング・Web開発をする大学院生のブログ

Vtuberユニット「ちゅこらら」について調べたこと

今回のブログはですます調とだである調が混じっているかもしれません。

イラストレーターparyiさんの配信を見た

なんとなくツイートを見て配信を見た。

初めて男性であることを知った。

深夜の配信を見た。Vtuberとのコラボ配信だった。

Vtuber配信終わるところで「いかないで」とかコメントあるの面白すぎる。

この「いかないで」は考えさせられるものがあった。

永久にこの頭を馬鹿にしていい時間が続けばいいのに。そういったことを考えているのではないか。

ちゅこららのメンバー

全部で6人いる。もともと7人(当時は5人?)いたらしいが、一人引退してしまったとのこと。このあたりは個人Vtuberとしての柔軟さがあるように思える。

古守ちゆ

吸血鬼で1200歳のロリVtuber。のじゃロリではない。個性としては一番ないのかもしれない。が個人的にはあまり他Vtuberを知らないので問題はなかった。

www.youtube.com

この子守ちゆはリスナーのことを「お肉」と呼ぶ。人間をやめて「お肉」になってしまいたいと思った。

Twitterでお肉を「お肉監視リスト」なるリストに入れている。これは他のVtuberもしてるんだろうか。入れてほしい。

千鈴めい

弾き語り動画やゲーム実況を上げているのが「千鈴めい」。猫又という妖怪?らしい。古守ちゆの元飼い猫らしい。個人的には声が好み。

弾き語りというジャンルは多分Vtuberでもやってる人少ないんじゃないだろうか。もはや現実とだいぶ近くなってる。こうやって個性を出していくのかな。 もはやVtuberは単なる一個性でしかない時代なんだなあと思う。

声がバンドやってそうな人の声。若干のハスキーボイスっていうんだろうか。すごくそれっぽい感じがする。

https://m.youtube.com/watch?v=1jcPGOvoSvQ&t=3150s

こっちのコラボ配信では、1時間ゲーム配信?の設定で配信時間が遅れて、その謝罪として即興で「詫びオーレ」(「カフェオーレが、飲みたいの♫」の替え歌)を弾き語りするという、色々面白いし実力派なところを見せつけている。笑

https://youtu.be/nBYGstafPis

1:10:00 付近

アンコール的な演出。

当然めちゃくちゃうまいとかそういうのではないけど(失礼だけど)、生配信という形にすごくあってるし、「(セルフ)アンコール」的な演出にちょっと痺れた。

1:48:30~

www.youtube.com

暁ちあき

サキュバスで警察勤務のお姉さん。ささやきボイスで、声の個性が一番ある。話のテンポがかなりゆったり。話が割と途切れるから、寝る時に聞くのがおすすめ。

好き勝手やってる他のちゅこららメンバーと違って、割と演技力が求められそう(地声がこれなのかな?)。でもコラボ配信ではキャラ的にあんまり喋らなくて、楽そう。笑

www.youtube.com

真白かのん

パチンカス系清楚Vtuber。12歳のエルフ。たまにたばこを吸っているような息使いもするwが、さすがにネタである・・?

www.youtube.com

他のちゅこららの配信者よりもリアルに近い話題が多いような気がして、友達と話しているような感覚になれるVtuber

「台パン」(パチンコの台をイライラしたときたたくこと)というワードが出てくるVtuberは珍しい・・よね。個人Vtuberだからこそなせる業?

他にも2人いる。全部で6人

ここでは全員は語りません。他にも2人いて、「乙女おと」っていう「5000兆円集金業者」がいる。面白いんだけど割と王道感がある他キャラと違い個性のだし方が個人的に少し滑ってるような・・でも話題性は一番あるし更新頻度も多いので頑張って欲しい。

www.youtube.com

追記:「緋赤エリオ」は、女騎士団長。あまりよくしらないが謙虚な姿勢がみられる感じがよい。

bilibiliへの投稿システムや配信体系が気になった

海外人気のあるイラストレータをママにしてキャラクターを作ることで海外人気の高いVtuberを作ろうっていう作戦なのか。後発組からすると楽なのかも。

bilibiliにも動画が上がっている

疑問: bilibili用に翻訳するシステムが気になるな やっぱり企業が絡んでるのか、単に個人で頼んでるのか、はたまたGoogle翻訳なのか

https://www.bilibili.com/video/av49522875

これに対しての答えとして、同じ「ちゅこらら」のVtuber乙女おとの動画では、企業がらみではなく個人Vtuberであると明言されている

https://youtu.be/AiIkKSrZ_e8

bilibili動画の収益は15%が翻訳者(翻訳だけではないと思うが)に渡されているらしい。15%というのが多いのか少ないのかは分からない。

ゲーム配信が多い

他のVtuberのことはあんまりわからないが、ゲーム配信が結構ある。グループVtuberだからやりやすいっていうことっぽい。

Vtuberと一緒にゲーム。これはしてみたい!とひさしぶりにやりたいことを見つけた感覚になった。

当然現実の女の子とリアルでゲームしたほうが楽しいだろうが声をかけるにも相手がいないしいたところでかわいいと思えるかという問題もある。それに現実は面倒ごとが多すぎる。

単純に楽しい。それで恐らく終わりな関係というのは最高だろうなと想像した。

注釈

Vtuber界で猫又ってジャンル流行ってるから(猫宮ひなた、ねこます等)のか、と思ったら、ちゅこらら自体が人外というか妖怪ユニットという設定、いや事実があるらしいから

【開発参加検討者向け】フリマサービス「ホクマ」の技術構成

今回は、北大IT研究会メンバーが開発・運営する「ホクマ」の技術構成についてお話しします。

技術的な話に興味がないという方はブラウザバックしてください。

今回はホクマの開発を引き継ぎたい方に向けての記事になります。

ホクマとは

まず、ホクマとは何なのか、知らない方に3行で説明します。

  • 北大生だけが使えるフリマWebサービス
  • 使わなくなった教科書などが売り買いされている
  • 直接会って渡すので完全手数料0。高く売れる、安く買える!

もっと知りたい方は以下の記事をお読みください。

hu-jagajaga.com

技術構成図

f:id:swiftfe:20190420175841j:plain
ホクマ技術構成図

一つずつ解説

1. Django

  • これは何?:Python製のWebアプリケーションフレームワーク(WAF)。ほぼ全ての処理はまずここから始まる。
  • なぜ必要?:サーバー側でPythonプログラムを動かすために必要。商品情報を保存するためにデータベースにアクセスしたり、ブラウザにHTMLを返したりする。ここにhtml,css,jsも含まれる。

2. PostgreSQL

  • これは何?:データベース。データベースを管理するシステムおよび、データベースを操作する言語。商品データを保存・更新するとき等に使う。
  • なぜ必要?:これがないとユーザが新しいデータを追加したり更新したりできない。また、効率よく保存したデータを検索したりするために必要。

3. Nginx

  • これは何?:Webサーバ。ユーザ(クライアント)からのリクエストをDjangoに伝え、リクエストを受け取ったDjangoから返されたHTML・CSSなどのファイルにメタ情報を追加した情報をブラウザに返す。ユーザからのリクエストをうまくさばくための色々な機能ももっている。
  • なぜ必要?:複数のユーザからの同時接続に耐えるために必要。HTTPS通信のために必要。ブラウザ側に快適な動作をさせるためにDjangoから受け取った情報に何かを追加したり加工したりする。

もっと詳しく知りたい

これらの記事を読むために必要な予備知識:DjangoなどのことをAPサーバー(アプリケーションサーバー)、NginxなどのことをWebサーバーと呼ぶことがあります。

4. Bootstrap(4)

  • これは何?:css・jsラリブラリ。Webページの見た目や使いやすさを向上させるために必要。htmlのclass名を指定するだけで、cssやjsを書かずに便利な機能をつけることができる。
  • なぜ必要?:見た目をかっこよくさせるために必要。PC・スマホ両対応も簡単にできる。Webページでインタラクティブな操作は必須だが、これを簡単に作れる。

5. Let's Encript

  • これは何?:HTTPS通信をするための証明書の管理をしてくれるツール(無料)。コマンドを使って証明書を更新できて便利。
  • なぜ必要?:ブラウザから送られるユーザのパスワードなどを暗号化して、通信路上で盗まれてもわからないようにする通信(HTTPS通信)を行うために必要。

6. Amazon AWS Sinple Storage Service(S3)

  • これは何?:画像やCSS・JSなどを保管しておく場所。不特定多数用のDropboxみたいな感じ。
  • なぜ必要?:画像などの容量が多いものをこのように別サーバに保管しておくことで費用を抑えるため。また、CloudFrontなどの別AWSサービスと連携させ、ユーザの位置と近いサーバーに画像などのファイルを置いて物理的にアクセス速度を高めるためにも必要。

7. Linuxサーバ(ConohaVPS)

  • これは何?:いろんなプログラムを動かすためのOS及びコンピュータ本体。
  • なぜ必要?:1,2,3など複数のプログラムを並行的に動かすために必要。自分のPCではないコンピュータで外部公開するという意味でも必要。

8. (Optional) Docker

  • これは何?:VMよりも軽量な仮想環境を作成できるツール。本番用サーバーと本番の一個手間サーバ(QAサーバー)、ローカルPCなどでほぼ同じ環境をコピーして使うことができて便利。
  • なぜ必要?:本番用サーバーとQAサーバー、ローカルPCなどでほぼ同じ環境をコピーして使いたいときに必要。仮想環境なので、開発初期に色々試して失敗してもすぐ消してやり直したりできる点も良い。

9. postfix

  • これは何?:メールの送受信用のツール。
  • なぜ必要?:ユーザへの通知をメールで送ったり、お問い合わせメールを受け取ったりする際に利用します。

おまけ

CI(継続的インテグレーション)ツール:CircleCI

  • これは何?:Djangoなどのプログラム修正したときに自動的にテスト(プログラムが意図した動作をするか自動確認すること)などを行ってくれるツール。
  • なぜ必要?:ホクマのように日々改善を続けるサービスに対してバグがないか改善のたびにチェックするために必要。

GitHub

  • これは何?:Gitというソースコード自体と更新差分を管理するツールをweb上で操作できる+タスク管理・コードレビューもできるwebサービス
  • なぜ必要?:ホクマのように日々改善を続けるサービスに対して複数人で更新状況を管理しやすくするために必須。使っていない企業は時代遅れ扱いされるが授業では習わない。

終わりに

ホクマはある程度saleorというリポジトリを意識した構成にしており、割とまともなサービス構成になっているかと思います。ホクマをまともに理解し運用・改善し続けることができれば、まともな個人開発経験があると言ってもよいのではないかと思っています。ホクマはまともに稼働してくれる開発・運用メンバーを募集しています。興味ある方はぜひご連絡ください!

もっと知りたい方へ

もう少し細かく書いたシリーズがあるので、そちらもよろしければご覧ください。

swiftfe0.hatenablog.com

連絡先

SPA・サーバレスハンズオン part3 React/Firebase Cloud Firestoreでチャットアプリを作る

シリーズ

前提

  • part1part2をやっている
  • html,javascriptを一度でも書いたことがある
  • プログラミングの「クラス」と「継承」を使ったことがある
  • Firebaseに登録している

今回できるようになること

  • Reactの基本の流れがわかる
  • Reactのstate管理の方法がわかる
  • Firestoreと連携してデータベースを使ったチャットを作れる
  • 次にやるべきことがわかる

チャットアプリを作る

今回はチャットアプリを作ります。

f:id:swiftfe:20190501071018p:plain

f:id:swiftfe:20190501073412p:plain

今回のアプリは以下の記事のFirestore版のようなものになります。

サンプル

デモページ: https://huit-tetsufe-0506.firebaseapp.com/ ソースコード: https://github.com/TetsuFe/huit_react_handson_20190506

参考

FirebaseのFirestoreを使うための初期設定をする

FirebaseにはHosting以外にもいろんな便利機能があります。

Cloud Firestore(Firestore)は、Firebaseのデータベースサービスで、web上にデータを保存したり、取得したりするのに使えます。

Firestoreの立ち位置としては以下の図のようになります。

f:id:swiftfe:20190501120118p:plain

f:id:swiftfe:20190501120121p:plain

さて、Hostingの際と同じように、Firebaseのセットアップをしましょう。

Firebaseのコンソール画面から、Firestoreを選択します。

f:id:swiftfe:20190429132020p:plain

f:id:swiftfe:20190429132026p:plain

f:id:swiftfe:20190429132031p:plain

これでひとまず初期設定は完了です。

今回はテストモードとして誰でもデータの改変が行えるようにしましたが、これは非常に危険です。Firestore側での認証設定やセキュリティルールは適切に設定しておかないとデータの改変が自由に行えてしまいます。(今回のように重要なデータでない場合は問題ありません)

reactページ側からFirestoreにアクセスするためのモジュールをインストールします。

$ npm install firebase --save

FirestoreにアクセスするためにはapiKeyなどの情報が必要です。

Firebaseの画面から、APIキーとprojectidをコピーします。これはサイトに埋め込んでも大丈夫です。

参考:Firebase apiKey ってさらしていいの? ほんとに? - Qiita

f:id:swiftfe:20190429133406p:plain

f:id:swiftfe:20190429132949p:plain

上の例では、以下のようにファイルを作成します。(値は全員少し違うはずです)

firebaseフォルダを作り、その中にconfig.jsを作りましょう。

firebase/config.js

export const firebaseConfig = {
    apiKey: 'AIzaSyBTXINX3dCCSCQcwypUxk7wqRiEYbxqgw0',
    projectId: 'huit-tetsufe-0506'
};

フォルダの構成は以下のようになっているはずです。

huit_handson_20190506
├── firebase
│   └── config.js

さらに、firebase/index.js というファイルを作り、データベースを操作するためのfirebaseDbというインスタンスを他のファイルでも使い回せるようにします。

firebase/index.js

import firebase from 'firebase';
import { firebaseConfig } from './config.js';

export const firebaseApp = firebase.initializeApp(firebaseConfig);
export const firebaseDb = firebaseApp.firestore();

フォルダの構成は以下のようになっているはずです。

huit_handson_20190506
├── firebase
│   └── config.js
│   └── index.js

Reactとコンポーネント

Reactは、コンポーネントという単位で表示する要素を小さなモジュール(ファイル)に分割することを推奨しています。

要は要素ごとに細かく分けるだけです。難しいことではありません。分ける単位もとりあえずは適当でいいでしょう。

コンポーネントを作る前に、srcフォルダの中に、componentsフォルダを作成しましょう。

componentsというフォルダ名に意味はありません。なぜ作る必要があるのでしょうか?

単にわかりやすいようにフォルダわけしただけです。ただ、これは地味に重要なことです。画面が複雑になればファイル数も多くなり、ファイルはどんどん見つけにくくなるからです。複雑になっていけば、componentsの中にさらにフォルダを作るなどする必要が出てくると思います。

src/components/Message.jsというファイルを作りましょう。

import React from "react";

export default class Message extends React.Component {
    render() {
        return (
            <div className="Message">
                <img className="" src={this.props.message.profile_image} />
                <div className="">
                    <p className="">@{this.props.message.user_name}</p>
                    <p className="">{this.props.message.text}</p>
                </div>
            </div>
        );
    }
}

さらに、src/components/ChatForm.jsというファイルを作りましょう。

import React from "react";

export default class ChatForm extends React.Component {
    render() {
        return (
            <div className="ChatForm">
                <div className="">
                    <input name='user_name' onChange={this.props.onTextChange} className="" placeholder="名前" />
                    <input name='profile_image' onChange={this.props.onTextChange} className="" placeholder="プロフィール画像URL" />
                </div>

                <textarea name='text' className="" onChange={this.props.onTextChange} />
                <button className="" onClick={this.props.onButtonClick}>送信</button>
            </div>
        );
    }
}

こんな感じの構造になっているはずです

$ tree src
src
├── App.css
├── App.js
├── App.test.js
├── components
│   ├── ChatForm.js
│   └── Message.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js

src/App.jsを編集して、MessageコンポーネントとChatFormコンポーネントを使いましょう。

import React from 'react';
import './App.css';
import { firebaseDb } from './firebase/index.js'
import Message from './components/Message.js'
import ChatForm from './components/ChatForm.js'

const messages = firebaseDb.collection('messages')

class App extends React.Component {
  constructor(props) {
    super(props);
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
    this.state = {
      text: "",
      user_name: "",
      profile_image: "",
      messages: []
    }
  }

  componentDidMount() {
    messages.onSnapshot
      ((querySnapshot) => {
        // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう
        let msgs = []
        querySnapshot.forEach((doc) => {
          // 新しい順に取得される
          const m = doc.data();
          msgs.push({
            'text': m.text,
            'user_name': m.user_name,
            'profile_image': m.profile_image,
          })
        });
        this.setState({
          messages: msgs
        });
      });
  }

  onTextChange(e) {
    if (e.target.name == 'user_name') {
      this.setState({
        "user_name": e.target.value,
      });
    } else if (e.target.name == 'profile_image') {
      this.setState({
        "profile_image": e.target.value,
      });
    } else if (e.target.name == 'text') {
      this.setState({
        "text": e.target.value,
      });
    }
  }

  onButtonClick() {
    // 簡単なバリデーション
    if (this.state.user_name == "") {
      alert('user_name empty')
      return
    } else if (this.state.text == "") {
      alert('text empty')
      return
    }
    messages.add({
      "user_name": this.state.user_name,
      "profile_image": this.state.profile_image,
      "text": this.state.text,
    })
      .then(function (docRef) {
        console.log("Document written with ID: ", docRef.id);
      })
      .catch(function (error) {
        console.error("Error adding document: ", error);
      });
  }

  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h2>Chat</h2>
        </div>
        <div className="MessageList">
          {this.state.messages.map((m, i) => {
            return <Message key={i} message={m} />
          })}
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
      </div>
    );
  }
}

export default App;

Reactのコードを読み解く

ここからはコードの解説に入ります。

src/App.js を見てください。src/App.jsでは、Appというクラスが宣言されています。今回の場合はこれだけです。ここからはAppクラスについて解説します。

まず、Appクラスですが、これはReact.Componentというクラスを継承する形で宣言されています。

import React from 'react';
// 省略

class App extends React.Component {
// 省略
}

React.Componentを継承する理由とはなんでしょうか。どういう意味を持っているかこれから説明します。

React.Componentを継承すると起こること

  • 1.<App/>などと書くことでReactのコンポーネントとして好きな場所に埋め込むことができる
  • 2.ライフサイクルメソッドを自動的にいいタイミングで実行してくれる
    • componentDidMound()
    • render()
    • その他

1. <App/>などと書くことでReactのコンポーネントとして好きな場所に埋め込むことができる

src/index.jsで、<App/>として使われています。React.Componentを継承すると、Reactのコンポーネントとして扱うことができます。これはとりあえずそういうルールなのだと覚えましょう。(おまじないが嫌い?ならばこれを読みなさい。React.Component – React

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
// 省略

ReactDOM.render(<App />, document.getElementById('root'));

/// 省略
serviceWorker.unregister();

「好きな場所に埋め込むことができる」とはどういうことでしょうか。これはAppクラスのrender()内を見ると意味がわかります。

以下の<ChatFrom>もReact.Componentを継承しています。(src/components/ChatForm.jsを見ればわかります)

src/App.js

// 省略
class App extends React.Component {
  // 省略
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h2>Chat</h2>
        </div>
        <div className="MessageList">
          {this.state.messages.map((m, i) => {
            return <Message key={i} message={m} />
          })}
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
      </div>
    );
  }
}

以上のように書くことで、<ChatFrom><div className="App-header">よりも下になるように配置できます。

f:id:swiftfe:20190501034849p:plain

2. ライフサイクルメソッドを自動的にいいタイミングで実行してくれる

1はまあそういうものか、という感じだと思ってもらえればOKです。2の方が重要です。

React.Component を継承している Appクラスには、componentDidMound()などの、あるタイミングで自動的に実行されるメソッドがあります。

これらの あるタイミングで自動的に実行されるメソッド群を、ライフサイクルメソッドといいます。

モバイルアプリやフレームワークを使ったフロントエンド開発では同じような概念が出てくるので、ここでライフサイクルメソッドというものの存在に慣れておくとReact以外を使う際に対応しやすいです。

Reactのライフサイクルメソッドの流れは、以下のようになります。

https://github.com/iktakahiro/react-component-lifecycle-diagram/blob/master/react-v16.2/react-component-lifecycle.png?raw=true

引用元:GitHub - iktakahiro/react-component-lifecycle-diagram

初期化とマウントに関するライフサイクルメソッド

参考:React コンポーネントのライフサイクルイベント その1 - React 入門

Appの初期化の際、まず最初に自動的に実行されるのがconstructor(props)というメソッドになります。

constructor(props)は、Appが作成されたときに一回だけ実行されます。このメソッドでは、this.stateなどの変数の初期化を主に行います。

src/App.js

class App extends React.Component {
  constructor(props) {
    super(props);
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
    this.state = {
      text: "",
      user_name: "",
      profile_image: "",
      messages: []
    }
  }
  // 省略
}

次がrender()です。render()は返り値を持ちます。returnで値を返していることに気をつけてください。

render()は、htmlを模したjsxという記法で書かれたオブジェクトを返します。つまり、これはhtmlではありません。jsxという独特の記法で、これはjavascriptのオブジェクトです。<div className="App">は、htmlではありません。javascriptです。

class App extends React.Component {
  // 省略
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h2>Chat</h2>
        </div>
        <div className="MessageList">
          {this.state.messages.map((m, i) => {
            return <Message key={i} message={m} />
          })}
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
      </div>
    );
  }
}

とはいっても、とりあえずは以下のように書けばよいと考えていればOKです。

class App extends React.Component {
  // 省略
  render() {
        return (
          // ここにhtmlっぽいjsxというものを書く
        );
  }
}

jsxの書き方は、色々書いてみてなれるしかないと思います・・

次がcomponentDidMount()です。今回は、Firestoreからのデータ取得と、React内のthis.stateの更新をしています。(実際には、データが更新された際にその処理をするという意味になります)

src/App.js

class App extends React.Component {
  // 省略
  componentDidMount() {
    messages.onSnapshot
      ((querySnapshot) => {
        // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう
        let msgs = []
        querySnapshot.forEach((doc) => {
          // 新しい順に取得される
          const m = doc.data();
          msgs.push({
            'text': m.text,
            'user_name': m.user_name,
            'profile_image': m.profile_image,
          })
        });
        this.setState({
          messages: msgs
        });
      });
  }
  // 省略
}

Reactの状態管理:setStateとライフサイクルメソッド

ここまででAppクラスの初期化の際の流れを知ることができたと思います。ここからは、ReactのsetStateとライフサイクルメソッドの関係について説明します。

Reactを使ったWebアプリでは、ユーザのアクションやDBのデータの変更に応じて変数(状態)を更新し、画面(ページ)を適宜書き換えるということを行います。例えば、チャット中に新しい投稿があった時、それを画面に反映するという場合をイメージしてください。

このような場合に使うのが、setStateメソッドです。

this.setState({
      messages: msgs
});

先ほどの図を再掲します。

https://github.com/iktakahiro/react-component-lifecycle-diagram/blob/master/react-v16.2/react-component-lifecycle.png?raw=true

引用元:GitHub - iktakahiro/react-component-lifecycle-diagram

ここで、setStateを実行した時、図の中央の黒丸の「state changed」が起こり、ライフサイクルメソッドが順に実行されます。図中ではshouldComponentUpdateなどのメソッドがありますが、今回はrender()だけ考慮すればOKです。

簡潔に言えば、setStateは、変数(状態)を変更した上で、render()などのライフサイクルメソッドを起動し、画面を更新するためのメソッドです。

src/App.jsをみてみましょう。

ここでは、チャットのDBデータが更新されたとき、データに合わせて画面を更新するという例を考えます。

  1. setStateは、firestoreから受け取ったデータmsgsを使ってthis.state.messagesを更新
  2. その後、setStateに反応したライフサイクルメソッドrender()が自動的に実行され、更新されたthis.state.messagesを使ってメッセージリストを画面に表示することができる

setStateはcomponentDidMount()内で実行されているように見えますが、実際にはcomponentDidMount()が呼ばれたタイミングとsetStateが実行されるタイミングは違う(ややこしいですが)のに注意してください。

class App extends React.Component {
  // 省略
  componentDidMount() {
    messages.onSnapshot
      ((querySnapshot) => {
        // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう
        let msgs = []
        querySnapshot.forEach((doc) => {
          // 新しい順に取得される
          const m = doc.data();
          msgs.push({
            'text': m.text,
            'user_name': m.user_name,
            'profile_image': m.profile_image,
          })
        });
        this.setState({
          messages: msgs
        });
      });
  }
  // 省略
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h2>Chat</h2>
        </div>
        <div className="MessageList">
          {this.state.messages.map((m, i) => {
            return <Message key={i} message={m} />
          })}
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
      </div>
    );
  }
}

Firestoreに保存するデータ構造

Firestoreに保存するデータ構造としては、messagesという箱(FirestoreではCollectionといいます)を用意して、その中にメッセージを追加していく形になります。以下がデータの例になります。

{
    "messages": [
        {
            "1": {
                "profile_image": "https://pbs.twimg.com/profile_images/1064542710327963648/eN983mCr_400x400.jpg",
                "text": "こんにちは",
                "user_name": "僕"
            }
        },
        {
            "2": {
                "profile_image": "http://hyohyolibrary.com/wp-content/uploads/2018/07/oregairuzoku9thumbnail.png",
                "text": "やっはろー",
                "user_name": "由比ヶ浜結衣"
            }
        }
    ]
}

Firestoreのaddメソッドを使うことで投稿処理を実装します。(注意:addメソッドの場合、新しいデータはcollectionの中で雑な順番で保存されます。全てのメッセージを一気に取得する際、順番はaddした順にはならないということです。チャットなどの場合、上から新しい順に表示するか上から古い順に表示するかという順番は重要です。例えばLINEの場合は上から古い順に表示ですよね。order_byを使うなどすることで、この問題は解決できます。https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja

src/App.js

  onButtonClick() {
    // 簡単なバリデーション
    if (this.state.user_name == "") {
      alert('user_name empty')
      return
    } else if (this.state.text == "") {
      alert('text empty')
      return
    }
    messages.add({
      "user_name": this.state.user_name,
      "profile_image": this.state.profile_image,
      "text": this.state.text,
    })
      .then(function (docRef) {
        console.log("Document written with ID: ", docRef.id);
      })
      .catch(function (error) {
        console.error("Error adding document: ", error);
      });
  }

フォームのボタンを押したとき、onButtonClicked()が呼ばれて(関数が実行されることを関数が呼ばれるという言い方をします)、onButtonClicked()の中でFirestoreのaddメソッドによってDBにデータを追加します。

DBデータの変更に応じたリアルタイム更新

チャットの場合、相手からの返事が来たときに自動的にページに反映されると嬉しいですよね。Firestoreには、それを実現できる機能があります!

FirestoreではonSnapshotメソッドを使うことで、DB上のデータがアップデートされたとき、自動的に接続中のクライアントのデータにも反映することができます。(getメソッドではできません)

ちなみに内部ではwebsocket通信をしているようです

参考:https://firebase.google.com/docs/firestore/query-data/listen?hl=ja

src/App.js

class App extends React.Component {
  // 省略
  componentDidMount() {
    messages.onSnapshot
      ((querySnapshot) => {
        // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう
        let msgs = []
        querySnapshot.forEach((doc) => {
          // 新しい順に取得される
          const m = doc.data();
          msgs.push({
            'text': m.text,
            'user_name': m.user_name,
            'profile_image': m.profile_image,
          })
        });
        this.setState({
          messages: msgs
        });
      });
  }
  // 省略
}

stateとprops

参考:React における State と Props の違い - Qiita

最後に、stateとpropsについて説明します。といっても、以下の2点を覚えておけばとりあえず問題ありません。

  • state: クラス内でsetStateを使って変更・更新したい変数として使う(更新される)
  • props: 他のコンポーネント(親コンポーネント)から受け取った変数(更新されない)

またsrc/App.jsの例をみてみます。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
    this.state = {
      text: "",
      user_name: "",
      profile_image: "",
      messages: []
    }
  }

  componentDidMount() {
    messages.onSnapshot
      ((querySnapshot) => {
        // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう
        let msgs = []
        querySnapshot.forEach((doc) => {
          // 新しい順に取得される
          const m = doc.data();
          msgs.push({
            'text': m.text,
            'user_name': m.user_name,
            'profile_image': m.profile_image,
          })
        });
        this.setState({
          messages: msgs
        });
      });
  }

  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h2>Chat</h2>
        </div>
        <div className="MessageList">
          {this.state.messages.map((m, i) => {
            return <Message key={i} message={m} />
          })}
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
      </div>
    );
  }
}

ここで、stateとして使われているのはtext, user_name, profile_image, messagesです。this.state.text, this.state.user_nameのようにアクセスすることができます。これら変数の値はこのクラス内で更新されます。そのため、このクラス内でthis.stateとして宣言します。これがstateです。

constructor(props) {
    super(props);
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
    this.state = {
      text: "",
      user_name: "",
      profile_image: "",
      messages: []
    }
  }

一方で、propsは、他のコンポーネントから受け取った変数のことを指します。もっというと、渡す側はstateとして扱っている変数が、渡された側ではpropsになります。

            <div className="MessageList">
              {this.state.messages.map((m, i) => {
                return <Message key={i} message={m} />
              })}
            </div>

この3行目のmessage={m}という部分で、変数の受け渡しが起こっています。Messageコンポーネント側から見ると、messageというpropsを受け取っています。

7行目をみてください。this.props.messageという形で使っていることがわかると思います。

src/components/Message.js

import React from "react";

export default class Message extends React.Component {
    render() {
        return (
            <div className="Message">
                <img className="" src={this.props.message.profile_image} />
                <div className="">
                    <p className="">@{this.props.message.user_name}</p>
                    <p className="">{this.props.message.text}</p>
                </div>
            </div>
        );
    }
}

propsとstateを分ける意味

propsはあくまで受け取るだけで、その値は受け取ってから変更されることがありません。これは、コードを読むときに変更されうる値かどうかがわかりやすくなるという効果があります。言い換えれば、そのコンポーネント内にpropsを更新する処理が書かれていないことを示します。propsの更新にバグがあると感じたときは、親コンポーネントを見ればいい、ということに瞬時に気づくことができます。

Firebase Hostingを使ってデプロイ(web公開)する

せっかくチャットを作ったので、友達や家族とチャットができるようにweb上に公開しましょう!

前提:Firebase Hostingの初期設定をpart1でやっていること

ターミナルを開いて、以下を実行します。($は省略してください。)

$ npm run build
$ firebase deploy --only hosting

これでみんなとチャットができます!

おまけ:CSSを書いてみる

ここまでのサンプルコードはかなりひどい見た目になっていると思います。

f:id:swiftfe:20190501071018p:plain

これをまともな見た目にして、恥ずかしくないようにします。

参考

src/components/Message.js

import React from "react";

export default class Message extends React.Component {

    render() {
        const style = {
            "item": {
                display: "flex",
                alignItems: "flex-start",
                marginBottom: "0.8em",
            },
            "itemImage": {
                width: "100px",
                height: "100px",
                objectFit: "cover",
                borderRadius: "20px",
            },
            "itemName": {
                textAlign: "left",
                fontSize: "75%"
            },
            "itemMessage": {
                position: "relative",
                display: "inline-block",
                padding: "0.8em",
                background: "#deefe8",
                borderRadius: "4px",
                lineHeight: "1.2em",
            }
        }

        return (
            <div className="Message" style={style.item}>
                <img style={style.itemImage} src={this.props.message.profile_image} />
                <div className="">
                    <p style={style.itemName}>@{this.props.message.user_name}</p>
                    <p style={style.itemMessage}>{this.props.message.text}</p>
                </div>
            </div>
        );
    }
}

f:id:swiftfe:20190501073307p:plain

「chat」という文字がデカすぎるので、なんとかします。

src/App.css

.App {
  text-align: center;
}

.App-header {
  background-color: #282c34;
  min-height: 10vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

min-height: 10vh としたので、小さくなりました。

f:id:swiftfe:20190501073412p:plain

画面が大きいと不恰好ですが、疲れたのでここまでにします。後はcss職人のみなさんの頑張りに期待します。cssむずいです。

ちなみに、CSSは以下の資料がわかりやすいので一度見てみるといいと思います。

speakerdeck.com

saruwakakun.com

Next Step Hint 1: 複数ページのサイトを作る

今回作ったサイトは一ページだけでした。複数のページにまたがっているパターンが普通だと思います。

react-routerというライブラリを使えば、SPAでも複数のページを表現できます。

qiita.com

Next Step Hint 2: デザインをGoogleっぽくする

Google系のサイトで使われているMaterial Designというデザインルールに準拠したコンポーネントを使って見た目をいい感じにすることもできます。Youtubeなどを見るとイメージがわきやすいかと思います。

The world's most popular React UI framework - Material-UI というライブラリを使えば、簡単にMaterial Designなサイトをつくれます。

例えばボタンは以下のリンクに作り方がのってます。

material-ui.com

導入方法は以下を参考にすると良いかと思います。

rennnosukesann.hatenablog.com

公式サイトに色々なパーツの使い方が載っているので、色々試してみると面白いと思います。

f:id:swiftfe:20190501064044p:plain

Next Step Hint 3: Firestoreを使ってDBの処理に慣れる

以下の記事がわかりやすそうでした。

React.js Firebase Tutorial: Building Firestore CRUD Web Application

シリーズ

SPA・サーバレスハンズオン part2 一般的なwebページとReactアプリの構成を知る

シリーズ

Webページの主な構成要素

ブラウザでは主にhtml・cssjavascriptの3種類のファイルを扱う

  • HTML
    • ページの構造を決める。ブラウザ処理の起点になり、必要なcssやjsはここから読み込む
  • CSS
    • ページのレイアウト・色などを設定する
  • JavaScript(JS)
    • ページを書き換える、計算や通信などをするプログラムを実行する

Reactとは

Reactは、状況に応じて動的に書きかわるようなページを書きやすくするためのJavaScriptフレームワークです。使えばなんとなくわかります。

なぜReactを使うのか?

こちらの記事にまとめました。気になる方は読んでみてください。

なぜReact.jsを使うのか、jQueryやVue.jsとの違いをいまさら調べてみた - 未熟学生エンジニアのブログ

create-react-appのデフォルトコードについて

create-react-appをした場合、テンプレートコードが生成されます。

src、publicフォルダの中にあるコードが重要です。

$ tree src
src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
$ tree public
public
├── favicon.ico
├── index.html
└── manifest.json

起点はsrc/index.jsです。create-react-appで作ったreactアプリは、まずこのindex.jsが実行されます。

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ここで、以下の部分でAppというモジュールがインポートされています。要は、他のファイル(./App.js)にあるAppというモジュールをこのindex.jsでも利用できるようにしているということです。

import App from './App';

そして、以下の行でAppを利用して処理を走らせます。

ReactDOM.render(<App />, document.getElementById('root'));

これは、Appをrootというidを持つDOM要素の中に挿入するという意味になります。どういうことでしょうか?

実は、public/index.html というwebページのベースになるHTMLファイルがあります。

Appは、以下のテンプレートHTMLの中の <div id="root"></div><div id="root"></div>の間に挿入されます。

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

Appが、<div id="root"></div>の間に挿入されることがわかりました。

では、Appとはなんなのか?その答えはsrc/App.jsにあります。なぜここに答えがあるとわかったと思いますか?なぜsrc/App.jsというファイル(答え)を探しあてることができたと思いますか?順をおって説明します。

React.jsを使ったプログラムは、基本的には単にjavascriptで書かれたプログラムです。javascriptがわかっていれば、答えにたどり着くことができます。

index.jsでのAppのインポートの方法を思い出してください。

src/index.js

import App from './App';

この構文は、「./App(./App.js)というファイルの中から、Appというモジュールをインポートする」という意味になります。

つまり、Appは./App.jsの中にあるということがわかり、Appの詳細を知りたければ./App.jsというファイルをみればいいということがわかります。これはReact.js特有の話ではなく、javascript一般に通じる話です。

ちなみに、./App.jsは、src/App.jsという意味です。(src/index.jsファイルの中では、.はsrcを意味します。ただし、これはファイルパスの一部として.が使われたときに限った話で、例えば先ほど出てきた以下のような例の場合は、.は別の意味になります。)

src/App.jsをみればいいということがわかったので、見ていきましょう。

src/App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

若干語弊はありますが、AppはApp()のことだったと考えてください。

ここで、App()は返り値として<div>で囲まれたオブジェクトを返しています。

実は、この<div>で囲まれた部分はreactのコード上ではjsのオブジェクトです。これを変換して、最終的にはHTMLの<div>タグと同じ意味のものになります。

ここで一度、ターミナルからサーバーを立ち上げて、ブラウザを見てみましょう。

$ npm start

以下のような画面が表示されるはずです。

f:id:swiftfe:20190430000840p:plain

ページを書き換えてみる

では、"Edit src/App.js and save to reload."を"Hello, my original app!"という文面に変えるには、どうしたらいいでしょうか?

f:id:swiftfe:20190430000932p:plain

先ほどのsrc/App.jsに、"Edit src/App.js and save to reload."という文字があるはずです。これを変えましょう。

src/App.js

        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>

以下のように変えます

        <p>
          Hello, my original app!
        </p>

編集して保存したら、ブラウザをみてみましょう。(実は、Reactを使うとブラウザのリロード操作がいりません!自動で更新されます。)

f:id:swiftfe:20190430001315p:plain

やりました!これでReactアプリを自分好みに編集していくことができるようになりました!

まとめ

  • javascriptの文法を読み解き、編集すべきファイルsrc/App.jsにたどり着けた
  • src/App.jsを編集すれば、自分のオリジナルアプリを書いていける
  • ベースとなるファイルindex.htmlに、Appというコンポーネントを挿入する形で要素を追加している

シリーズ

なぜReact.jsを使うのか、jQueryやVue.jsとの違いをいまさら調べてみた

巷の人はなぜReact.jsを使っているのか理解するため、他のJavaScript(js)ライブラリ・フレームワークとの違いなどを調べてみました。

最初にまとめ

  • SPAというページの一部を更新するタイプの構成が流行りだよ
  • Reactは仮想DOMを使うからページの一部を更新するのが速い
  • Reactは宣言的記述ができるから複雑なコードでも読みやすい。間違いにくい
    • jQueryは複雑になると読みにくい
  • 個人開発ではVue.jsとReact.js好きな方を使えば良い
    • Vue.jsも上の同じ特性を持ってるよ

なぜjsが必要なのか?

そもそもjsはなぜ必要なのでしょうか?答えは単純です。ユーザのアクション(入力・クリックなど)やDBのデータ更新などに応じて動的にページを書きかえたいからです。

SPAを作りたい

SPA(シングルページアプリケーション)ではページ遷移を行わず、ページの遷移・更新はjsによるdom(jsで操作できる、HTMLを表現するオブジェクト)操作による差分更新により擬似的に行うという特徴があります。これによりサーバーからのリクエストを最初の一回だけで全てやってしまうのでその後の動作が軽量になるというメリットがあります。

近年はこのSPAの導入によるwebページのパフォーマンスアップが常識になってきているということがReact.js導入の動機となっています。

ここで、なぜSPAならReact.jsが使われるのか、という話になると思います。先ほど述べたようにSPAはページの遷移・更新はjsによるdom操作による差分更新により擬似的に行います。

Reactは、この差分更新を適切にやってくれる+更新処理をわかりやすく書けるため、SPAのweb開発で用いられているのです。以下では、その仕組みについてお話しして行きたいと思います。

参考

仮想DOMという概念

Reactでは、仮想DOMという方法を使い、今までのDOM操作をより効率よく行うことで高速化を実現しています。

ともかく、仮想DOMというものを使ってそこを経由してDOM操作を行うことで、高速になるらしいです。詳しいことはわかりませんが、そういうことです。

ページの一部だけを更新するのがJavaScriptの基本の処理なので、そこが早くなるのは当然嬉しいというわけですね。要は高速な処理ができるからReactはいいぞということ。

参考: VirtualDOMの仕事ってなに?(Reactの表示速度がはやい理由) - Qiita

従来:jQueryなどの手続き型のDOM操作

React.jsでは「宣言的記述」がキーワードとしてあります。jQueryなどの手続き型の記述と違っています。

「宣言的記述」では、テンプレートに変数を書いておき、その変数の値の変更に応じて自動的にレンダリングされます。

「手続き型の記述」では、DOMを直接操作し、例えばリストの値を一つ追加する際には以下の様な手続き型のプログラムを書きます。

「宣言的記述」の例

引用:Reactを使うとなぜjQueryが要らなくなるのか - Qiita

// ItemListのコンポーネント定義(実体は関数)
const ItemList = props => {
    return <ul className="item-list">
        {props.items.map(item => <ItemDetail item={item} />)}
    </ul>;
};

// ItemDetailのコンポーネント定義
const ItemDetail = props => {
    const item = props.item;
    return <li className={'item' + item.stock === 0 ? ' soldout' : ''}>
        <div className="item-name">{item.name}</div>
        <div className="item-price">{item.price}</div>
    </li>;
};

fetch('/api/items').then(res => res.json()).then(data => {
    ReactDOM.render(
        <ItemList items={data.items} />, // これを
        document.getElementById('container') // ここにレンダリングしろ
    );
});

「手続き型の記述」の例

引用:Reactを使うとなぜjQueryが要らなくなるのか - Qiita

$.getJSON('/api/items').then(data => {
    const ul = $('ul.item-list').empty();
    data.items.forEach(item => {
        const li = $('<li>').addClass('item').appendTo(ul);
        if (item.stock === 0) li.addClass('soldout');
        $('<div>').addClass('item-name').text(item.name).appendTo(li);
        $('<div>').addClass('item-price').text(item.price).appendTo(li);
    });
});

一見、宣言的記述の方が記述量が多いだけのようにも思えるかもしれませんが、宣言的記述では見た目がほぼHTMLで、正しい構造であることを確認しやすいのです。

手続き型の記述の場合、とてもじゃないですがHTMLには見えません。複雑なコードになった際、正しい構造であることを確認しづらくなり、間違ったHTMLタグを混入させてしまう可能性などがあり、バグを生みやすくなります。

こういった点で宣言的記述が有利であり、複雑化しやすいフロントエンドのコードを管理しやすくしています。

類似フレームワークVue.jsとの違い

他のフレームワークとの比較 — Vue.js によると、Vue.jsとReact.jsとの違いはそれほど大きくなさそうです。

大きな違いとしては、htmlの書き方にあります。ReactはJSX、VueはTemplatesという書き方を利用することになります。

我らがReact様のJSXのメリットとしては、JSXはhtmlのように見えるjsであり、jsとしてhtmlを書くことができることです。ただし、見た目はHTMLとは少し違いますし、CSSもJSXに組み込むように書くことが多いので、慣れていない人にとっては慣れるまで時間がかかります。

VueのTemplatesのメリットとしては、HTML・CSSはほぼ普通のそれと同じように書くことができる点です。とにかく、とっつきやすい。

[React] JSXの例

引用:ReactとVueってどう違う?全く同じアプリをReactとVueで作成してみて分かった相違点 | コリス

createNewToDoItem = () => {
    this.setState( ({ list, todo }) => ({
      list: [
          ...list,
        {
          todo
        }
      ],
      todo: ''
    })
  );
};


handleInput = e => {
  this.setState({
    todo: e.target.value
  });
};


<input type="text" 
       value={this.state.todo} 
       onChange={this.handleInput}/>

[Vue] Templatesの例

引用:ReactとVueってどう違う?全く同じアプリをReactとVueで作成してみて分かった相違点 | コリス

<script>
createNewToDoItem() {
    this.list.push(
        {
            'todo': this.todo
        }
    );
    this.todo = '';
}
</script>

<template>
  <input type="text" v-model="todo"/>
</template>

個人開発においてはどちらを使っても大差はないでしょう。

参考

Flutterメモ 番外編 GitHubとCIツールの連携(Codemagic)

今回やること

  • CodemagicとGitHubを連携させてFlutterアプリをビルド
  • slack通知連携
  • iOSアプリAdHoc配信
  • Readmeにバッジをつける

Codemagicを選んだ理由

CIツールを使ってみたかったんですが、調べてみるとCodemagicというCIツールが評判がいいらしい。どうやらFlutterに特化しているらしく、競合(Flutterにおいては後発っぽい)のBitriseよりもいろいろ充実してるっぽい。

ただ、ビルドが若干遅いらしく、対応中とのことらしいです。

今回はこのCodemagicの方を試してみることにしました。

GitHubリポジトリを用意

今回はこのリポジトリを用意しました。今回はPublicリポジトリですが、CodemagicはPrivateでも使えるっぽいです。

https://github.com/TetsuFe/spring_app_2019github.com

Codemagicへの登録

以下のリンクから登録します。

codemagic.io

GitHub連携認証を使ったのですが、登録が完了すると同時に以下のような画面に。

f:id:swiftfe:20190423061550p:plain

CIを導入したいリポジトリに対して「Start your first build」ボタンを押すと、なんといきなりビルドが始まりました!

f:id:swiftfe:20190423061618p:plain

簡単すぎる・・(他のCIツールもそうだったかも?)

トリガー

このままでは手動でCIを回さないといけないので、トリガーを設定します。

App Settingsから、Build Triggerを展開します。

以下のように設定できます。

  • ブランチ名
  • タイミング
    • push時(Trigger on every push)
      • artifacts(appの生成)はあり
    • pull requestのupdate時(Trigger on pull request update)
      • artifacts(appの生成)はなし

f:id:swiftfe:20190424014144p:plain

また、説明は割愛しますが、走らせる処理をブランチごとに分岐させたい時は、workflow自体を複数作ることで可能らしいです。

参考:CodemagicでFlutterアプリのビルド・配信をする — iOS編 – Flutter 🇯🇵 – Medium

この記事の「トリガー」を参照してください。

slack連携

設定画面(App Settings?)を開いて、publishの項目を展開すると、以下の様にslack連携の項目が現れます。

f:id:swiftfe:20190423070320p:plain

連携に成功すると、以下の様にチャンネルと通知のトリガーを選択します。

f:id:swiftfe:20190423070427p:plain

次回のビルドから、以下のようにslack通知が来るようになります!ビルドは結構時間がかかるので、通知を設定しておくと便利そうです。

f:id:swiftfe:20190423070432p:plain:w500

Adhoc配信

Adhoc配信自体が正直よくわかっていませんが、とりあえずやってみます。

App Settingsから、publishの項目を展開し、さらに「iOS code signing」を展開します。ここのフォームにアカウント情報を登録します。今回はAutomaticを選択します。

f:id:swiftfe:20190423071327p:plain

もう一度「Start new build」してみます。

slackの通知にipaファイルが追加されています!

f:id:swiftfe:20190423072222p:plain

iPhoneからリンクをクリックしてみると・・(slackからsafariへ移動しましょう)

インストール確認ダイアログが!

ホーム画面にインストールされました!

ちゃんと起動しました!!簡単すぎる…

このリンクを配布すればいろんな人にダウンロードしてもらえるのでかなり便利です。

GitHubのReadmeにバッジをつける

f:id:swiftfe:20190423070612p:plain

README.mdにここにあるmarkdown用のスニペットを追加してコミットするだけです。

いい感じです!

f:id:swiftfe:20190424012813p:plain

参考

Flutterメモ 2. 開発環境の構築(Android)

Flutter環境構築、今回はAndroid編です。

Flutter sdkがインストール済みであることが前提です。よろしければこの記事をご参照ください。

swiftfe0.hatenablog.com

Android Studioのインストール

以下からインストールしましょう。

android用の build tool, ライセンスをインストールする

$ flutter doctor --android-licenses

Android StudioからFlutterプロジェクトを作ってみる

Android Studioを起動して、まずは Android Studio > Preference > Plugin と進み、以下の2つのプラグインをインストールしましょう。

f:id:swiftfe:20190422124832p:plain

成功したら、Android Studioを再起動しましょう。

再起動後は、Start a new Flutter project から、Flutter プロジェクトが作成できるようになっています。

f:id:swiftfe:20190422124804p:plain

project nameに適当な名前を入れます。

f:id:swiftfe:20190422124817p:plain

company domainには一意なドメインを入れます。特に公開を考えていない場合はexample.comなどで大丈夫だと思います

f:id:swiftfe:20190422124819p:plain

f:id:swiftfe:20190422124820p:plain

実行

Android Studioから、エミュレータを切り替えることができます。iOSバイスも選択することができます。

f:id:swiftfe:20190422125340p:plain

f:id:swiftfe:20190422125602p:plain

f:id:swiftfe:20190422125345p:plain

Android上でサンプルアプリが起動しました!(デフォルトでカウンターアプリが実装されています)

f:id:swiftfe:20190422130033p:plain

iOSも実行できます。

f:id:swiftfe:20190422124931p:plain

参考