見出し画像

KotlinとChatGPT APIで共感型カウンセリングAndroidアプリを作成する

今回は、KotlinとChatGPT APIを使って、共感型カウンセリングアプリを作成する方法をご紹介します。人工知能(AI)を活用したアプリ開発は、今や大きな注目を集めています。この記事を通して、AIアプリ開発の世界に触れてみませんか?

プログラミング初心者の方は、新しい分野へのチャレンジに不安を感じるかもしれません。しかし、一歩一歩学んでいくことで、アプリ開発の面白さと可能性に気づくことができます。すでにプログラミング経験のある方も、新しい技術や手法を学ぶことで、さらなるスキルアップを目指せます。

このチュートリアルでは、Android Studioを使ってAndroidアプリを作成します。最新のUI開発ツールであるJetpack Composeを用いて、LINE風のチャットアプリのUIを実装していきます。Jetpack Composeは、宣言的UIの概念を採用しており、より直感的かつ効率的にUIを構築できます。プログラミング経験者の方は、この新しいツールに興味を持っていただけるでしょう。

また、バックエンドではChatGPT APIを利用して、ユーザーとの対話を実現します。ChatGPT APIは、OpenAIが提供する強力な言語モデルで、自然な会話を生成することができます。これを活用することで、共感型のカウンセリングアプリを作成し、ユーザーにとって親身で寄り添うようなエクスペリエンスを提供することを目指します。

これから作るアプリのイメージはこちらです。LINE風チャットアプリのUIを採用して、ChatGPTと対話を進める中で問題解決に導きます。

完成イメージ

記事をご覧いただき、ありがとうございます。noteで3つ目の有料記事を公開できました。公開記念として10部限定で、100円で販売します。興味のある方はお早めにご購入ください。


記事概要

本記事は、プログラミング初心者向けに、KotlinとChatGPT APIを使った共感型カウンセリングアプリの作成方法を解説します。記事は3部構成になっており、各部で以下の内容を扱います。

第1部では、Androidアプリ開発の基礎を学びます。Android Studioの概要や新規プロジェクトの作成方法を説明した後、Jetpack Composeを使ったユーザーインターフェースの作成方法を解説します。また、シミュレーターを使ったコードのデバッグ方法も紹介します。

第2部では、ChatGPT APIとの連携方法を説明します。APIの概念を簡単に説明した後、OpenAIアカウントの作成とAPIキーの取得方法を解説します。次に、ChatGPTとの連携を管理するクラスと、実際の連携を実装する方法を示します。これにより、AIカウンセラーとしての機能を実現します。

第3部では、カウンセリング結果を読み上げる機能の実装方法を説明します。AndroidのTTS(Text-to-Speech)機能を活用し、テキストを音声に変換して読み上げます。TTSを管理するクラスの作成方法と、レスポンスを読み上げる方法を解説します。

本記事を通して、プログラミング初心者の方でもKotlinとChatGPT APIを使ったAndroidアプリ開発の一連の流れを理解することができます。共感型カウンセリングアプリの作成を通して、生成AIの活用方法やアプリ開発の基礎を学ぶことができるでしょう。

第1部 Androidアプリ開発を開始する

Androidアプリ開発の世界へようこそ!このセクションでは、Googleが提供する統合開発環境(IDE)であるAndroid Studioを使って、Androidアプリ開発の第一歩を踏み出します。

プログラミング初心者の方にとって、新しいツールや概念の習得は大変そうに感じるかもしれません。しかし、心配はいりません。本記事では、Android Studioの基本的な使い方から、Jetpack Composeを使ったユーザーインターフェースの作成、シミュレーターでのデバッグまで、丁寧に解説していきます。

Android Studioについて

Android Studioは、Android アプリ開発のための統合開発環境(IDE)です。コーディング、デバッグ、テスト、そしてアプリのビルドとデプロイを行うための強力なツールセットを提供しています。Android Studioは、IntelliJ IDEAをベースにしており、Androidアプリ開発に特化した機能を追加しています。

Android Studioを使用することで、効率的かつ生産的にアプリ開発を進めることができます。コードエディタは、コード補完、構文ハイライト、リアルタイムのエラーチェックなどの機能を備えており、開発者の作業をサポートします。また、ビジュアルエディタを使用して、アプリのユーザーインターフェース(UI)を直感的に設計することもできます。

Android Studioを起動する

Android Studioを起動するには、まずAndroid Studioをダウンロードしてインストールする必要があります。Android Studioの公式ウェブサイトにアクセスし、自分のオペレーティングシステムに合わせたバージョンをダウンロードしてください。本稿と同じ環境で作業したい方は、原稿執筆時の最新バージョンである「Android Studio Iguana | 2023.2.x」を選ぶことをお勧めします。バージョンが異なると、UIの配置や一部の設定項目が変更されている可能性があり、本稿の手順通りに進めない場合があることにご注意ください。

インストールが完了したら、Android Studioを起動します。起動時に、「Welcome to Android Studio」画面が表示されます。ここから、新規プロジェクトの作成や既存のプロジェクトを開くことができます。

新規プロジェクトの作成する

fig01 Welcome to Android Studio

Android Studioを起動すると、「Welcome to Android Studio」ウインドウが表示されます。このウインドウでは、「新規プロジェクトの作成」、「既存のプロジェクトを開く」、または「Gitレポジトリをクローンする」を選択できます。ここで、「New Project」を選んで、新規プロジェクトを作成します。(「File」メニューから「New」を選択し、「New Project」をクリックする方法もあります。)

fig02 プロジェクトテンプレート

プロジェクトのテンプレートを選択する画面が表示されます。ここで、開発したいアプリケーションタイプに合わせてテンプレートを選択します。例えば、Androidアプリを開発する場合は、「Phone and Tablet / Empty Activity」を選択し、「Next」をクリックします。(メニューが紫のものが、Jetpack Composeのテンプレートです。旧来のXMLレイアウトを使うときは、緑のテンプレートを使います。)

fig03 プロジェクトの設定

次は、プロジェクトの設定を行います。Nameフィールドは「CounselingApp」を、Package nameフィールドは「com.example.counselingapp」を入力して下さい。こうすることで、これから示すコードをそのままコピペするだけで、動作確認ができるようになります。

Save locationは、新規プロジェクトを保存する場所を選びます。著者はユーザーディレクトリ直下に「AndroidStudio」フォルダーを作成し、そこにすべてのプロジェクトを保存しています。この方法は、ターミナルからのアクセスが容易であり、プロジェクト管理を簡単にするため、初心者にも推奨されます。

Minimum SDKは、アプリがサポートする最小のAndroidバージョンを指定し、より多くのデバイスとの互換性を保ちつつ、必要な機能へのアクセスを決定します。ここでは、広範なデバイスサポートと現代的なAndroid機能のバランスを取るために初期値「API 24」を推奨します。Bundle configuration languageでは、プロジェクトの依存関係や設定を管理するために、推奨される「Kotlin DSL」を選んでください。これは、Kotlinに慣れ親しんでいる開発者にとってより直感的な設定管理を可能にします。「Finish」をクリックすると、新しいプロジェクトが作成されます。

fig04 新しいプロジェクト

Android Studioで新しいプロジェクトが開かれたら、まずはその画面構成について簡単に理解しましょう。Android Studioの画面は大きく分けて3つの主要なエリアから構成されています。

プロジェクトビューアー: 画面の左側に位置するプロジェクトビューアーからは、プロジェクト内のファイルとフォルダに簡単にアクセスできます。ここでは、アプリのソースコードやリソースファイル、Gradleスクリプトなど、開発に必要なすべてのファイルを見つけることができます。特定のファイルを探している場合、このビューアーを利用して迅速にファイルを見つけることが可能です。

エディタエリア: 画面の中央には、エディタエリアがあります。ここでは、選択されたファイルの内容を表示し、編集することができます。コードの記述、リソースの編集、XMLレイアウトのビジュアル編集など、アプリ開発に必要なほとんどの作業がこのエリアで行われます。また、エディタエリアは複数のタブを開くことができるため、同時に複数のファイルを編集することも可能です。

ツールウィンドウ: 画面の下部や右側には、様々なツールウィンドウが配置されています。これには、Logcat(ログ出力ビューア)、Build(ビルド出力ビューア)、TODO(タスクリスト)などがあります。これらのツールウィンドウを使用することで、アプリのデバッグ、ビルドプロセスの監視、プロジェクト内のタスクの管理など、開発作業を効率的に行うことができます。

Android Studioの画面構成を理解することは、開発プロセスの効率化につながります。各エリアの機能と役割を覚えておくことで、よりスムーズに開発作業を進めることが可能になります。

 次のセクションでは、最新のUI開発ツールであるJetpack Composeについて説明します。

Jetpack Composeについて

Jetpack Composeは、AndroidのモダンなUI開発ツールキットです。宣言的なUIの構築を可能にし、より簡潔で読みやすいコードでUIを記述できます。Jetpack Composeは、Kotlin言語の特徴を活かして設計されており、Kotlinの言語機能を最大限に活用できます。

Jetpack Composeでは、UIの構成要素をComposable関数として定義します。Composable関数は、UIの一部を表現し、再利用可能なコンポーネントとして機能します。これらのComposable関数を組み合わせることで、複雑なUIを構築できます。

Jetpack Composeは、リアクティブプログラミングの原則に基づいています。UIの状態が変更されると、自動的にUIが更新されます。これにより、UIとデータの同期を簡単に保つことができ、アプリのパフォーマンスと応答性が向上します。

Jetpack Composeは、Android Studioと緊密に統合されており、開発者はビジュアルエディタを使ってUIを設計したり、プレビュー機能を使ってリアルタイムにUIの変更を確認したりすることができます。

これで、Androidアプリ開発を始めるための準備が整いました。次のセクションでは、実際にJetpack Composeを使ってLINE風のチャットアプリのUIを実装していきます。

ユーザーインターフェースの実装:MessageItem

このセクションでは、実践的なプログラミングを通じてJetpack Composeの基礎を学んでいきます。LINE風のチャットアプリは、そのよい題材として選定されました。

初めに、チャット画面の部品を作成します。LINEのように角丸四角でメッセージを囲むコードを実行を作成します。送信メッセージは右に、受信メッセージは左に表示して見分けやすいようにします。このコードは、MessageItemという名前の新しいファイルに記述して下さい。

新規ファイルを作成するには、以下の手順を実行します。

fig06 新規ファイルの作成

新規ファイルを作成するには、プロジェクトナビゲーターで「app > kotlin+java > com.example.counselingapp」を右クリックしてください。表示されたメニューから「New > Kotlin Class/File」を選ぶことで、クラス名を決めるモーダルウインドウがホップアップします。

fig07 ファイルの名前

新しいファイル名「MessageItem」を入力します。ファイルのタイプは「File」を選んで、エンターキーで確定してください。エディタエリアに、新しいファイルが開かれます。以上で新規ファイルの作成は完了しました。

次に、UI実装のコードを書いていきます。新しく作成したファイル(MessageItem.kt)に次に示すコードを記述して下さい。コード全体をコピーして、貼り付けます。

package com.example.counselingapp

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

data class Message(val text: String, val isSent: Boolean) // (0)

@Composable
fun MessageItem(message: Message) {
    Box( // (1)
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        contentAlignment = if (message.isSent) Alignment.CenterEnd else Alignment.CenterStart
    ) {
        Box( // (2)
            modifier = Modifier
                .widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.9f)
                .background(
                    if (message.isSent) Color(0xFF1E88E5).copy(alpha = 0.2f) else Color.Gray.copy(alpha = 0.2f),
                    shape = RoundedCornerShape(16.dp)
                )
                .padding(16.dp)
        ) {
            Text(message.text) // (3)
        }
    }
}

@Preview
@Composable
fun MessageItemPreview() { // (4)
    MessageItem(Message(" こんにちは", true)) // (5)
    MessageItem(Message(" こんにちは", false)) // (6)
}

このコードは、チャットアプリのメッセージアイテムを表示するためのComposable関数MessageItemとそのプレビューを定義しています。

コードの理解を深めるために、コメントの番号部分を詳しく解説していきます。これにより、コピー&ペーストしたコードの機能や、その記述がなされた背景を理解するようにして下さい。

(0)では、Messageという名前のデータクラスを定義しています。Kotlinにおけるdata classは、データを保持する目的でよく使用される特別なクラスの一種です。textプロパティは、メッセージの本文を保持します。String型であり、テキストメッセージの内容を表します。isSentプロパティは、メッセージが送信されたものか(true)、受信されたものか(false)を表します。Boolean型で、メッセージがユーザーによって送信されたものなのか、それとも相手から受信したものなのかを区別するために使用されます。

(1)の最初のBoxコンポーネントは、メッセージアイテムのコンテナ(入れもの)として機能します。modifierは、fillMaxWidth()を使用してアイテムの幅を親の幅いっぱいに広げ、padding(16.dp)で周囲に余白を設定しています。dpという単位はDensity-independent Pixelsの略で、AndroidにおいてUI要素のサイズや位置を指定するための単位です。dpは、異なる画面サイズや解像度のデバイスで一貫したレイアウトを実現するために使用されます。

contentAlignmentは、メッセージの送信者に応じてアイテムの配置を決定します。送信メッセージ(message.isSentがtrue)の場合はAlignment.CenterEnd(右寄せ)、受信メッセージの場合はAlignment.CenterStart(左寄せ)になります。

(2)の内側のBoxコンポーネントは、メッセージの背景とスタイルを定義します(これが中身)。modifierのwidthIn(max =)でボックスの幅がデバイスサイズの90%に制限しています。backgroundは、メッセージの送信者に応じて背景色を設定します。送信メッセージの場合はColor(0xFF1E88E5)(青色)、受信メッセージの場合はColor.Gray(灰色)を使用し、copy(alpha = 0.2f)で透明度を20%に設定しています。

shapeは、RoundedCornerShape(16.dp)を使用して、メッセージの角を丸くしています。padding(16.dp)で、メッセージの内容とその背景の間に内側の余白を設定しています。

(3)のTextコンポーネントは、メッセージのテキスト内容を表示します。message.textを使用して、Messageオブジェクトのテキストプロパティを表示しています。

(4)の@Previewアノテーションは、MessageItemPreview関数がプレビュー用の関数であることを示しています。このアノテーションを使うことで、アプリを実行せずにJetpack ComposeのUIをプレビューして、効率よく開発を進められます。

MessageItemPreview関数内で、MessageItem関数を2回呼び出しています。最初の呼び出しでは、送信メッセージ(isSentがtrue)としてメッセージを表示します(5)。
2回目の呼び出しでは、受信メッセージ(isSentがfalse)としてメッセージを表示します(6)。

以上で、MessageItem関数のコードの説明を終了します。これで、Jetpack Composeを使用してメイン画面を作成する基本的な流れを理解できたと思います。次に、Jetpack Composeで実装したUIを簡単にチェックする方法を紹介します。このコードのUIをAndroid Studioのプレビューエリアで確認すると、次のようになります。

fig07 メッセージ部分のプレビュー

fig07は、Android Studioでメッセージ部分のプレビューを行ったときのスクリーンショットです。右上のアイコンで、「コードのみ/コード+プレビュー/プレビューのみ」に変更ができます。プレビューエリアに受信メッセージと送信メッセージが設計通り表示されていることが確認できました。以上で、MessageItemについての説明を終わります。

次は、チャット画面全体を作成します。メッセージが上から順番に並ぶレイアウトをChatScreen関数に実装します。「ChatScreen」という名前のファイルを先ほどと同じ手順で作成して下さい。

ユーザーインターフェースの実装:ChatScreen

ChatScreen.ktが作成できたら、次に示すコードを記述して下さい。

package com.example.counselingapp

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.counselingapp.ui.theme.CounselingAppTheme
import kotlinx.coroutines.launch

@Composable
fun ChatScreen(initialMessages: List<Message> = emptyList()) {
    var messages by remember { mutableStateOf(emptyList<Message>()) } // (1)
    var inputText by remember { mutableStateOf("") } // (2)
    val listState = rememberLazyListState() // ここから(3)
    val coroutineScope = rememberCoroutineScope() // ここまで(3)
    val focusManager = LocalFocusManager.current // (4)

        fun sendMessage() { // (5)
        if (inputText.isNotBlank()) {
            messages = messages + Message(inputText, true)
            messages = messages + Message(inputText, false)
            coroutineScope.launch { // ここから(6)
                listState.animateScrollToItem(messages.size - 1)
            }
            focusManager.clearFocus() // ここまで(6)
            inputText = "" // (7)
        }
    }

    Column( // (8)
        modifier = Modifier
            .fillMaxSize()
            .imePadding()
    ) {
        LazyColumn( // (9)
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
            state = listState
        ) {
            items(initialMessages + messages) { message ->
                MessageItem(message) // (10)
            }
        }

        Spacer(modifier = Modifier.height(8.dp)) // (11)

        Row( // (12)
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 8.dp)
        ) {
            TextField( // (13)
                value = inputText,
                onValueChange = { inputText = it },
                modifier = Modifier
                    .weight(1f)
                    .padding(end = 16.dp)
                    .border(BorderStroke(1.dp, Color.Gray), shape = RectangleShape)
                    .heightIn(min = 100.dp),
                label = { Text("メッセージを入力") },
                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), // ここから(14)
                keyboardActions = KeyboardActions(
                    onSend = {
                        sendMessage()
                    }
                ) // ここまで(14)
            )

            Button( // (15)
                onClick = {
                    sendMessage()
                }
            ) {
                Text("送信")
            }
        }
    }
}

@Preview(showBackground = true) // (16)
@Composable
fun ChatScreenPreview() {
    val messages = listOf( // (17)
        Message("こんにちは", true),
        Message("はい、こんにちは!", false),
        Message("調子はどうですか?", true),
        Message("良いですよ。ありがとうございます!", false)
    )

    CounselingAppTheme {
        ChatScreen(messages)
    }
}

(1)は、チャットアプリのメッセージリストを管理するための状態変数messagesを定義しています。remember { mutableStateOf(...) }の使用により、messagesの状態はアプリの再描画が行われても保持されます。これは、Composeがリコンポジション(コンポーネントの再描画)を行う際に、指定された値を「覚えておく」ためのものです。mutableStateOfは、値が変更されたときにリコンポジションをトリガーするComposeの特別な状態ホルダーです。emptyList<Message>()は、初期状態としてメッセージリストが空であることを示します。

(2)で、入力テキストの状態管理inputTextを定義しています。ユーザーがUI上で入力するテキストを保持するための変数です。この変数もrememberとmutableStateOfを使用しているため、ユーザー入力がリコンポジションを通して保持され、変更をトリガーできます。初期値として空の文字列が設定されており、ユーザーが何も入力していない状態を表します。

(3)は、スクロール状態listStateとコルーチンスコープcoroutineScopeを定義しています。これらの変数は、メッセージがたくさん表示されたときに、画面をスクロールして最新メッセージが表示されるように実装するときに使います。

ememberLazyListState()は、LazyColumnで使用されるリストのスクロール位置を記憶するためのものです。これにより、アプリがリコンポジションされたときにユーザーのスクロール位置が維持されます。
rememberCoroutineScope()は、コルーチンを利用するためのスコープを提供します。コルーチンは非同期処理を簡潔に扱うためのKotlinの機能です。このスコープは、非同期タスク(リストの自動スクロール)を実行する際に使用されます。

(4)は、フォーカス管理focusManagerを定義しています。キーボードが自動で閉じる実装で使用します。この行では、LocalFocusManager.currentを使用して現在のフォーカスマネージャーを取得します。フォーカスマネージャーは、キーボードフォーカスを管理するためのオブジェクトで、テキストフィールドなどのUI要素にフォーカスを与えたり、フォーカスを外したりすることができます。

(5)は、メッセージ送信するメソッドです。sendMessageメソッドは、ユーザーが入力したテキストが空でない場合(inputText.isNotBlank())、そのテキストを使って新しいMessageオブジェクトを作成し、messagesリストに追加します。ここでは、同じテキストで送信メッセージと受信メッセージの両方を表現しています。(次の第2部で、受信メッセージは、ChatGPTからの応答に変更されます)

(6)は、送信ボタンを押した後のUIの変化を実装しています。coroutineScope.launchを使用して、メッセージリストの最下部にスクロールする非同期タスクを開始します。これは、新しいメッセージが追加されたときにリストの最下部を表示するために使用されます。focusManager.clearFocus()で、現在のフォーカス(例えば、テキストフィールドから)をクリアします。これにより、キーボードが非表示になり、メッセージが確認しやすくなります。最後に、inputText = ""で入力フィールドをクリアし、次のメッセージ入力の準備をします。

(7)で、inputText = ""で入力フィールドをクリアし、次のメッセージ入力の準備をします。これにより、ユーザーが毎回手動で入力フィールドをクリアする手間が省け、より快適にチャットアプリケーションを使用できるようになります。

(8)は、画面全体のレイアウトをしています。画面上部にメッセージを下部に入力フィールドを配置します。Columnは、その子要素を縦方向に並べるコンテナです。Modifier.fillMaxSize()は、Columnが親コンテナ(この場合はデバイスの画面)の最大サイズを埋めるようにします。つまり、画面全体に広がります。.imePadding()は、ソフトキーボードが表示されている時に、キーボードによってコンテンツが隠れないように余白を自動的に調整するモディファイアです。これにより、ユーザーが入力フィールドをタップしてキーボードが表示された際に、入力フィールドがキーボードによって隠れることがなくなります。

(9)は、メッセージリストを表示します。LazyColumnは、項目を遅延して表示するリストを構築するためのコンポーネントです。これは、大量のデータを持つリストを効率的に表示するために使用されます。表示されていない項目はメモリに保持されず、スクロールに応じて動的に生成されます。

Modifier.fillMaxWidth()は、LazyColumnが親コンテナの最大幅に合わせて幅を広げるようにします。.weight(1f)は、Column内でLazyColumnが利用可能なスペースを他の要素と比較して柔軟に占めるようにします。この場合、可能な限り多くの空間を占めるようになります。state = listStateで、LazyColumnのスクロール状態を管理します。これにより、例えばメッセージが追加された時にリストの末尾に自動スクロールするなど、リストの挙動を制御できます。

(10)は、LazyColumn内でメッセージを一覧表示するために使用されます。items関数は、リスト内の各項目に対して実行され、ここではinitialMessagesとユーザーによって追加されたmessagesの両方を結合しています。
各メッセージに対して、MessageItemコンポーネント(このコード例では定義されていませんが、メッセージを表示するためのカスタムコンポーネントと想定されます)が呼び出され、リスト内にメッセージが表示されます。

(11)のSpacerは、コンポーネント間に空間を追加するために使用されます。この場合、Modifier.height(8.dp)を使って、縦方向に8dpの高さを持つ空間を作成しています。これにより、UI要素間の視覚的な区切りが明確になり、ユーザーインターフェイスが読みやすくなります。

(12)は、入力フィールドと送信ボタンを表示します。owコンポーネントは、その子要素を横方向に並べます。ここでは、メッセージ入力フィールドと送信ボタンがRow内に配置されています。
Modifier.fillMaxWidth()は、Rowが親コンテナの最大幅に合わせて幅を広げるようにします。.padding(horizontal = 16.dp, vertical = 8.dp)は、Rowとその子要素の周囲に水平方向に16dp、垂直方向に8dpのパディングを追加します。これにより、要素と画面端または他の要素との間に適切な余白が生まれ、UIの見た目が整います。

(13)は、入力フィールドです。TextFieldコンポーネントはユーザーがテキスト入力を行うためのUI要素です。ここでは、ユーザーがメッセージを入力するためのフィールドを表しています。

value = inputTextは、テキストフィールドの現在の値を指定します。inputTextはユーザー入力を保持する状態変数です。onValueChange = { inputText = it }は、テキストフィールドの内容が変更されたときに実行されるコールバックです。ユーザーがテキストを入力するたびにinputTextの値が更新されます。label = { Text("メッセージを入力") }で、テキストフィールドが空のときに表示されるラベル(プレースホルダー)を指定しています。

(14)は、キーボードの設定です。keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send)で、キーボードの送信ボタンを有効にしています。これにより、キーボードから直接メッセージを送信できます。keyboardActionsで、キーボードの送信アクションがトリガーされたときの処理を定義しています。ここでは、sendMessage関数が呼び出され、メッセージが送信されます。

(15)は、送信ボタンを表示します。Buttonコンポーネントは、ユーザーがタップすることでアクションをトリガーするUI要素です。ここでは、ユーザーがメッセージを送信するためのボタンを提供しています。
onClick = { sendMessage() }は、ボタンがクリックされたときに実行されるコールバック関数を指定します。ここでは、sendMessage関数が呼び出され、入力されたメッセージの送信処理が行われます。
Text("送信")は、ボタン上に表示されるテキストを定義しています。

(16)で、プレビュー機能を有効にしています。アプリを実行せずに、実装中のUIを確認できるので開発の効率が上がります。@Previewアノテーションを使用して、Android Studioのプレビュー機能にこのコンポーザブル関数を表示させます。showBackground = trueで、プレビューに背景を表示させることができます。

(17)は、プレビュー用のサンプルメッセージです。ChatScreenPreview関数で使用されるサンプルメッセージのリストを定義しています。これは、UIをデザインする際にどのように見えるかを確認するために使用されます。

以上で、ChatScreen関数の説明を終わります。チャットアプリのような複雑なUIが比較的簡単なコードで実装できました。プレビューエリアで確認した図を下に示します。

fig08 ChatScreen関数のプレビュー

fig08を見ると、Android Studioのプレビューエリアにチャットアプリの画面が表示されています。設計通り、角丸で囲まれたテキストが左右に振り分けて表示されていることが確認できました。

もしメッセージテキストの背景色を変えたいなど、UIを修正したいときは、コードエディタの該当部分を修正してみましょう。すると、プレビューエリアで結果をすぐに確認できます。

以上で、ChatScreen関数の説明を終わります。次は、MainActivityアクティビティの修正を行います。次に示すコードをMainActivity.ktにコピペして下さい。

ユーザーインターフェースの実装:MainActivityの修正

package com.example.counselingapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.counselingapp.ui.theme.CounselingAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) { // (1)
        super.onCreate(savedInstanceState) // (2)
        setContent { // (3)
            CounselingAppTheme { // (4)
                // A surface container using the 'background' color from the theme
                Surface( // (5)
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    ChatScreen() // (6)
                }
            }
        }
    }
}

このコードは、Androidアプリケーションのメインアクティビティを定義しており、Jetpack Composeを使ったUIの実装例です。各部分の解説は以下の通りです。

(1)のonCreateは、Activityのライフサイクル中で最初に呼ばれるメソッドで、アクティビティの初期化処理を行います。このメソッド内でUIのセットアップや、他の初期化処理を実行します。onCreateメソッドはBundle?型の引数を取ります。このBundleは、以前の状態を保存していた場合にそれを渡すために使用されます(例えば、デバイスの回転後に状態を復元する場合など)。

(2)では、ComponentActivityのonCreateメソッドを呼び出しています。これは、Activityの初期化処理を行うために必要です。superキーワードは、親クラスのメソッドやプロパティにアクセスするために使用されます。

(3)のsetContentは、このアクティビティのUIをJetpack Composeを使用して定義します。このブロック内で宣言されたコンポーザブル関数がアクティビティのUIとして描画されます。

(4)のCounselingAppThemeは、アプリケーションのテーマを適用するためのコンポーザブル関数です。これは、カスタムテーマやMaterial Designのスタイルをアプリケーション全体に適用するために使用されます。このコードでは、CounselingAppTheme内でUIの定義が行われます。

(5)のSurfaceは、画面の表面を表現するためのコンポーザブル関数です。ここでは、アプリケーションの背景として機能します。modifier = Modifier.fillMaxSize()は、このSurfaceが画面全体に広がるようにするモディファイアです。color = MaterialTheme.colorScheme.backgroundは、Surfaceの背景色をテーマの背景色に設定しています。MaterialThemeは、現在適用されているMaterial Designのテーマにアクセスするためのオブジェクトです。

(6)で、ChatScreenコンポーザブル関数が呼び出されています。ChatScreenは、チャットアプリケーションのメイン画面を構築するためのコンポーザブル関数でしょう。このコードでは具体的な実装は省略されていますが、おそらくメッセージのリスト表示やメッセージ送信機能などを含むUIが定義されていると考えられます。

以上で、UI実装は終了です。Androidエミュレーターで動作確認をしてみましょう。

ユーザーインターフェースの実装:デバッグ

fig09 Androidエミュレーター

上図は、Android Studioを使って、Androidエミュレーターでデバッグした際のスクリーンショットです。ツールバー中央で、エミュレーターの種類を選びます。▶︎実行ボタンをクリックすると、エミュレーターが表示され、アプリが起動します。入力フィールドにテキストを入力して、送信ボタンをタップすると、メッセージが表示されます。以上で、デバッグは終了です。

初期設定だと、日本語入力ができないはずです。付録にAndroidエミュレーターの設定を変更して日本語入力できるようにする方法を解説しました。

次に私たちが取り組むミッションは、AndroidアプリをChatGPTと連携させることです。具体的には、ユーザーからの質問に対して、カウンセラーのように回答を返すアプリを作成します。このプロジェクトを通じて、あなたはAPIの使用方法を学び、アプリ開発のさらなるステップに進むことができるようになります。

第2部 ChatGPTとのAPI連携

このセクションでは、私たちのAndroidアプリがChatGPTと会話できるように、APIを介して二つを連携させる方法について学びます。しかし、まずAPIとは何か、そしてそれがどのように機能するのかについて簡単に説明しましょう。

APIとは?

fig10 APIリクエストとレスポンス

APIは「Application Programming Interface」の略で、アプリケーション間で情報を交換するための架け橋のようなものです。簡単に言えば、APIはあるプログラムが別のプログラムに対して、「こんなことをしてほしい」とリクエストを送るための方法、またそのリクエストに応じて「こういう情報を返すよ」と応答を返す仕組みです。
例えば、天気予報アプリがあったとします。このアプリがユーザーの位置情報に基づいて最新の天気を表示するためには、外部の天気予報サービスからデータを取得する必要があります。ここでAPIが活躍します。アプリは、指定されたAPIを通じて天気予報サービスにリクエストを送り、サービスはそのリクエストに応じた天気情報をAPIを通じてアプリに返します。
APIを使用する主なメリットは以下の通りです。

  1. 開発の効率化  APIを利用することで、開発者は複雑な処理をAPIサーバー側に任せることができます。これにより、アプリケーション開発に専念でき、開発の効率化につながります。例えば、天気予報アプリを開発する際、天気データを取得するための複雑な処理をゼロから実装する必要はありません。天気予報APIを利用することで、簡単にデータを取得し、アプリに組み込むことができます。

  2. 高度な機能の実現  APIを活用することで、人工知能や機械学習などの高度な機能を簡単に実装できます。例えば、ChatGPT APIを利用すれば、自然言語処理や対話システムをアプリに組み込むことができます。顔認識APIを使えば、写真から顔を検出し、識別することができます。このように、専門的な知識や技術がなくても、APIを通して高度な機能を実現できます。

  3. コストの削減  APIを利用することで、開発コストを大幅に削減できます。APIサーバーは、多くの開発者に共通の機能を提供するため、個々の開発者がゼロから実装する必要がありません。APIを利用することで、開発時間が短縮され、人件費や資源のコストを抑えることができます。

  4. スケーラビリティの向上  APIを利用することで、アプリケーションのスケーラビリティを向上できます。APIサーバーは、多数のリクエストを処理するように設計されています。アプリケーションの利用者が増加しても、APIサーバーがリクエストを処理するため、アプリケーション自体のインフラを大規模に拡張する必要がありません。

  5. 最新の技術へのアクセス  APIを通して、最新の技術や機能にアクセスできます。API提供者は常に最新の技術を導入し、改善を行っています。開発者はAPIを利用することで、自動的に最新の技術を活用できます。これにより、アプリケーションの機能を常に最新の状態に保つことができます。

APIを活用することで、開発者は複雑な処理を簡単に実装でき、高度な機能を実現できます。また、開発コストの削減、スケーラビリティの向上、最新技術へのアクセスなど、多くのメリットがあります。APIを上手く活用することが、効率的で高品質なアプリケーション開発につながります。
APIの理解ができたところで、次は、ChatGPT APIを利用するために必須の作業である「OpenAIアカウントの作成」「APIキーの作成」について説明します。(もしChatGPT APIキーの取得がお済みの方は、「ChatGPT連携:ChatGPTService」のセクションまで進んで下さい)

OpenAIアカウントの作成

このセクションで初めに行うべき作業は、OpenAIのアカウントを作成することです。このアカウントを通じて、ChatGPTのAPIキーを取得し、Androidアプリと生成AIを連携させることができます。この連携により、Androidアプリにより複雑でリアルタイムなカウンセリング機能を付加することができるようになります。
OpenAIアカウントの作成は、2024年3月10日に行いました。以下の作業手順やスクリーンショットは変更する可能性がある点にご留意ください。次の、APIキー作成ページにアクセスします。
https://platform.openai.com/account/api-keys

fig11 OpenAIアカウントのサインアップ

APIキーを生成するため、まずはOpenAIのアカウントを作成することから始めます。アカウント作成ページで「Sign up」を選択し、メールアドレスとパスワードを入力して登録してください。このアカウントによって、OpenAIが提供するAPIを利用するための認証キーを受け取ることができます。
登録の次のステップとして、指定したメールアドレスにOpenAIから認証用のメールが送信されます。そのメール内のリンクをクリックし、メールアドレスの確認を行ってください。これは、セキュリティの一環として、アカウントの正当性を保証するためのものです。

fig12 電話番号認証

メールでの認証が完了したら、必要な個人情報(名前、組織、生年月日)を入力します。そして、「Start verification」ボタンをクリックして、電話番号の認証も行ってください。これにより、OpenAIとの連携の信頼性が高まります。
以上の手続きを終えると、アカウントの設定が完了し、APIキーを生成する準備が整います。(無料トライアルの範囲なら、クレジットカードの登録は必要ありません。)

APIキーの作成

fig13 APIキーを作成する

「Create new secret key」ボタンをクリックし、APIキーの生成を始めます。ポップアップされるモーダルウィンドウにある「Name」フィールドに、APIキーの名前を入力してください。後でキーを識別しやすいように、区別がつきやすい名称を選んでください。「Create secret key」ボタンをクリックすると、新しいAPIキーが生成され、そのキーは画面上に一度だけ表示されます。このキーは重要な認証情報であるため、他人に知られないように慎重に管理してください。表示されたキーは、次のセクションで、コードに入力する必要があるので、確実にコピーしておきましょう。これでAPIキーの作成プロセスは完了です。
ChatGPT APIのサービスは有料のため、使用状況を常に把握しておく必要があります。仮に、自分のAPIキーがなんらかの理由で流出し不正に利用されると、莫大な利用料金が請求されることにもなりかねません。そういった事故を防ぐためにも、使用状況の管理が重要になります。次は、使用状況の確認の方法を説明します。

使用状況の確認

fig14 使用状況

左メニューの棒グラフのアイコンをクリックして、Usageページを開きます。このページで毎日の使用状況を確認できます。不正な使用が行われていないかを定期的にチェックしてください。画面右に、Credit Grantsとして、5.00ドルのクレジットがチャージされています。これは、フリートライアルとして使えるお金で、表示された期限(ここでは、2024年6月10日)まで使用することができます。5ドル以上の使用、または期限以降も使用するときは、クレジットカードを登録することになります。
次に、クレジットカードの登録と、使用制限について説明します。

fig15 支払い設定

右側のメニューから「Settings / Billing」を選択し、「Billing Settings」ページを開きます。ここでは、将来的にAPIの使用料が発生した場合の支払い方法を設定できます。「Payment methods」セクションを通じて、サービス利用料の支払いに使用するクレジットカード情報を登録してください。APIサービスの利用開始に先立って、支払い情報を登録することが推奨されます。クレジットカードを登録したら、必ず「Usage limits」セクションを開いて確認します。
「Usage limits」セクションでは、API利用における使用制限を設定することが可能です。デフォルト設定では、96ドルの使用に達した際に登録されたメールアドレスに通知が届きます。また、利用料が120ドルを超えるとAPIサービスの利用が一時停止されるため、予期せぬ料金超過を防ぐための安全策です。これらの金額は、予算に合わせて調整可能なため、状況に応じて制限額を変更することを検討してください。

ChatGPT APIの利用料金は、使用するモデルによって異なります。GPT3をベースにする「gpt-3.5-turbo」は、1000トークン当たり、0.0005ドル(約0.074円)です(Pricingページ)。トークンという単位は、文字の単語の中間を表し、日本語では、1文字で1~3トークン程度を消費します。安価だと思って使いすぎると、すぐに無料トライアルの5ドルは使い切ってしまいます。定期的に、Usageページで現在の使用状況を確認しましょう。

以上で、OpenAIのアカウントおよびAPIキーのセットアップが完了しました。これで生成AIを組み込んだ音声アシスタントの開発に取りかかる準備が整いました。次に、API実装の設計を行います。

API実装の設計

開発者がAPIを利用したアプリケーションを構築する際には、APIとの通信をどのように行うかが重要なポイントになります。ここでは、APIエンドポイントへの問い合わせ、認証方法、リクエストの形式、およびAPIからのレスポンスの処理方法について説明します。

APIエンドポイントへの問い合わせ

APIエンドポイントは、APIが提供する特定の機能へアクセスするためのURLです。開発者は、このエンドポイントを使ってAPIにリクエストを送信し、必要なデータを取得または送信します。たとえば、ChatGPT APIにおいて、ユーザーの質問に対する回答を取得するためのAPIエンドポイントは、"https://api.openai.com/v1/chat/completions"になります。

認証方法

APIの利用には、正当な使用者であることを証明するための認証が必要になることがあります。ChatGPT APIにおいては、APIキーをリクエストのヘッダーに含めることで認証を行います。ヘッダーに含まれたキーにより、API提供者はリクエストが正当な使用者からのものであることを確認します。

APIリクエストの形式

APIへのリクエストは、決められた形式でデータを送信する必要があります。多くの場合、JSON形式でテキストデータを作成し、HTTPのPOSTメソッドを使用してエンドポイントへ送信します。JSONはJavaScript Object Notationの略で、軽量なデータ交換フォーマットです。例えば、ユーザーの質問を送るリクエストのJSONデータは以下のようになります。

{
     "model": "gpt-3.5-turbo",
     "messages": [{"role": "user", "content": "ここにユーザーの質問が入る。"}],
   }

APIレスポンスの処理

APIからのレスポンスも通常、JSON形式で返されます。レスポンスには、リクエストに対する結果が含まれています。開発者はこのレスポンスデータから必要な情報を抽出して、アプリケーションに表示するなどの処理を行います。例えば、レスポンスのJSONデータは以下のようになります。少し複雑な形をしていますが、choices配列の最初の要素を抜き出し、messageキー > contentキーから目的の回答を取得できます。

{
    "id": "chatcmpl-abc123",
    "object": "chat.completion",
    "created": 1677858242,
    "model": "gpt-3.5-turbo-0613",
    "usage": {
        "prompt_tokens": 13,
        "completion_tokens": 7,
        "total_tokens": 20
    },
    "choices": [
        {
            "message": {
                "role": "assistant",
                "content": "ここにChatGPTの回答が入る。"
            },
            "logprobs": null,
            "finish_reason": "stop",
            "index": 0
        }
    ]
}

APIを利用したアプリケーション開発では、エンドポイントへの正しい問い合わせ、適切な認証方法の使用、決められた形式でのリクエスト送信、そしてレスポンスの効果的な処理が重要になります。これらのステップを適切に実行することで、開発者はAPIの力を活用し、機能豊かなアプリケーションを構築することができます。
以上で、ChatGPT連携のための準備が整いました。次のステップとして、Android Studioの作業に戻り、アプリの基本設定を変更して、アプリがインターネットにアクセスできるようにします。

ChatGPT連携:AndroidManifest.xmlを修正

ここから、ChatGPT連携のためのプログラムの説明に入っていきます。第1部と同じように、コードをコピペして、素早くサンプルアプリを完成させるアプローチで進めていきましょう。まず、アプリ全体の設定を管理するAndroidManifest.xmlを修正します。AndroidManifest.xmlファイルは、プロジェクトナビゲーターで、「app > manifests」の下に位置しています。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- インターネットアクセスのパーミッションを追加 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        // 以下略

このコードは、Androidアプリケーションでインターネットアクセスの許可を求める方法を示しています。特に、AndroidManifest.xmlは、Androidアプリケーションの中心的なファイルで、アプリの名前、アイコン、使用するパーミッション、起動時に実行されるアクティビティなど、アプリの基本的な設定を記述します。このファイルはアプリケーションのルートディレクトリに置かれ、アプリがインストールされる際にシステムによって読み込まれます。

追加した1行は、アプリがインターネットにアクセスするためのパーミッション(許可)を要求しています。Androidでは、ユーザーのプライバシーを保護するため、アプリが特定の機能(例えばインターネットアクセス、カメラの使用など)を利用する際には、明示的なパーミッションが必要です。uses-permissionタグを使って、そのアプリがどのようなパーミッションを必要とするかを宣言します。

アプリがインターネットアクセスを必要とする場合(例えばウェブAPIからデータを取得する場合)、このパーミッションがAndroidManifest.xmlに含まれていることを確認する必要があります。これにより、アプリのインストール時にユーザーに対して必要なパーミッションについて情報が提供され、ユーザーはそのアプリを安全に使用できるようになります。

次は、アプリをビルドを管理するbuild.gradle.ktsを編集します。プロジェクトナビゲーターの「Gradle Scripts」の下に配置されている「build.gradle.kts (Module:app)」を開いてください。

ChatGPT連携:build.gradle.ktsを修正



dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)

    // 追加した依存関係
    implementation("io.ktor:ktor-client-core:2.3.2") // (1)
    implementation("io.ktor:ktor-client-android:2.0.0") // (2)
    implementation("io.ktor:ktor-client-content-negotiation:2.3.2") // (3)
}

 このコードは、Androidアプリケーションのbuild.gradle.ktsファイルの一部で、アプリの依存関係を定義しています。build.gradle.ktsファイルはKotlin DSL(Domain Specific Language)を使用してGradleビルドスクリプトを記述するためのファイルです。GradleはAndroidアプリケーションのビルドシステムで、このファイルを通じてアプリのビルド設定や依存するライブラリを管理します。

 build.gradle.ktsは、プロジェクトレベルまたはアプリモジュールレベルで使用されるGradleビルドスクリプトです。Kotlin DSLを使用することで、より安全で読みやすく、効率的なビルド設定が可能になります。このファイル内でライブラリの依存関係を定義することで、Gradleは必要なライブラリを自動的にダウンロードし、プロジェクトに組み込みます。

 追記された部分では、アプリがCoroutine、ViewModel、LiveData、Gsonなどの機能を使用するための依存関係が追加されています。追加した依存関係の解説をします。

(1)は、Ktorクライアントのコア機能を提供します。HTTPリクエストの送信、レスポンスの受信、およびクライアント設定の基本を含んでいます。プラットフォーム非依存なので、共通コードで使用できます。

(2)は、ndroid固有のKtor HTTPクライアントエンジンを提供します。これにより、Androidアプリケーション内でHTTPリクエストを効率的に処理できるようになります。特に、Androidプラットフォームの特性を活かした非同期処理やネットワーク通信の最適化が行われます。

(3)は、コンテンツネゴシエーションのサポートを追加します。これは、クライアントとサーバー間でデータ交換する際に使用されるデータ形式(例:JSON, XMLなど)を自動的に扱うための機能です。特にAPIとの通信でJSONなどの特定のフォーマットを扱う場合に有用です。

 これらのライブラリをプロジェクトに追加することで、アプリケーションの開発において非同期処理、アーキテクチャの設計、データ処理が容易になり、より効率的かつ安全なコードの実装が可能になります。Gradleはビルド時にこれらの依存関係を解決し、必要なライブラリをダウンロードしてプロジェクトに組み込むことで、開発者がこれらの機能を直接使用できるようにします。

 上記コードを追記すると、Android Studioのエディタエリア上部に「Sync now」の表示が出ますので、クリックして同期します。これで、ビルド時に各種ライブラリが自動で組み込まれるようになります。

 以上で、ビルドの設定が完了しました。次は、ChatGPTとの連携処理を記述するクラスを作成します。

ChatGPT連携:ChatGPTService

fig16 新しいObjectを作成する

「ChatGPTService」という名前の新しいファイルを作成します。ファイルのタイプを選ぶモーダルウインドウで「Object」を選んでください。

Kotlinでは、「Object」はシングルトンオブジェクトを定義するために使用されます。シングルトンとは、クラスのインスタンスが1つしか存在しないことを保証するデザインパターンです。シングルトンの主な目的は、グローバルな状態や共有リソースの管理、コードの簡素化、遅延初期化などです。

ファイルが作成できたら、次のコードをコピペして下さい。

ここから先は

20,218字 / 11画像

¥ 100 (数量限定:残り 6 / 10)

この記事が気に入ったらサポートをしてみませんか?