SDKドキュメント

Android SDK(gtncontact-sdk)

GTN Contact の顧客向けチャットをネイティブAndroidアプリに組み込むための Jetpack Compose / Material3 SDK です。会話一覧・チャット・添付・CSATアンケートまでを含む画面一式を提供します。

概要

項目内容
minSdk / compileSdk29 / 35
言語Kotlin 2.1
UIJetpack Compose(Compose BOM、Material3標準コンポーネントのみ)。文言は日本語
依存OkHttp(HTTP/WebSocket)、kotlinx-serialization-json、kotlinx-coroutines、Compose標準のみ
モジュール:gtncontact-sdk(com.android.library)

インストール(Gradle)

settings.gradle.kts に :gtncontact-sdk モジュールを include し、アプリモジュールから依存します。

// settings.gradle.kts
include(":gtncontact-sdk")

// app/build.gradle.kts
dependencies {
    implementation(project(":gtncontact-sdk"))
}

クイックスタート

1. SDKを初期化(configure)

UIエントリポイントの起動前に必ず GTNContact.configure を呼び出します。

GTNContact.configure(
    baseUrl = "https://api.example.com",          // 開発時エミュレータ: http://10.0.2.2:3010
    tokenProvider = { myAuthBackend.fetchContactToken() }, // suspend関数
    language = "ja",
)

2. トークン供給(tokenProvider)について

  • SDKは絶対に自分でトークンを発行しません。tokenProvider(suspend () -> String)でホストアプリが顧客トークンを供給します。
  • 取得したトークンはSDK内部でキャッシュされ、RESTとWebSocketの両方に使われます。
  • devモードのトークン形式は dev:<customerId> です(サンプルアプリでは POST /v1/guest で自前取得)。

3. サポート画面を起動

一番簡単な方法はSDK内蔵の GTNContactActivity です(会話一覧+チャット画面一式を内包)。

GTNContactActivity.start(
    activityContext = this,
    sourceScreen = "請求明細画面",                    // 起動元画面名でサポートフローが自動解決される
    contextMap = mapOf("契約プラン" to "プレミアム"),  // フローに渡す追加コンテキスト
)

認証

tokenProvider と 401 リトライ

  • 全リクエストに Authorization: Bearer <token> が付与されます。
  • APIが 401 を返した場合、SDKはキャッシュを破棄して tokenProvider を再呼び出しし、1回だけリトライします。再取得後も401なら GTNContactException.Unauthorized を投げます。
  • トークン失効時の再取得(リフレッシュ)はホストアプリの責務です。tokenProvider は常に「現在有効なトークン」を返すよう実装してください。

ゲスト→ログイン統合(claimGuest)

ゲスト時代の会話をログイン後の顧客に統合します(POST /v1/identities/claim)。tokenProvider がログイン後のトークンを返すようになった後に呼び出してください。

val res = GTNContact.claimGuest(guestCustomerId) // res.mergedConversations = 統合された会話数

プッシュトークン登録

// FCMトークン取得後・更新時に登録(POST /v1/push-tokens、platform: "android")
GTNContact.registerPushToken(fcmToken)
// ログアウト時に解除(DELETE /v1/push-tokens)
GTNContact.unregisterPushToken(fcmToken)

画面の埋め込み方

エントリポイント用途
GTNContactActivity.start(activityContext:, sourceScreen:, contextMap:)最短導線。SDK内蔵Activityで画面一式を表示
SupportHomeScreen(sourceScreen, context, onClose)会話一覧+新規問い合わせのコンポーザブル。自前のCompose画面に埋め込む(内部でチャットへ遷移)
ChatScreen(conversationId, initialMessages, initialConversation, onBack)特定の会話のチャット画面を直接埋め込む(プッシュ通知タップからの遷移など)
// 会話一覧+新規問い合わせ(内部でチャット画面へ遷移)
SupportHomeScreen(
    sourceScreen = "ホーム画面",
    context = mapOf("契約プラン" to "プレミアム"),
    onClose = { /* 閉じる */ },
)

// 特定の会話のチャット画面
ChatScreen(conversationId = conversationId)

機能リファレンス

会話

  • 会話一覧(GET /v1/conversations): 新しい順表示、プルリフレッシュ
  • 会話開始(POST /v1/conversations): sourceScreen でフローが自動解決され、チケットも自動作成されます
  • ステータスは5種類で、日本語バッジ表示されます
status表示
botボット対応中
queued接続待ち
agent対応中
waiting_customer返信待ち
closed終了

構造化メッセージ(7種)

MessageBubbles が contentType に応じてレンダリングします。

contentType内容挙動
textテキスト通常バブル
buttons選択肢ボタンタップで value をtext送信。最新メッセージのみ有効、selected は押下不可
form入力フォーム最新のみ編集可、過去分は読み取り専用
carouselカルーセル横スクロールカード
fileファイルSAS取得→外部アプリで表示
link_cardリンクカード外部ブラウザで開く
systemシステム通知中央寄せグレー小文字

差分同期とページング(tail / before / after)

メッセージの seq は会話内で単調増加するカーソルです。GET /v1/conversations/{id}/messages は3つのクエリを受け付けます。

パラメータ用途レスポンス
tail=1初回表示: 最新50件(limit 指定可)を取得prevCursor(次の before に渡すseq)と hasMoreBefore が返る
before=<prevCursor>上スクロール: 過去メッセージを遡って取得同上(さらに古い分があれば hasMoreBefore: true)
after=<maxSeq>差分同期: 保持している最大seqより新しい分だけ取得nextCursor(次の after に渡すseq)と hasMore

推奨パターン: 初回は tail=1 で最新50件 → 上スクロールで before=prevCursor を繰り返して遡り → 新着・再接続時は after=maxSeq で差分同期。

内蔵の ChatScreen は3つのカーソルをすべて使い分けます: 初回表示は getLatestMessages(tail=1)で最新50件のみを取得して最下部を表示し、上スクロールで先頭に到達すると getMessagesBefore(before=prevCursor)で古いページを追加読み込みします(firstVisibleItemの補正でスクロール位置は維持)。新着・再接続時の取りこぼし回復は従来どおり after=maxSeq の差分同期+id による重複排除です。REST APIを直接呼ぶ場合も同じ3カーソルが利用できます。

WebSocket(リアルタイム)

SocketClient が会話単位で GET /v1/ws?conversationId=<id>&token=<bearer token> に接続し、SharedFlow<SocketEvent> でイベントを配信します。

イベント内容
Connected接続確立(再接続含む)。受信側は after=maxSeq で差分同期し、readを再送すること
Disconnected切断(自動再接続が走る)
MessageCreated新着メッセージ。自分の送信もエコーされるため id で重複排除する
ConversationUpdatedstatus変更・エージェント参加など
Typing入力中通知(SDKは送信側500msデバウンス、受信時「入力中…」表示)
Read既読通知(senderType: AGENT なら自分のメッセージに既読表示)
Presence閲覧者一覧(顧客UIでは未使用)
ReactionUpdated絵文字リアクションの追加/削除。対象メッセージの全リアクションのスナップショットが届くため丸ごと置き換える
  • 自動再接続: 指数バックオフ 1s → 最大10s。再接続時は差分取得+既読再送を行います。
  • 画面破棄時は必ず close() を呼びます(内蔵UIは自動で処理)。

送達・既読

  • 送信は clientMessageId でべき等。ネットワークエラー時は同一IDで最大3回自動リトライし、失敗時は手動再送できます。
  • 表示は 送信中 → 送達(チェック)→ 既読 の3段階。既読は会話詳細の reads とWSの Read イベントで更新されます。

絵文字リアクション

  • バブルの長押しでクイックリアクション(👍 🙏 ❤️ 😂 🎉 😢)を付けられます。バブル下のチップをタップすると自分のリアクションをトグルできます。
  • メニュー末尾の「+」から全絵文字ピッカー(Google公式 androidx.emoji2:emoji2-emojipicker)を開き、任意の絵文字を選べます(カテゴリ・最近使った絵文字・肌色バリアント対応)。
  • API: addReaction(conversationId, messageId, emoji) / removeReaction(conversationId, messageId, emoji)(POST / DELETE /v1/conversations/{id}/messages/{messageId}/reactions)。追加は同一絵文字の再送に対してべき等です。
  • どちらも対象メッセージの全リアクション(スナップショット)を返します。他の参加者の変更はWSの ReactionUpdated で届きます。
  • ViewModelでは toggleReaction(message, emoji) / hasMyReaction(message, emoji) を利用できます(会話は単一顧客のため reactorType == CUSTOMER が自分の判定)。

添付(SASフロー)

  1. createAttachmentUpload(conversationId, fileName, contentType) — POST /v1/conversations/{id}/attachments でアップロード用SASを発行
  2. uploadAttachment(sas, data, mimeType) — 返却された uploadUrl へ headers(x-ms-blob-type 等)付きで Azure Blobへ直接PUT
  3. sendMessage(contentType = MessageContentType.FILE) — blobName / fileName / fileSize を含む file メッセージを送信
  4. 閲覧時は attachmentDownloadUrl(conversationId, blobName) でダウンロード用SAS(60分有効)を取得し、外部アプリで表示

内蔵UIはPhoto Picker / ドキュメント選択からこのフローを自動実行します。

CSAT(満足度アンケート)

  • 会話クローズ後、CsatFormDialog がアンケートを表示します(入力欄は無効化されCSAT導線を表示)。
  • 設問は csatQuestions()(GET /v1/csat/questions、有効設問のみorder順)で取得。設問タイプは star5 / binary / nps / text の4種で、必須チェック付き。
  • 回答は submitCsat(conversationId, score, comment, answers)(POST /v1/conversations/{id}/csat)で送信します(クローズ後のみ・1会話1回。409回答済みにも対応)。

エラーハンドリング

エラーは GTNContactException(sealed class)で投げられ、message はそのままユーザーに表示できる日本語です。内蔵UIはアラート表示します。

例外発生条件日本語メッセージ
NotConfiguredconfigure 未呼び出しSDKが初期化されていません。GTNContact.configure を呼び出してください。
Networkネットワーク到達不可・タイムアウト通信に失敗しました。ネットワーク接続を確認して、もう一度お試しください。
Unauthorizedトークン再取得後も401認証に失敗しました。しばらくしてからもう一度お試しください。
ConversationClosed409 conversation_closedこの会話は終了しているため、メッセージを送信できません。
CsatAlreadySubmitted409 already_submittedこのアンケートにはすでに回答済みです。
ConversationNotClosed409 conversation_not_closed会話が終了していないため、アンケートに回答できません。
Server(status, code, detail)その他のサーバーエラーエラーが発生しました(code): detail
Decodingレスポンスの解析失敗サーバー応答の解析に失敗しました。

409 コード一覧

HTTPerror コード意味
409conversation_closedクローズ済み会話へのメッセージ送信
409already_submittedCSATにすでに回答済み
409conversation_not_closed未クローズ会話へのCSAT回答

サンプルアプリの起動方法

  1. APIサーバーをホストPCで起動(http://localhost:3010)
  2. インストール:
./gradlew :sample-app:installDebug
  1. アプリを起動 → 「デモユーザーでログイン」(POST /v1/guest でトークンを自前取得しSDKへ供給)
  2. 「ホーム画面から起動」または「請求明細画面から起動」(請求明細画面では選択肢ボタン付きフローが動く)

ビルド・テスト:

./gradlew :gtncontact-sdk:assembleDebug :sample-app:assembleDebug   # ビルド
./gradlew :gtncontact-sdk:testDebugUnitTest                          # ユニットテスト

FAQ

エミュレータからローカルAPI(localhost)に接続できない

エミュレータからホストPCの localhost へは http://10.0.2.2:3010 で接続します。平文HTTPを使う場合はマニフェストでcleartextを許可してください(サンプルアプリは設定済み)。

<application android:usesCleartextTraffic="true">

Auth0 接続時に変更は必要?

SDK側のコード変更は不要で、tokenProvider の実装だけが変わります。

  • devモード(サーバーの ALLOW_DEV_AUTH が有効)では dev:<customerId> 形式のトークンをそのまま渡せます。
  • サーバーに AUTH0_DOMAIN が設定されている場合、tokenProvider は Auth0が発行した顧客用JWT(AUTH0_CUSTOMER_AUDIENCE のaudience)を返すようにします。エージェント用audienceとは分離されています。
  • トークン失効時はSDKの401リトライで tokenProvider が再呼び出しされるので、そこでリフレッシュ済みトークンを返してください。
  • ゲスト運用から移行する場合は、ログイン完了後に GTNContact.claimGuest(guestCustomerId) でゲスト時代の会話を統合できます。

横断検索

チケット・会話・記事・フローを検索