Goalist Developers Blog

機械学習のモデルを理解しましょう!

歴史

機械学習がもう30年ほど存在してきたが、最近のプロセシング力に至って、使える場面が物凄く増えてきました。昔も、今も数学的なアルゴリズム(Naive Bayesなど)を使い予測などを行ってますが、最近の復活の原因はほとんど、「Deep Learning」というやり方のおかげです。これは最新のプロセシング技術に頼って、今までにない複雑なモデルの作成ができます。

Deep learningの力

さて、このモデルとは何?美人さん? f:id:c-pattamada:20181025143637j:plain

すみませんでした。もちろん、違いませんね。画像を使いたかっただけです、恨まないでください。


例えば、こういうデータがあります。

f:id:c-pattamada:20181025143712p:plain

なんか、高校の数学授業を思い出す。この赤い点のを通して線を書きましょう。例えば「y = 2x + 4」 。これが「モデル」と言います。

そして、このデータをよく描いているモデルだとも言えます。

f:id:c-pattamada:20181025143731p:plain

この場合は一般線形モデル、[y= 2x + 4」を使いました(辞書がそういうから。英語ではlinear modelです)。

一直線が十分なので、機械学習なんかより高校生にでも聞けば良いかと思います。

ではもうちょっと複雑な例を見ましょう。そして、似たようなモデルを使ってみましょう。

f:id:c-pattamada:20181025143746p:plain

こちらは明らかに使えません。 そこで、2乗や3乗などの y = 0.2x4-5x2+10x+5 (これも「モデル」と言います)みたいな数式でできるでしょう。(数式はちょっと適当に選びました)

f:id:c-pattamada:20181025143757p:plain

実際にはどうなる?

この場合はたった一つの変数、「x」、を使って、「y」を予測してます。つまり、もし家賃の値段が「y」と、それを計算する時に「家の広さ」という「x」のみを見ているようなものです。現実の世界には、家賃の値段が実際には - 駅からの近さ、(「z」) - 部屋の数 (「w」) - 何年前に構築された (「v」) 見たいな変数にも影響されているでしょう。 その場合、4つの変数でn乗まで計算しようとすると、結構複雑になります。y = 2(x2)zw3 - 3 (v/z) + ..... 見たいに、もう読めないぐらい。 このような複雑なモデルの作成にはdeep learningが非常に強いです。

そうですよ。モデルの心って、複雑ですよ。

(写真はこちらから) Free stock photos · Pexels

Angular6とPlayFramework2.6でWebアプリの雛形制作(リクエスト送受信)

こんにちは。
増田です。

この間Angular6とPlayFramework2.6での環境構築の資料を作成する機会に巡り合ったので、
せっかくなのでそれを流用し、その2つを使ったリクエストの送受信までの手順を記事として残しておこうかと思います。
目標としては、画面上のボタンを押すと画面にサーバー用プログラムからのレスポンスが表示されるだけの簡単なものです。
あくまでWebアプリの雛形。

Macを買ったばかりという想定で、何もないところから全部入れていきます。

環境

ハードウェア:Mac
フロントエンド:Angular6
バックエンド:PlayFramework2.6

必要なものの準備

いろいろと必要になってきますので、それらを一つ一つ入れていきます。
すでに入っているものはさっくり飛ばしていただければ。

必要なもの

  • Java8以上 / JDK
  • Xcode
  • Command Line Tools for Xcode
  • Homebrew
  • sbt
  • Nodebrew
  • Node.js / npm
  • Angular CLI
  • IDE

それぞれが何のためのものなのか、僕はよく知らないので割愛します。
調べていただければたくさん出てくると思います。

Java8以上

初っ端から他の方のサイトを参考するのもどうかと思うのですが、
僕が書く必要もなさそうなので。ぺい。
macOS に Oracle Java 8 (JDK) をインストールする手順

最終的に、ターミナルにjava -versionと入力して、
f:id:s-masuda:20181017101022p:plain
というように返ってきたら大丈夫です。
java version "1.8.〜"というのが重要です。

Xcode

App Storeで「Xcode」と検索すると出てきます。 f:id:s-masuda:20181017101057p:plain
ダウンロードして、一度開いて、ライセンスに同意しておきましょう。

Command Line Tools for Xcode

またしても他の方のサイトを参考に...。ぺいっ。
MacにHomebrewをインストールする

けど、一応書いておきます。
ターミナルにxcode-select --installと入力して、
f:id:s-masuda:20181017101132p:plain
が出てきたら、インストールボタンを押してください。
ライセンスの使用許諾にも同意しておきましょう。

Homebrew

上のものと同じサイトが参考になります。
ターミナルに/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"と入力すればおしまいです。
あとはダウンロードされるのをしばし待ちましょう。

もしダメだった場合、例えば
f:id:s-masuda:20181017101157p:plain
というようなものが返ってきた場合には、
Homebrewにアクセスして
f:id:s-masuda:20181017101218p:plain
の白い文字の部分をターミナルに入力してください。

そうして上手くいくと、途中でEnterキーの入力とパスワードを
f:id:s-masuda:20181017101315p:plain
f:id:s-masuda:20181017101311p:plain
こんな感じで求められるので、入力しましょう。
パスワードは自分のアカウントのログインパスワードです。

インストールが終わったらターミナルにbrew -vと入力してみてください。
f:id:s-masuda:20181017101430p:plain
というような感じで返ってきたら大丈夫です。

sbt

参考に、というか公式サイトをぺいっ。
Mac への sbt のインストール

ターミナルにbrew install sbt@1と入力しましょう。
インストールが終わったらsbtと入力してみてください。
すると色々ダウンロードが終わったあとで対話モード(左側に矢印が出ている状態)になると思います。
その状態でexitと入力すると対話モードを終えれます。
終えましょう。

Nodebrew

とりあえず参考にさせていただいたサイトをぺいっ。
買いたてのMacにNode.jsとnpmをインストール

ターミナルにbrew install nodebrewと入力しましょう。

インストールが終わったら、ターミナルにnodebrew -vと入力してみてください。
f:id:s-masuda:20181017101741p:plain
一番上にこんな感じで返ってきたら大丈夫です。
その下に長々と出てくる分は無視しています。

Node.js / npm

上と同じサイトを参考にしています。

ターミナルにnodebrew install-binary latestと入力しましょう。
もしこんな感じのエラー(download failed)
f:id:s-masuda:20181017101859p:plain
が出た場合には、ある場所にディレクトリ(フォルダ)を一つ作る必要があります。
なので、その場合には、ターミナルにmkdir -p ~/.nodebrew/srcと入力しましょう。
これで大丈夫です。
そのあとにもう一度nodebrew install-binary latestと入力してみてください。
インストールされるはずなので。
f:id:s-masuda:20181017101919p:plain

ターミナルにnodebrew listと入力して、
f:id:s-masuda:20181017101951p:plain
こんな感じで返って来れば大丈夫です。

インストール直後は current: none となっていると思います。
currentには使用するバージョンが来るのですが、そこがnoneになっているので、
それを指定します。
ターミナルにnodebrew use v10.10.0などと入力しましょう。
ただし、v以下はnodebrew listと入力した時に表示された値にしてください。
v10.10.0ではないかもしれません。

次にnodeコマンドへのパスを.bash_profileに保存します。
「nodeコマンド」とか「パス」とか「.bash_profile」とか分からないかもしれませんが、
僕もよく分かってないのできっと大丈夫です(あかん
ターミナルに

echo 'export PATH=$PATH:/Users/****/.nodebrew/current/bin' >> ~/.bash_profile  

と入力しましょう。ただし、上記のコマンドの****の部分には自身のユーザー名を入力してください。
ターミナルの設定がデフォルトなら、ターミナルを開いたときに

(PCの名前):~ (ユーザー名)$  

という形で左側に表示されていると思いますので、そのユーザー名の部分を****のところに入力してください。

コマンドを打つ形ではなく、ホームディレクトリに.bash_profileというファイルを作成し、
そこに直接書き込むという形でも大丈夫です。
そちらの方が分かりやすい気もします。

.bash_profileへの記述まで終われば、最後それを反映するためにsource ~/.bash_profileと入力しましょう。
参考のページには~/.bashrcを利用していますが、使用しなくても大丈夫だったので気にしないことにします。

確認として、ターミナルにnode -vと入力してみてください。
f:id:s-masuda:20181017102043p:plain
こんな感じで返って来れば大丈夫です。
Node.jsがインストールされたついでに、npmというのもインストールされているはずなので、
ターミナルにnpm -vと入力してみてください。
f:id:s-masuda:20181017102111p:plain
こんな感じで返ってきたら大丈夫です。

Angular CLI

Angularの環境構築(Angular CLIで構築)が参考になります。
と言っても、することとしてはターミナルにnpm install -g @angular/cliと入力するだけです。

IDE

これは何か適当に使いましょう。
そもそもIDEがどういうものかについては、
他のたくさんのサイトを参考にしていただければと思います。
ちなみに、僕はWebStorm(Angular用)とEclipse(PlayFramework用)を使っています。

ここまでで必要なものは揃いました。
次はいよいよAngularとPlayFrameworkのプロジェクト作成に入ります。

プロジェクト作成

今回は適当にデスクトップ上に作ってみます。

project
    ┗ frontend
    ┗ backend

というディレクトリ構成にすることにして、projectディレクトリをデスクトップ上に作成します。
二本指でタップして「新規フォルダ」ボタンを押すか、あるいは、
ターミナルで、cd desktopからのmkdir -p projectをしてください。

そして、ターミナルでそのprojectの中に移動しましょう。
今デスクトップ上にいたりいなかったりするかもしれないので、
cd && cd desktop/project/を入力してください。
するとデスクトップ上のprojectディレクトリに移動できたかと思います。

Angularプロジェクト作成

そのままprojectディレクトリ内でng new frontendと入力すると、
frontendという名前のプロジェクトが作成されます。
そこからcd frontendをしてng serve --openとするとたぶんこんなページが表示されます。
f:id:s-masuda:20181017102710p:plain
これでAngularプロジェクトの雛形ができました。

雛形をいじって、ボタンを作るところまでやっておきます。
frontend/src/appにあるapp.component.htmlの中身を全部消して

<div class="center">
  {{content}}
  <button (click)="onClick()">ボタン</button>
</div>

と書いてください。
で、同じ階層のapp.component.cssに

.center {
  display: flex;
  flex-direction: column;
  text-align: center;

  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;

  width: 200px;
  height: 100px;
}

と。別にこんなもの必要ないのですが、趣味です。

最後、app.component.tsの

export class AppComponent {
  title = 'app';
}

を、

export class AppComponent {
  content = 'はじめの第一歩';

  onClick() {
    this.content = 'ボタンが押されました';
  }
}

としましょう。

すると、こんな画面になって、
f:id:s-masuda:20181017102736p:plain
で、真ん中のボタンを押すと、
f:id:s-masuda:20181017102747p:plain
こうなると思います。

一応ざっくりと説明しておきますと、

{{content}}

というのが、app.component.tsのAppComponentクラス内の変数contentを指していて、
その変数の内容を表示するようになっています。
最初、contentには「はじめの第一歩」という文字列が入っているので、それが表示されています。

<button (click)="onClick()">ボタン</button>

ここのonClick()というのが同じようにAppComponentクラス内のメソッドonClick()を指していて、
そのボタンをクリックした時に、そのメソッドが実行されるようになっています。
なのでボタンを押すと、「はじめの第一歩」という表示が「ボタンが押されました」という表示に変わります。

とりあえずボタンを設置できたのでこれでよしとします。
あとでこのonClick()の中身をリクエストを飛ばすように書き換えます。

次はバックエンド!

Playプロジェクト作成

またターミナルに戻って、まずprojectディレクトリにまで行きましょう。
cd && cd desktop/projectとかですかね。

そこで、sbt new playframework/play-java-seed.g8と入力しましょう。
そしたら、プロジェクト名をどうするか聞かれると思いますが、
今回はbackendという名前でやっていきます。
なのでbackendと入力して、Enterキー。
あとorganaizationなど聞かれるかもしれませんが、何も入力せずEnterキーです。

すると、Playプロジェクトの雛形ができると思いますので、
終わったらbackendディレクトリに移動して、sbt runとターミナルに打ってみましょう。
f:id:s-masuda:20181017102828p:plain
というような表示が出たら、ブラウザでlocalhost:9000にアクセスしてみてください。
すると、
f:id:s-masuda:20181017102845p:plain
こんな表示が出ると思います。

雛形はできたので、次はリクエスト用のAPIを作ります。

API作成

以前の記事でAPIを作成する手順も書いたので、さっくり行きます。

以前と同じようにしてroutesファイルに

GET     /api/get                    controllers.ApiController.get

と書いて、 backend/app/controllersにApiControllerクラスを作成し、

public class ApiController extends Controller{

    public Result get() {
        String res = "レスポンスです!";
        return ok(Json.toJson(res));
    }

}

と書きましょう。必要なものはimportしつつ。

このままリクエストを送ると、
f:id:s-masuda:20181017102922p:plain
のようなエラーが出るかもしれないので、
application.confに

play.filters.enabled += "play.filters.cors.CORSFilter"

と書いておきましょう。
なぜかエラーを回避できます(おい

とりあえずバックを動かしておきましょう。
backendディレクトリに移動した状態でsbt runです。

これでAPIは作成できたので、次はフロントからリクエストを飛ばします。

リクエスト送信

Angularプロジェクトの方で最初に書いた

onClick() {
    this.content = 'ボタンが押されました';
}

を編集します。

まず、Httpリクエストを飛ばせるようにするために、
app.module.tsに2行書き加えます。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http'; // <-------コレ!

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule, // <-------コレ!
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

「コレ!」って書いてある部分です。

そして、app.component.tsにも2行

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http'; //  <-------コレ!

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  content = 'はじめの第一歩';

  constructor(private http: HttpClient) {} // <-------コレ!

  onClick() {
    this.content = 'ボタンが押されました';
  }
}

書き加えましょう。

これでリクエストを飛ばす用意が整ったので、onClick()メソッド内でhttpリクエストを飛ばしてみましょう。

onClick() {
  this.http.get('http://localhost:9000/api/get').subscribe(
    (res: string) => {
      this.content = res;
    }
  );
}

とするだけです。
this.httpというのがconstructorの部分で取ってきたHttpClientにあたり、
getでGETメソッドを利用し、引数にリクエスト先を書き込みます。
それをsubscribeすることで、レスポンスを受け取ることができます。

あとは、作ったボタンを押してみましょう。
すると、
f:id:s-masuda:20181017103009p:plain

f:id:s-masuda:20181017103013p:plain
わお!

めでたしめでたし。

あとはAPIやらあれこ作ってみたり、
フロントで画面を作り込んでみたりすると、
Webアプリが作れるわけですね。すごい。

次はこのアプリ(とは言えない何か)をデータベース(MySQL)と連携させるお話を書く予定でいます。
そのうち。
いつかきっと。

Firebase(FCM)を用いたAndroid(Kotlin)でのプッシュ通知

こんにちは。
増田です。

FirebaseのCloud Messagingを利用してAndroidでプッシュ通知を実装したくなった、
というよりしないといけなくなったのでそのまとめを。

ただ、Androidを触ったこともなければKotlinでコードを書いたこともない状態からの
いきなりのプッシュ通知の実装だったので、よく分かっていない部分が多いですが、
多少なりともご参考になれば幸いです。

通知が表示されるようになるまでに割と時間がかかってしまったのですが、
それもこれも全部全然関係のないところに出ていたエラーのせいでした。
エラーが出ている場合はまずそこを潰しましょう。
基本中の基本なのかもしれませんが、僕は新人なので()

プロジェクト作成

プロジェクトがないと始まらないので、空っぽのプロジェクトを作ります。
Android Studioを使って、適当に。
ここは他の方の良い記事 がありますのでさっくり割愛。

というわけで、空っぽのプロジェクトができました。

プロジェクトにFirebaseを追加

公式の資料がありますので、そちらを。
とはいえ省きすぎるのもどうかと思うので、資料に書いてあることそのままにはなりますが、
この辺からちょこっと丁寧に。

まず、Firebaseプロジェクトがなければ作ります。
Firebaseコンソールに行き、

f:id:s-masuda:20181009124728p:plain

を押して、適当にプロジェクト名を決めて、

f:id:s-masuda:20181009124758p:plain

同意して、プロジェクトを作成。
その後、作ったプロジェクトに追加すべく、

f:id:s-masuda:20181009124859p:plain

のAndroidのアイコンを押します。
そして、必要な情報を入力していきます。

f:id:s-masuda:20181009124949p:plain

とは言っても今回はパッケージ名だけで大丈夫みたいです。
パッケージ名というのは、作ったプロジェクトのAndroidManifest.xmlにある

f:id:s-masuda:20181009125112p:plain

このpackageのことです。
で、「アプリを登録」ボタンを押すと、

f:id:s-masuda:20181009125203p:plain

こんな感じの画面になるので、書かれてある通りにgoogle-services.jsonをダウンロードして、
appの下に入れます。そして「次へ」ボタンを押す前に、ちょこっとコードを書いておきます。

ルートレベルのbuild.gradleに

buildscript {
    // ...
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.1.0' // google-services plugin    <----コレ
    }
}

を追記します。
ルートレベルのbuild.gradleというのは、Android Studioで見たときに

f:id:s-masuda:20181009125802p:plain

のようにして見える上の方のbuild.gradleです。
で、モジュールのbuild.gradle(画像での下の方のbuild.gradle)に

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-core:16.0.3'

  // Getting a "Could not find" error? Make sure you have
  // added the Google maven respository to your root build.gradle
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

を追記します。最後の行の apply plugin: 〜はbuild.gradleの最後(一番下)に書く必要があるらしいです。コメントにも注意書きがありますし。

で、ここまでで準備が整ったので、さっき飛ばした「次へ」ボタンを押します。
すると、おそらく

f:id:s-masuda:20181009130917p:plain

というのが表示されるので、アプリを実行します。 そうしてしばらく経つと、

f:id:s-masuda:20181009130947p:plain

というメッセージが表示されるのではないかと。
ここまできたらFirebaseをプロジェクトに追加できたということみたいです。

Firebase Cloud Messagingを利用できるように

次は、Cloud Messaging を利用できるようにします。
モジュールのbuild.gradleに

dependencies {
    compile 'com.google.firebase:firebase-messaging:17.3.1'
}

を追記します。これだけです。
資料ではバージョンが12.0.1になっているのですが、それだとおそらく

f:id:s-masuda:20181009133047p:plain

というようなエラーがずらーっと出てきます。
なのでバージョンをちょこっと変えています。

バックグラウンドで通知メッセージを受け取った場合の設定

バックグラウンド時の通知メッセージ(notification)限定です。
詳しいことは公式の資料を見ていただければ。

プッシュ通知用の諸々のデフォルト設定をAndroidManifest.xmlに追加します。
追加する箇所は<application 〜>の後ろくらいに。
公式のクイックスタートがありますので、そちらをご参考いただければ。

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/default_notification_channel_id"/>
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
     See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_launcher_foreground" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
     notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

別にコメントは必要ありませんけど、一応。
あと、@string/default_notification_channel_idがないと言われるかと思いますので、
strings.xmlに追加します。

<string name="default_notification_channel_id" translatable="false">my_app_fcm_default_channel</string>

channel_idで指定されている文字列が一体どういうものを指しているのか、
どういうところで利用されているのか等々一切理解していません。
とにかく何か適当な文字列を入れておけば動くみたいなので...。

これで準備はできました。

メッセージ送信

というわけで試しにメッセージを送信してみます。
Firebaseコンソールに行き、左側の「拡大」という部分にあるCloud Messagingというところに飛んで

f:id:s-masuda:20181009141256p:plain

こんな感じで、

f:id:s-masuda:20181009141425p:plain

メッセージ本文とターゲットを指定して「メッセージを送信」ボタンを押すだけです。
今回面倒なのでとりあえずターゲットはアプリにしています。

はい。送信しました。
この時点でもしLogcatによく分からないエラーが出ていたら、
エミュレーターをCold Bootしてみると良いかもしれません。

すると、

f:id:s-masuda:20181009174124p:plain

はい。ハッピーです。 正直、お仕事はここまでで十分なのですが、
一応その通知を細かくカスタマイズできるようになるところまで書いておきます。

フォアグラウンド時で受け取った場合などの設定

バックグラウンド時にシンプルな通知メッセージを受け取った場合は、
上記の設定だけで十分なのですが、もしそうでない場合、
例えばアプリがフォアグラウンド時にも通知を出したいというような場合には、
ちょこっとコードを書く必要があります。

まず、どこで受け取るのかというのをAndroidManifest.xmlで指定しておきます。

<service
    android:name=".services.FirebaseNotificationService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
 </service>

.services.FirebaseNotificationServiceというのは今から作ります。

メッセージ受信時のメソッドを実装

というわけで、com.example.〜の下にservicesというパッケージを作成し、
その中にFirebaseNotificationServiceというクラスを作ります。名前は適当です。
そしてそこに通知を受け取った際のメソッドを実装します。

class FirebaseNotificationService : FirebaseMessagingService() {
    // プッシュ通知を受け取った際の処理
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {

        // Check if message contains a data payload.
        remoteMessage?.data?.isNotEmpty()?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

        // Check if message contains a notification payload.
        remoteMessage?.notification?.let {
            Log.d(TAG, "Message Notification Body: ${it.body}")
        }

        this.sendNotification()
    }

    // 新しいTokenが作成された際の処理
    override fun onNewToken(token: String?) {
        // サーバーに送信するなど
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     */
    private fun sendNotification() {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT)

        val channelId = getString(R.string.default_notification_channel_id)
        val channelName = getString(R.string.default_notification_channel_name)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.drawable.ic_launcher_foreground) // 必須
                .setColor(Color.parseColor("#00ABA9")) // theme colorを文字列指定(リソースでの指定方法が分からなかったので)
                .setContentTitle("タイトル")
                .setContentText("コンテンツ")
                .setShowWhen(true)
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            channel.description = "description" // どこに使われるものなのか分かっていません
            channel.setShowBadge(true)
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }

    companion object {
        private var TAG = "FirebaseMessagingService"
    }
}

さらっとR.string.default_notification_channel_nameというのを入れたのですが、
もちろんそれに対応するものをstrings.xmlに追記する必要があります。

<string name="default_notification_channel_name" translatable="true">my_app_push_message</string>

あと、一点注意しないといけないのは、資料では

<service
    android:name=".MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

現在のトークンを取得する必要がある場合は、FirebaseInstanceId.getInstance().getToken() を呼び出します。

というような記述がある(2018/10/09)のですが、
com.google.firebase.INSTANCE_ID_EVENT.getToken()も非推奨になっているらしいです。
参考ページ

これで一応全ての準備が整いました。
なので、フォアグラウンド状態でFirebaseコンソールからメッセージを送ってみます。

f:id:s-masuda:20181009175252p:plain

うい。
めでたしめでたし。
満足です。

PlayFramework2.6(Java)での全リクエスト/レスポンスのログ出力

こんにちは。
増田です。

以前PlayFrameworkで全リクエスト/レスポンスを取る必要に駆られたので、
その方法をあれこれ考えたり調べりした結果をここに記しておきます。

準備

何はともあれその準備から

プロジェクト作成

ターミナルで

> sbt new playframework/play-java-seed.g8

あと僕はEclipseで開発しているので、それ用の設定としてちょこっと。
project/plugins.sbtに1行追記

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")

そして

> sbt eclipse

これでEclipseのための準備は整いました。

API作成

APIがないとリクエスト飛ばして試せないので仕方なく作ります。
routesに

GET     /api/get                    controllers.ApiController.get
GET     /api/getWithArg/:arg        controllers.ApiController.getWithArg(arg: String)
+nocsrf
POST    /api/post                   controllers.ApiController.post

GETリクエストに、POSTリクエスト。
で、Actionとして

public class ApiController extends Controller{

    public Result get() {
        Request req = request();
        String res = "This is a response from get.";
        return ok(Json.toJson(res));
    }

    public Result getWithParam(String param) {
        Request req = request();
        String res = "This is a response from get(String).";
            return ok(Json.toJson(res));
    }

    public Result post() {
        Request req = request();
        String res = "This is a response from get(String).";
        return ok(Json.toJson(res));
    }

}

を設定。
適当にsessionも入っていたりする設定です。

本題

そんな状況で、可能な限り完璧な状態の全リクエスト/レスポンスの内容をログに出力する、
というのがやりたいことです。
「完璧な」というのはリクエストメソッド、リクエストURL、リクエスト/レスポンスbody、セッション、など。

リクエストはcurl使うなりChromeの拡張使うなりフロント作りなりして適当に。
僕はChromeの拡張を使いました。便利!

気合いでLoggerを挟む

こんな感じです。

public Result get() {
    Request req = request();
    Logger.debug("Request(Controller) : " + req.toString());
    Logger.debug("Session(Controller) : " + session("SESSION"));

    String res = "This is a response from get().";
    Logger.debug("Response(Controller) : " + res + "\n");
    return ok(Json.toJson(res));
}

public Result getWithParam(String param) {
    Request req = request();
    Logger.debug("Request(Controller) : " + req.toString());
    Logger.debug("Session(Controller) : " + session("SESSION"));

    String res = "This is a response from get(String).";
    Logger.debug("Response(Controller) : " + res + "\n");
    return ok(Json.toJson(res));
}

public Result post() {
    Request req = request();
    Logger.debug("Request(Controller) : " + req.toString() + " ; " + req.body().asJson());
    Logger.debug("Session(Controller) : " + session("SESSION"));

    String res = "This is a response from post().";
    Logger.debug("Response(Controller) : " + res + "\n");
    return ok(Json.toJson(res));
}

すると、まぁ普通にログを取れて、

[debug] application - Request(Controller) : GET /api/get
[debug] application - Session(Controller) : session_content
[debug] application - Response(Controller) : This is a response from get().

[debug] application - Request(Controller) : GET /api/getWithArg/arguments
[debug] application - Session(Controller) : session_content
[debug] application - Response(Controller) : This is a response from get(arg).

[debug] application - Request(Controller) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(Controller) : session_content
[debug] application - Response(Controller) : This is a response from post().

ただ、これは気合いでしかありません。

今回の例ではAPIが3つしかないから全然問題ありませんけど、
このAPIが10、20、30...と増えてきたら、それはもう大変面倒なことに...。
でも精々数十くらいなら書いてしまっても大丈夫といえば大丈夫な気がします。

ちなみに、sessionは

session("SESSION", "session_content");

みたいな感じで設定されているものとしています。

これはしんどいので、別の方法を考えます。

Filterを利用する

Filterが何かは僕もよく分からないので割愛します。
他の方々のありがたい記事がありますのでそちらを参考にしていただいて。
とりあえずリクエストやレスポンスをフィルターしているものなのだろうという理解です。
Filterを使ってリクエスト/レスポンスをログに出力してみます。
まず、app下にfiltersディレクトリを作って、その下にMyFilterクラスを適当に作ります。
こんな感じです。

public class MyFilter extends Filter{
@Inject
    public MyFilter(Materializer mat) {
        super(mat);
    }

    @Override
    public CompletionStage<Result> apply(
            Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
            Http.RequestHeader requestHeader) {
        long startTime = System.currentTimeMillis();
        return nextFilter.apply(requestHeader).thenApply(result -> {
            long endTime = System.currentTimeMillis();
            long requestTime = endTime - startTime;

            Logger.debug("Request(Filter) : " + requestHeader.method() + " " + requestHeader.uri());
            Logger.debug("Response(Filter) : " + result.body() + "\n");

            return result.withHeader("Request-Time", "" + requestTime);
        });
    }
}

あと、このフィルターを有効にするためにapplication.confに1行

play.filters.enabled += filters.MyFilter

を追記します。

すると、こんな感じに。

[debug] application - Request(Filter) : POST /api/post
[debug] application - Response(Filter) : play.http.HttpEntity$Strict@273ad9ad

面倒になったのでPOSTリクエストだけで試しました。
この方法だとリクエストボディもセッションもレスポンスボディの内容も取れないので、残念。
そもそも用途が明らかに違うのでそんなこと言っても仕方ないのですが。

ただ、これなら一々全てのAPIに何かを加えたりという気合いは必要ありません。

Authenticatorを利用する

Authenticatorが何かは僕もよく分からないので割愛します。
他の方々のありがたい記事がありますのでそちらを参考にしていただいて。こちらなど。
なんだかリクエストをインターセプトしているみたいなのでそれを利用しようという魂胆です。
適当にappの下にauthenticatorsというディレクトリを作って、その下にMyAuthenticatorというクラスを作りました。

public class MyAuthenticator extends Authenticator {

    @Override
    public String getUsername(Context ctx) {
        String session = ctx.session().get("SESSION");
        Logger.debug("Request(Authenticator) : " + ctx.request() + " ; " + ctx.request().body().asJson());
        Logger.debug("Session(Authenticator) : " + session);
        Logger.debug("Response(Authenticator) : " + ctx.response());
        if (session != null) {
            return "ok";
        } else {
            return null;
        }
    }

    @Override
    public Result onUnauthorized(Context ctx) {
        String session = ctx.session().get("SESSION");
        if (StringUtils.isEmpty(session)) {
            return unauthorized();
        } else {
            return forbidden();
        }
    }

}

というようなことを書いて、あとはcontrollersの各Actionにアノテーションを

@Authenticated(MyAuthenticator.class)

というように付与すれば、そのアノテーションをつけられたActionへのリクエストは、
設定したAuthenticatorを通るようになります。
名前の通り本来は認可のための機構でしょうけど、細かいことは気にしません。
Authenticatorを通るので、だったらそこでログ取れば良いのでは、という安易な考えです。
でも、これだとリクエストは取れるものの、レスポンスが取れません。
こんな感じです。

[debug] application - Request(Authenticator) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(Authenticator) : session_content
[debug] application - Response(Authenticator) : play.mvc.Http$Response@52f8ea91

レスポンスボディ...。

ちなみに、このアノテーションはAction毎にではなくクラス単位で付けることができます。

ActionCreatorを利用する

ActionCreatorが(割愛
他の方々の(割愛
適当にappの下にinterceptorsというディレクトリを作って、その下にMyActionCreatorというクラスを作りました。

public class MyActionCreator implements play.http.ActionCreator {
    @Override
    public Action createAction(Http.Request request, Method actionMethod) {
        return new Action.Simple() {
            @Override
            public CompletionStage<Result> call(Http.Context ctx) {

                Logger.debug("Request(ActionCreator) : " + request.method() + " " + request.uri() + " ; " + request.body().asJson());
                Logger.debug("Session(ActionCreator) :"+ctx.session().get("SESSION"));

                CompletionStage<Result> stage = delegate.call(ctx);
                stage.thenAccept(result ->{
                    Logger.debug("Request(ActionCreator) : " + result.body());
                });
                return stage;
            }
        };
    }

}

そしてFilterと同じように、これを有効にするためにapplication.confに1行

play.http.actionCreator = interceptors.MyActionCreator

を追記します。

これで出力させるとこんな感じです。

[debug] application - Request(ActionCreator) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(ActionCreator) :session_content
[debug] application - Request(ActionCreator) : play.http.HttpEntity$Strict@21b69a9

どうしてもレスポンスボディだけ取れませんでした。
残念。

まとめ

というわけで自分にはこれが限界でした。
とりあえずActionCreatorを使っておけばたくさん取れるという認識です。
そして切り替えが簡単。
ちゃんと取れる上に何かもっときれいな方法はあるのだろうか...。

ちなみに、以上のものを全て繋げるとこんな感じで出力されます。

[debug] application - Request(Authenticator) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(Authenticator) : session_content
[debug] application - Response(Authenticator) : play.mvc.Http$Response@32532696
  
[debug] application - Request(ActionCreator) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(ActionCreator) :session_content
[debug] application - Request(Controller) : POST /api/post ; {"body":"body_content"}
[debug] application - Session(Controller) : session_content
[debug] application - Response(Controller) : This is a response from post().
  
[debug] application - Request(ActionCreator) : play.http.HttpEntity$Strict@21b69a9
[debug] application - Request(Filter) : POST /api/post
[debug] application - Response(Filter) : play.http.HttpEntity$Strict@d1c58c

へぇ、そんな順番なんだぁ、というただそれだけです。
こちらからの報告は以上です。

Go言語の構文

こんにちは。
暇な時に、経験がないことを触ってみたいと思いましたから、Go言語の勉強を始めました。
まず、Go言語の構文について紹介させていただきます。

1. パッケージ

Goのプログラムは、パッケージで構成されます。
書いているパッケージに、括弧で他のパッケージのインポートをグループ化し、factoredインポートステートメント( factored import statement )と言うことはできます。

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.Pi)
}

2. 関数

Go言語で、関数のキーワードはfuncです。
関数は、0個以上の引数を取ることができます。
型名は変数名の後ろに書かれます。
関数は複数の戻り値を返すことができます。

package main

import "fmt"

func addAndSub(x, y int) (int, int) {
    return x + y, y-x
}

func main() {
    fmt.Println(addAndSub(5, 10))
}

上記のプログラムの出力結果は 15 5です。

3. 変数

Go言語で、変数を宣言するためのキーワードはvarです。
関数の中では、 var 宣言の代わりに、短い:= の代入文を使えます。
なお、関数の外では、キーワードではじまる宣言(var, func, など)が必要で、 := での暗黙的な宣言は利用できません。

package main

import "fmt"
var i, j int = 1, 2
func main() {
    k := 3
    fmt.Println(i, j, k)
}

上記のプログラムの出力結果は 1 2 3です。

4. Forループ

Go言語で、for ステートメントの3つの部分を括る括弧 ( ) はありません。
初期化ステートメントと後処理ステートメントがない場合、他の言語(JavaやPHPなど)のwhile ステートメントとして使えます。

package main

import "fmt"

func main() {
 
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
 
    check:= false
    for !check {
        fmt.Println("Hello World")
        check = true;
    }
}

5. Ifステートメント

Go言語で、if ステートメントは、先ほどの for ループと同様に、括弧 ( ) は不要で、中括弧 { } は必要です。

package main

import (
    "fmt"
)

func signOfNum(x int) string {
    if (x > 0) {
        return "positive"
    } else if (x < 0) {
        return "negative"
    } else {
        return "zero"
    }
}

func main() {
    fmt.Println(signOfNum(2))
}

6. Switchステートメント

Go言語で、switch は他の言語(JavaやPHPなど)の switch と似ていますが、下記に重要な2つの違いことはあります。

  • 選択された case だけを実行してそれに続く全ての case は実行されません。 他の言語(JavaやPHPなど)の case の最後に必要な break ステートメントが Go では自動的に提供されます。
  • switchcase は定数である必要はなく、 関係する値は整数である必要もありません。
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("When's Saturday?")
    today := time.Now().Weekday()
    switch time.Saturday {
    case today + 0:
        fmt.Println("Today.")
    case today + 1:
        fmt.Println("Tomorrow.")
    case today + 2:
        fmt.Println("In two days.")
    default:
        fmt.Println("Too far away.")
    }
}

Go言語で、switch条件のないswitchは、switch true と書くことと同じです。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}

7. Deferステートメント

Go言語で、defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

defer へ渡した関数が複数ある場合、その呼び出しはスタックされます。 呼び出し元の関数がreturnするとき、 defer へ渡した関数は LIFO(last-in-first-out) の順番で実行されます。

package main

import "fmt"

func main() {
    count:=0;
    for i := 0; i < 3; i++ {
        count++;
        defer fmt.Println(count)   
    }
    fmt.Println("done")
    fmt.Println(count)
}

上記のプログラムの出力結果は:

done
3
3
2
1

参考書類

今回の記事でGoの構文について少し紹介しました。
詳しい情報は下記のリンクにご参考ください。

EC2とELBでかんたんhttps環境構築メモ

AWSでhttps通信できる開発環境をさくっとつくる

こちらのお手軽パターンです

https://recipe.kc-cloud.jp/archives/11067

ドメインはすでに購入済み前提
証明書はACM(AWS Certificate Manager)で取得します

  • Amazon Linux 2
  • nginx 1.15
  • ALB(Application Load Balancer)

手順

  1. EC2インスタンス作成
    1.1. インスタンス作成
    1.2. インスタンス初期設定
    1.3. nginx導入
  2. ロードバランサー作成
    2.1. ALB作成 2.2. 証明書の設定
  3. http→httpsリダイレクト設定
  4. Route53設定

詳しく

1. EC2インスタンス作成

1.1. インスタンス作成

いつもこちらのqiitaみてます😘

qiita.com

今はAmazonLinux1と2のAMIどちらも選べますけど
今後のことを考えて2を選んでおきます

https://qiita.com/michimani/items/146d1f986d78e06d510e

EC2インスタンスにかけてるセキュリティグループで、80番ポートをオープンしておく

ロードバランサーの設定で使うため
サブネットは違うアベイラビリティゾーンでもうひとつ切っとく、使わないけど

こんな状態にしていきます

f:id:y-iio:20180925134027p:plain

1.2. インスタンス初期設定

yum更新

sudo yum update

タイムゾーンの設定

sudo timedatectl set-timezone Asia/Tokyo

1.3. nginx導入

こちらを参考にしました

www.rem-system.com

コミュニティリポジトリ使わないといけないので
後々面倒なことになったりするのかしら

sudo vi /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1

インストールと自動起動設定

sudo yum install nginx
nginx -version
sudo systemctl start nginx
sudo systemctl enable nginx

IPでブラウザからアクセスしたら

f:id:y-iio:20180925132459p:plain

デフォルトページが表示されてればおっけ〜です

2. ロードバランサー作成

2.1. ALB作成

いつもの😘

qiita.com

httpsリスナーを追加します

f:id:y-iio:20180925132531p:plain

2.2. 証明書の設定

ドメインはすでに持っている前提
ACMから新しい証明書をリクエストして使います

f:id:y-iio:20180925132544p:plain

あとは手順1で作ったインスタンスをターゲットグループに登録してELB作成

EC2インスタンスにかけてるセキュリティグループで、ロードバランサーのセキュリティグループを許可

ロードバランサーのDNSでブラウザからアクセスできるようになりました〜

3. http→httpsリダイレクト設定

公式を参考に設定ファイルを書き換えて

ELB を使用して HTTP トラフィックを HTTPS にリダイレクトする

sudo nginx -t
sudo systemctl restart nginx

4. Route53設定

route53のAレコードにロードバランサーのDNSを登録

ELB ロードバランサーへのトラフィックのルーティング - Amazon Route 53

これだけ!

Gitがやばい(3/3)!

Gitがやばいの最終編です!前回はbranch, merge, fetch, pullについて書きました。普通のプロジェクトにはここまでは必ず使うでしょう。

実は、もう一つのよくみる現象があります。それは「マージコンフリクト」です。「コンフリクト」が入ってるために何か怖いものに聞こえますが、とてもよくあって、とても簡単に解決できるものです。

マージコンフリクトを再現する

一編から使ってるプロジェクトを使って、マージコンフリクトを見てみましょう。

下準備

ちょっとした準備しましょう。masterブランチでA.txtとB.txtというファイルを作成してコミットしましょう。

$ nano A.txt
$ nano B.txt
$ git add .
$ git commit -m "A.txtとB.txtを追加しました。"

私は上記のようにしましたが、コミットすればなんでもOKです。 こういうのが出ます。

(Create mode image)
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 A.txt
 create mode 100644 B.txt

コンフリクトはどういう状況でおきるのか

さて、状況を想像しましょう。

このプロジェクトには、二人が参加しています。二人はそれぞれのタスクがあります。 AくんはプロジェクトでA.txtのファイルで、「Aがすごい」を書くのがタスクです。 BさんのタスクはA.txtに「B.txtをみて!」を書き、B.txtに「Bが一番すごい」を書くことです。

くだらない内容ですが、重要なのはAくんとBさんが両方A.txtを編集することになります。こういう状況ではマージコンフリクトが起きる可能性があります。

シミュレーション

やってみましょう。AくんとBさんのためにそれぞれのブランチを切りましょう。

git branch B-branch

でBのブランチが作成できます(すでに知ってましたね)。

git checkout -b A-branch

上記のコードは

git branch A-branch
git checkout A-branch

を一行で書くためのやり方です。

f:id:c-pattamada:20180918160640p:plain

はい、現在A-branchにあります。では、Aくんのタスクをしましょう。 A.txtの中で、「Aがすごい」と書いて、保存して、コミットしましょう。

f:id:c-pattamada:20180918160723p:plain

masterに移って、マージしましょう。

git checkout master
git merge A-branch

ここでは、mergeのあとで書くブランチ名が現在checkoutされたブランチにマージされます。

f:id:c-pattamada:20180918160757p:plain

git logでAくんのコミットがみれます。ここは無事マージできましたね。問題なし!

マージコンフリクトが登場

では、Bさんのタスクも完了しましょう。git checkout B-branch

ここに気づくべきことが、A.txtがまだ空っぽのままです。なぜかというと、BさんのブランチがAくんのコミットの前の状態に切られたからです。 実際のプロジェクトでは、二人が同時に仕事している想定ですと、この状況はすごくよくあるかと思います。 A.txtに「B.txtをみて!」を書き、 B.txtに「Bが一番すごい」を書き、コミットします。 そして、masterに移ってマージしましょう。

git checkout master
git merge B-branch

うまくいきましたか?!や、ダメでした。 AくんとBさんが同じファイル(A.txt)を編集しましたので、gitが上書きすればいいのか、両方入れた方がいいのかなどがわかりません。 マージコンフリクトが行ってしまいました。

f:id:c-pattamada:20180918160825p:plain

マージコンフリクトを解決する

マージコンフリクトが起こっています。文字エディターでA.txtを開いてみますと驚くかもしれませんが…

<<<<<<< HEAD
Aがすごい
=======
B.txtをみて!
>>>>>>> B-branch

が書かれてます。javaだった場合には必ずコンパイルエラーが出るでしょう。

現在はmasterのブランチにB-branchをマージしようとしてますので、masterの現在のコミットはHEADと書かれてます。 <<<<HEAD=======の間には現在のmasterの状態。 =======>>>>>>> B-branchの間にはB-branchの状態。

今回は私が管理者で決める立場にあるので、両方の変更を含めたいと思います。 中身を編集して

「 Aがすごい
B.txtをみて!」

の状態で保存、または古い方を削除する判断もありえます。

ターミナルに戻り、git statusで様子みましょう。 マージしても大丈夫のファイル、B.txtが緑で、A.txtがまだaddされてないので

git add A.txt
git commit -m "マージコンフリクト解決"

おめでとう!初コンフリクトを解決できましたね。

最後に

ここでは、マージコンフリクトがどういう状況で起きるのか、そしてどんな風に解決できるのかについて書いてみました。この記事の内容と以前投稿した二つの記事で通常の時のgitは使えるかと思います。

Gitの機能は他にも山々あります。特にrebase, cherry-pick, submoduleなどが面白いですが、興味があるかたはそちらも調べてみてください(またあとで、私が投稿する可能性もありますw)。

これでGitに対しの自信が持てるようになってたら嬉しいです!

ちょっとした復習に、

Gitを使う利点

1) バージョニング管理で定期的にサーブ・ロールバックができる。

2) 複数の人が同時に複数の関係ない機能を同時に開発できるシステム

だと思いますので、一人のプロジェクトでも、大きいなプロジェクトでもとても便利です!

ぜひ、使ってみてください!