LoginSignup
3

More than 3 years have passed since last update.

Dialogflow の Java client library は Android で動作するのか?(DetectIntent編)

Last updated at Posted at 2019-12-19

はじめに

Android で Dialogflow を使いたい!というのはよくある話だと思う。
さて、作ってみよう!とまずは SDK が無いか調べてみる。

https://cloud.google.com/dialogflow/docs/reference/libraries/java
image.png

おっと、Android はサポートしていないだって。
みんなどうやって開発してるんだろ?と思って調べてみると、api.ai なるものを発見!

image.png

なんだって。
Dialogflow API V2 を使いたいなら client library を使え?
サポート外って書いてるじゃん…。

はてさて、REST API 使うしかないのか。
けど、SDK は音声入力ストリームもできるようで、是非とも使いたい!

そこで、Java client library がどこまで動くのか検証してみました。

今回は DetectIntent が動くか確認してみます。
別途、音声入力ストリームも試す予定です。

(追記)
音声入力ストリーム編もアップしました!
https://qiita.com/r-hashioka/items/a9d0ca0704ef8dd739a2
ただ、今回記事ありきで書いてるので結構端折ってます。

対象者

Dialogflow の機能説明などは省きます。
はじめましての人は公式ドキュメントなどを見てから読むといいかもしれません。

環境

言語:Kotlin
端末:Pixcel3a (Android 10)

Dialogflow の 事前準備

今回使用する intent は次の2点です。
image.png

Default Welcome Intent

image.png
image.png
image.png
image.png
image.png

today's_weather

image.png
image.png
image.png
image.png

説明

  • 挨拶をすると、Default Welcome Intent に入り、コマンド受付状態に入ります。
  • 「今日の天気は?」と問いかけると「今日の天気は晴れです」と応答がある。といった流れです。

Let's Programming!!!

まずはライブラリ import から

dependencies に下記2つを追加してください。
Dialogflow のライブラリだけではダメだったようです。

app/build.gradle
    implementation 'com.google.cloud:google-cloud-dialogflow:0.105.0-alpha'

    // https://mvnrepository.com/artifact/io.grpc/grpc-okhttp
    // GCP サービスとコネクション貼るときに使用
    implementation 'io.grpc:grpc-okhttp:1.25.0'

Manifest に PERMISSION 追加

  • Dialogflow に接続するので INTERNET を追加
  • 音声入力ストリームも見越して、RECORD_AUDIO も追加
AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

credentials.json を配置

image.png

※※注意※※

Credential を端末に持たせるのはいいことではありません。
今回は検証でとりあえず、とやっているだけですので、対策必須です!

layout を作成

下記画像のような画面を作りたいと思います
image.png

各 UI の機能は下記の通りです。

  • 「こんにちは」ボタンを押すと、Dialogflow に「こんにちは」を送信
  • 「今日の天気」ボタンを押すと、Dialogflow に「今日の天気は?」を送信
  • Dialogflow からの応答を Hello World! の TextView に表示
  • 「RESET」ボタンを押すと、contexts を削除します。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/greetingBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="こんにちは"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/weatherBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="今日の天気"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/greetingBtn" />

    <Button
        android:id="@+id/resetBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="RESET"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/weatherBtn" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity にボタンイベント追加

※この時点ではエラー発生します。

MainActivity.kt
package jp.hashioka.ryo.dialogflowsample

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import jp.hashioka.ryo.dialogflowsample.dialogflow.DetectIntent


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val detectIntent = DetectIntent(this)

        findViewById<Button>(R.id.greetingBtn).setOnClickListener {
            findViewById<TextView>(R.id.textView).text = detectIntent.send("こんにちは")
        }
        findViewById<Button>(R.id.weatherBtn).setOnClickListener {
            findViewById<TextView>(R.id.textView).text = detectIntent.send("今日の天気は?")
        }
        findViewById<Button>(R.id.resetBtn).setOnClickListener {
            detectIntent.resetContexts()
            findViewById<TextView>(R.id.textView).text = "-"
        }
    }

    companion object {
        const val TAG = "MainActivity"
    }
}

DetectIntent クラス作成

dialogflow パッケージを作成し、DetectIntent.kt ファイルを作成します。
image.png

dialogflow/DetectIntent.kt
package jp.hashioka.ryo.dialogflowsample.dialogflow

import android.content.Context
import android.util.Log
import com.google.api.gax.core.FixedCredentialsProvider
import com.google.auth.oauth2.GoogleCredentials
import com.google.cloud.dialogflow.v2.*
import jp.hashioka.ryo.dialogflowsample.R

/**
 * Dialogflow の detectIntent に関するクラス
 */
class DetectIntent (
    context: Context
) {

    companion object {
        private const val TAG = "DetectIntent"
        const val PROJECT_ID = "voice-recognition-trial-261200"
        const val LANGUAGE_CODE = "ja" // TODO: Dialogflow の言語コードはグローバル対応するときに設定ファイルで管理
        val SCOPE = listOf("https://www.googleapis.com/auth/cloud-platform")

        /**
         * セッションを取得する。
         * TODO : Dialogflow のセッションはクライアント毎にユニークとなるよう処理を記述する。
         */
        private fun getSession() : String {
            return "hogehoge"
        }
    }

    private val sessionsClient : SessionsClient
    private val contextClient : ContextsClient

    init {
        // 認証情報セット
        val credentials = GoogleCredentials
            .fromStream(context.resources.openRawResource(R.raw.credentials))
            .createScoped(SCOPE)
        sessionsClient = createSessions(credentials)
        contextClient = createContexts(credentials)
    }

    /**
     * SessionClient を作成する。
     */
    private fun createSessions(credentials: GoogleCredentials): SessionsClient {
        val sessionsSetting =
            SessionsSettings.newBuilder()
                .setCredentialsProvider(FixedCredentialsProvider.create(credentials))
                .build()
        return SessionsClient.create(sessionsSetting)
    }

    /**
     * ContextsClient を作成する。
     */
    private fun createContexts(credentials: GoogleCredentials) : ContextsClient {
        val contextsSettings =
            ContextsSettings.newBuilder()
                .setCredentialsProvider(FixedCredentialsProvider.create(credentials))
                .build()
        return ContextsClient.create(contextsSettings)
    }

    /**
     * detectIntent を実行し、その結果を返却
     * 指定されたテキストを送信するだけ。
     */
    fun send(text: String) : String {
        val request = DetectIntentRequest.newBuilder()
            .setQueryInput(
                QueryInput.newBuilder()
                    .setText(
                        TextInput.newBuilder()
                            .setText(text)
                            .setLanguageCode(LANGUAGE_CODE)
                    )
                    .build())
            .setSession(SessionName.format(PROJECT_ID, getSession()))
            .build()

        val res = sessionsClient.detectIntent(request)
        Log.d(TAG, "response result : ${res.queryResult}")
        return res.queryResult.fulfillmentText
    }

    /**
     * detectIntent を実行し、その結果を返却
     * context 指定可能
     */
    fun send(text: String, contexts: List<String>) : String {
        val queryParametersBuilder = QueryParameters.newBuilder()
        contexts.forEach {
            queryParametersBuilder
                .addContexts(
                    com.google.cloud.dialogflow.v2.Context.newBuilder()
                        .setName(ContextName.format(PROJECT_ID, getSession(), it))
                        .setLifespanCount(5) // TODO: context の Lifespan を動的にする。
                        .build()
                )
        }

        // Dialogflow に投げるテキスト、コンテキストなどセット
        val request = DetectIntentRequest.newBuilder()
            .setQueryParams(queryParametersBuilder.build())
            .setQueryInput(
                QueryInput.newBuilder()
                    .setText(
                        TextInput.newBuilder()
                            .setText(text)
                            .setLanguageCode(LANGUAGE_CODE)
                    )
                    .build())
            .setSession(SessionName.format(PROJECT_ID, getSession()))
            .build()

        val res = sessionsClient.detectIntent(request)
        Log.d(TAG, "response result : ${res.queryResult}")
        return res.queryResult.fulfillmentText
    }

    /**
     * context をリセットする。
     */
    fun resetContexts() {
        contextClient.deleteAllContexts(SessionName.format(PROJECT_ID, getSession()))
    }
}

おお!動いた!

さいごに

Android でも DetectIntent はできました。
多分、REST API の機能くらいはできるんでは?と思ってます。

結構走り書きでしたが、最後まで読んでいただきありがとうございます。

次回、音声入力ストリーム編。

(追記)
ソースコードを GitHub に公開しました。(音声入力ストリームも含む)
https://github.com/ryohashioka/DialogflowSampleForAndroid

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3