---
title: バグと改善意見を送信する
description: BugReportとFeedbackReportの送信、エラー処理、重複を防ぐ再試行方法です。
---

# バグと改善意見を送信する {#submit-bugs-and-feedback}

ログインと参加承認が完了したら、`submitBug()`または`submitFeedback()`を呼びます。どちらも`SubmittedIssue`を返す`suspend`関数です。

SDKはアプリが渡したテキストだけを送ります。画面、ログ、入力、端末情報、クラッシュを自動収集しません。送信前に内容をユーザーに提示してください。

## バグを送信する {#submit-a-bug}

```kotlin
import dev.opentester.sdk.BugReport

val report = BugReport(
    title = "保存ボタンが動作しません",
    description = "新規プロジェクトでタイトルを入力し、保存を押しました。",
    steps = "1. 新規プロジェクトを開く\n2. タイトルを入力\n3. 保存を押す",
    expected = "プロジェクトを保存して一覧に移動",
    actual = "画面は変わらず入力内容が残ります",
    appVersion = "1.0.0-test",
    deviceInfo = "Pixel; Android 16",
)

// 認証済みのtesterをコルーチンから呼びます。
val submitted = tester.submitBug(report)
```

## 改善意見を送信する {#submit-feedback}

```kotlin
import dev.opentester.sdk.FeedbackReport

val report = FeedbackReport(
    title = "検索欄の横にフィルターを配置してください",
    description = "現在は一覧の最後までスクロールしないとフィルターが見つかりません。",
    appVersion = "1.0.0-test",
)
val submitted = tester.submitFeedback(report)
```

改善意見は`kind: "feedback"`として保存されます。`FeedbackReport.deviceInfo`は省略可能です。`BugReport.deviceInfo`は必須のコンストラクター引数ですが、送る情報がなければ空文字列にできます。

## 入力制限 {#input-limits}

サーバーは前後の空白を除去してから以下を検証します。フォームにも同じ制限を適用してください。

| フィールド | 必須 | 制限 |
| --- | --- | --- |
| `title` | 必須 | 1–160文字 |
| `description` | 必須 | 1–10,000文字 |
| `steps` | バグで任意 | 最大10,000文字 |
| `expected` | バグで任意 | 最大5,000文字 |
| `actual` | バグで任意 | 最大5,000文字 |
| `appVersion` | 必須 | 1–100文字 |
| `deviceInfo` | 値は任意 | 最大500文字 |
| `clientRequestId` | 必須、既定で自動生成 | UUID |

タイトルや説明が空の場合、通信前に`IllegalArgumentException`が発生します。その他の制限はサーバーで検証し、違反時はステータス`400`の`OpenTesterException.Api`を返します。

## 重複防止と再試行 {#retry}

報告オブジェクトの生成時に`clientRequestId`が作られます。通信失敗で結果が不明な場合は、**同じ報告オブジェクトまたはUUID**で再試行します。同じアプリ・テスター・リクエストIDに対して既存の報告を返します。

```kotlin
// フォーム確定時に一度だけ生成し、ViewModelなどに保持します。
val pending = BugReport(
    title = "一覧が更新されません",
    description = "追加した項目が一覧に表示されません。",
    appVersion = "1.0.0-test",
    deviceInfo = "",
)

// 初回も再試行もpendingを渡します。
val result = tester.submitBug(pending)
```

同じUUIDで別の内容を送っても、更新されずに既存の報告が返ります。新規または内容を変更した報告には新しいUUIDを使います。SDKは自動再試行やオフラインキューを提供しません。

## エラーを処理する {#handle-errors}

認証済みの`tester`と`report`をコルーチン内で使用します。`statusText`をアプリの状態表示に接続してください。

```kotlin
import dev.opentester.sdk.OpenTesterException
import kotlinx.coroutines.CancellationException

val statusText = try {
    val issue = tester.submitBug(report)
    "送信完了: ${issue.id}"
} catch (e: CancellationException) {
    throw e
} catch (e: OpenTesterException.NotAuthenticated) {
    "テスターとしてログインしてください。"
} catch (e: OpenTesterException.Network) {
    "接続を確認し、同じ報告で再試行してください。"
} catch (e: OpenTesterException.Api) {
    when (e.statusCode) {
        400 -> "必須項目と文字数を確認してください。"
        401 -> "セッションが切れました。再度ログインしてください。"
        403 -> "このアプリの参加承認状態を確認してください。"
        429 -> "リクエストが多すぎます。しばらくして再試行してください。"
        else -> e.message ?: "リクエストを処理できませんでした。"
    }
} catch (e: OpenTesterException) {
    e.message ?: "レスポンスを処理できませんでした。"
} catch (e: IllegalArgumentException) {
    "タイトルと説明を入力してください。"
}
```

`401`または`403`ではSDKがメモリ内セッションを削除します。参加状態を確認し、再ログインしてから送信してください。コルーチンのキャンセルを通常エラーとして扱わず、`CancellationException`を再スローします。

## 送信後の流れ {#after-submission}

新しい報告は`open`です。開発者はコンソールで確認し、`confirmed`、`fixed`、`closed`に変更できます。送信だけでXPや報酬は付与されず、貢献審査が別途行われます。

SDKは`id`、`title`、`status`を返します。貢献・報酬履歴はWebログイン後に確認します。SDKトークンで開発者APIや報酬受信箱にはアクセスできません。
