

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# SDK Perpesanan Klien Obrolan IVS
<a name="chat-sdk"></a>

SDK Perpesanan Klien Obrolan Amazon Interactive Video Services (IVS) adalah untuk para developer yang membangun aplikasi dengan Amazon IVS. SDK ini didesain untuk memanfaatkan arsitektur Amazon IVS dan akan mendapatkan pembaruan, bersama dengan Obrolan Amazon IVS. Sebagai SDK native, SDK ini didesain untuk meminimalkan dampak performa pada aplikasi Anda dan pada perangkat yang digunakan pengguna untuk mengakses aplikasi Anda.

## Persyaratan Platform
<a name="chat-sdk-platform-requirements"></a>

### Peramban Desktop
<a name="chat-desktop-browsers"></a>


| Peramban | Versi yang Didukung | 
| --- | --- | 
| Chrome | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Edge | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Firefox | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Opera | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Safari | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 

### Peramban Seluler
<a name="chat-mobile-browsers"></a>


| Peramban | Versi yang Didukung | 
| --- | --- | 
| Chrome untuk Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Firefox untuk Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Opera untuk Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| WebView Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Internet Samsung | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Safari untuk iOS | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 

### Platform Native
<a name="chat-native-platforms"></a>


| Platform | Versi yang Didukung | 
| --- | --- | 
| Android | 5.0 dan versi yang lebih baru | 
| iOS |  13.0 dan versi yang lebih baru  | 

## Support
<a name="chat-sdk-support"></a>

Jika Anda mengalami kesalahan atau masalah lain dengan ruang obrolan Anda, tentukan pengenal ruang unik melalui API Obrolan IVS (lihat [ListRooms](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_ListRooms.html)).

Bagikan pengidentifikasi ruang obrolan ini kepada dukungan AWS. Dengan pengidentifikasi tersebut, mereka bisa mendapatkan informasi untuk membantu memecahkan masalah Anda.

**Catatan:** Lihat [Catatan Rilis Obrolan Amazon IVS](release-notes.md) untuk versi yang tersedia dan masalah yang diperbaiki. Jika perlu, sebelum menghubungi dukungan, perbarui versi SDK Anda dan lihat apakah versi tersebut menyelesaikan masalah Anda.

### Penentuan versi
<a name="chat-sdk-support-versioning"></a>

Pesan Klien Obrolan Amazon IVS SDKs menggunakan versi [semantik](https://semver.org/).

Untuk diskusi ini, misalkan:
+ Rilis terbaru adalah 4.1.3.
+ Rilis terbaru dari versi utama sebelumnya adalah 3.2.4.
+ Rilis terbaru versi 1.x adalah 1.5.6.

Fitur baru yang kompatibel dengan versi sebelumnya ditambahkan sebagai rilis minor dari versi terbaru. Dalam hal ini, rangkaian fitur baru berikutnya akan ditambahkan sebagai versi 4.2.0.

Perbaikan bug minor yang kompatibel dengan versi sebelumnya ditambahkan sebagai rilis patch dari versi terbaru. Di sini, set perbaikan bug minor berikutnya akan ditambahkan sebagai versi 4.1.4.

Perbaikan bug besar yang kompatibel dengan versi sebelumnya ditangani secara berbeda; perbaikan ini ditambahkan ke beberapa versi:
+ Rilis patch versi terbaru. Di sini, ini adalah versi 4.1.4.
+ Rilis patch dari versi kecil sebelumnya. Di sini, ini adalah versi 3.2.5.
+ Rilis patch dari rilis versi 1.x terbaru. Di sini, ini adalah versi 1.5.7.

Perbaikan bug besar ditentukan oleh tim produk Amazon IVS. Contoh umumnya adalah pembaruan keamanan yang penting dan perbaikan pilihan lainnya yang diperlukan pelanggan.

**Catatan:** Dalam contoh di atas, versi yang dirilis meningkat tanpa melewatkan angka apa pun (misalnya, dari 4.1.3 ke 4.1.4). Pada kenyataannya, satu atau beberapa nomor patch mungkin tetap bersifat internal dan tidak dirilis, sehingga versi yang dirilis dapat meningkat dari 4.1.3 menjadi, katakanlah, 4.1.6.

Selain itu, versi 1.x akan didukung hingga akhir tahun 2023 atau saat 3.x dirilis, mana yang lebih dahulu.

## Obrolan Amazon IVS APIs
<a name="chat-sdk-chat-apis"></a>

Di sisi server (tidak dikelola oleh SDKs), ada dua APIs, masing-masing dengan tanggung jawabnya sendiri:
+ **Data plane** — [IVS Chat Messaging API](https://docs.aws.amazon.com//ivs/latest/chatmsgapireference/welcome.html) adalah WebSockets API yang dirancang untuk digunakan oleh aplikasi front-end (iOS, Android, macOS, dll) yang didorong oleh skema otentikasi berbasis token. Dengan menggunakan token obrolan yang dibuat sebelumnya, Anda terhubung ke ruang obrolan yang sudah ada menggunakan API ini.

  *Pesan SDKs Klien Obrolan Amazon IVS hanya berkaitan dengan bidang data. SDKs Asumsikan bahwa Anda sudah menghasilkan token obrolan melalui backend Anda. Pengambilan token ini diasumsikan dikelola oleh aplikasi front-end Anda, bukan. SDKs*
+ **Bidang kontrol** — [API Bidang Kontrol Obrolan IVS](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/Welcome.html) menyediakan antarmuka untuk *aplikasi backend* Anda sendiri guna mengelola dan membuat ruang obrolan serta pengguna yang bergabung dengan mereka. Anggap ini sebagai panel admin untuk pengalaman obrolan aplikasi Anda yang dikelola oleh *backend Anda sendiri*. Ada operasi pesawat kontrol yang bertanggung jawab untuk membuat *token obrolan* yang perlu diautentikasi oleh pesawat data ke ruang obrolan.

  **Penting:** *Pesan Klien Obrolan IVS SDKs tidak memanggil operasi bidang kontrol apa pun. Anda harus menyiapkan backend untuk membuat token obrolan. Aplikasi front-end Anda harus berkomunikasi dengan backend untuk mengambil token obrolan ini.*

# SDK Pesan Klien Obrolan IVS: Panduan Android
<a name="chat-sdk-android"></a>

SDK Android Perpesanan Klien Obrolan Amazon Interactive Video (IVS) menyediakan antarmuka agar Anda dapat dengan mudah menggabungkan [API Perpesanan Obrolan IVS](https://docs.aws.amazon.com//ivs/latest/chatmsgapireference/welcome.html) kita di platform yang menggunakan Android.

Paket `com.amazonaws:ivs-chat-messaging` mengimplementasikan antarmuka yang dijelaskan dalam dokumen ini.

**SDK Android Perpesanan Klien Obrolan IVS versi terbaru:** 1.1.0 ([Catatan Rilis](https://docs.aws.amazon.com//ivs/latest/ChatUserGuide/release-notes.html#jan31-23))

**Dokumentasi referensi:** [Untuk informasi tentang metode terpenting yang tersedia di Amazon IVS Chat Client Messaging Android SDK, lihat dokumentasi referensi di: https://aws.github.io/ -sdk-android/1.1.0/ amazon-ivs-chat-messaging](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.1.0/)

**Contoh kode:** [Lihat contoh repositori Android di GitHub: -android-demo https://github.com/aws-samples/ amazon-ivs-chat-for](https://github.com/aws-samples/amazon-ivs-chat-for-android-demo)

**Persyaratan platform:** Android 5.0 (API tingkat 21) atau lebih tinggi diperlukan untuk pengembangan.

# Memulai dengan IVS Chat Client Messaging Android SDK
<a name="chat-android-getting-started"></a>

Sebelum memulai, Anda harus memahami cara [Memulai Obrolan Amazon IVS](getting-started-chat.md).

## Menambahkan Paket
<a name="chat-android-add-package"></a>

Tambahkan `com.amazonaws:ivs-chat-messaging` ke dependensi `build.gradle` Anda:

```
dependencies {
   implementation 'com.amazonaws:ivs-chat-messaging'
}
```

## Tambahkan Aturan Proguard
<a name="chat-android-proguard-rules"></a>

Tambahkan entri berikut ke file aturan R8/Proguard Anda (`proguard-rules.pro`):

```
-keep public class com.amazonaws.ivs.chat.messaging.** { *; }
-keep public interface com.amazonaws.ivs.chat.messaging.** { *; }
```

## Siapkan Backend Anda
<a name="chat-android-setup-backend"></a>

Integrasi ini memerlukan titik akhir di server Anda yang akan berbicara dengan [API Amazon IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyAPIReference/Welcome.html). Gunakan [pustaka AWS resmi](https://aws.amazon.com/developer/tools/) untuk akses ke API Amazon IVS dari server Anda. Pustaka ini dapat diakses dalam beberapa bahasa dari paket publik; misalnya, node.js dan Java.

Selanjutnya, buat titik akhir server yang berbicara dengan [API Obrolan Amazon IVS](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/Welcome.html) dan membuat token.

## Menyiapkan Koneksi Server
<a name="chat-android-setup-server"></a>

Buat metode yang menggunakan `ChatTokenCallback` sebagai param dan mengambil token obrolan dari backend Anda. Teruskan token tersebut ke metode `onSuccess` panggilan balik. Jika terjadi kesalahan, teruskan pengecualian ke metode `onError` dari panggilan balik. Hal ini diperlukan untuk instantiasi entitas `ChatRoom` utama di langkah berikutnya.

Di bawah ini Anda dapat menemukan contoh kode yang mengimplementasikan hal-hal di atas dengan menggunakan panggilan `Retrofit`.

```
// ...

private fun fetchChatToken(callback: ChatTokenCallback) {
    apiService.createChatToken(userId, roomId).enqueue(object : Callback<ChatToken> {
        override fun onResponse(call: Call<ExampleResponse>, response: Response<ExampleResponse>) {
            val body = response.body()
            val token = ChatToken(
                body.token,
                body.sessionExpirationTime,
                body.tokenExpirationTime
            )
            callback.onSuccess(token)
        }

        override fun onFailure(call: Call<ChatToken>, throwable: Throwable) {
            callback.onError(throwable)
        }
    })
}
// ...
```

# Menggunakan IVS Chat Client Messaging Android SDK
<a name="chat-android-using-sdk"></a>

Dokumen ini akan membawa Anda melalui langkah-langkah yang terlibat dalam menggunakan Amazon IVS chat client messaging Android SDK.

## Menginisialisasi Instans Ruang Obrolan
<a name="chat-android-initialize-room"></a>

Buat instans dari kelas `ChatRoom`. Langkah ini mengharuskan penyediaan `regionOrUrl`, yang biasanya merupakan wilayah AWS tempat ruang obrolan Anda di-host, dan `tokenProvider` yang merupakan metode pengambilan token yang dibuat pada langkah sebelumnya.

```
val room = ChatRoom(
    regionOrUrl = "us-west-2",
    tokenProvider = ::fetchChatToken
)
```

Selanjutnya, buat objek pendengar yang akan mengimplementasikan handler untuk peristiwa yang terkait obrolan, dan tetapkan objek tersebut ke properti `room.listener`:

```
private val roomListener = object : ChatRoomListener {
    override fun onConnecting(room: ChatRoom) {
      // Called when room is establishing the initial connection or reestablishing connection after socket failure/token expiration/etc
    }

    override fun onConnected(room: ChatRoom) {
        // Called when connection has been established
    }

    override fun onDisconnected(room: ChatRoom, reason: DisconnectReason) {
        // Called when a room has been disconnected
    }

    override fun onMessageReceived(room: ChatRoom, message: ChatMessage) {
        // Called when chat message has been received
    }

    override fun onEventReceived(room: ChatRoom, event: ChatEvent) {
        // Called when chat event has been received
    }

    override fun onDeleteMessage(room: ChatRoom, event: DeleteMessageEvent) {
       // Called when DELETE_MESSAGE event has been received
    }
}

val room = ChatRoom(
    region = "us-west-2",
    tokenProvider = ::fetchChatToken
)

room.listener = roomListener // <- add this line

// ...
```

Langkah terakhir dari inisialisasi dasar adalah menghubungkan ke ruangan tertentu dengan membuat WebSocket koneksi. Untuk melakukan hal tersebut, panggil metode `connect()` dalam instans ruang. Sebaiknya lakukan langkah tersebut dalam metode siklus hidup `onResume() ` untuk memastikannya tetap terhubung jika aplikasi Anda dilanjutkan dari latar belakang.

```
room.connect()
```

SDK akan mencoba membuat koneksi ke suatu ruang obrolan yang dienkode dalam token obrolan yang diterima dari server Anda. Jika gagal, SDK akan mencoba menghubungkan kembali sebanyak yang ditentukan dalam instans ruang.

## Melakukan Tindakan di Ruang Obrolan
<a name="chat-android-room-actions"></a>

Kelas `ChatRoom` memiliki tindakan untuk mengirim dan menghapus pesan serta memutus koneksi pengguna lain. Tindakan ini menerima parameter panggilan balik opsional yang memungkinkan Anda untuk mendapatkan konfirmasi permintaan atau notifikasi penolakan.

### Mengirim Pesan
<a name="chat-android-room-actions-send-message"></a>

Untuk permintaan ini, Anda harus memiliki kemampuan `SEND_MESSAGE` yang dienkode dalam token obrolan Anda.

Untuk memicu permintaan kirim-pesan:

```
val request = SendMessageRequest("Test Echo")
room.sendMessage(request)
```

Untuk mendapatkan confirmation/rejection permintaan, berikan callback sebagai parameter kedua:

```
room.sendMessage(request, object : SendMessageCallback {
   override fun onConfirmed(request: SendMessageRequest, response: ChatMessage) {
      // Message was successfully sent to the chat room.
   }
   override fun onRejected(request: SendMessageRequest, error: ChatError) {
      // Send-message request was rejected. Inspect the `error` parameter for details.
   }
})
```

### Menghapus Pesan
<a name="chat-android-room-actions-delete-message"></a>

Untuk permintaan ini, Anda harus memiliki kemampuan DELETE\$1MESSAGE yang dienkode dalam token obrolan.

Untuk memicu permintaan hapus-pesan:

```
val request = DeleteMessageRequest(messageId, "Some delete reason")
room.deleteMessage(request)
```

Untuk mendapatkan confirmation/rejection permintaan, berikan callback sebagai parameter kedua:

```
room.deleteMessage(request, object : DeleteMessageCallback {
   override fun onConfirmed(request: DeleteMessageRequest, response: DeleteMessageEvent) {
      // Message was successfully deleted from the chat room.
   }
   override fun onRejected(request: DeleteMessageRequest, error: ChatError) {
      // Delete-message request was rejected. Inspect the `error` parameter for details.
   }
})
```

### Memutus Koneksi Pengguna Lain
<a name="chat-android-room-actions-disconnect-user"></a>

Untuk permintaan ini, Anda harus memiliki kemampuan `DISCONNECT_USER` yang dienkode dalam token obrolan Anda.

Guna memutus koneksi pengguna lain untuk tujuan moderasi:

```
val request = DisconnectUserRequest(userId, "Reason for disconnecting user")
room.disconnectUser(request)
```

Untuk mendapatkan confirmation/rejection permintaan, berikan callback sebagai parameter kedua:

```
room.disconnectUser(request, object : DisconnectUserCallback {
   override fun onConfirmed(request: SendMessageRequest, response: ChatMessage) {
      // User was disconnected from the chat room.
   }
   override fun onRejected(request: SendMessageRequest, error: ChatError) {
      // Disconnect-user request was rejected. Inspect the `error` parameter for details.
   }
})
```

## Memutus Koneksi dari Ruang Obrolan
<a name="chat-android-disconnect-room"></a>

Untuk menutup koneksi Anda ke ruang obrolan, panggil metode `disconnect()` pada instans ruang:

```
room.disconnect()
```

Karena WebSocket koneksi akan berhenti bekerja setelah waktu yang singkat ketika aplikasi dalam keadaan latar belakang, kami menyarankan Anda secara manual connect/disconnect saat mentransisikan from/to status latar belakang. Untuk melakukannya, pasangkan panggilan `room.connect()` dalam metode siklus hidup `onResume()`, di `Activity` atau `Fragment` Android, dengan panggilan `room.disconnect()` dalam metode siklus hidup `onPause()`.

# SDK Pesan Klien Obrolan IVS: Tutorial Android Bagian 1: Ruang Obrolan
<a name="chat-sdk-android-tutorial-chat-rooms"></a>

Ini adalah bagian pertama dari tutorial dua bagian. Anda akan mempelajari hal-hal yang penting dalam bekerja menggunakan SDK Perpesanan Obrolan Amazon IVS dengan membangun aplikasi Android yang berfungsi sepenuhnya menggunakan bahasa pemrograman [Kotlin](https://kotlinlang.org/). Kami menyebut aplikasi itu *Chatterbox*.

Sebelum Anda memulai modul, luangkan beberapa menit untuk membiasakan diri dengan prasyarat, konsep utama di balik token obrolan, dan server backend yang diperlukan untuk membuat ruang obrolan.

Tutorial ini dibuat untuk para developer Android berpengalaman yang baru mengenal SDK Perpesanan Obrolan IVS. Anda harus merasa nyaman dengan bahasa pemrograman Kotlin dan membuat UIs di platform Android.

Bagian pertama dari tutorial ini dipecah menjadi beberapa bagian:

1. [Menyiapkan Authentication/Authorization Server Lokal](#chat-android-rooms-auth-server)

1. [Membuat Proyek Chatterbox](#chat-android-rooms-chatterbox)

1. [Hubungkan dengan Ruang Obrolan dan Amati Pembaruan Koneksi](#chat-android-rooms-connect-state)

1. [Membangun Penyedia Token](#chat-android-rooms-token-provider)

1. [Langkah Berikutnya](#chat-android-rooms-next-steps)

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK for Android Referensi](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/) (aktif). GitHub

## Prasyarat
<a name="chat-android-rooms-prerequisites"></a>
+ Kenali Kotlin dan cara membuat aplikasi di platform Android. Jika Anda tidak terbiasa membuat aplikasi untuk Android, pelajari dasar-dasarnya dalam panduan [Membangun aplikasi pertama Anda](https://developer.android.com/codelabs/basic-android-kotlin-compose-first-app#0) untuk para developer Android.
+ Baca dan pahami [Memulai Obrolan Amazon IVS](getting-started-chat.md) dengan saksama.
+ Buat pengguna AWS IAM dengan kemampuan `CreateChatToken` dan `CreateRoom` yang ditentukan dalam kebijakan IAM yang ada. (Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md)).
+ Pastikan secret/access kunci untuk pengguna ini disimpan dalam file kredensial AWS. Untuk instruksinya, lihat [Panduan Pengguna AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) (terutama [Pengaturan file konfigurasi dan kredensial](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)).
+ Buat ruang obrolan dan simpan ARN-nya. Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md). (Jika Anda tidak menyimpan ARN, Anda dapat mencarinya nanti dengan konsol atau API Obrolan.)

## Menyiapkan Authentication/Authorization Server Lokal
<a name="chat-android-rooms-auth-server"></a>

Server backend Anda akan bertanggung jawab untuk membuat ruang obrolan dan menghasilkan token obrolan yang diperlukan SDK Android Obrolan IVS guna mengautentikasi dan mengotorisasi klien untuk ruang obrolan Anda.

Lihat [Membuat Token Obrolan](getting-started-chat-auth.md) di *Memulai Obrolan Amazon IVS*. Seperti yang ditunjukkan pada diagram alur di sana, aplikasi sisi server Anda bertanggung jawab untuk membuat token obrolan. Hal ini berarti aplikasi Anda harus menyediakan caranya sendiri untuk menghasilkan token obrolan dengan memintanya dari aplikasi sisi server Anda.

Kami menggunakan kerangka kerja [Ktor](https://ktor.io/) untuk membuat server lokal langsung yang mengelola pembuatan token obrolan menggunakan lingkungan AWS lokal Anda.

Pada titik ini, kami berharap Anda telah menyiapkan kredensial AWS Anda dengan benar. Untuk step-by-step petunjuknya, lihat [Menyiapkan AWS Credentials and Region for](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html) Development.

Buat direktori baru dan beri nama `chatterbox`, lalu di dalamnya, buat direktori lain yang diberi nama `auth-server`. 

Folder server kita akan memiliki struktur sebagai berikut:

```
- auth-server
  - src
    - main
      - kotlin
        - com
          - chatterbox
            - authserver
              - Application.kt
       - resources
         - application.conf
         - logback.xml
   - build.gradle.kts
```

*Catatan: Anda dapat langsung kode copy/paste di sini ke file yang direferensikan.*

Selanjutnya, kita menambahkan semua dependensi dan plugin yang diperlukan agar server autentikasi berfungsi:

**Skrip Kotlin:**

```
// ./auth-server/build.gradle.kts

plugins {
   application
   kotlin("jvm")
   kotlin("plugin.serialization").version("1.7.10")
}

application {
   mainClass.set("io.ktor.server.netty.EngineMain")
}

dependencies {
   implementation("software.amazon.awssdk:ivschat:2.18.1")
   implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20")

   implementation("io.ktor:ktor-server-core:2.1.3")
   implementation("io.ktor:ktor-server-netty:2.1.3")
   implementation("io.ktor:ktor-server-content-negotiation:2.1.3")
   implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3")

   implementation("ch.qos.logback:logback-classic:1.4.4")
}
```

Sekarang kita perlu menyiapkan fungsionalitas pembuatan log untuk server autentikasi. (Untuk informasi selengkapnya, lihat [Konfigurasikan pembuat log](https://ktor.io/docs/logging.html#configure-logger).)

**XML:**

```
// ./auth-server/src/main/resources/logback.xml

<configuration>
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
   </appender>
   <root level="trace">
      <appender-ref ref="STDOUT"/>
   </root>
   <logger name="org.eclipse.jetty" level="INFO"/>
   <logger name="io.netty" level="INFO"/>
</configuration>
```

Server [Ktor](https://ktor.io/docs/welcome.html) memerlukan pengaturan konfigurasi, yang dimuat dari file `application.*` di direktori `resources` secara otomatis, sehingga kita menambahkannya juga. (Untuk informasi selengkapnya, lihat [Konfigurasi dalam file](https://ktor.io/docs/configurations.html#configuration-file).)

**HOCON:**

```
// ./auth-server/src/main/resources/application.conf

ktor {
   deployment {
      port = 3000
   }
   application {
      modules = [ com.chatterbox.authserver.ApplicationKt.main ]
   }
}
```

Terakhir, mari kita implementasikan server kita:

**Kotlin:**

```
// ./auth-server/src/main/kotlin/com/chatterbox/authserver/Application.kt

package com.chatterbox.authserver

import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import software.amazon.awssdk.services.ivschat.IvschatClient
import software.amazon.awssdk.services.ivschat.model.CreateChatTokenRequest

@Serializable
data class ChatTokenParams(var userId: String, var roomIdentifier: String)

@Serializable
data class ChatToken(
   val token: String,
   val sessionExpirationTime: String,
   val tokenExpirationTime: String,
)

fun Application.main() {
   install(ContentNegotiation) {
      json(Json)
   }

   routing {
      post("/create_chat_token") {
         val callParameters = call.receive<ChatTokenParams>()
         val request = CreateChatTokenRequest.builder().roomIdentifier(callParameters.roomIdentifier)
            .userId(callParameters.userId).build()
         val token = IvschatClient.create()
            .createChatToken(request)

         call.respond(
            ChatToken(
                token.token(),
                token.sessionExpirationTime().toString(),
                token.tokenExpirationTime().toString()
            )
         )
      }
   }
}
```

## Membuat Proyek Chatterbox
<a name="chat-android-rooms-chatterbox"></a>

Untuk membuat proyek Android, instal dan buka [Android Studio](https://developer.android.com/studio).

Ikuti langkah-langkah yang tercantum dalam [Panduan Membuat Proyek](https://developer.android.com/studio/projects/create-project) Android yang resmi.
+ Dalam [Pilih tipe proyek Anda](https://developer.android.com/studio/projects/create-project), pilih templat proyek **Aktivitas Kosong** untuk aplikasi Chatterbox kita.
+ Di [Konfigurasikan proyek Anda](https://developer.android.com/studio/projects/create-project#configure), pilih nilai berikut untuk bidang konfigurasi:
  + **Nama**: Aplikasi Saya
  + **Nama paket**: com.chatterbox.myapp
  + **Simpan lokasi**: arahkan ke direktori `chatterbox` yang dibuat di langkah sebelumnya
  + **Bahasa**: Kotlin
  + **Tingkat API minimum**: API 21: Android 5.0 (Lollipop)

Setelah menentukan semua parameter konfigurasi dengan benar, struktur file kita di dalam folder `chatterbox` akan terlihat seperti berikut:

```
- app
  - build.gradle
  ...
- gradle
- .gitignore
- build.gradle
- gradle.properties
- gradlew
- gradlew.bat
- local.properties
- settings.gradle
- auth-server
  - src
    - main
      - kotlin
        - com
          - chatterbox
            - authserver
              - Application.kt
       - resources
         - application.conf
         - logback.xml
   - build.gradle.kts
```

Sekarang kita memiliki proyek Android yang berfungsi, kita dapat menambahkan [com.amazonaws: ivs-chat-messaging](https://mvnrepository.com/artifact/com.amazonaws/ivs-chat-messaging) ke dependensi kita. `build.gradle` (Untuk informasi selengkapnya tentang kit alat build [Gradle](https://gradle.org/), lihat [Mengonfigurasi build Anda](https://developer.android.com/build).)

**Catatan:** Di bagian atas setiap cuplikan kode, ada jalur ke file tempat Anda harus membuat perubahan dalam proyek. Jalur tersebut bersifat relatif terhadap root proyek.

*Di dalam kode di bawah ini, ganti `<version>` dengan nomor versi SDK Android Obrolan saat ini (misalnya, 1.0.0)*.

**Kotlin**:

```
// ./app/build.gradle

plugins {
// ...
}

android {
// ...
}

dependencies {
   implementation("com.amazonaws:ivs-chat-messaging:<version>")
// ...
}
```

Setelah dependensi baru ditambahkan, jalankan **Sinkronkan Proyek dengan File Gradle** di Android Studio untuk menyinkronkan proyek dengan dependensi baru. (Untuk informasi selengkapnya, lihat [Menambahkan dependensi build](https://developer.android.com/build/dependencies).)

Agar mudah menjalankan server autentikasi (yang dibuat di bagian sebelumnya) dari root proyek, kita memasukkannya sebagai modul baru di `settings.gradle`. (Untuk informasi selengkapnya, lihat [Menyusun dan Membangun Komponen Perangkat Lunak dengan Gradle](https://docs.gradle.org/current/userguide/multi_project_builds.html).)

**Skrip Kotlin:**

```
// ./settings.gradle

// ...

rootProject.name = "Chatterbox"
include ':app'
include ':auth-server'
```

Mulai sekarang, karena `auth-server` disertakan dalam proyek Android, Anda dapat menjalankan server autentikasi dengan perintah berikut dari root proyek:

**Shell:**

```
./gradlew :auth-server:run
```

## Hubungkan dengan Ruang Obrolan dan Amati Pembaruan Koneksi
<a name="chat-android-rooms-connect-state"></a>

Untuk membuka koneksi ruang obrolan, kita menggunakan [panggilan balik siklus hidup aktivitas onCreate()](https://developer.android.com/guide/components/activities/activity-lifecycle), yang diaktifkan saat aktivitas pertama kali dibuat. [ChatRoom Konstruktor](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html) mengharuskan kami untuk menyediakan `region` dan `tokenProvider` membuat instance koneksi ruangan.

**Catatan:** Fungsi `fetchChatToken` dalam cuplikan di bawah ini akan diimplementasikan di [bagian berikutnya](#chat-android-rooms-token-provider).

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp

// ...
import androidx.appcompat.app.AppCompatActivity
// ...

// AWS region of the room that was created in Getting Started with Amazon IVS Chat
const val REGION = "us-west-2"

class MainActivity : AppCompatActivity() {
    private var room: ChatRoom? = null
    // ...

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

      // Create room instance
      room = ChatRoom(REGION, ::fetchChatToken)
   }

// ...
}
```

Menampilkan dan bereaksi terhadap perubahan dalam koneksi ruang obrolan adalah bagian penting dari pembuatan aplikasi obrolan, seperti `chatterbox`. Sebelum kita dapat mulai berinteraksi dengan ruang tersebut, kita harus berlangganan peristiwa status koneksi ruang obrolan untuk mendapatkan pembaruan.

[ChatRoom](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html)mengharapkan kami untuk melampirkan implementasi [ChatRoomListener antarmuka](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/listener.html) untuk meningkatkan peristiwa siklus hidup. Untuk saat ini, fungsi pendengar hanya akan membuat log pesan konfirmasi saat diinvokasi:

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

// ...
package com.chatterbox.myapp
// ...
const val TAG = "IVSChat-App"

class MainActivity : AppCompatActivity() {
// ...

    private val roomListener = object : ChatRoomListener {
        override fun onConnecting(room: ChatRoom) {
            Log.d(TAG, "onConnecting")
        }

        override fun onConnected(room: ChatRoom) {
            Log.d(TAG, "onConnected")
        }

        override fun onDisconnected(room: ChatRoom, reason: DisconnectReason) {
            Log.d(TAG, "onDisconnected $reason")
        }

        override fun onMessageReceived(room: ChatRoom, message: ChatMessage) {
            Log.d(TAG, "onMessageReceived $message")
        }

        override fun onMessageDeleted(room: ChatRoom, event: DeleteMessageEvent) {
            Log.d(TAG, "onMessageDeleted $event")
        }

        override fun onEventReceived(room: ChatRoom, event: ChatEvent) {
            Log.d(TAG, "onEventReceived $event")
        }

        override fun onUserDisconnected(room: ChatRoom, event:    DisconnectUserEvent) {
            Log.d(TAG, "onUserDisconnected $event")
        }
    }
}
```

Setelah menerapkan `ChatRoomListener`, kita melampirkannya ke instans ruang:

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)

   // Create room instance
   room = ChatRoom(REGION, ::fetchChatToken).apply {
      listener = roomListener
   }
}

private val roomListener = object : ChatRoomListener {
// ...
}
```

Setelah ini, kita perlu menyediakan kemampuan untuk membaca status koneksi ruang. Kita akan menyimpannya di [properti](https://kotlinlang.org/docs/properties.html) `MainActivity.kt` dan menginisialisasinya ke status TERPUTUS default untuk ruang (lihat `ChatRoom state` di [Referensi SDK Android Obrolan IVS](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/latest/)). Agar tetap dapat memperbarui status lokal, kita perlu mengimplementasikan fungsi state-updater; sebut saja `updateConnectionState`:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

enum class ConnectionState {
   CONNECTED,
   DISCONNECTED,
   LOADING
}

class MainActivity : AppCompatActivity() {
   private var connectionState = ConnectionState.DISCONNECTED
// ...

   private fun updateConnectionState(state: ConnectionState) {
      connectionState = state

      when (state) {
         ConnectionState.CONNECTED -> {
            Log.d(TAG, "room connected")
         }
         ConnectionState.DISCONNECTED -> {
            Log.d(TAG, "room disconnected")
         }
         ConnectionState.LOADING -> {
            Log.d(TAG, "room loading")
         }
      }
   }
}
```

[Selanjutnya, kita mengintegrasikan fungsi state-updater kita dengan properti.listener: ChatRoom](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/listener.html)

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
// ...

   private val roomListener = object : ChatRoomListener {
      override fun onConnecting(room: ChatRoom) {
         Log.d(TAG, "onConnecting")
         runOnUiThread {
            updateConnectionState(ConnectionState.LOADING)
         }
      }

      override fun onConnected(room: ChatRoom) {
         Log.d(TAG, "onConnected")
         runOnUiThread {
            updateConnectionState(ConnectionState.CONNECTED)
         }
      }

      override fun onDisconnected(room: ChatRoom, reason: DisconnectReason) {
         Log.d(TAG, "[${Thread.currentThread().name}] onDisconnected")
         runOnUiThread {
            updateConnectionState(ConnectionState.DISCONNECTED)
         }
      }
   }
}
```

Sekarang kita memiliki kemampuan untuk menyimpan, mendengarkan, dan bereaksi terhadap pembaruan [ChatRoom](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html)status, saatnya untuk menginisialisasi koneksi:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

enum class ConnectionState {
   CONNECTED,
   DISCONNECTED,
   LOADING
}

class MainActivity : AppCompatActivity() {
   private var connectionState = ConnectionState.DISCONNECTED
// ...

   private fun connect() {
      try {
         room?.connect()
      } catch (ex: Exception) {
         Log.e(TAG, "Error while calling connect()", ex)
      }
   }

   private val roomListener = object : ChatRoomListener {
      // ...
      override fun onConnecting(room: ChatRoom) {
         Log.d(TAG, "onConnecting")
         runOnUiThread {
            updateConnectionState(ConnectionState.LOADING)
         }
      }

      override fun onConnected(room: ChatRoom) {
         Log.d(TAG, "onConnected")
         runOnUiThread {
            updateConnectionState(ConnectionState.CONNECTED)
         }
      }
      // ...
   }
}
```

## Membangun Penyedia Token
<a name="chat-android-rooms-token-provider"></a>

Kini saatnya membuat fungsi yang bertanggung jawab untuk membuat dan mengelola token obrolan di aplikasi. Di dalam contoh ini, kita menggunakan [Klien HTTP Retrofit untuk Android](https://square.github.io/retrofit/).

Sebelum kita dapat mengirim lalu lintas jaringan yang ada, kita harus menyiapkan konfigurasi keamanan jaringan untuk Android. (Untuk informasi selengkapnya, lihat [Konfigurasikan keamanan jaringan](https://developer.android.com/privacy-and-security/security-config).) Kita mulai dengan menambahkan izin jaringan ke file [Manifes Aplikasi](https://developer.android.com/guide/topics/manifest/manifest-intro). Perhatikan tanda `user-permission` dan atribut `networkSecurityConfig` yang ditambahkan, yang akan mengarahkan ke konfigurasi keamanan jaringan baru kita. *Di dalam kode di bawah ini, ganti `<version>` dengan nomor versi SDK Android Obrolan saat ini (misalnya, 1.0.0)*.

**XML**:

```
// ./app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.chatterbox.myapp">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:fullBackupContent="@xml/backup_rules"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
// ...

// ./app/build.gradle


dependencies {
   implementation("com.amazonaws:ivs-chat-messaging:<version>")
// ...

   implementation("com.squareup.retrofit2:retrofit:2.9.0")
}
```

Nyatakan domain `10.0.2.2` dan `localhost` sebagai tepercaya, untuk mulai bertukar pesan dengan backend kami:

**XML**:

```
// ./app/src/main/res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">10.0.2.2</domain>
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>
```

Selanjutnya, kita perlu menambahkan dependensi baru, bersama dengan [Penambahan konverter gson](https://github.com/square/retrofit/tree/trunk/retrofit-converters/gson) untuk mengurai respons HTTP. *Di dalam kode di bawah ini, ganti `<version>` dengan nomor versi SDK Android Obrolan saat ini (misalnya, 1.0.0)*.

**Skrip Kotlin:**

```
// ./app/build.gradle

dependencies {
   implementation("com.amazonaws:ivs-chat-messaging:<version>")
// ...

   implementation("com.squareup.retrofit2:retrofit:2.9.0")
}
```

Untuk mengambil token obrolan, kita perlu membuat permintaan HTTP POST dari aplikasi `chatterbox` kita. Kita menentukan permintaan dalam antarmuka Retrofit yang akan diimplementasikan. (Lihat [dokumentasi Retrofit](https://square.github.io/retrofit/). Juga biasakan diri Anda dengan spesifikasi [CreateChatToken](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html#API_CreateChatToken_RequestBody)operasi.)

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/network/ApiService.kt

package com.chatterbox.myapp.network
// ...


import androidx.annotation.Keep
import com.amazonaws.ivs.chat.messaging.ChatToken
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

data class CreateTokenParams(var userId: String, var roomIdentifier: String)

interface ApiService {
   @POST("create_chat_token")
   fun createChatToken(@Body params: CreateTokenParams): Call<ChatToken>
}
```

Dengan jaringan yang telah siap, kini saatnya menambahkan fungsi yang bertanggung jawab untuk membuat dan mengelola token obrolan kita. Kita menambahkannya ke `MainActivity.kt`, yang secara otomatis dibuat ketika proyek [dibuat](#chat-android-rooms-chatterbox):

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt


package com.chatterbox.myapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.amazonaws.ivs.chat.messaging.*
import com.chatterbox.myapp.network.CreateTokenParams
import com.chatterbox.myapp.network.RetrofitFactory
import retrofit2.Call
import java.io.IOException
import retrofit2.Callback
import retrofit2.Response

// custom tag for logging purposes
const val TAG = "IVSChat-App"

// any ID to be associated with auth token
const val USER_ID = "test user id"
// ID of the room the app wants to access. Must be an ARN. See Amazon Resource Names(ARNs)
const val ROOM_ID = "arn:aws:..."
// AWS region of the room that was created in Getting Started with Amazon IVS Chat
const val REGION = "us-west-2"

class MainActivity : AppCompatActivity() {
   private val service = RetrofitFactory.makeRetrofitService()
   private lateinit var userId: String

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

   private fun fetchChatToken(callback: ChatTokenCallback) {
      val params = CreateTokenParams(userId, ROOM_ID)
      service.createChatToken(params).enqueue(object : Callback<ChatToken> {
         override fun onResponse(call: Call<ChatToken>, response: Response<ChatToken>) {
            val token = response.body()
            if (token == null) {
               Log.e(TAG, "Received empty token response")
               callback.onFailure(IOException("Empty token response"))
               return
            }

            Log.d(TAG, "Received token response $token")
            callback.onSuccess(token)
         }

         override fun onFailure(call: Call<ChatToken>, throwable: Throwable) {
            Log.e(TAG, "Failed to fetch token", throwable)
            callback.onFailure(throwable)
         }
      })
   }
}
```

## Langkah Berikutnya
<a name="chat-android-rooms-next-steps"></a>

Setelah Anda membuat koneksi ruang obrolan, lanjutkan ke Bagian 2 dari tutorial Android ini, [Pesan dan Peristiwa](chat-sdk-android-tutorial-messages-events.md).

# SDK Pesan Klien Obrolan IVS: Tutorial Android Bagian 2: Pesan dan Acara
<a name="chat-sdk-android-tutorial-messages-events"></a>

Bagian kedua (dan terakhir) dari tutorial ini dipecah menjadi beberapa bagian:

1. [Membuat UI untuk Mengirim Pesan](#chat-android-messages-events-create_ui)

   1. [Tata Letak Utama UI](#create-ui-main-layout)

   1. [Sel Teks Abstraksi UI untuk Menampilkan Teks Secara Konsisten](#create-ui-text-cell)

   1. [Pesan Obrolan Kiri UI](#create-ui-left-chat-message)

   1. [Pesan Obrolan Kanan UI](#create-ui-right-chat-message)

   1. [Nilai Warna Tambahan UI](#create-ui-color-values)

1. [Menerapkan Ikatan Tampilan](#chat-android-messages-events-view-binding)

1. [Mengelola Permintaan Pesan-Obrolan](#chat-android-messages-events-requests)

1. [Langkah Terakhir](#chat-android-messages-events-final-steps)

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK for Android Referensi](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/latest/) (aktif). GitHub

## Prasyarat
<a name="chat-android-messages-events-prerequisite"></a>

Pastikan Anda telah menyelesaikan Bagian 1 dari tutorial ini, [Ruang Obrolan](chat-sdk-android-tutorial-chat-rooms.md).

## Membuat UI untuk Mengirim Pesan
<a name="chat-android-messages-events-create_ui"></a>

Setelah kita berhasil menginisialisasi koneksi ruang obrolan, kini saatnya mengirim pesan pertama kami. UI diperlukan untuk fitur ini. Kami akan menambahkan:
+ Tombol `connect/disconnect`
+ Input pesan dengan tombol `send`
+ Daftar pesan dinamis. Untuk membangun ini, kami menggunakan Android Jetpack [RecyclerView](https://developer.android.com/develop/ui/views/layout/recyclerview).

### Tata Letak Utama UI
<a name="create-ui-main-layout"></a>

Lihat [Tata Letak](https://developer.android.com/develop/ui/views/layout/declaring-layout) Android Jetpack di dokumentasi developer Android.

**XML:**

```
// ./app/src/main/res/layout/activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:id="@+id/connect_view"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:gravity="center"
                  android:orientation="vertical">

        <androidx.cardview.widget.CardView
                android:id="@+id/connect_button"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_gravity=""
                android:layout_marginStart="16dp"
                android:layout_marginTop="4dp"
                android:layout_marginEnd="16dp"
                android:clickable="true"
                android:elevation="16dp"
                android:focusable="true"
                android:foreground="?android:attr/selectableItemBackground"
                app:cardBackgroundColor="@color/purple_500"
                app:cardCornerRadius="10dp">

            <TextView
                    android:id="@+id/connect_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentEnd="true"
                    android:layout_gravity="center"
                    android:layout_weight="1"
                    android:paddingHorizontal="12dp"
                    android:text="Connect"
                    android:textColor="@color/white"
                    android:textSize="16sp"/>

            <ProgressBar
                    android:id="@+id/activity_indicator"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_gravity="center"
                    android:layout_marginHorizontal="20dp"
                    android:indeterminateOnly="true"
                    android:indeterminateTint="@color/white"
                    android:indeterminateTintMode="src_atop"
                    android:keepScreenOn="true"
                    android:visibility="gone"/>
        </androidx.cardview.widget.CardView>

    </LinearLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/chat_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:visibility="visible"
            tools:context=".MainActivity">

        <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintBottom_toTopOf="@+id/layout_message_input"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clipToPadding="false"
                    android:paddingTop="70dp"
                    android:paddingBottom="20dp"/>
        </RelativeLayout>

        <RelativeLayout
                android:id="@+id/layout_message_input"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:clipToPadding="false"
                android:drawableTop="@android:color/black"
                android:elevation="18dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <EditText
                    android:id="@+id/message_edit_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginStart="16dp"
                    android:layout_toStartOf="@+id/send_button"
                    android:background="@android:color/transparent"
                    android:hint="Enter Message"
                    android:inputType="text"
                    android:maxLines="6"
                    tools:ignore="Autofill"/>

            <Button
                    android:id="@+id/send_button"
                    android:layout_width="84dp"
                    android:layout_height="48dp"
                    android:layout_alignParentEnd="true"
                    android:background="@color/black"
                    android:foreground="?android:attr/selectableItemBackground"
                    android:text="Send"
                    android:textColor="@color/white"
                    android:textSize="12dp"/>
        </RelativeLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>


</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

### Sel Teks Abstraksi UI untuk Menampilkan Teks Secara Konsisten
<a name="create-ui-text-cell"></a>

**XML:**

```
// ./app/src/main/res/layout/common_cell.xml
   
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/layout_container"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:background="@color/light_gray"
              android:minWidth="100dp"
              android:orientation="vertical">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

        <TextView
                android:id="@+id/card_message_me_text_view"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginBottom="8dp"
                android:maxWidth="260dp"
                android:paddingLeft="12dp"
                android:paddingTop="8dp"
                android:paddingRight="12dp"
                android:text="This is a Message"
                android:textColor="#ffffff"
                android:textSize="16sp"/>

        <TextView
                android:id="@+id/failed_mark"
                android:layout_width="40dp"
                android:layout_height="match_parent"
                android:paddingRight="5dp"
                android:src="@drawable/ic_launcher_background"
                android:text="!"
                android:textAlignment="viewEnd"
                android:textColor="@color/white"
                android:textSize="25dp"
                android:visibility="gone"/>
    </LinearLayout>

</LinearLayout>
```

### Pesan Obrolan Kiri UI
<a name="create-ui-left-chat-message"></a>

**XML**:

```
// ./app/src/main/res/layout/card_view_left.xml
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginStart="8dp"
              android:layout_marginBottom="12dp"
              android:orientation="vertical">

    <TextView
            android:id="@+id/username_edit_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="UserName"/>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <androidx.cardview.widget.CardView
                android:id="@+id/card_message_other"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="left"
                android:layout_marginBottom="4dp"
                android:foreground="?android:attr/selectableItemBackground"
                app:cardBackgroundColor="@color/light_gray_2"
                app:cardCornerRadius="10dp"
                app:cardElevation="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <include layout="@layout/common_cell"/>
        </androidx.cardview.widget.CardView>

        <TextView
                android:id="@+id/dateText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="4dp"
                android:layout_marginBottom="4dp"
                android:text="10:00"
                app:layout_constraintBottom_toBottomOf="@+id/card_message_other"
                app:layout_constraintLeft_toRightOf="@+id/card_message_other"/>
    </androidx.constraintlayout.widget.ConstraintLayout>


</LinearLayout>
```

### Pesan Obrolan Kanan UI
<a name="create-ui-right-chat-message"></a>

**XML**:

```
// ./app/src/main/res/layout/card_view_right.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"                                                   android:layout_width="match_parent"                                                   android:layout_height="wrap_content" 
android:layout_marginEnd="8dp">

    <androidx.cardview.widget.CardView
            android:id="@+id/card_message_me"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_marginBottom="10dp"
            android:foreground="?android:attr/selectableItemBackground"
            app:cardBackgroundColor="@color/purple_500"
            app:cardCornerRadius="10dp"
            app:cardElevation="0dp"
            app:cardPreventCornerOverlap="false"
            app:cardUseCompatPadding="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent">

        <include layout="@layout/common_cell"/>

    </androidx.cardview.widget.CardView>

    <TextView
            android:id="@+id/dateText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="12dp"
            android:layout_marginBottom="4dp"
            android:text="10:00"
            app:layout_constraintBottom_toBottomOf="@+id/card_message_me"
            app:layout_constraintRight_toLeftOf="@+id/card_message_me"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

### Nilai Warna Tambahan UI
<a name="create-ui-color-values"></a>

**XML:**

```
// ./app/src/main/res/values/colors.xml
 
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--    ...-->
    <color name="dark_gray">#4F4F4F</color>
    <color name="blue">#186ED3</color>
    <color name="dark_red">#b30000</color>
    <color name="light_gray">#B7B7B7</color>
    <color name="light_gray_2">#eef1f6</color>
</resources>
```

## Menerapkan Ikatan Tampilan
<a name="chat-android-messages-events-view-binding"></a>

Kami memanfaatkan fitur [Ikatan Tampilan](https://developer.android.com/topic/libraries/view-binding) Android untuk dapat mereferensikan kelas ikatan pada tata letak XML. Untuk mengaktifkan fitur tersebut, atur opsi build `viewBinding` ke `true` pada `./app/build.gradle`:

**Skrip Kotlin:**

```
 // ./app/build.gradle

android {
//    ...

    buildFeatures {
        viewBinding = true
    }
//    ...
}
```

Sekarang saatnya menghubungkan UI dengan kode Kotlin:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt
package com.chatterbox.myapp
// ...
const val TAG = "Chatterbox-MyApp"


class MainActivity : AppCompatActivity() {
//    ...

    private fun sendMessage(request: SendMessageRequest) {
        try {
            room?.sendMessage(
                request,
                object : SendMessageCallback {
                    override fun onRejected(request: SendMessageRequest, error: ChatError) {
                        runOnUiThread {
                            entries.addFailedRequest(request)
                            scrollToBottom()
                            Log.e(TAG, "Message rejected: ${error.errorMessage}")
                        }
                    }
                }
            )

            entries.addPendingRequest(request)

            binding.messageEditText.text.clear()
            scrollToBottom()
        } catch (error: Exception) {
            Log.e(TAG, error.message ?: "Unknown error occurred")
        }
    }

    private fun scrollToBottom() {
        binding.recyclerView.smoothScrollToPosition(entries.size - 1)
    }

    private fun sendButtonClick(view: View) {
        val content = binding.messageEditText.text.toString()
        if (content.trim().isEmpty()) {
            return
        }

        val request = SendMessageRequest(content)
        sendMessage(request)
    }
}
```

Kita juga menambahkan metode untuk menghapus pesan dan memutus koneksi pengguna dari obrolan, yang dapat diinvokasi menggunakan menu konteks pesan obrolan:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
//    ...

class MainActivity : AppCompatActivity() {
//    ...

    private fun deleteMessage(request: DeleteMessageRequest) {
        room?.deleteMessage(
            request,
            object : DeleteMessageCallback {
                override fun onRejected(request: DeleteMessageRequest, error: ChatError) {
                    runOnUiThread {
                        Log.d(TAG, "Delete message rejected: ${error.errorMessage}")
                    }
                }
            }
        )
    }

    private fun disconnectUser(request: DisconnectUserRequest) {
        room?.disconnectUser(
            request,
            object : DisconnectUserCallback {
                override fun onRejected(request: DisconnectUserRequest, error: ChatError) {
                    runOnUiThread {
                        Log.d(TAG, "Disconnect user rejected: ${error.errorMessage}")
                    }
                }
            }
        )
    }
}
```

## Mengelola Permintaan Pesan-Obrolan
<a name="chat-android-messages-events-requests"></a>

Kami membutuhkan cara untuk mengelola permintaan pesan-obrolan melalui semua kemungkinan statusnya:
+ Tertunda — Pesan telah dikirim ke ruang obrolan, tetapi belum dikonfirmasi atau ditolak.
+ Dikonfirmasi — Pesan dikirim oleh ruang obrolan ke semua pengguna (termasuk kita).
+ Ditolak — Pesan ditolak oleh ruang obrolan dengan objek kesalahan.

Kita akan menyimpan permintaan obrolan dan pesan obrolan yang belum terselesaikan dalam suatu [daftar](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/mutable-list-of.html). Daftar ini layak mendapat kelas terpisah, yang kita sebut `ChatEntries.kt`:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/ChatEntries.kt

package com.chatterbox.myapp

import com.amazonaws.ivs.chat.messaging.entities.ChatMessage
import com.amazonaws.ivs.chat.messaging.requests.SendMessageRequest

sealed class ChatEntry() {
    class Message(val message: ChatMessage) : ChatEntry()
    class PendingRequest(val request: SendMessageRequest) : ChatEntry()
    class FailedRequest(val request: SendMessageRequest) : ChatEntry()
}

class ChatEntries {
    /* This list is kept in sorted order. ChatMessages are sorted by date, while pending and failed requests are kept in their original insertion point. */
    val entries = mutableListOf<ChatEntry>()
    var adapter: ChatListAdapter? = null

    val size get() = entries.size

    /**
     * Insert pending request at the end.
     */
    fun addPendingRequest(request: SendMessageRequest) {
        val insertIndex = entries.size
        entries.add(insertIndex, ChatEntry.PendingRequest(request))
        adapter?.notifyItemInserted(insertIndex)
    }

    /**
     * Insert received message at proper place based on sendTime. This can cause removal of pending requests.
     */
    fun addReceivedMessage(message: ChatMessage) {
        /* Skip if we have already handled that message. */
        val existingIndex = entries.indexOfLast { it is ChatEntry.Message && it.message.id == message.id }
        if (existingIndex != -1) {
            return
        }

        val removeIndex = entries.indexOfLast {
            it is ChatEntry.PendingRequest && it.request.requestId == message.requestId
        }
        if (removeIndex != -1) {
            entries.removeAt(removeIndex)
        }

        val insertIndexRaw = entries.indexOfFirst { it is ChatEntry.Message && it.message.sendTime > message.sendTime }
        val insertIndex = if (insertIndexRaw == -1) entries.size else insertIndexRaw
        entries.add(insertIndex, ChatEntry.Message(message))

        if (removeIndex == -1) {
            adapter?.notifyItemInserted(insertIndex)
        } else if (removeIndex == insertIndex) {
            adapter?.notifyItemChanged(insertIndex)
        } else {
            adapter?.notifyItemRemoved(removeIndex)
            adapter?.notifyItemInserted(insertIndex)
        }
    }

    fun addFailedRequest(request: SendMessageRequest) {
        val removeIndex = entries.indexOfLast {
            it is ChatEntry.PendingRequest && it.request.requestId == request.requestId
        }
        if (removeIndex != -1) {
            entries.removeAt(removeIndex)
            entries.add(removeIndex, ChatEntry.FailedRequest(request))
            adapter?.notifyItemChanged(removeIndex)
        } else {
            val insertIndex = entries.size
            entries.add(insertIndex, ChatEntry.FailedRequest(request))
            adapter?.notifyItemInserted(insertIndex)
        }
    }

    fun removeMessage(messageId: String) {
        val removeIndex = entries.indexOfFirst { it is ChatEntry.Message && it.message.id == messageId }
        entries.removeAt(removeIndex)
        adapter?.notifyItemRemoved(removeIndex)
    }

    fun removeFailedRequest(requestId: String) {
        val removeIndex = entries.indexOfFirst { it is ChatEntry.FailedRequest && it.request.requestId == requestId }
        entries.removeAt(removeIndex)
        adapter?.notifyItemRemoved(removeIndex)
    }

    fun removeAll() {
        entries.clear()
    }
}
```

Untuk menghubungkan daftar dengan UI, kita menggunakan [Adaptor](https://developer.android.com/reference/android/widget/Adapter). Untuk informasi selengkapnya, lihat [Mengikat ke Data dengan AdapterView](https://developer.android.com/develop/ui/views/layout/binding) dan [kelas pengikatan yang dihasilkan](https://developer.android.com/topic/libraries/data-binding/generated-binding).

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/ChatListAdapter.kt

package com.chatterbox.myapp

import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.recyclerview.widget.RecyclerView
import com.amazonaws.ivs.chat.messaging.requests.DisconnectUserRequest
import java.text.DateFormat


class ChatListAdapter(
    private val entries: ChatEntries,
    private val onDisconnectUser: (request: DisconnectUserRequest) -> Unit,
) :
    RecyclerView.Adapter<ChatListAdapter.ViewHolder>() {
    var context: Context? = null
    var userId: String? = null

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val container: LinearLayout = view.findViewById(R.id.layout_container)
        val textView: TextView = view.findViewById(R.id.card_message_me_text_view)
        val failedMark: TextView = view.findViewById(R.id.failed_mark)
        val userNameText: TextView? = view.findViewById(R.id.username_edit_text)
        val dateText: TextView? = view.findViewById(R.id.dateText)
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        if (viewType == 0) {
            val rightView = LayoutInflater.from(viewGroup.context).inflate(R.layout.card_view_right, viewGroup, false)
            return ViewHolder(rightView)
        }
        val leftView = LayoutInflater.from(viewGroup.context).inflate(R.layout.card_view_left, viewGroup, false)
        return ViewHolder(leftView)
    }

    override fun getItemViewType(position: Int): Int {
        // Int 0 indicates to my message while Int 1 to other message
        val chatMessage = entries.entries[position]
        return if (chatMessage is ChatEntry.Message && chatMessage.message.sender.userId != userId) 1 else 0
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        return when (val entry = entries.entries[position]) {
            is ChatEntry.Message -> {
                viewHolder.textView.text = entry.message.content

                val bgColor = if (entry.message.sender.userId == userId) {
                    R.color.purple_500
                } else {
                    R.color.light_gray_2
                }
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, bgColor))

                if (entry.message.sender.userId != userId) {
                    viewHolder.textView.setTextColor(Color.parseColor("#000000"))
                }

                viewHolder.failedMark.isGone = true

                viewHolder.itemView.setOnCreateContextMenuListener { menu, _, _ ->
                    menu.add("Kick out").setOnMenuItemClickListener {
                        val request = DisconnectUserRequest(entry.message.sender.userId, "Some reason")
                        onDisconnectUser(request)
                        true
                    }
                }

                viewHolder.userNameText?.text = entry.message.sender.userId
                viewHolder.dateText?.text =
                    DateFormat.getTimeInstance(DateFormat.SHORT).format(entry.message.sendTime)
            }

            is ChatEntry.PendingRequest -> {
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, R.color.light_gray))
                viewHolder.textView.text = entry.request.content
                viewHolder.failedMark.isGone = true
                viewHolder.itemView.setOnCreateContextMenuListener(null)
                viewHolder.dateText?.text = "Sending"
            }

            is ChatEntry.FailedRequest -> {
                viewHolder.textView.text = entry.request.content
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, R.color.dark_red))
                viewHolder.failedMark.isGone = false
                viewHolder.dateText?.text = "Failed"
            }
        }
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        context = recyclerView.context
    }

    override fun getItemCount() = entries.entries.size
}
```

## Langkah Terakhir
<a name="chat-android-messages-events-final-steps"></a>

Saatnya menghubungkan adaptor baru kita, dengan mengikat kelas `ChatEntries` ke `MainActivity`:

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

import com.chatterbox.myapp.databinding.ActivityMainBinding
import com.chatterbox.myapp.ChatListAdapter
import com.chatterbox.myapp.ChatEntries

class MainActivity : AppCompatActivity() {
    // ...
    private var entries = ChatEntries()
    private lateinit var adapter: ChatListAdapter
    private lateinit var binding: ActivityMainBinding

    /* see https://developer.android.com/topic/libraries/data-binding/generated-binding#create */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        /* Create room instance. */
        room = ChatRoom(REGION, ::fetchChatToken).apply {
            listener = roomListener
        }

        binding.sendButton.setOnClickListener(::sendButtonClick)
        binding.connectButton.setOnClickListener { connect() }

        setUpChatView()

        updateConnectionState(ConnectionState.DISCONNECTED)
    }

    private fun setUpChatView() {
        /* Setup Android Jetpack RecyclerView - see https://developer.android.com/develop/ui/views/layout/recyclerview.*/
        adapter = ChatListAdapter(entries, ::disconnectUser)
        entries.adapter = adapter

        val recyclerViewLayoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.VERTICAL, false)
        binding.recyclerView.layoutManager = recyclerViewLayoutManager
        binding.recyclerView.adapter = adapter

        binding.sendButton.setOnClickListener(::sendButtonClick)
        binding.messageEditText.setOnEditorActionListener { _, _, event ->
            val isEnterDown = (event.action == KeyEvent.ACTION_DOWN) && (event.keyCode == KeyEvent.KEYCODE_ENTER)
            if (!isEnterDown) {
                return@setOnEditorActionListener false
            }

            sendButtonClick(binding.sendButton)
            return@setOnEditorActionListener true
        }
    }
}
```

Karena kita sudah memiliki kelas yang bertanggung jawab untuk melacak permintaan obrolan (`ChatEntries`), kita siap mengimplementasikan kode untuk memanipulasi `entries` di `roomListener`. Kita akan memperbarui `entries` dan `connectionState` sesuai dengan peristiwa yang kita tanggapi:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
    //...
    

    private fun sendMessage(request: SendMessageRequest) {
    //...

    }

    private fun scrollToBottom() {
        binding.recyclerView.smoothScrollToPosition(entries.size - 1)
    }

    private val roomListener = object : ChatRoomListener {
        override fun onConnecting(room: ChatRoom) {
            Log.d(TAG, "[${Thread.currentThread().name}] onConnecting")
            runOnUiThread {
                updateConnectionState(ConnectionState.LOADING)
            }
        }

        override fun onConnected(room: ChatRoom) {
            Log.d(TAG, "[${Thread.currentThread().name}] onConnected")
            runOnUiThread {
                updateConnectionState(ConnectionState.CONNECTED)
            }
        }

        override fun onDisconnected(room: ChatRoom, reason: DisconnectReason) {
            Log.d(TAG, "[${Thread.currentThread().name}] onDisconnected")
            runOnUiThread {
                updateConnectionState(ConnectionState.DISCONNECTED)
                entries.removeAll()
            }
        }

        override fun onMessageReceived(room: ChatRoom, message: ChatMessage) {
            Log.d(TAG, "[${Thread.currentThread().name}] onMessageReceived $message")
            runOnUiThread {
                entries.addReceivedMessage(message)
                scrollToBottom()
            }
        }

        override fun onEventReceived(room: ChatRoom, event: ChatEvent) {
            Log.d(TAG, "[${Thread.currentThread().name}] onEventReceived $event")
        }

        override fun onMessageDeleted(room: ChatRoom, event: DeleteMessageEvent) {
            Log.d(TAG, "[${Thread.currentThread().name}] onMessageDeleted $event")
        }

        override fun onUserDisconnected(room: ChatRoom, event: DisconnectUserEvent) {
            Log.d(TAG, "[${Thread.currentThread().name}] onUserDisconnected $event")
        }
    }
}
```

Sekarang Anda seharusnya dapat menjalankan aplikasi\$1 (Lihat [Membangun dan menjalankan aplikasi Anda](https://developer.android.com/studio/run#basic-build-run).) Ingatlah untuk menjalankan server backend Anda saat menggunakan aplikasi. Anda dapat memutarnya dari terminal di root proyek kita dengan perintah ini: `./gradlew :auth-server:run` atau dengan menjalankan tugas Gradle `auth-server:run` langsung dari Android Studio.

# SDK Pesan Klien Obrolan IVS: Tutorial Coroutines Kotlin Bagian 1: Ruang Obrolan
<a name="chat-sdk-kotlin-tutorial-chat-rooms"></a>

Ini adalah bagian pertama dari tutorial dua bagian. Anda akan mempelajari hal-hal penting dalam bekerja menggunakan SDK Perpesanan Obrolan Amazon IVS dengan membangun aplikasi Android yang berfungsi sepenuhnya menggunakan bahasa pemrograman [Kotlin](https://kotlinlang.org/) dan [coroutine](https://kotlinlang.org/docs/coroutines-overview.html). Kami menyebut aplikasi itu *Chatterbox*.

Sebelum Anda memulai modul, luangkan beberapa menit untuk membiasakan diri dengan prasyarat, konsep utama di balik token obrolan, dan server backend yang diperlukan untuk membuat ruang obrolan.

Tutorial ini dibuat untuk para developer Android berpengalaman yang baru mengenal SDK Perpesanan Obrolan IVS. Anda harus merasa nyaman dengan bahasa pemrograman Kotlin dan membuat UIs di platform Android.

Bagian pertama dari tutorial ini dipecah menjadi beberapa bagian:

1. [Menyiapkan Authentication/Authorization Server Lokal](#chat-kotlin-rooms-auth-server)

1. [Membuat Proyek Chatterbox](#chat-kotlin-rooms-chatterbox)

1. [Hubungkan dengan Ruang Obrolan dan Amati Pembaruan Koneksi](#chat-kotlin-rooms-connect)

1. [Membangun Penyedia Token](#chat-kotlin-rooms-token-provider)

1. [Langkah Berikutnya](#chat-kotlin-rooms-next-steps)

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK for Android Referensi](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/latest/) (aktif). GitHub

## Prasyarat
<a name="chat-kotlin-rooms-prerequisites"></a>
+ Kenali Kotlin dan cara membuat aplikasi di platform Android. Jika Anda tidak terbiasa membuat aplikasi untuk Android, pelajari dasar-dasarnya dalam panduan [Membangun aplikasi pertama Anda](https://developer.android.com/codelabs/basic-android-kotlin-compose-first-app#0) untuk para developer Android.
+ Baca dan pahami [Memulai Obrolan Amazon IVS](getting-started-chat.md).
+ Buat pengguna AWS IAM dengan kemampuan `CreateChatToken` dan `CreateRoom` yang ditentukan dalam kebijakan IAM yang ada. (Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md)).
+ Pastikan secret/access kunci untuk pengguna ini disimpan dalam file kredensial AWS. Untuk instruksinya, lihat [Panduan Pengguna AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) (terutama [Pengaturan file konfigurasi dan kredensial](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)).
+ Buat ruang obrolan dan simpan ARN-nya. Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md). (Jika Anda tidak menyimpan ARN, Anda dapat mencarinya nanti dengan konsol atau API Obrolan.)

## Menyiapkan Authentication/Authorization Server Lokal
<a name="chat-kotlin-rooms-auth-server"></a>

Server backend Anda akan bertanggung jawab untuk membuat ruang obrolan dan menghasilkan token obrolan yang diperlukan SDK Android Obrolan IVS guna mengautentikasi dan mengotorisasi klien untuk ruang obrolan Anda.

Lihat [Membuat Token Obrolan](getting-started-chat-auth.md) di *Memulai Obrolan Amazon IVS*. Seperti yang ditunjukkan pada diagram alur di sana, aplikasi sisi server Anda bertanggung jawab untuk membuat token obrolan. Hal ini berarti aplikasi Anda harus menyediakan caranya sendiri untuk menghasilkan token obrolan dengan memintanya dari aplikasi sisi server Anda.

Kami menggunakan kerangka kerja [Ktor](https://ktor.io/) untuk membuat server lokal langsung yang mengelola pembuatan token obrolan menggunakan lingkungan AWS lokal Anda. 

Pada titik ini, kami berharap Anda telah menyiapkan kredensial AWS Anda dengan benar. Untuk step-by-step petunjuknya, lihat [Menyiapkan kredensial sementara AWS dan Wilayah AWS untuk pengembangan](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html).

Buat direktori baru dan beri nama `chatterbox`, lalu di dalamnya, buat direktori lain yang diberi nama `auth-server`*.*

Folder server kita akan memiliki struktur sebagai berikut:

```
- auth-server
  - src
    - main
      - kotlin
        - com
          - chatterbox
            - authserver
              - Application.kt
       - resources
         - application.conf
         - logback.xml
   - build.gradle.kts
```

*Catatan: Anda dapat langsung kode copy/paste di sini ke file yang direferensikan.*

Selanjutnya, kita menambahkan semua dependensi dan plugin yang diperlukan agar server autentikasi berfungsi:

**Skrip Kotlin:**

```
// ./auth-server/build.gradle.kts

plugins {
   application
   kotlin("jvm")
   kotlin("plugin.serialization").version("1.7.10")
}

application {   
   mainClass.set("io.ktor.server.netty.EngineMain")
}

dependencies {
   implementation("software.amazon.awssdk:ivschat:2.18.1")
   implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20")

   implementation("io.ktor:ktor-server-core:2.1.3")
   implementation("io.ktor:ktor-server-netty:2.1.3")
   implementation("io.ktor:ktor-server-content-negotiation:2.1.3")
   implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3")

   implementation("ch.qos.logback:logback-classic:1.4.4")
}
```

Sekarang kita perlu menyiapkan fungsionalitas pembuatan log untuk server autentikasi. (Untuk informasi selengkapnya, lihat [Konfigurasikan pembuat log](https://ktor.io/docs/logging.html#configure-logger).)

**XML:**

```
// ./auth-server/src/main/resources/logback.xml

<configuration>
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
   </appender>
   <root level="trace">
      <appender-ref ref="STDOUT"/>
   </root>
   <logger name="org.eclipse.jetty" level="INFO"/>
   <logger name="io.netty" level="INFO"/>
</configuration>
```

Server [Ktor](https://ktor.io/docs/welcome.html) memerlukan pengaturan konfigurasi, yang dimuat dari file `application.*` di direktori `resources` secara otomatis, sehingga kita menambahkannya juga. (Untuk informasi selengkapnya, lihat [Konfigurasi dalam file](https://ktor.io/docs/configurations.html#configuration-file).)

**HOCON:**

```
// ./auth-server/src/main/resources/application.conf

ktor {
   deployment {
      port = 3000
   }
   application {
      modules = [ com.chatterbox.authserver.ApplicationKt.main ]
   }
}
```

Terakhir, mari kita implementasikan server kita:

**Kotlin:**

```
// ./auth-server/src/main/kotlin/com/chatterbox/authserver/Application.kt

package com.chatterbox.authserver

import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import software.amazon.awssdk.services.ivschat.IvschatClient
import software.amazon.awssdk.services.ivschat.model.CreateChatTokenRequest

@Serializable
data class ChatTokenParams(var userId: String, var roomIdentifier: String)

@Serializable
data class ChatToken(
   val token: String,
   val sessionExpirationTime: String,
   val tokenExpirationTime: String,
)

fun Application.main() {
   install(ContentNegotiation) {
      json(Json)
   }

   routing {
      post("/create_chat_token") {
         val callParameters = call.receive<ChatTokenParams>()
         val request = CreateChatTokenRequest.builder().roomIdentifier(callParameters.roomIdentifier)
            .userId(callParameters.userId).build()
         val token = IvschatClient.create()
            .createChatToken(request)

         call.respond(
            ChatToken(
                token.token(),
                token.sessionExpirationTime().toString(),
                token.tokenExpirationTime().toString()
            )
         )
      }
   }
}
```

## Membuat Proyek Chatterbox
<a name="chat-kotlin-rooms-chatterbox"></a>

Untuk membuat proyek Android, instal dan buka [Android Studio](https://developer.android.com/studio).

Ikuti langkah-langkah yang tercantum dalam [Panduan Membuat Proyek](https://developer.android.com/studio/projects/create-project) Android yang resmi. 
+ Di [Pilih proyek Anda](https://developer.android.com/studio/projects/create-project), pilih templat proyek **Aktivitas Kosong** untuk aplikasi Chatterbox kami.
+ Di [Konfigurasikan proyek Anda](https://developer.android.com/studio/projects/create-project#configure), pilih nilai berikut untuk bidang konfigurasi:
  + **Nama**: Aplikasi Saya
  + **Nama paket**: com.chatterbox.myapp
  + **Simpan lokasi**: arahkan ke direktori `chatterbox` yang dibuat di langkah sebelumnya
  + **Bahasa**: Kotlin
  + **Tingkat API minimum**: API 21: Android 5.0 (Lollipop)

Setelah menentukan semua parameter konfigurasi dengan benar, struktur file kita di dalam folder `chatterbox` akan terlihat seperti berikut:

```
- app
  - build.gradle
  ...
- gradle
- .gitignore
- build.gradle
- gradle.properties
- gradlew
- gradlew.bat
- local.properties
- settings.gradle
- auth-server
  - src
    - main
      - kotlin
        - com
          - chatterbox
            - authserver
              - Application.kt
       - resources
         - application.conf
         - logback.xml
   - build.gradle.kts
```

Sekarang kita memiliki proyek Android yang berfungsi, kita dapat menambahkan [com.amazonaws: ivs-chat-messaging dan [org.jetbrains.kotlinx](https://github.com/Kotlin/kotlinx.coroutines):](https://mvnrepository.com/artifact/com.amazonaws/ivs-chat-messaging) ke dependensi kita. kotlinx-coroutines-core `build.gradle` (Untuk informasi selengkapnya tentang kit alat build [Gradle](https://gradle.org/), lihat [Mengonfigurasi build Anda](https://developer.android.com/build).) 

**Catatan:** Di bagian atas setiap cuplikan kode, ada jalur ke file tempat Anda harus membuat perubahan dalam proyek. Jalur tersebut bersifat relatif terhadap root proyek.

**Kotlin:**

```
// ./app/build.gradle

plugins {
// ...
}

android {
// ...
}

dependencies {
    implementation 'com.amazonaws:ivs-chat-messaging:1.1.0'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'

// ...
}
```

Setelah dependensi baru ditambahkan, jalankan **Sinkronkan Proyek dengan File Gradle** di Android Studio untuk menyinkronkan proyek dengan dependensi baru. (Untuk informasi selengkapnya, lihat [Menambahkan dependensi build](https://developer.android.com/build/dependencies).)

Agar mudah menjalankan server autentikasi (yang dibuat di bagian sebelumnya) dari root proyek, kita memasukkannya sebagai modul baru di `settings.gradle`. (Untuk informasi selengkapnya, lihat [Menyusun dan Membangun Komponen Perangkat Lunak dengan Gradle](https://docs.gradle.org/current/userguide/multi_project_builds.html).)

**Skrip Kotlin:**

```
// ./settings.gradle

// ...

rootProject.name = "My App"
include ':app'
include ':auth-server'
```

Mulai sekarang, karena `auth-server` disertakan dalam proyek Android, Anda dapat menjalankan server autentikasi dengan perintah berikut dari root proyek:

**Shell:**

```
./gradlew :auth-server:run         
```

## Hubungkan dengan Ruang Obrolan dan Amati Pembaruan Koneksi
<a name="chat-kotlin-rooms-connect"></a>

Untuk membuka koneksi ruang obrolan, kita menggunakan [panggilan balik siklus hidup aktivitas onCreate()](https://developer.android.com/guide/components/activities/activity-lifecycle), yang diaktifkan saat aktivitas pertama kali dibuat. [ChatRoom Konstruktor](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html) mengharuskan kami untuk menyediakan `region` dan `tokenProvider` membuat instance koneksi ruangan.

**Catatan:** Fungsi `fetchChatToken` dalam cuplikan di bawah ini akan diimplementasikan di [bagian berikutnya](#chat-kotlin-rooms-token-provider).

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

// AWS region of the room that was created in Getting Started with Amazon IVS Chat
const val REGION = "us-west-2"

class MainActivity : AppCompatActivity() {
    private var room: ChatRoom? = null
    // ...

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

      // Create room instance
      room = ChatRoom(REGION, ::fetchChatToken)
   }

// ...
}
```

Menampilkan dan bereaksi terhadap perubahan dalam koneksi ruang obrolan adalah bagian penting dari pembuatan aplikasi obrolan, seperti `chatterbox`. Sebelum kita dapat mulai berinteraksi dengan ruang tersebut, kita harus berlangganan peristiwa status koneksi ruang obrolan untuk mendapatkan pembaruan.

Di SDK Obrolan untuk coroutine, [https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html) mengharapkan kami untuk menangani peristiwa siklus hidup ruang di [Flow](https://kotlinlang.org/docs/flow.html). Untuk saat ini, fungsi tersebut hanya akan mencatat pesan konfirmasi, ketika diinvokasi:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

const val TAG = "Chatterbox-MyApp"

class MainActivity : AppCompatActivity() {
// ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        // Create room instance
        room = ChatRoom(REGION, ::fetchChatToken).apply {
            lifecycleScope.launch {
                stateChanges().collect { state ->
                    Log.d(TAG, "state change to $state")
                }
            }

            lifecycleScope.launch {
                receivedMessages().collect { message ->
                    Log.d(TAG, "messageReceived $message")
                }
            }

            lifecycleScope.launch {
                receivedEvents().collect { event ->
                    Log.d(TAG, "eventReceived $event")
                }
            }

            lifecycleScope.launch {
                deletedMessages().collect { event ->
                    Log.d(TAG, "messageDeleted $event")
                }
            }

            lifecycleScope.launch {
                disconnectedUsers().collect { event ->
                    Log.d(TAG, "userDisconnected $event")
                }
            }
        }
    }
}
```

Setelah ini, kita perlu menyediakan kemampuan untuk membaca status koneksi ruang. Kita akan menyimpannya di [properti](https://kotlinlang.org/docs/properties.html) `MainActivity.kt` dan menginisialisasinya ke status TERPUTUS default untuk ruang (lihat `state` `ChatRoom` di [Referensi SDK Android Obrolan IVS](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/latest/)). Agar tetap dapat memperbarui status lokal, kita perlu mengimplementasikan fungsi state-updater; sebut saja `updateConnectionState`:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
   private var connectionState = ChatRoom.State.DISCONNECTED

// ...

   private fun updateConnectionState(state: ChatRoom.State) {
      connectionState = state

     when (state) {
          ChatRoom.State.CONNECTED -> {
              Log.d(TAG, "room connected")
          }
          ChatRoom.State.DISCONNECTED -> {
              Log.d(TAG, "room disconnected")
          }
          ChatRoom.State.CONNECTING -> {
              Log.d(TAG, "room connecting")
          }
      }
}
```

[Selanjutnya, kita mengintegrasikan fungsi state-updater kita dengan properti.listener: ChatRoom](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/listener.html)

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
// ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        // Create room instance
        room = ChatRoom(REGION, ::fetchChatToken).apply {
            lifecycleScope.launch {
                stateChanges().collect { state ->
                    Log.d(TAG, "state change to $state")
                    updateConnectionState(state)

                }
            }

      // ...

      }
   }
}
```

Sekarang kita memiliki kemampuan untuk menyimpan, mendengarkan, dan bereaksi terhadap pembaruan [ChatRoom](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/1.0.0/-amazon%20-i-v-s%20-chat%20-messaging%20-s-d-k%20for%20-android/com.amazonaws.ivs.chat.messaging/-chat-room/index.html)status, saatnya untuk menginisialisasi koneksi:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
// ...

   private fun connect() {
      try {
         room?.connect()
      } catch (ex: Exception) {
         Log.e(TAG, "Error while calling connect()", ex)
      }
   }

   // ...
}
```

## Membangun Penyedia Token
<a name="chat-kotlin-rooms-token-provider"></a>

Kini saatnya membuat fungsi yang bertanggung jawab untuk membuat dan mengelola token obrolan di aplikasi. Di dalam contoh ini, kita menggunakan [Klien HTTP Retrofit untuk Android](https://square.github.io/retrofit/).

Sebelum kita dapat mengirim lalu lintas jaringan yang ada, kita harus menyiapkan konfigurasi keamanan jaringan untuk Android. (Untuk informasi selengkapnya, lihat [Konfigurasikan keamanan jaringan](https://developer.android.com/privacy-and-security/security-config).) Kita mulai dengan menambahkan izin jaringan ke file [Manifes Aplikasi](https://developer.android.com/guide/topics/manifest/manifest-intro). Perhatikan tanda `user-permission` dan atribut `networkSecurityConfig` yang ditambahkan, yang akan mengarahkan ke konfigurasi keamanan jaringan baru kita. *Di dalam kode di bawah ini, ganti *`<version>`* dengan nomor versi SDK Android Obrolan saat ini (misalnya, 1.1.0).*

**XML:**

```
// ./app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.chatterbox.myapp">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:fullBackupContent="@xml/backup_rules"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
// ...

// ./app/build.gradle


dependencies {
   implementation("com.amazonaws:ivs-chat-messaging:<version>")
// ...

   implementation("com.squareup.retrofit2:retrofit:2.9.0")
   implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}
```

Nyatakan alamat IP Anda, misalnya domain `10.0.2.2` dan `localhost`, sebagai tepercaya untuk mulai bertukar pesan dengan backend kami:

**XML:**

```
// ./app/src/main/res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">10.0.2.2</domain>
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>
```

Selanjutnya, kita perlu menambahkan dependensi baru, bersama dengan [Penambahan konverter gson](https://github.com/square/retrofit/tree/trunk/retrofit-converters/gson) untuk mengurai respons HTTP. *Di dalam kode di bawah ini, ganti *`<version>`* dengan nomor versi SDK Android Obrolan saat ini (misalnya, 1.1.0).*

**Skrip Kotlin:**

```
// ./app/build.gradle

dependencies {
   implementation("com.amazonaws:ivs-chat-messaging:<version>")
// ...

   implementation("com.squareup.retrofit2:retrofit:2.9.0")
   implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}
```

Untuk mengambil token obrolan, kita perlu membuat permintaan HTTP POST dari aplikasi `chatterbox` kita. Kita menentukan permintaan dalam antarmuka Retrofit yang akan diimplementasikan. (Lihat [dokumentasi Retrofit](https://square.github.io/retrofit/). Juga biasakan diri Anda dengan spesifikasi [CreateChatToken](https://docs.aws.amazon.com/ivs/latest/ChatAPIReference/API_CreateChatToken.html#API_CreateChatToken_RequestBody)operasi.)

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/network/ApiService.kt

package com.chatterbox.myapp.network

import com.amazonaws.ivs.chat.messaging.ChatToken
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

data class CreateTokenParams(var userId: String, var roomIdentifier: String)

interface ApiService {
   @POST("create_chat_token")
   fun createChatToken(@Body params: CreateTokenParams): Call<ChatToken>
}


// ./app/src/main/java/com/chatterbox/myapp/network/RetrofitFactory.kt

package com.chatterbox.myapp.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitFactory {
   private const val BASE_URL = "http://10.0.2.2:3000"

   fun makeRetrofitService(): ApiService {
       return Retrofit.Builder()
           .baseUrl(BASE_URL)
           .addConverterFactory(GsonConverterFactory.create())
           .build().create(ApiService::class.java)
   }
}
```

Dengan jaringan yang telah siap, kini saatnya menambahkan fungsi yang bertanggung jawab untuk membuat dan mengelola token obrolan kita. Kita menambahkannya ke `MainActivity.kt`, yang secara otomatis dibuat ketika proyek [dibuat](#chat-kotlin-rooms-chatterbox):

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import com.amazonaws.ivs.chat.messaging.*
import com.amazonaws.ivs.chat.messaging.coroutines.*
import com.chatterbox.myapp.network.CreateTokenParams
import com.chatterbox.myapp.network.RetrofitFactory
import retrofit2.Call
import java.io.IOException
import retrofit2.Callback
import retrofit2.Response

// custom tag for logging purposes
const val TAG = "Chatterbox-MyApp"

// any ID to be associated with auth token
const val USER_ID = "test user id"
// ID of the room the app wants to access. Must be an ARN. See Amazon Resource Names(ARNs)
const val ROOM_ID = "arn:aws:..."
// AWS region of the room that was created in Getting Started with Amazon IVS Chat
const val REGION = "us-west-2"

class MainActivity : AppCompatActivity() {

   private val service = RetrofitFactory.makeRetrofitService()
   private var userId: String = USER_ID

// ...

   private fun fetchChatToken(callback: ChatTokenCallback) {
      val params = CreateTokenParams(userId, ROOM_ID)
      service.createChatToken(params).enqueue(object : Callback<ChatToken> {
         override fun onResponse(call: Call<ChatToken>, response: Response<ChatToken>) {
            val token = response.body()
            if (token == null) {
               Log.e(TAG, "Received empty token response")
               callback.onFailure(IOException("Empty token response"))
               return
            }

            Log.d(TAG, "Received token response $token")
            callback.onSuccess(token)
         }

         override fun onFailure(call: Call<ChatToken>, throwable: Throwable) {
            Log.e(TAG, "Failed to fetch token", throwable)
            callback.onFailure(throwable)
         }
      })
   }
}
```

## Langkah Berikutnya
<a name="chat-kotlin-rooms-next-steps"></a>

Sekarang, setelah Anda membuat koneksi ruang obrolan, lanjutkan ke Bagian 2 dari tutorial Coroutine Kotlin, [Pesan dan Peristiwa](chat-sdk-kotlin-tutorial-messages-events.md).

# IVS Chat Client Messaging SDK: Tutorial Kotlin Coroutines Bagian 2: Pesan dan Acara
<a name="chat-sdk-kotlin-tutorial-messages-events"></a>

Bagian kedua (dan terakhir) dari tutorial ini dipecah menjadi beberapa bagian:

1. [Membuat UI untuk Mengirim Pesan](#chat-kotlin-messages-events-ui)

   1. [Tata Letak Utama UI](#chat-kotlin-messages-events-ui-main)

   1. [Sel Teks Abstraksi UI untuk Menampilkan Teks Secara Konsisten](#chat-kotlin-messages-events-consistent-text)

   1. [Pesan Obrolan Kiri UI](#chat-kotlin-messages-events-ui-left)

   1. [Pesan Kanan UI](#chat-kotlin-messages-events-ui-right)

   1. [Nilai Warna Tambahan UI](#chat-kotlin-messages-events-additional-color)

1. [Menerapkan Ikatan Tampilan](#chat-kotlin-messages-events-apply-view-binding)

1. [Mengelola Permintaan Pesan-Obrolan](#chat-kotlin-messages-events-chat-message)

1. [Langkah Terakhir](#chat-kotlin-messages-events-final-steps)

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK for Android Referensi](https://aws.github.io/amazon-ivs-chat-messaging-sdk-android/latest/) (aktif). GitHub

## Prasyarat
<a name="chat-kotlin-messages-events-prerequisite"></a>

Pastikan Anda telah menyelesaikan Bagian 1 dari tutorial ini, [Ruang Obrolan](chat-sdk-kotlin-tutorial-chat-rooms.md).

## Membuat UI untuk Mengirim Pesan
<a name="chat-kotlin-messages-events-ui"></a>

Setelah kita berhasil menginisialisasi koneksi ruang obrolan, kini saatnya mengirim pesan pertama kami. UI diperlukan untuk fitur ini. Kami akan menambahkan:
+ Tombol `connect`/`disconnect`
+ Input pesan dengan tombol `send`
+ Daftar pesan dinamis. Untuk membangun ini, kami menggunakan Android Jetpack [RecyclerView](https://developer.android.com/develop/ui/views/layout/recyclerview).

### Tata Letak Utama UI
<a name="chat-kotlin-messages-events-ui-main"></a>

Lihat [Tata Letak](https://developer.android.com/develop/ui/views/layout/declaring-layout) Android Jetpack di dokumentasi developer Android.

**XML:**

```
// ./app/src/main/res/layout/activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:id="@+id/connect_view"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:gravity="center"
                  android:orientation="vertical">

        <androidx.cardview.widget.CardView
                android:id="@+id/connect_button"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_gravity=""
                android:layout_marginStart="16dp"
                android:layout_marginTop="4dp"
                android:layout_marginEnd="16dp"
                android:clickable="true"
                android:elevation="16dp"
                android:focusable="true"
                android:foreground="?android:attr/selectableItemBackground"
                app:cardBackgroundColor="@color/purple_500"
                app:cardCornerRadius="10dp">

            <TextView
                    android:id="@+id/connect_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentEnd="true"
                    android:layout_gravity="center"
                    android:layout_weight="1"
                    android:paddingHorizontal="12dp"
                    android:text="Connect"
                    android:textColor="@color/white"
                    android:textSize="16sp"/>

            <ProgressBar
                    android:id="@+id/activity_indicator"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_gravity="center"
                    android:layout_marginHorizontal="20dp"
                    android:indeterminateOnly="true"
                    android:indeterminateTint="@color/white"
                    android:indeterminateTintMode="src_atop"
                    android:keepScreenOn="true"
                    android:visibility="gone"/>
        </androidx.cardview.widget.CardView>

    </LinearLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/chat_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:visibility="visible"
            tools:context=".MainActivity">

        <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintBottom_toTopOf="@+id/layout_message_input"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clipToPadding="false"
                    android:paddingTop="70dp"
                    android:paddingBottom="20dp"/>
        </RelativeLayout>

        <RelativeLayout
                android:id="@+id/layout_message_input"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:clipToPadding="false"
                android:drawableTop="@android:color/black"
                android:elevation="18dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <EditText
                    android:id="@+id/message_edit_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginStart="16dp"
                    android:layout_toStartOf="@+id/send_button"
                    android:background="@android:color/transparent"
                    android:hint="Enter Message"
                    android:inputType="text"
                    android:maxLines="6"
                    tools:ignore="Autofill"/>

            <Button
                    android:id="@+id/send_button"
                    android:layout_width="84dp"
                    android:layout_height="48dp"
                    android:layout_alignParentEnd="true"
                    android:background="@color/black"
                    android:foreground="?android:attr/selectableItemBackground"
                    android:text="Send"
                    android:textColor="@color/white"
                    android:textSize="12dp"/>
        </RelativeLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>


</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

### Sel Teks Abstraksi UI untuk Menampilkan Teks Secara Konsisten
<a name="chat-kotlin-messages-events-consistent-text"></a>

**XML:**

```
// ./app/src/main/res/layout/common_cell.xml
   
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/layout_container"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:background="@color/light_gray"
              android:minWidth="100dp"
              android:orientation="vertical">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

        <TextView
                android:id="@+id/card_message_me_text_view"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginBottom="8dp"
                android:maxWidth="260dp"
                android:paddingLeft="12dp"
                android:paddingTop="8dp"
                android:paddingRight="12dp"
                android:text="This is a Message"
                android:textColor="#ffffff"
                android:textSize="16sp"/>

        <TextView
                android:id="@+id/failed_mark"
                android:layout_width="40dp"
                android:layout_height="match_parent"
                android:paddingRight="5dp"
                android:src="@drawable/ic_launcher_background"
                android:text="!"
                android:textAlignment="viewEnd"
                android:textColor="@color/white"
                android:textSize="25dp"
                android:visibility="gone"/>
    </LinearLayout>

</LinearLayout>
```

### Pesan Obrolan Kiri UI
<a name="chat-kotlin-messages-events-ui-left"></a>

**XML:**

```
// ./app/src/main/res/layout/card_view_left.xml
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginStart="8dp"
              android:layout_marginBottom="12dp"
              android:orientation="vertical">

    <TextView
            android:id="@+id/username_edit_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="UserName"/>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <androidx.cardview.widget.CardView
                android:id="@+id/card_message_other"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="left"
                android:layout_marginBottom="4dp"
                android:foreground="?android:attr/selectableItemBackground"
                app:cardBackgroundColor="@color/light_gray_2"
                app:cardCornerRadius="10dp"
                app:cardElevation="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent">

            <include layout="@layout/common_cell"/>
        </androidx.cardview.widget.CardView>

        <TextView
                android:id="@+id/dateText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="4dp"
                android:layout_marginBottom="4dp"
                android:text="10:00"
                app:layout_constraintBottom_toBottomOf="@+id/card_message_other"
                app:layout_constraintLeft_toRightOf="@+id/card_message_other"/>
    </androidx.constraintlayout.widget.ConstraintLayout>


</LinearLayout>
```

### Pesan Kanan UI
<a name="chat-kotlin-messages-events-ui-right"></a>

**XML:**

```
// ./app/src/main/res/layout/card_view_right.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"                                                   android:layout_width="match_parent"                                                   android:layout_height="wrap_content" 
android:layout_marginEnd="8dp">

    <androidx.cardview.widget.CardView
            android:id="@+id/card_message_me"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_marginBottom="10dp"
            android:foreground="?android:attr/selectableItemBackground"
            app:cardBackgroundColor="@color/purple_500"
            app:cardCornerRadius="10dp"
            app:cardElevation="0dp"
            app:cardPreventCornerOverlap="false"
            app:cardUseCompatPadding="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent">

        <include layout="@layout/common_cell"/>

    </androidx.cardview.widget.CardView>

    <TextView
            android:id="@+id/dateText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="12dp"
            android:layout_marginBottom="4dp"
            android:text="10:00"
            app:layout_constraintBottom_toBottomOf="@+id/card_message_me"
            app:layout_constraintRight_toLeftOf="@+id/card_message_me"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

### Nilai Warna Tambahan UI
<a name="chat-kotlin-messages-events-additional-color"></a>

**XML:**

```
// ./app/src/main/res/values/colors.xml
 
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--    ...-->
    <color name="dark_gray">#4F4F4F</color>
    <color name="blue">#186ED3</color>
    <color name="dark_red">#b30000</color>
    <color name="light_gray">#B7B7B7</color>
    <color name="light_gray_2">#eef1f6</color>
</resources>
```

## Menerapkan Ikatan Tampilan
<a name="chat-kotlin-messages-events-apply-view-binding"></a>

Kami memanfaatkan fitur [Ikatan Tampilan](https://developer.android.com/topic/libraries/view-binding) Android untuk dapat mereferensikan kelas ikatan pada tata letak XML. Untuk mengaktifkan fitur tersebut, atur opsi build `viewBinding` ke `true` pada `./app/build.gradle`:

**Skrip Kotlin:**

```
 // ./app/build.gradle

android {
//    ...

    buildFeatures {
        viewBinding = true
    }
//    ...
}
```

Sekarang saatnya menghubungkan UI dengan kode Kotlin:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
    // ...
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Create room instance
        room = ChatRoom(REGION, ::fetchChatToken).apply {
            // ...
        }

        binding.sendButton.setOnClickListener(::sendButtonClick)
        binding.connectButton.setOnClickListener {connect()}

        setUpChatView()

        updateConnectionState(ChatRoom.State.DISCONNECTED)
    }

    private fun sendMessage(request: SendMessageRequest) {
        lifecycleScope.launch {
           try {
               binding.messageEditText.text.clear()
               room?.awaitSendMessage(request)
           } catch (exception: ChatException) {
               Log.e(TAG, "Message rejected: ${exception.message}")
           } catch (exception: Exception) {
               Log.e(TAG, exception.message ?: "Unknown error occurred")
           }
        }
    }

    private fun sendButtonClick(view: View) {
        val content = binding.messageEditText.text.toString()
        if (content.trim().isEmpty()) {
            return
        }

        val request = SendMessageRequest(content)
        sendMessage(request)
    }
// ...

}
```

Kita juga menambahkan metode untuk menghapus pesan dan memutus koneksi pengguna dari obrolan, yang dapat diinvokasi menggunakan menu konteks pesan obrolan:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
//    ...

class MainActivity : AppCompatActivity() {
//    ...

    private fun deleteMessage(request: DeleteMessageRequest) {
        lifecycleScope.launch {
           try {
               room?.awaitDeleteMessage(request)
           } catch (exception: ChatException) {
               Log.e(TAG, "Delete message rejected: ${exception.message}")
           } catch (exception: Exception) {
               Log.e(TAG, exception.message ?: "Unknown error occurred")
           }
        }
    }

    private fun disconnectUser(request: DisconnectUserRequest) {
        lifecycleScope.launch {
           try {
               room?.awaitDisconnectUser(request)
           } catch (exception: ChatException) {
               Log.e(TAG, "Disconnect user rejected: ${exception.message}")
           } catch (exception: Exception) {
               Log.e(TAG, exception.message ?: "Unknown error occurred")
           }
        }
    }
}
```

## Mengelola Permintaan Pesan-Obrolan
<a name="chat-kotlin-messages-events-chat-message"></a>

Kami membutuhkan cara untuk mengelola permintaan pesan-obrolan melalui semua kemungkinan statusnya:
+ Tertunda — Pesan telah dikirim ke ruang obrolan, tetapi belum dikonfirmasi atau ditolak.
+ Dikonfirmasi — Pesan dikirim oleh ruang obrolan ke semua pengguna (termasuk kita).
+ Ditolak — Pesan ditolak oleh ruang obrolan dengan objek kesalahan.

Kita akan menyimpan permintaan obrolan dan pesan obrolan yang belum terselesaikan dalam suatu [daftar](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/mutable-list-of.html). Daftar ini layak mendapat kelas terpisah, yang kita sebut `ChatEntries.kt`:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/ChatEntries.kt

package com.chatterbox.myapp

import com.amazonaws.ivs.chat.messaging.entities.ChatMessage
import com.amazonaws.ivs.chat.messaging.requests.SendMessageRequest

sealed class ChatEntry() {
    class Message(val message: ChatMessage) : ChatEntry()
    class PendingRequest(val request: SendMessageRequest) : ChatEntry()
    class FailedRequest(val request: SendMessageRequest) : ChatEntry()
}

class ChatEntries {
    /* This list is kept in sorted order. ChatMessages are sorted by date, while pending and failed requests are kept in their original insertion point. */
    val entries = mutableListOf<ChatEntry>()
    var adapter: ChatListAdapter? = null

    val size get() = entries.size

    /**
     * Insert pending request at the end.
     */
    fun addPendingRequest(request: SendMessageRequest) {
        val insertIndex = entries.size
        entries.add(insertIndex, ChatEntry.PendingRequest(request))
        adapter?.notifyItemInserted(insertIndex)
    }

    /**
     * Insert received message at proper place based on sendTime. This can cause removal of pending requests.
     */
    fun addReceivedMessage(message: ChatMessage) {
        /* Skip if we have already handled that message. */
        val existingIndex = entries.indexOfLast { it is ChatEntry.Message && it.message.id == message.id }
        if (existingIndex != -1) {
            return
        }

        val removeIndex = entries.indexOfLast {
            it is ChatEntry.PendingRequest && it.request.requestId == message.requestId
        }
        if (removeIndex != -1) {
            entries.removeAt(removeIndex)
        }

        val insertIndexRaw = entries.indexOfFirst { it is ChatEntry.Message && it.message.sendTime > message.sendTime }
        val insertIndex = if (insertIndexRaw == -1) entries.size else insertIndexRaw
        entries.add(insertIndex, ChatEntry.Message(message))

        if (removeIndex == -1) {
            adapter?.notifyItemInserted(insertIndex)
        } else if (removeIndex == insertIndex) {
            adapter?.notifyItemChanged(insertIndex)
        } else {
            adapter?.notifyItemRemoved(removeIndex)
            adapter?.notifyItemInserted(insertIndex)
        }
    }

    fun addFailedRequest(request: SendMessageRequest) {
        val removeIndex = entries.indexOfLast {
            it is ChatEntry.PendingRequest && it.request.requestId == request.requestId
        }
        if (removeIndex != -1) {
            entries.removeAt(removeIndex)
            entries.add(removeIndex, ChatEntry.FailedRequest(request))
            adapter?.notifyItemChanged(removeIndex)
        } else {
            val insertIndex = entries.size
            entries.add(insertIndex, ChatEntry.FailedRequest(request))
            adapter?.notifyItemInserted(insertIndex)
        }
    }

    fun removeMessage(messageId: String) {
        val removeIndex = entries.indexOfFirst { it is ChatEntry.Message && it.message.id == messageId }
        entries.removeAt(removeIndex)
        adapter?.notifyItemRemoved(removeIndex)
    }

    fun removeFailedRequest(requestId: String) {
        val removeIndex = entries.indexOfFirst { it is ChatEntry.FailedRequest && it.request.requestId == requestId }
        entries.removeAt(removeIndex)
        adapter?.notifyItemRemoved(removeIndex)
    }

    fun removeAll() {
        entries.clear()
    }
}
```

Untuk menghubungkan daftar dengan UI, kita menggunakan [Adaptor](https://developer.android.com/reference/android/widget/Adapter). Untuk informasi selengkapnya, lihat [Mengikat ke Data dengan AdapterView](https://developer.android.com/develop/ui/views/layout/binding) dan [kelas pengikatan yang dihasilkan](https://developer.android.com/topic/libraries/data-binding/generated-binding).

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/ChatListAdapter.kt

package com.chatterbox.myapp

import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.recyclerview.widget.RecyclerView
import com.amazonaws.ivs.chat.messaging.requests.DisconnectUserRequest
import java.text.DateFormat


class ChatListAdapter(
    private val entries: ChatEntries,
    private val onDisconnectUser: (request: DisconnectUserRequest) -> Unit,
) :
    RecyclerView.Adapter<ChatListAdapter.ViewHolder>() {
    var context: Context? = null
    var userId: String? = null

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val container: LinearLayout = view.findViewById(R.id.layout_container)
        val textView: TextView = view.findViewById(R.id.card_message_me_text_view)
        val failedMark: TextView = view.findViewById(R.id.failed_mark)
        val userNameText: TextView? = view.findViewById(R.id.username_edit_text)
        val dateText: TextView? = view.findViewById(R.id.dateText)
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        if (viewType == 0) {
            val rightView = LayoutInflater.from(viewGroup.context).inflate(R.layout.card_view_right, viewGroup, false)
            return ViewHolder(rightView)
        }
        val leftView = LayoutInflater.from(viewGroup.context).inflate(R.layout.card_view_left, viewGroup, false)
        return ViewHolder(leftView)
    }

    override fun getItemViewType(position: Int): Int {
        // Int 0 indicates to my message while Int 1 to other message
        val chatMessage = entries.entries[position]
        return if (chatMessage is ChatEntry.Message && chatMessage.message.sender.userId != userId) 1 else 0
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        return when (val entry = entries.entries[position]) {
            is ChatEntry.Message -> {
                viewHolder.textView.text = entry.message.content

                val bgColor = if (entry.message.sender.userId == userId) {
                    R.color.purple_500
                } else {
                    R.color.light_gray_2
                }
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, bgColor))

                if (entry.message.sender.userId != userId) {
                    viewHolder.textView.setTextColor(Color.parseColor("#000000"))
                }

                viewHolder.failedMark.isGone = true

                viewHolder.itemView.setOnCreateContextMenuListener { menu, _, _ ->
                    menu.add("Kick out").setOnMenuItemClickListener {
                        val request = DisconnectUserRequest(entry.message.sender.userId, "Some reason")
                        onDisconnectUser(request)
                        true
                    }
                }

                viewHolder.userNameText?.text = entry.message.sender.userId
                viewHolder.dateText?.text =
                    DateFormat.getTimeInstance(DateFormat.SHORT).format(entry.message.sendTime)
            }

            is ChatEntry.PendingRequest -> {
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, R.color.light_gray))
                viewHolder.textView.text = entry.request.content
                viewHolder.failedMark.isGone = true
                viewHolder.itemView.setOnCreateContextMenuListener(null)
                viewHolder.dateText?.text = "Sending"
            }

            is ChatEntry.FailedRequest -> {
                viewHolder.textView.text = entry.request.content
                viewHolder.container.setBackgroundColor(ContextCompat.getColor(context!!, R.color.dark_red))
                viewHolder.failedMark.isGone = false
                viewHolder.dateText?.text = "Failed"
            }
        }
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        context = recyclerView.context
    }

    override fun getItemCount() = entries.entries.size
}
```

## Langkah Terakhir
<a name="chat-kotlin-messages-events-final-steps"></a>

Saatnya menghubungkan adaptor baru kita, dengan mengikat kelas `ChatEntries` ke `MainActivity`:

**Kotlin**:

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

import com.chatterbox.myapp.databinding.ActivityMainBinding
import com.chatterbox.myapp.ChatListAdapter
import com.chatterbox.myapp.ChatEntries

class MainActivity : AppCompatActivity() {
    // ...
    private var entries = ChatEntries()
    private lateinit var adapter: ChatListAdapter

    // ...

    private fun setUpChatView() {
        adapter = ChatListAdapter(entries, ::disconnectUser)
        entries.adapter = adapter

        val recyclerViewLayoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.VERTICAL, false)
        binding.recyclerView.layoutManager = recyclerViewLayoutManager
        binding.recyclerView.adapter = adapter

        binding.sendButton.setOnClickListener(::sendButtonClick)
        binding.messageEditText.setOnEditorActionListener { _, _, event ->
            val isEnterDown = (event.action == KeyEvent.ACTION_DOWN) && (event.keyCode == KeyEvent.KEYCODE_ENTER)
            if (!isEnterDown) {
                return@setOnEditorActionListener false
            }

            sendButtonClick(binding.sendButton)
            return@setOnEditorActionListener true
        }
    }
}
```

Karena kami sudah memiliki kelas yang bertanggung jawab untuk melacak permintaan obrolan (`ChatEntries`), kami siap mengimplementasikan kode untuk memanipulasi `entries` di roomListener. Kita akan memperbarui `entries` dan `connectionState` sesuai dengan peristiwa yang kita tanggapi:

**Kotlin:**

```
// ./app/src/main/java/com/chatterbox/myapp/MainActivity.kt

package com.chatterbox.myapp
// ...

class MainActivity : AppCompatActivity() {
// ...


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Create room instance
        room = ChatRoom(REGION, ::fetchChatToken).apply {
            lifecycleScope.launch {
                stateChanges().collect { state ->
                    Log.d(TAG, "state change to $state")
                    updateConnectionState(state)
                    if (state == ChatRoom.State.DISCONNECTED) {
                       entries.removeAll()
                    }
                }
            }

            lifecycleScope.launch {
                receivedMessages().collect { message ->
                    Log.d(TAG, "messageReceived $message")
                    entries.addReceivedMessage(message)
                }
            }

            lifecycleScope.launch {
                receivedEvents().collect { event ->
                    Log.d(TAG, "eventReceived $event")
                }
            }

            lifecycleScope.launch {
                deletedMessages().collect { event ->
                    Log.d(TAG, "messageDeleted $event")
                    entries.removeMessage(event.messageId)
                }
            }

            lifecycleScope.launch {
                disconnectedUsers().collect { event ->
                    Log.d(TAG, "userDisconnected $event")
                }
            }
        }

        binding.sendButton.setOnClickListener(::sendButtonClick)
        binding.connectButton.setOnClickListener {connect()}

        setUpChatView()

        updateConnectionState(ChatRoom.State.DISCONNECTED)
    }

// ...

}
```

Sekarang Anda seharusnya dapat menjalankan aplikasi\$1 (Lihat [Membangun dan menjalankan aplikasi Anda](https://developer.android.com/studio/run#basic-build-run).) Ingatlah untuk menjalankan server backend Anda saat menggunakan aplikasi. Anda dapat memutarnya dari terminal di root proyek kita dengan perintah ini: `./gradlew :auth-server:run` atau dengan menjalankan tugas Gradle `auth-server:run` langsung dari Android Studio.

# SDK Pesan Klien Obrolan IVS: Panduan iOS
<a name="chat-sdk-ios"></a>

SDK iOS Perpesanan Klien Obrolan Amazon Interactive Video (IVS) menyediakan antarmuka yang memungkinkan Anda untuk menggabungkan [API Perpesanan Obrolan IVS](https://docs.aws.amazon.com//ivs/latest/chatmsgapireference/welcome.html) kami di platform yang menggunakan [bahasa pemrograman Swift](https://developer.apple.com/swift/) Apple.

**Versi terbaru dari IVS Chat Client Messaging iOS SDK:** [1.0.1 (Catatan Rilis)](https://docs.aws.amazon.com//ivs/latest/ChatUserGuide/release-notes.html#aug08-25)

**Dokumentasi dan tutorial referensi:** Untuk informasi tentang metode terpenting yang tersedia di Amazon IVS Chat Client Messaging iOS SDK, lihat dokumentasi referensi di: [https://aws.github.io/ amazon-ivs-chat-messaging](https://aws.github.io/amazon-ivs-chat-messaging-sdk-ios/1.0.1/) -sdk-ios/1.0.1/. Repositori ini juga berisi berbagai artikel dan tutorial.

**Contoh kode:** [Lihat contoh repositori iOS di GitHub: https://github.com/aws-samples/ amazon-ivs-chat-for -ios-demo.](https://github.com/aws-samples/amazon-ivs-chat-for-ios-demo)

**Persyaratan platform:** iOS 13.0 atau lebih tinggi diperlukan untuk pengembangan.

# Memulai dengan IVS Chat Client Messaging iOS SDK
<a name="chat-ios-getting-started"></a>

Kami menyarankan Anda untuk mengintegrasikan SDK melalui [Manajer Paket Swift](#chat-ios-install-sdk-swiftpm). Atau, Anda dapat [mengintegrasikan kerangka kerja secara manual](#chat-ios-install-sdk-manual).

Setelah mengintegrasikan SDK, Anda dapat mengimpor SDK dengan menambahkan kode berikut di bagian atas file Swift yang relevan:

```
import AmazonIVSChatMessaging
```

## Manajer Paket Swift
<a name="chat-ios-install-sdk-swiftpm"></a>

Untuk menggunakan pustaka `AmazonIVSChatMessaging` dalam proyek Manajer Paket Swift, tambahkan pustaka tersebut ke dependensi untuk paket Anda dan dependensi untuk target yang relevan:

1. Unduh yang terbaru `.xcframework` dari [https://ivschat.live-video.net/1.0.1/Amazon IVSChat Messaging.XCFramework.zip](https://ivschat.live-video.net/1.0.1/AmazonIVSChatMessaging.xcframework.zip).

1. Di Terminal Anda, jalankan:

   ```
   shasum -a 256 path/to/downloaded/AmazonIVSChatMessaging.xcframework.zip
   ```

1. Ambil output dari langkah sebelumnya dan tempelkan ke properti checksum `.binaryTarget` seperti yang ditunjukkan di bawah ini dalam file `Package.swift` proyek Anda:

   ```
   let package = Package(
      // name, platforms, products, etc.
      dependencies: [
         // other dependencies
      ],
      targets: [
         .target(
            name: "<target-name>",
            dependencies: [
               // If you want to only bring in the SDK
               .binaryTarget(
                  name: "AmazonIVSChatMessaging",
                  url: "https://ivschat.live-video.net/1.0.1/AmazonIVSChatMessaging.xcframework.zip",
                  checksum: "<SHA-extracted-using-steps-detailed-above>"
               ),
               // your other dependencies
            ],
         ),
         // other targets
      ]
   )
   ```

## Instalasi Manual
<a name="chat-ios-install-sdk-manual"></a>

1. Unduh versi terbaru dari [https://ivschat.live-video.net/1.0.1/Amazon IVSChat Messaging.XCFramework.zip](https://ivschat.live-video.net/1.0.1/AmazonIVSChatMessaging.xcframework.zip).

1. Ekstrak konten arsip. `AmazonIVSChatMessaging.xcframework` berisi SDK untuk perangkat dan simulator.

1. Sematkan `AmazonIVSChatMessaging.xcframework` yang telah diekstrak dengan menyeretnya ke bagian **Kerangka Kerja, Pustaka, dan Konten Tersemat** pada tab **Umum** untuk target aplikasi Anda:  
![\[Bagian Kerangka Kerja, Pustaka, dan Konten Tersemat pada tab Umum untuk target aplikasi Anda.\]](http://docs.aws.amazon.com/id_id/ivs/latest/ChatUserGuide/images/Chat_SDK_iOS_Manual_Installation.png)

# Menggunakan IVS Chat Client Messaging iOS SDK
<a name="chat-ios-using-sdk"></a>

Dokumen ini akan membawa Anda melalui langkah-langkah yang terlibat dalam menggunakan Amazon IVS chat client messaging iOS SDK.

## Menghubungkan ke Ruang Obrolan
<a name="chat-ios-connect-room"></a>

Sebelum memulai, Anda harus memahami cara [Memulai Obrolan Amazon IVS](getting-started-chat.md). Lihat juga contoh aplikasi untuk [Web](https://github.com/aws-samples/amazon-ivs-chat-web-demo), [Android](https://github.com/aws-samples/amazon-ivs-chat-for-android-demo), dan [iOS](https://github.com/aws-samples/amazon-ivs-chat-for-ios-demo).

Agar dapat terhubung ke ruang obrolan, aplikasi Anda memerlukan beberapa cara pengambilan token obrolan yang disediakan oleh backend. Aplikasi Anda mungkin akan mengambil token obrolan menggunakan permintaan jaringan ke backend Anda.

Untuk mengomunikasikan token obrolan yang diambil dengan SDK, model `ChatRoom` SDK mewajibkan Anda untuk menyediakan fungsi `async` atau instans objek yang sesuai dengan protokol `ChatTokenProvider` yang disediakan pada titik inisialisasi. Nilai yang dikembalikan oleh salah satu metode ini harus merupakan instans dari model `ChatToken` SDK.

**Catatan:** Anda mengisi instans model `ChatToken` menggunakan data yang diambil dari backend. Bidang yang diperlukan untuk menginisialisasi `ChatToken` instance sama dengan bidang dalam [CreateChatToken](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html)respons. Untuk informasi selengkapnya tentang menginisialisasi instance `ChatToken` model, lihat [Membuat instance dari](#chat-ios-create-chattoken). ChatToken Ingat, *backend Anda* bertanggung jawab untuk menyediakan data dalam respons `CreateChatToken` terhadap aplikasi. Cara Anda memutuskan untuk berkomunikasi dengan backend guna menghasilkan token obrolan bergantung pada aplikasi dan infrastrukturnya.

Setelah memilih strategi untuk menyediakan `ChatToken` untuk SDK, panggil `.connect()` setelah berhasil menginisialisasi instans `ChatRoom` dengan penyedia token Anda dan *wilayah AWS* yang digunakan backend untuk membuat ruang obrolan yang Anda coba hubungkan. Perhatikan bahwa `.connect()` adalah fungsi asikron lemparan:

```
import AmazonIVSChatMessaging

let room = ChatRoom(
   awsRegion: <region-your-backend-created-the-chat-room-in>,
   tokenProvider: <your-chosen-token-provider-strategy>
)
try await room.connect()
```

### Sesuai dengan Protokol ChatTokenProvider
<a name="chat-ios-chattokenprovider-protocol"></a>

Untuk parameter `tokenProvider` dalam penginisialisasi untuk `ChatRoom`, Anda dapat menyediakan instans `ChatTokenProvider`. Berikut adalah contoh dari objek yang sesuai dengan `ChatTokenProvider`:

```
import AmazonIVSChatMessaging

// This object should exist somewhere in your app
class ChatService: ChatTokenProvider {
   func getChatToken() async throws -> ChatToken {
      let request = YourApp.getTokenURLRequest
      let data = try await URLSession.shared.data(for: request).0
      ...
      return ChatToken(
         token: String(data: data, using: .utf8)!,
         tokenExpirationTime: ..., // this is optional
         sessionExpirationTime: ... // this is optional
      )
   }
}
```

Anda kemudian dapat mengambil instans dari objek yang sesuai ini dan memberikannya ke penginisialisasi untuk `ChatRoom`:

```
// This should be the same AWS Region that you used to create
// your Chat Room in the Control Plane
let awsRegion = "us-west-2"
let service = ChatService()
let room = ChatRoom(
   awsRegion: awsRegion,
   tokenProvider: service
)
try await room.connect()
```

### Menyediakan Fungsi asinkron di Swift
<a name="chat-ios-retrievechattoken-async-function"></a>

Misalkan Anda sudah memiliki manajer yang digunakan untuk mengelola permintaan jaringan aplikasi Anda. Manajer tersebut mungkin akan terlihat seperti ini:

```
import AmazonIVSChatMessaging

class EndpointManager {
   func getAccounts() async -> AppUser {...}
   func signIn(user: AppUser) async {...}
   ...
}
```

Anda hanya dapat menambahkan fungsi lain di manajer untuk mengambil `ChatToken` dari backend:

```
import AmazonIVSChatMessaging

class EndpointManager {
   ...
   func retrieveChatToken() async -> ChatToken {...}
}
```

Kemudian, gunakan referensi untuk fungsi tersebut di Swift saat menginisialisasi `ChatRoom`:

```
import AmazonIVSChatMessaging

let endpointManager: EndpointManager
let room = ChatRoom(
   awsRegion: endpointManager.awsRegion,
   tokenProvider: endpointManager.retrieveChatToken
)
try await room.connect()
```

## Buat sebuah Instance dari ChatToken
<a name="chat-ios-create-chattoken"></a>

Anda dapat dengan mudah membuat instans `ChatToken` dengan menggunakan penginisialisasi yang disediakan di SDK. Lihat dokumentasi di `Token.swift` untuk mempelajari selengkapnya tentang properti di `ChatToken`. 

```
import AmazonIVSChatMessaging

let chatToken = ChatToken(
   token: <token-string-retrieved-from-your-backend>,
   tokenExpirationTime: nil, // this is optional
   sessionExpirationTime: nil // this is optional
)
```

### Menggunakan Decodable
<a name="chat-ios-create-chattoken-decodable"></a>

Jika, saat berinteraksi dengan IVS Chat API, backend Anda memutuskan untuk hanya meneruskan [CreateChatToken](https://docs.aws.amazon.com/ivs/latest/ChatAPIReference/API_CreateChatToken.html)respons ke aplikasi frontend Anda, Anda dapat memanfaatkan kesesuaian dengan protokol `ChatToken` Swift. `Decodable` Namun, ada hal yang harus diperhatikan.

Muatan respons `CreateChatToken` menggunakan string untuk tanggal yang diformat dengan menggunakan [standar ISO 8601 untuk stempel waktu internet](https://en.wikipedia.org/wiki/ISO_8601). Di Swift, biasanya [Anda perlu menyediakan](https://www.hackingwithswift.com/example-code/language/how-to-use-iso-8601-dates-with-jsondecoder-and-codable) `JSONDecoder.DateDecodingStrategy.iso8601` sebagai nilai untuk properti `JSONDecoder`, yaitu `.dateDecodingStrategy`. Namun, `CreateChatToken` menggunakan detik pecahan presisi tinggi dalam string-nya, dan ini tidak didukung oleh `JSONDecoder.DateDecodingStrategy.iso8601`.

Untuk kenyamanan Anda, SDK menyediakan ekstensi publik pada `JSONDecoder.DateDecodingStrategy` dengan strategi `.preciseISO8601` tambahan agar Anda dapat berhasil menggunakan `JSONDecoder` saat mendekode instans `ChatToken`:

```
import AmazonIVSChatMessaging

// The CreateChatToken data forwarded by your backend
let responseData: Data

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .preciseISO8601
let token = try decoder.decode(ChatToken.self, from: responseData)
```

## Memutus Koneksi dari Ruang Obrolan
<a name="chat-ios-disconnect-room"></a>

Untuk memutus koneksi secara manual dari instans `ChatRoom` yang berhasil Anda sambungkan, panggil `room.disconnect()`. Secara default, ruang obrolan secara otomatis memanggil fungsi ini saat tidak dialokasikan.

```
import AmazonIVSChatMessaging

let room = ChatRoom(...)
try await room.connect()

// Disconnect
room.disconnect()
```

## Menerima Pesan/Peristiwa Obrolan
<a name="chat-ios-receive-message"></a>

Untuk mengirim dan menerima pesan di ruang obrolan, Anda perlu menyediakan objek yang sesuai dengan protokol `ChatRoomDelegate`, setelah Anda berhasil menginisialisasi instans `ChatRoom` dan memanggil `room.connect()`. Berikut adalah contoh umum penggunaan `UIViewController`:

```
import AmazonIVSChatMessaging
import Foundation
import UIKit

class ViewController: UIViewController {
   let room: ChatRoom = ChatRoom(
      awsRegion: "us-west-2",
      tokenProvider: EndpointManager.shared
   )

   override func viewDidLoad() {
      super.viewDidLoad()
      Task { try await setUpChatRoom() }
   }

   private func setUpChatRoom() async throws {
      // Set the delegate to start getting notifications for room events
      room.delegate = self
      try await room.connect()
   }
}

extension ViewController: ChatRoomDelegate {
   func room(_ room: ChatRoom, didReceive message: ChatMessage) { ... }
   func room(_ room: ChatRoom, didReceive event: ChatEvent) { ... }
   func room(_ room: ChatRoom, didDelete message: DeletedMessageEvent) { ... }
}
```

## Mendapatkan Notifikasi saat Koneksi Berubah
<a name="chat-ios-room-connection-state"></a>

Seperti yang diperkirakan, Anda tidak dapat melakukan tindakan seperti mengirim pesan di suatu ruang sampai ruang terhubung sepenuhnya. Arsitektur SDK mencoba mendorong koneksi ke thread ChatRoom di latar belakang melalui APIs async. Jika Anda ingin membangun sesuatu di UI yang menonaktifkan sesuatu seperti tombol kirim pesan, SDK menyediakan dua strategi untuk mendapatkan notifikasi saat status koneksi ruang obrolan berubah, yaitu dengan menggunakan `Combine` atau `ChatRoomDelegate`. Hal ini dijelaskan di bawah ini.

**Penting:** Status koneksi ruang obrolan juga dapat berubah karena beberapa hal seperti koneksi jaringan yang terputus. Pertimbangkan hal ini saat membangun aplikasi Anda.

### Menggunakan Kombinasi
<a name="room-connection-state-combine"></a>

Setiap instans `ChatRoom` hadir dengan penerbit `Combine` sendiri dalam bentuk properti `state`:

```
import AmazonIVSChatMessaging
import Combine

var cancellables: Set<AnyCancellable> = []

let room = ChatRoom(...)
room.state.sink { state in
   switch state {
   case .connecting:
      let image = UIImage(named: "antenna.radiowaves.left.and.right")
      sendMessageButton.setImage(image, for: .normal)
      sendMessageButton.isEnabled = false
   case .connected:
      let image = UIImage(named: "paperplane.fill")
      sendMessageButton.setImage(image, for: .normal)
      sendMessageButton.isEnabled = true
   case .disconnected:
      let image = UIImage(named: "antenna.radiowaves.left.and.right.slash")
      sendMessageButton.setImage(image, for: .normal)
      sendMessageButton.isEnabled = false
   }
}.assign(to: &cancellables)

// Connect to `ChatRoom` on a background thread
Task(priority: .background) {
   try await room.connect()
}
```

### Menggunakan ChatRoomDelegate
<a name="room-connection-state-chatroomdelegate"></a>

Atau, gunakan fungsi opsional `roomDidConnect(_:)`, `roomIsConnecting(_:)`, dan `roomDidDisconnect(_:)` dalam objek yang sesuai dengan `ChatRoomDelegate`. Berikut adalah contoh penggunaan `UIViewController`:

```
import AmazonIVSChatMessaging
import Foundation
import UIKit

class ViewController: UIViewController {
   let room: ChatRoom = ChatRoom(
      awsRegion: "us-west-2",
      tokenProvider: EndpointManager.shared
   )

   override func viewDidLoad() {
      super.viewDidLoad()
      Task { try await setUpChatRoom() }
   }

   private func setUpChatRoom() async throws {
      // Set the delegate to start getting notifications for room events
      room.delegate = self
      try await room.connect()
   }
}

extension ViewController: ChatRoomDelegate {
   func roomDidConnect(_ room: ChatRoom) {
      print("room is connected!")
   }
   func roomIsConnecting(_ room: ChatRoom) {
      print("room is currently connecting or fetching a token")
   }
   func roomDidDisconnect(_ room: ChatRoom) {
      print("room disconnected!")
   }
}
```

## Melakukan Tindakan di Ruang Obrolan
<a name="chat-ios-room-actions"></a>

Pengguna yang berbeda memiliki kemampuan berbeda pula untuk tindakan yang dapat mereka lakukan di ruang obrolan; misalnya, mengirim pesan, menghapus pesan, atau memutus koneksi pengguna. Untuk melakukan salah satu tindakan ini, panggil `perform(request:)` pada `ChatRoom` yang terhubung, dengan memberikan instans dari salah satu objek `ChatRequest` yang disediakan di SDK. Permintaan yang didukung ada di `Request.swift`.

Beberapa tindakan yang dilakukan di ruang obrolan mewajibkan pengguna yang terhubung untuk memiliki kemampuan khusus yang diberikan kepada mereka saat aplikasi backend Anda memanggil `CreateChatToken`. SDK dirancang agar tidak dapat membedakan kemampuan pengguna yang terhubung. Oleh karena itu, sementara Anda dapat mencoba melakukan tindakan moderator dalam instans `ChatRoom` yang terhubung, API bidang kontrol pada akhirnya memutuskan apakah tindakan tersebut akan berhasil.

Semua tindakan yang melalui `room.perform(request:)` akan menunggu hingga ruang menerima instans model yang diharapkan (tipe yang dikaitkan dengan objek permintaan itu sendiri) yang cocok dengan `requestId` dari model yang diterima dan objek permintaan. Jika ada masalah dengan permintaan tersebut, `ChatRoom` selalu melemparkan kesalahan dalam bentuk `ChatError`. Definisi dari `ChatError` ada di `Error.swift`.

### Mengirim Pesan
<a name="room-action-send-message"></a>

Untuk mengirim pesan obrolan, gunakan instans `SendMessageRequest`:

```
import AmazonIVSChatMessaging

let room = ChatRoom(...)
try await room.connect()
try await room.perform(
   request: SendMessageRequest(
      content: "Release the Kraken!"
   )
)
```

Seperti yang disebutkan di atas, `room.perform(request:)` kembali setelah `ChatMessage` yang sesuai diterima oleh `ChatRoom`. Jika ada masalah dengan permintaan (seperti melebihi batas karakter pesan untuk suatu ruang), instans `ChatError` akan dilemparkan untuk meresponsnya. Anda kemudian dapat memunculkan informasi yang berguna ini di UI:

```
import AmazonIVSChatMessaging

do {
   let message = try await room.perform(
      request: SendMessageRequest(
         content: "Release the Kraken!"
      )
   )
   print(message.id)
} catch let error as ChatError {
   switch error.errorCode {
   case .invalidParameter:
      print("Exceeded the character limit!")
   case .tooManyRequests:
      print("Exceeded message request limit!")
   default:
      break
   }

   print(error.errorMessage)
}
```

### Menambahkan Metadata ke Pesan
<a name="room-action-message-metadata"></a>

Saat [mengirim pesan](#room-action-send-message), Anda dapat menambahkan metadata yang akan dikaitkan dengan pesan tersebut. `SendMessageRequest` memiliki properti `attributes`, yang dapat Anda gunakan untuk menginisialisasi permintaan. Data yang Anda lampirkan di sana dilampirkan pada pesan ketika orang lain menerima pesan tersebut di dalam ruang.

Berikut adalah contoh melampirkan data emote ke pesan yang sedang dikirim:

```
import AmazonIVSChatMessaging

let room = ChatRoom(...)
try await room.connect()
try await room.perform(
   request: SendMessageRequest(
      content: "Release the Kraken!",
      attributes: [
         "messageReplyId" : "<other-message-id>",
         "attached-emotes" : "krakenCry,krakenPoggers,krakenCheer"
      ]
   )
)
```

Penggunaan `attributes` dalam `SendMessageRequest` dapat sangat berguna untuk membangun fitur yang kompleks dalam produk obrolan Anda. Misalnya, seseorang dapat membangun fungsionalitas threading dengan menggunakan kamus atribut `[String : String]` di `SendMessageRequest`\$1

Muatan `attributes` sangat fleksibel dan kuat. Gunakan muatan tersebut untuk mendapatkan informasi tentang pesan yang tidak akan bisa Anda dapatkan dengan cara lain. Menggunakan atribut jauh lebih mudah daripada, misalnya, mengurai string pesan untuk mendapatkan informasi tentang hal-hal seperti emote.

### Menghapus Pesan
<a name="room-action-delete-message"></a>

Menghapus pesan obrolan sama seperti mengirim pesan. Gunakan fungsi `room.perform(request:)` pada `ChatRoom` untuk melakukan ini dengan membuat instans `DeleteMessageRequest`.

Agar dapat mengakses instans pesan Obrolan yang diterima sebelumnya dengan mudah, berikan nilai `message.id` ke penginisialisasi `DeleteMessageRequest`.

Atau, sediakan string alasan untuk `DeleteMessageRequest` agar Anda dapat memunculkannya di UI.

```
import AmazonIVSChatMessaging

let room = ChatRoom(...)
try await room.connect()
try await room.perform(
   request: DeleteMessageRequest(
      id: "<other-message-id-to-delete>",
      reason: "Abusive chat is not allowed!"
   )
)
```

Karena ini adalah tindakan moderator, pengguna Anda mungkin tidak benar-benar memiliki kemampuan untuk menghapus pesan pengguna lain. Anda dapat menggunakan mekanisme fungsi Swift yang dapat dilemparkan untuk memunculkan pesan kesalahan di UI saat pengguna mencoba menghapus pesan tanpa kemampuan yang sesuai.

Ketika backend Anda memanggil `CreateChatToken` untuk pengguna, backend harus meneruskan `"DELETE_MESSAGE"` ke bidang `capabilities` guna mengaktifkan fungsionalitas tersebut untuk pengguna obrolan yang terhubung.

Berikut adalah contoh tangkapan kesalahan kemampuan yang dilemparkan saat mencoba menghapus pesan tanpa izin yang sesuai:

```
import AmazonIVSChatMessaging

do {
   // `deleteEvent` is the same type as the object that gets sent to
   // `ChatRoomDelegate`'s `room(_:didDeleteMessage:)` function
   let deleteEvent = try await room.perform(
      request: DeleteMessageRequest(
         id: "<other-message-id-to-delete>",
         reason: "Abusive chat is not allowed!"
      )
   )
   dataSource.messages[deleteEvent.messageID] = nil
   tableView.reloadData()
} catch let error as ChatError {
   switch error.errorCode {
   case .forbidden:
      print("You cannot delete another user's messages. You need to be a mod to do that!")
   default:
      break
   }

   print(error.errorMessage)
}
```

### Memutus Koneksi Pengguna Lain
<a name="room-action-disconnect-user"></a>

Gunakan `room.perform(request:)` untuk memutus koneksi pengguna lain dari ruang obrolan. Secara khusus, gunakan instans `DisconnectUserRequest`. Semua `ChatMessage` yang diterima oleh `ChatRoom` memiliki properti `sender`, yang berisi ID pengguna yang harus Anda inisialisasi dengan instans `DisconnectUserRequest` secara tepat. Atau, sediakan string alasan untuk meminta pemutusan koneksi.

```
import AmazonIVSChatMessaging

let room = ChatRoom(...)
try await room.connect()

let message: ChatMessage = dataSource.messages["<message-id>"]
let sender: ChatUser = message.sender
let userID: String = sender.userId
let reason: String = "You've been disconnected due to abusive behavior"

try await room.perform(
   request: DisconnectUserRequest(
      id: userID,
      reason: reason
   )
)
```

Karena ini adalah contoh tindakan moderator yang lain, Anda dapat mencoba untuk memutus koneksi pengguna lain, tetapi tidak akan dapat melakukannya kecuali memiliki kemampuan `DISCONNECT_USER`. Kemampuan akan diatur ketika aplikasi backend Anda memanggil `CreateChatToken` dan memasukkan string `"DISCONNECT_USER"` ke bidang `capabilities`.

Jika pengguna Anda tidak memiliki kemampuan untuk memutus koneksi pengguna lain, `room.perform(request:)` akan melemparkan instans `ChatError`, seperti permintaan lainnya. Anda dapat memeriksa properti `errorCode` kesalahan untuk menentukan apakah permintaan gagal karena tidak mempunyai hak istimewa moderator:

```
import AmazonIVSChatMessaging

do {
   let message: ChatMessage = dataSource.messages["<message-id>"]
   let sender: ChatUser = message.sender
   let userID: String = sender.userId
   let reason: String = "You've been disconnected due to abusive behavior"

   try await room.perform(
      request: DisconnectUserRequest(
         id: userID,
         reason: reason
      )
   )
} catch let error as ChatError {
   switch error.errorCode {
   case .forbidden:
      print("You cannot disconnect another user. You need to be a mod to do that!")
   default:
      break
   }

   print(error.errorMessage)
}
```

# SDK Pesan Klien Obrolan IVS: Tutorial iOS
<a name="chat-sdk-ios-tutorial"></a>

 SDK iOS Perpesanan Klien Obrolan Amazon Interactive Video (IVS) menyediakan antarmuka untuk memungkinkan Anda untuk menggabungkan [API Perpesanan Obrolan IVS](https://docs.aws.amazon.com//ivs/latest/chatmsgapireference/welcome.html) kami di platform yang menggunakan [bahasa pemrograman Swift Apple](https://developer.apple.com/swift/). 

 Untuk tutorial Chat iOS SDK, lihat [https://aws.github. io/amazon-ivs-chat-messaging-sdk-ios/latest/tutorials/table-dari konten/](https://aws.github.io/amazon-ivs-chat-messaging-sdk-ios/latest/tutorials/table-of-contents/). 

# SDK Pesan Klien Obrolan IVS: Panduan JavaScript
<a name="chat-sdk-js"></a>



Amazon Interactive Video (IVS) Chat Client Messaging JavaScript SDK memungkinkan Anda untuk menggabungkan [https://docs.aws.amazon.com/ivs/latest/chatmsgapireference/welcome.html](https://docs.aws.amazon.com/ivs/latest/chatmsgapireference/welcome.html) kami pada platform menggunakan browser Web.

**Versi terbaru dari IVS Chat Client Messaging JavaScript SDK:** [1.0.2 (Catatan Rilis)](https://docs.aws.amazon.com//ivs/latest/ChatUserGuide/release-notes.html#nov09-22)

**Dokumentasi referensi:** [Untuk informasi tentang metode terpenting yang tersedia di Amazon IVS Chat Client Messaging JavaScript SDK, lihat dokumentasi referensi di: https://aws.github.io/ -sdk-js/1.0.2/ amazon-ivs-chat-messaging](https://aws.github.io/amazon-ivs-chat-messaging-sdk-js/1.0.2/)

**Kode contoh:** [Lihat repositori sampel aktif GitHub, untuk demo khusus Web menggunakan SDK: -demo JavaScript https://github.com/aws-samples/ amazon-ivs-chat-web](https://github.com/aws-samples/amazon-ivs-chat-web-demo)

# Memulai SDK Pesan JavaScript Klien Obrolan IVS
<a name="chat-js-getting-started"></a>

Sebelum memulai, Anda harus memahami cara [Memulai Obrolan Amazon IVS](getting-started-chat.md).

## Menambahkan Paket
<a name="chat-js-add-package"></a>

Gunakan:

```
$ npm install --save amazon-ivs-chat-messaging
```

atau:

```
$ yarn add amazon-ivs-chat-messaging
```

## Dukungan React Native
<a name="chat-js-react-native-support"></a>

IVS Chat Client Messaging JavaScript SDK memiliki `uuid` ketergantungan yang menggunakan metode ini. `crypto.getRandomValues` Karena metode ini tidak didukung di React Native, Anda perlu menginstal polyfill tambahan `react-native-get-random-value` dan mengimpornya di bagian atas file `index.js`:

```
import 'react-native-get-random-values';
import {AppRegistry} from 'react-native';
import App from './src/App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);
```

## Siapkan Backend Anda
<a name="chat-js-setup-backend"></a>

Integrasi ini memerlukan titik akhir di server Anda yang akan berbicara dengan [API Obrolan Amazon IVS](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/Welcome.html). Gunakan [pustaka AWS resmi](https://aws.amazon.com/developer/tools/) untuk mendapatkan akses ke API Amazon IVS dari server Anda. Pustaka ini dapat diakses dalam beberapa bahasa dari paket publik; misalnya, [node.js](https://www.npmjs.com/package/aws-sdk), [java](https://github.com/aws/aws-sdk-java), dan [go](https://github.com/aws/aws-sdk-go).

Buat endpoint server yang berbicara dengan [CreateChatToken](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html)operasi Amazon IVS Chat API, untuk membuat token obrolan bagi pengguna obrolan.

# Menggunakan SDK Pesan JavaScript Klien Obrolan IVS
<a name="chat-js-using-sdk"></a>

Dokumen ini akan membawa Anda melalui langkah-langkah yang terlibat dalam menggunakan JavaScript SDK perpesanan klien obrolan Amazon IVS.

## Menginisialisasi Instans Ruang Obrolan
<a name="chat-js-initialize-room"></a>

Buat instans dari kelas `ChatRoom`. Langkah tersebut memerlukan penerusan `regionOrUrl` (wilayah AWS tempat ruang obrolan Anda di-host) dan `tokenProvider` (metode pengambilan token akan dibuat pada langkah berikutnya): 

```
const room = new ChatRoom({
  regionOrUrl: 'us-west-2',
  tokenProvider: tokenProvider,
});
```

## Fungsi Penyedia Token
<a name="chat-js-token-provider-function"></a>

Buat fungsi penyedia token asinkron yang mengambil token obrolan dari backend Anda:

```
type ChatTokenProvider = () => Promise<ChatToken>;
```

Fungsi tersebut seharusnya tidak menerima parameter dan mengembalikan [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) yang berisi objek token obrolan:

```
type ChatToken = {
  token: string;
  sessionExpirationTime?: Date;
  tokenExpirationTime?: Date;
}
```

Fungsi ini diperlukan untuk [menginisialisasi ChatRoom objek](#chat-js-initialize-room). Di bawah ini, isi bidang `<token>` dan `<date-time>` dengan nilai yang diterima dari backend Anda:

```
// You will need to fetch a fresh token each time this method is called by
// the IVS Chat Messaging SDK, since each token is only accepted once.
function tokenProvider(): Promise<ChatToken> {
  // Call your backend to fetch chat token from IVS Chat endpoint: 
  // e.g. const token = await appBackend.getChatToken()
  return {
    token: "<token>",
    sessionExpirationTime: new Date("<date-time>"),
    tokenExpirationTime: new Date("<date-time>")
  }
}
```

 Ingatlah untuk meneruskan `tokenProvider` ke ChatRoom konstruktor. ChatRoom menyegarkan token saat koneksi terputus atau sesi kedaluwarsa. Jangan gunakan `tokenProvider` untuk menyimpan token di mana saja; ChatRoom menanganinya untuk Anda. 

## Menerima Peristiwa
<a name="chat-js-receive-event"></a>

Selanjutnya, berlangganan peristiwa ruang obrolan untuk menerima peristiwa siklus hidup, serta pesan dan peristiwa yang dikirimkan di ruang obrolan:

```
/**
* Called when room is establishing the initial connection or reestablishing
* connection after socket failure/token expiration/etc
*/
const unsubscribeOnConnecting = room.addListener('connecting', () => { });

/** Called when connection has been established. */
const unsubscribeOnConnected = room.addListener('connect', () => { });

/** Called when a room has been disconnected. */
const unsubscribeOnDisconnected = room.addListener('disconnect', () => { });

/** Called when a chat message has been received. */
const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
 /* Example message:
  * {
  *   id: "5OPsDdX18qcJ",
  *   sender: { userId: "user1" },
  *   content: "hello world",
  *   sendTime: new Date("2022-10-11T12:46:41.723Z"),
  *   requestId: "d1b511d8-d5ed-4346-b43f-49197c6e61de"
  * }
  */
});

/** Called when a chat event has been received. */
const unsubscribeOnEventReceived = room.addListener('event', (event) => {
 /* Example event:
  * {
  *   id: "5OPsDdX18qcJ",
  *   eventName: "customEvent,
  *   sendTime: new Date("2022-10-11T12:46:41.723Z"),
  *   requestId: "d1b511d8-d5ed-4346-b43f-49197c6e61de",
  *   attributes: { "Custom Attribute": "Custom Attribute Value" }
  * }
  */
});

/** Called when `aws:DELETE_MESSAGE` system event has been received. */
const unsubscribeOnMessageDelete = room.addListener('messageDelete', (deleteMessageEvent) => {
 /* Example delete message event:
  * {
  *   id: "AYk6xKitV4On",
  *   messageId: "R1BLTDN84zEO",
  *   reason: "Spam",
  *   sendTime: new Date("2022-10-11T12:56:41.113Z"),
  *   requestId: "b379050a-2324-497b-9604-575cb5a9c5cd",
  *   attributes: { MessageID: "R1BLTDN84zEO", Reason: "Spam" }
  * }
  */
});

/** Called when `aws:DISCONNECT_USER` system event has been received. */
const unsubscribeOnUserDisconnect = room.addListener('userDisconnect', (disconnectUserEvent) => {
 /* Example event payload:
  * {
  *   id: "AYk6xKitV4On",
  *   userId": "R1BLTDN84zEO",
  *   reason": "Spam",
  *   sendTime": new Date("2022-10-11T12:56:41.113Z"),
  *   requestId": "b379050a-2324-497b-9604-575cb5a9c5cd",
  *   attributes": { UserId: "R1BLTDN84zEO", Reason: "Spam" }
  * }
  */
});
```

## Hubungkan ke Ruang Obrolan
<a name="chat-js-connect-to-chat-room"></a>

Langkah terakhir dari inisialisasi dasar adalah menghubungkan ke ruang obrolan dengan membuat WebSocket koneksi. Untuk melakukan hal tersebut, cukup panggil metode `connect()` dalam instans ruang:

```
room.connect();
```

 SDK akan mencoba membuat koneksi ke ruang obrolan yang dienkode dalam token obrolan yang diterima dari server Anda. 

 Setelah Anda memanggil `connect()`, ruang akan beralih ke status `connecting` dan mengeluarkan peristiwa `connecting`. Ketika ruang berhasil terhubung, ruang tersebut beralih ke status `connected` dan memancarkan peristiwa `connect`. 

 Kegagalan koneksi mungkin terjadi karena masalah saat mengambil token atau saat menghubungkan ke WebSocket. Dalam hal ini, ruang mencoba untuk menyambung kembali secara otomatis hingga berapa kali yang ditunjukkan oleh parameter konstruktor `maxReconnectAttempts`. Selama upaya penyambungan kembali, ruang berada dalam status `connecting` dan tidak mengeluarkan peristiwa tambahan. Setelah upaya penyambungan kembali habis, ruang bertransisi ke status `disconnected` dan mengeluarkan peristiwa `disconnect` (dengan alasan pemutusan koneksi yang relevan). Dalam status `disconnected`, ruang tidak lagi mencoba terhubung; Anda harus memanggil `connect()` lagi untuk memicu proses koneksi. 

## Melakukan Tindakan di Ruang Obrolan
<a name="chat-js-room-actions"></a>

SDK Perpesanan Obrolan Amazon IVS menyediakan tindakan pengguna untuk mengirim pesan, menghapus pesan, dan memutus koneksi pengguna lain. Tindakan ini tersedia pada instans `ChatRoom`. Tindakan ini mengembalikan objek `Promise` yang memungkinkan Anda untuk menerima konfirmasi atau penolakan permintaan. 

### Mengirim Pesan
<a name="chat-js-room-actions-send-message"></a>

Untuk permintaan ini, Anda harus memiliki kapasitas `SEND_MESSAGE` yang dienkode dalam token obrolan Anda.

Untuk memicu permintaan kirim-pesan:

```
const request = new SendMessageRequest('Test Echo');
room.sendMessage(request);
```

Untuk mendapatkan konfirmasi atau penolakan permintaan, `await` janji yang dikembalikan atau gunakan metode `then()`:

```
try {
  const message = await room.sendMessage(request);
  // Message was successfully sent to chat room
} catch (error) {
  // Message request was rejected. Inspect the `error` parameter for details.
}
```

### Menghapus Pesan
<a name="chat-js-room-actions-delete-message"></a>

Untuk permintaan ini, Anda harus memiliki kapasitas `DELETE_MESSAGE` yang dienkode dalam token obrolan Anda.

Guna menghapus pesan untuk tujuan moderasi, panggil metode `deleteMessage()`: 

```
const request = new DeleteMessageRequest(messageId, 'Reason for deletion');
room.deleteMessage(request);
```

Untuk mendapatkan konfirmasi atau penolakan permintaan, `await` janji yang dikembalikan atau gunakan metode `then()`:

```
try {
  const deleteMessageEvent = await room.deleteMessage(request);
  // Message was successfully deleted from chat room
} catch (error) {
  // Delete message request was rejected. Inspect the `error` parameter for details.
}
```

### Memutus Koneksi Pengguna Lain
<a name="chat-js-room-actions-disconnect-user"></a>

Untuk permintaan ini, Anda harus memiliki kapasitas `DISCONNECT_USER` yang dienkode dalam token obrolan Anda.

Guna memutus koneksi pengguna lain untuk tujuan moderasi, panggil metode `disconnectUser()`: 

```
const request = new DisconnectUserRequest(userId, 'Reason for disconnecting user');
room.disconnectUser(request);
```

Untuk mendapatkan konfirmasi atau penolakan permintaan, `await` janji yang dikembalikan atau gunakan metode `then()`:

```
try {
  const disconnectUserEvent = await room.disconnectUser(request);
  // User was successfully disconnected from the chat room
} catch (error) {
  // Disconnect user request was rejected. Inspect the `error` parameter for details.
}
```

## Memutus Koneksi dari Ruang Obrolan
<a name="chat-js-disconnect-room"></a>

Untuk menutup koneksi Anda ke ruang obrolan, panggil metode `disconnect()` pada instans `room`:

```
room.disconnect();
```

Memanggil metode ini menyebabkan ruangan menutup yang mendasarinya WebSocket secara tertib. Instans ruang beralih ke status `disconnected` dan mengeluarkan peristiwa pemutusan koneksi, dengan alasan `disconnect` diatur menjadi `"clientDisconnect"`. 

# SDK Pesan Klien Obrolan IVS: JavaScript Tutorial Bagian 1: Ruang Obrolan
<a name="chat-sdk-js-tutorial-chat-rooms"></a>

Ini adalah bagian pertama dari tutorial dua bagian. Anda akan mempelajari hal-hal penting bekerja dengan Amazon IVS Chat Client Messaging JavaScript SDK dengan membangun aplikasi yang berfungsi penuh menggunakan/. JavaScript TypeScript Kami menyebut aplikasi itu *Chatterbox*.

Audiens yang dimaksudkan adalah para developer berpengalaman yang baru mengenal SDK Perpesanan Obrolan Amazon IVS. Anda harus merasa nyaman dengan bahasa JavaScript/TypeScript pemrograman dan perpustakaan React.

Untuk singkatnya, kita akan merujuk ke Amazon IVS Chat Client Messaging JavaScript SDK sebagai Chat JS SDK.

**Catatan**: Dalam beberapa kasus, contoh kode untuk JavaScript dan TypeScript identik, sehingga digabungkan.

Bagian pertama dari tutorial ini dipecah menjadi beberapa bagian:

1. [Mengatur Authentication/Authorization Server Lokal](#chat-js-rooms-auth-server)

1. [Buat Proyek Chatterbox](#chat-js-rooms-chatterbox)

1. [Menghubungkan ke Ruang Obrolan](#chat-js-rooms-connect)

1. [Membangun Penyedia Token](#chat-js-rooms-token-provider)

1. [Mengamati Pembaruan Koneksi](#chat-js-rooms-connection-state)

1. [Membuat Komponen Tombol Kirim](#chat-js-rooms-send-button)

1. [Buat Input Pesan](#chat-js-rooms-message-input)

1. [Langkah Berikutnya](#chat-js-rooms-next-steps)

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK untuk JavaScript ](https://aws.github.io/amazon-ivs-chat-messaging-sdk-js/latest/) Referensi (aktif). GitHub

## Prasyarat
<a name="chat-js-rooms-prerequisites"></a>
+ Jadilah akrab dengan JavaScript/TypeScript dan perpustakaan React. Jika Anda tidak terbiasa dengan React, pelajari dasar-dasar dalam [Tic-Tac-Toe Tutorial](https://react.dev/learn/tutorial-tic-tac-toe) ini.
+ Baca dan pahami [Memulai Obrolan Amazon IVS](getting-started-chat.md).
+ Buat pengguna AWS IAM dengan CreateRoom kemampuan CreateChatToken dan yang ditentukan dalam kebijakan IAM yang ada. (Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md)).
+ Pastikan secret/access kunci untuk pengguna ini disimpan dalam file kredensial AWS. Untuk instruksinya, lihat [Panduan Pengguna AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) (terutama [Pengaturan file konfigurasi dan kredensial](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)).
+ Buat ruang obrolan dan simpan ARN-nya. Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md). (Jika Anda tidak menyimpan ARN, Anda dapat mencarinya nanti dengan konsol atau API Obrolan.)
+ Instal lingkungan Node.js 14\$1 dengan NPM atau manajer paket Yarn.

## Mengatur Authentication/Authorization Server Lokal
<a name="chat-js-rooms-auth-server"></a>

Aplikasi backend Anda bertanggung jawab untuk membuat ruang obrolan dan menghasilkan token obrolan yang diperlukan untuk SDK JS Obrolan guna mengautentikasi dan mengotorisasi klien Anda untuk ruang obrolan Anda. Anda harus menggunakan backend milik sendiri karena Anda tidak dapat menyimpan kunci AWS dengan aman di aplikasi seluler; penyerang canggih dapat mengekstraknya dan mendapatkan akses ke akun AWS Anda.

Lihat [Buat Token Obrolan](getting-started-chat-auth.md) di *Memulai Obrolan Amazon IVS*. Seperti yang ditunjukkan pada diagram alur di sana, aplikasi sisi server Anda bertanggung jawab untuk membuat token obrolan. Hal ini berarti aplikasi Anda harus menyediakan caranya sendiri untuk menghasilkan token obrolan dengan memintanya dari aplikasi sisi server Anda.

Di bagian ini, Anda akan mempelajari dasar-dasar membuat penyedia token di backend Anda. Kami memanfaatkan kerangka kerja ekspres untuk membuat server lokal langsung yang mengelola pembuatan token obrolan menggunakan lingkungan AWS lokal Anda.

Buat proyek `npm` kosong menggunakan NPM. Buat direktori untuk menyimpan aplikasi Anda, dan jadikan sebagai direktori kerja Anda:

```
$ mkdir backend & cd backend
```

Gunakan `npm init` untuk membuat file `package.json` pada aplikasi Anda:

```
$ npm init
```

Perintah ini meminta beberapa hal pada Anda, termasuk nama dan versi aplikasi Anda. Saat ini, cukup tekan **KEMBALI** untuk menerima default sebagian besar perintah tersebut, dengan pengecualian berikut:

```
entry point: (index.js)
```

Tekan **KEMBALI** untuk menerima nama file default yang disarankan `index.js` atau masukkan nama apa pun yang Anda inginkan untuk file utama.

Sekarang instal dependensi yang diperlukan:

```
$ npm install express aws-sdk cors dotenv
```

`aws-sdk` memerlukan variabel lingkungan konfigurasi, yang memuat secara otomatis dari file bernama `.env` yang terletak di direktori root. Untuk mengonfigurasikannya, buat file baru bernama `.env` dan isi informasi konfigurasi yang hilang:

```
# .env

# The region to send service requests to.
AWS_REGION=us-west-2

# Access keys use an access key ID and secret access key
# that you use to sign programmatic requests to AWS.

# AWS access key ID.
AWS_ACCESS_KEY_ID=...

# AWS secret access key.
AWS_SECRET_ACCESS_KEY=...
```

Sekarang kita buat file titik masuk di direktori root dengan nama yang Anda masukkan di atas dalam perintah `npm init`. Dalam hal ini, kami menggunakan `index.js`, dan mengimpor semua paket yang diperlukan:

```
// index.js
import express from 'express';
import AWS from 'aws-sdk';
import 'dotenv/config';
import cors from 'cors';
```

Sekarang, buat instans baru `express`:

```
const app = express();
const port = 3000;

app.use(express.json());
app.use(cors({ origin: ['http://127.0.0.1:5173'] }));
```

Setelah itu, Anda dapat membuat metode POST titik akhir pertama Anda untuk penyedia token. Ambil parameter yang diperlukan dari isi permintaan (`roomId`, `userId`, `capabilities`, dan `sessionDurationInMinutes`):

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};
});
```

Tambahkan validasi bidang yang wajib diisi:

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`' });
    return;
  }
});
```

Setelah menyiapkan metode POST, kita mengintegrasikan `createChatToken` dengan `aws-sdk` untuk fungsionalitas inti autentikasi/otorisasi:

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId || !capabilities) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`, `capabilities`' });
    return;
  }

  ivsChat.createChatToken({ roomIdentifier, userId, capabilities, sessionDurationInMinutes }, (error, data) => {
    if (error) {
      console.log(error);
      res.status(500).send(error.code);
    } else if (data.token) {
      const { token, sessionExpirationTime, tokenExpirationTime } = data;
      console.log(`Retrieved Chat Token: ${JSON.stringify(data, null, 2)}`);

      res.json({ token, sessionExpirationTime, tokenExpirationTime });
    }
  });
});
```

Di akhir file, tambahkan pendengar port untuk aplikasi `express` Anda:

```
app.listen(port, () => {
  console.log(`Backend listening on port ${port}`);
});
```

Sekarang Anda dapat menjalankan server dengan perintah berikut dari root proyek:

```
$ node index.js
```

**Tip**: Server ini menerima permintaan URL di https://localhost:3000.

## Buat Proyek Chatterbox
<a name="chat-js-rooms-chatterbox"></a>

Pertama, Anda buat proyek React yang disebut `chatterbox`. Jalankan perintah ini:

```
npx create-react-app chatterbox
```

Anda dapat mengintegrasikan SDK JS Perpesanan Klien Obrolan melalui [Manajer Paket Simpul](https://www.npmjs.com/) atau [Manajer Paket Yarn](https://yarnpkg.com/):
+ Npm: `npm install amazon-ivs-chat-messaging`
+ Yarn: `yarn add amazon-ivs-chat-messaging`

## Menghubungkan ke Ruang Obrolan
<a name="chat-js-rooms-connect"></a>

Di sini Anda membuat `ChatRoom` dan menghubungkannya menggunakan metode asinkron. Kelas `ChatRoom` mengelola koneksi pengguna Anda ke SDK JS Obrolan. Agar berhasil terhubung ke ruang obrolan, Anda harus menyediakan instans `ChatToken` dalam aplikasi React Anda.

Arahkan ke file `App` yang dibuat dalam proyek `chatterbox` default dan hapus semuanya di antara dua tanda `<div>`. Tidak memerlukan kode yang telah diisi sebelumnya. Saat ini, `App` kami cukup kosong.

```
// App.jsx / App.tsx

import * as React from 'react';

export default function App() {
  return <div>Hello!</div>;
}
```

Buat instans `ChatRoom` baru dan teruskan ke status menggunakan hook `useState`. Ini memerlukan `regionOrUrl` penerusan (wilayah AWS tempat ruang obrolan Anda di-host) dan `tokenProvider` (digunakan untuk authentication/authorization aliran backend yang dibuat pada langkah selanjutnya).

**Penting**: Anda harus menggunakan wilayah AWS yang sama dengan wilayah tempat Anda membuat ruang di [Memulai Obrolan Amazon IVS](getting-started-chat-create-room.md). API adalah layanan regional AWS. Untuk daftar wilayah yang didukung dan titik akhir layanan HTTPS Obrolan Amazon IVS, lihat halaman [wilayah Obrolan Amazon IVS](https://docs.aws.amazon.com/general/latest/gr/ivs.html#ivs_region).

```
// App.jsx / App.tsx

import React, { useState } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [room] = useState(() =>
    new ChatRoom({
      regionOrUrl: process.env.REGION as string,
      tokenProvider: () => {},
    }),
  );

  return <div>Hello!</div>;
}
```

## Membangun Penyedia Token
<a name="chat-js-rooms-token-provider"></a>

Sebagai langkah berikutnya, kita perlu membangun fungsi `tokenProvider` tanpa parameter yang diperlukan oleh konstruktor `ChatRoom`. Pertama, kita akan membuat fungsi `fetchChatToken` yang akan membuat permintaan POST ke aplikasi backend yang Anda siapkan di [Mengatur Authentication/Authorization Server Lokal](#chat-js-rooms-auth-server). Token obrolan berisi informasi yang diperlukan agar SDK berhasil membuat koneksi ruang obrolan. API Obrolan menggunakan token ini sebagai cara aman untuk memvalidasi identitas pengguna, kemampuan dalam ruang obrolan, dan durasi sesi.

Di navigator Proyek, buat TypeScript/JavaScript file baru bernama`fetchChatToken`. Bangun permintaan pengambilan ke aplikasi `backend` dan kembalikan objek `ChatToken` dari respons. Tambahkan properti isi permintaan yang diperlukan untuk membuat token obrolan. Gunakan aturan yang ditentukan untuk [Amazon Resource Names (ARNs)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html). Properti ini didokumentasikan dalam [CreateChatToken](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html#API_CreateChatToken_RequestBody)operasi.

**Catatan**: URL yang Anda gunakan di sini adalah URL yang sama dengan yang dibuat oleh server lokal Anda saat menjalankan aplikasi backend.

------
#### [ TypeScript ]

```
// fetchChatToken.ts

import { ChatToken } from 'amazon-ivs-chat-messaging';

type UserCapability = 'DELETE_MESSAGE' | 'DISCONNECT_USER' | 'SEND_MESSAGE';

export async function fetchChatToken(
  userId: string,
  capabilities: UserCapability[] = [],
  attributes?: Record<string, string>,
  sessionDurationInMinutes?: number,
): Promise<ChatToken> {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------
#### [ JavaScript ]

```
// fetchChatToken.js

export async function fetchChatToken(
  userId,
  capabilities = [],
  attributes,
  sessionDurationInMinutes) {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------

## Mengamati Pembaruan Koneksi
<a name="chat-js-rooms-connection-state"></a>

Bereaksi terhadap perubahan status koneksi ruang obrolan adalah bagian penting dalam membuat aplikasi obrolan. Mari kita mulai dengan berlangganan peristiwa yang relevan:

```
// App.jsx / App.tsx

import React, { useState, useEffect } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION as string,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {});
    const unsubscribeOnConnected = room.addListener('connect', () => {});
    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {});

    return () => {
      // Clean up subscriptions.
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

Selanjutnya, kita perlu menyediakan kemampuan untuk membaca status koneksi. Kita menggunakan hook `useState` untuk membuat beberapa status lokal di `App` dan mengatur status koneksi di dalam setiap pendengar.

------
#### [ TypeScript ]

```
// App.tsx

import React, { useState, useEffect } from 'react';
import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {  
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION as string,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setConnectionState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setConnectionState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setConnectionState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

------
#### [ JavaScript ]

```
// App.jsx

import React, { useState, useEffect } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );
  const [connectionState, setConnectionState] = useState('disconnected');

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setConnectionState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setConnectionState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setConnectionState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

------

Setelah berlangganan status koneksi, tampilkan status koneksi dan hubungkan ke ruang obrolan dengan menggunakan metode `room.connect` di dalam hook `useEffect`:

```
// App.jsx / App.tsx

// ...

useEffect(() => {
  const unsubscribeOnConnecting = room.addListener('connecting', () => {
    setConnectionState('connecting');
  });

  const unsubscribeOnConnected = room.addListener('connect', () => {
    setConnectionState('connected');
  });

  const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
    setConnectionState('disconnected');
  });

  room.connect();

  return () => {
    unsubscribeOnConnecting();
    unsubscribeOnConnected();
    unsubscribeOnDisconnected();
  };
}, [room]);

// ...

return (
  <div>
    <h4>Connection State: {connectionState}</h4>
  </div>
);

// ...
```

Anda telah berhasil mengimplementasikan koneksi ruang obrolan.

## Membuat Komponen Tombol Kirim
<a name="chat-js-rooms-send-button"></a>

Di bagian ini, Anda membuat tombol kirim yang memiliki desain berbeda untuk setiap status koneksi. Tombol kirim memfasilitasi pengiriman pesan di ruang obrolan. Ini juga berfungsi sebagai indikator visual whether/when pesan dapat dikirim; misalnya, dalam menghadapi koneksi terputus atau sesi obrolan yang kedaluwarsa.

Pertama, buat file baru di direktori `src` proyek Chatterbox Anda dan beri nama `SendButton`. Kemudian, buat komponen yang akan menampilkan tombol untuk aplikasi obrolan Anda. Ekspor `SendButton` Anda dan impor ke `App`. Di `<div></div>` yang kosong, tambahkan `<SendButton />`.

------
#### [ TypeScript ]

```
// SendButton.tsx

import React from 'react';

interface Props {
  onPress?: () => void;
  disabled?: boolean;
}

export const SendButton = ({ onPress, disabled }: Props) => {
  return (
    <button disabled={disabled} onClick={onPress}>
      Send
    </button>
  );
};

// App.tsx

import { SendButton } from './SendButton';

// ...

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton />
  </div>
);
```

------
#### [ JavaScript ]

```
// SendButton.jsx

import React from 'react';

export const SendButton = ({ onPress, disabled }) => {
  return (
    <button disabled={disabled} onClick={onPress}>
      Send
    </button>
  );
};

// App.jsx

import { SendButton } from './SendButton';

// ...

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton />
  </div>
);
```

------

Selanjutnya di `App`, tentukan fungsi bernama `onMessageSend` dan berikan fungsi tersebut ke properti `SendButton onPress`. Tentukan variabel lain bernama `isSendDisabled` (yang mencegah pengiriman pesan ketika ruang tidak terhubung) dan berikan variabel tersebut ke properti `SendButton disabled`.

```
// App.jsx / App.tsx

// ...

const onMessageSend = () => {};

const isSendDisabled = connectionState !== 'connected';

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);

// ...
```

## Buat Input Pesan
<a name="chat-js-rooms-message-input"></a>

Bilah pesan Chatterbox adalah komponen yang akan berinteraksi dengan Anda untuk mengirim pesan ke ruang obrolan. Biasanya, bilah ini berisi input teks untuk menulis pesan dan tombol untuk mengirim pesan Anda.

Untuk membuat komponen `MessageInput`, pertama buat file baru di direktori `src` dan beri nama `MessageInput`. Kemudian, buat komponen input terkontrol yang akan menampilkan input untuk aplikasi obrolan Anda. Ekspor `MessageInput` Anda dan impor ke `App` (di atas `<SendButton />`).

Buat status baru bernama `messageToSend` menggunakan hook `useState`, dengan string kosong sebagai nilai default-nya. Di bagian utama aplikasi Anda, berikan `messageToSend` ke `value` dari `MessageInput` dan berikan `setMessageToSend` ke properti `onMessageChange`:

------
#### [ TypeScript ]

```
// MessageInput.tsx

import * as React from 'react';

interface Props {
  value?: string;
  onValueChange?: (value: string) => void;
}

export const MessageInput = ({ value, onValueChange }: Props) => {
  return (
    <input type="text" value={value} onChange={(e) => onValueChange?.(e.target.value)} placeholder="Send a message" />
  );
};


// App.tsx

// ...  

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...

return (
  <div>
    <h4>Connection State: {connectionState}</h4>
    <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);
```

------
#### [ JavaScript ]

```
// MessageInput.jsx

import * as React from 'react';

export const MessageInput = ({ value, onValueChange }) => {
  return (
    <input type="text" value={value} onChange={(e) => onValueChange?.(e.target.value)} placeholder="Send a message" />
  );
};

// App.jsx

// ...  

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...


return (
  <div>
    <h4>Connection State: {connectionState}</h4>
    <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);
```

------

## Langkah Berikutnya
<a name="chat-js-rooms-next-steps"></a>

Sekarang setelah Anda selesai membangun bilah pesan untuk Chatterbox, lanjutkan ke Bagian 2 dari JavaScript tutorial ini, [Pesan dan Acara](chat-sdk-js-tutorial-messages-events.md).

# SDK Pesan Klien Obrolan IVS: JavaScript Tutorial Bagian 2: Pesan dan Acara
<a name="chat-sdk-js-tutorial-messages-events"></a>

Bagian kedua (dan terakhir) dari tutorial ini dipecah menjadi beberapa bagian:

1. [Berlangganan Peristiwa Pesan Obrolan](#chat-js-messages-events-subscribe)

1. [Menampilkan Pesan yang Diterima](#chat-js-messages-events-show)

   1.  [Membuat Komponen Pesan](#chat-js-messages-create-component)

   1. [Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini](#chat-js-messages-recognize)

   1. [Membuat Komponen Daftar Pesan](#chat-js-messages-create-list-component)

   1. [Me-render Daftar Pesan Obrolan](#chat-js-messages-render-list)

1. [Melakukan Tindakan di Ruang Obrolan](#chat-js-messages-events-room-actions)

   1. [Mengirim Pesan](#chat-js-room-actions-sending-message)

   1. [Menghapus Pesan](#chat-js-room-actions-deleting-message)

1. [Langkah Berikutnya](#chat-js-messages-events-next-steps)

**Catatan**: Dalam beberapa kasus, contoh kode untuk JavaScript dan TypeScript identik, sehingga digabungkan.

Untuk dokumentasi SDK lengkap, mulailah dengan [Amazon IVS Chat Client Messaging SDK](chat-sdk.md) (di sini, di *Panduan Pengguna Obrolan Amazon IVS*) dan [Pesan Klien Obrolan: SDK untuk JavaScript ](https://aws.github.io/amazon-ivs-chat-messaging-sdk-js/latest/) Referensi (aktif). GitHub

## Prasyarat
<a name="chat-js-messages-events-prerequisite"></a>

Pastikan Anda telah menyelesaikan Bagian 1 dari tutorial ini, [Ruang Obrolan](chat-sdk-js-tutorial-chat-rooms.md).

## Berlangganan Peristiwa Pesan Obrolan
<a name="chat-js-messages-events-subscribe"></a>

Instans `ChatRoom` menggunakan peristiwa untuk berkomunikasi ketika peristiwa terjadi di ruang obrolan. Untuk mulai mengimplementasikan pengalaman obrolan, Anda harus menunjukkan kepada pengguna saat orang lain mengirim pesan di ruang yang terhubung dengan mereka.

Di sini, Anda berlangganan peristiwa pesan obrolan. Selanjutnya, kami akan menunjukkan cara memperbarui daftar pesan yang Anda buat, yang diperbarui dengan setiap pesan/peristiwa.

Di `App` Anda, di dalam hook `useEffect`, berlangganan semua peristiwa pesan:

```
// App.tsx / App.jsx

useEffect(() => {
  // ...
  const unsubscribeOnMessageReceived = room.addListener('message', (message) => {});

  return () => {
    // ...
    unsubscribeOnMessageReceived();
  };
}, []);
```

## Menampilkan Pesan yang Diterima
<a name="chat-js-messages-events-show"></a>

Menerima pesan adalah bagian inti dari pengalaman obrolan. Dengan SDK JS Obrolan, Anda dapat menyiapkan kode agar dapat dengan mudah menerima peristiwa dari pengguna lain yang terhubung ke ruang obrolan.

Selanjutnya, kami akan menunjukkan cara melakukan tindakan di ruang obrolan yang memanfaatkan komponen yang Anda buat di sini.

Di `App` Anda, tentukan status bernama `messages` dengan tipe array `ChatMessage` yang bernama `messages`:

------
#### [ TypeScript ]

```
// App.tsx

// ...

import { ChatRoom, ChatMessage, ConnectionState } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);

  //...
}
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

export default function App() {
  const [messages, setMessages] = useState([]);

  //...
}
```

------

Selanjutnya, di fungsi pendengar `message`, tambahkan `message` ke array `messages`:

```
// App.jsx / App.tsx

// ...

const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
  setMessages((msgs) => [...msgs, message]);
});

// ...
```

Di bawah ini, kita akan menjalankan langkah demi langkah untuk menampilkan pesan yang diterima:

1.  [Membuat Komponen Pesan](#chat-js-messages-create-component)

1. [Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini](#chat-js-messages-recognize)

1. [Membuat Komponen Daftar Pesan](#chat-js-messages-create-list-component)

1. [Me-render Daftar Pesan Obrolan](#chat-js-messages-render-list)

### Membuat Komponen Pesan
<a name="chat-js-messages-create-component"></a>

Komponen `Message` bertanggung jawab untuk me-render konten pesan yang diterima oleh ruang obrolan Anda. Di bagian ini, Anda membuat komponen pesan untuk me-render pesan obrolan individu di `App`.

Buat file baru di direktori `src` dan beri nama `Message`. Berikan tipe `ChatMessage` untuk komponen ini, dan berikan string `content` dari properti `ChatMessage` untuk menampilkan teks pesan yang diterima dari pendengar pesan ruang obrolan. Di Navigator Proyek, buka `Message`.

------
#### [ TypeScript ]

```
// Message.tsx

import * as React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  return (
    <div style={{ backgroundColor: 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
    </div>
  );
};
```

------
#### [ JavaScript ]

```
// Message.jsx

import * as React from 'react';

export const Message = ({ message }) => {
  return (
    <div style={{ backgroundColor: 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
    </div>
  );
};
```

------

Tip: Gunakan komponen ini untuk menyimpan properti berbeda yang ingin Anda render di baris pesan; misalnya, avatar URLs, nama pengguna, dan cap waktu saat pesan dikirim.

### Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini
<a name="chat-js-messages-recognize"></a>

Untuk mengenali pesan yang dikirim oleh pengguna saat ini, kita mengubah kode dan membuat konteks React untuk menyimpan `userId` pengguna saat ini.

Buat file baru di direktori `src` dan beri nama `UserContext`:

------
#### [ TypeScript ]

```
// UserContext.tsx

import React, { ReactNode, useState, useContext, createContext } from 'react';

type UserContextType = {
  userId: string;
  setUserId: (userId: string) => void;
};

const UserContext = createContext<UserContextType | undefined>(undefined);

export const useUserContext = () => {
  const context = useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

type UserProviderType = {
  children: ReactNode;
}

export const UserProvider = ({ children }: UserProviderType) => {
  const [userId, setUserId] = useState('Mike');

  return <UserContext.Provider value={{ userId, setUserId }}>{children}</UserContext.Provider>;
};
```

------
#### [ JavaScript ]

```
// UserContext.jsx

import React, { useState, useContext, createContext } from 'react';

const UserContext = createContext(undefined);

export const useUserContext = () => {
  const context = useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

export const UserProvider = ({ children }) => {
  const [userId, setUserId] = useState('Mike');

  return <UserContext.Provider value={{ userId, setUserId }}>{children}</UserContext.Provider>;
};
```

------

Catatan: Di sini kita menggunakan hook `useState` untuk menyimpan nilai `userId`. Ke depannya, Anda dapat menggunakan `setUserId` untuk mengubah konteks pengguna atau untuk tujuan login.

Selanjutnya, ganti `userId` pada parameter pertama yang diberikan ke `tokenProvider`, dengan menggunakan konteks yang dibuat sebelumnya:

```
// App.jsx / App.tsx

// ...

import { useUserContext } from './UserContext';

// ...


export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const { userId } = useUserContext();
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']),
      }),
  );

  // ...
}
```

Dalam komponen `Message` Anda, gunakan `UserContext` yang dibuat sebelumnya, nyatakan variabel `isMine`, cocokkan `userId` pengirim dengan `userId` dari konteks, dan terapkan gaya pesan yang berbeda untuk pengguna saat ini.

------
#### [ TypeScript ]

```
// Message.tsx

import * as React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  const { userId } = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <div style={{ backgroundColor: isMine ? 'lightblue' : 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
    </div>
  );
};
```

------
#### [ JavaScript ]

```
// Message.jsx

import * as React from 'react';
import { useUserContext } from './UserContext';

export const Message = ({ message }) => {
  const { userId } = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <div style={{ backgroundColor: isMine ? 'lightblue' : 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
    </div>
  );
};
```

------

### Membuat Komponen Daftar Pesan
<a name="chat-js-messages-create-list-component"></a>

Komponen `MessageList` bertanggung jawab untuk menampilkan percakapan ruang obrolan dari waktu ke waktu. File `MessageList` ini adalah kontainer yang menyimpan semua pesan kita. `Message` adalah satu baris di `MessageList`.

Buat file baru di direktori `src` dan beri nama `MessageList`. Tentukan `Props` dengan `messages` tipe array `ChatMessage`. Di dalam isi, petakan properti `messages` dan teruskan `Props` ke komponen `Message` Anda.

------
#### [ TypeScript ]

```
// MessageList.tsx

import React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { Message } from './Message';

interface Props {
  messages: ChatMessage[];
}

export const MessageList = ({ messages }: Props) => {
  return (
    <div>
      {messages.map((message) => (
        <Message key={message.id} message={message}/>
      ))}
    </div>
  );
};
```

------
#### [ JavaScript ]

```
// MessageList.jsx

import React from 'react';
import { Message } from './Message';

export const MessageList = ({ messages }) => {
  return (
    <div>
      {messages.map((message) => (
        <Message key={message.id} message={message} />
      ))}
    </div>
  );
};
```

------

### Me-render Daftar Pesan Obrolan
<a name="chat-js-messages-render-list"></a>

Sekarang bawa `MessageList` baru ke komponen `App` utama Anda:

```
// App.jsx / App.tsx

import { MessageList } from './MessageList';
// ...

return (
  <div style={{ display: 'flex', flexDirection: 'column', padding: 10 }}>
    <h4>Connection State: {connectionState}</h4>
    <MessageList messages={messages} />
    <div style={{ flexDirection: 'row', display: 'flex', width: '100%', backgroundColor: 'red' }}>
      <MessageInput value={messageToSend} onValueChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </div>
  </div>
);

// ...
```

Semua potongan puzzle sekarang sudah siap sehingga `App` Anda dapat mulai me-render pesan yang diterima oleh ruang obrolan Anda. Lanjutkan langkah di bawah ini untuk melihat cara melakukan tindakan di ruang obrolan yang memanfaatkan komponen yang telah Anda buat.

## Melakukan Tindakan di Ruang Obrolan
<a name="chat-js-messages-events-room-actions"></a>

Mengirim pesan dan melakukan tindakan moderator dalam ruang obrolan adalah beberapa cara utama Anda dalam berinteraksi dengan ruang obrolan. Di sini, Anda akan belajar cara menggunakan berbagai objek `ChatRequest` untuk melakukan tindakan umum di Chatterbox, seperti mengirim pesan, menghapus pesan, dan memutus koneksi pengguna lain.

Semua tindakan di ruang obrolan mengikuti pola umum: untuk setiap tindakan yang Anda lakukan di ruang obrolan, ada objek permintaan yang sesuai. Untuk setiap permintaan, ada objek respons yang sesuai yang Anda terima setelah konfirmasi permintaan.

Selama pengguna Anda diberi izin yang benar saat Anda membuat token obrolan, mereka akan berhasil melakukan tindakan yang sesuai menggunakan objek permintaan untuk melihat permintaan apa yang dapat Anda lakukan di ruang obrolan.

Di bawah ini, kami menjelaskan cara [mengirim pesan](#chat-js-room-actions-sending-message) dan [menghapus pesan](#chat-js-room-actions-deleting-message).

### Mengirim Pesan
<a name="chat-js-room-actions-sending-message"></a>

Kelas `SendMessageRequest` memungkinkan pengiriman pesan di ruang obrolan. Di sini, Anda memodifikasi `App` untuk mengirim permintaan pesan menggunakan komponen yang Anda buat di [Buat Input Pesan](chat-sdk-js-tutorial-chat-rooms.md#chat-js-rooms-message-input) (di Bagian 1 tutorial ini).

Untuk memulai, tentukan properti boolean baru bernama `isSending` dengan hook `useState`. Gunakan properti baru ini untuk mengaktifkan status nonaktif elemen HTML `button` Anda, dengan menggunakan konstanta `isSendDisabled`. Di handler peristiwa untuk `SendButton` Anda, kosongkan nilai untuk `messageToSend` dan atur `isSending` ke true.

*Karena Anda akan melakukan panggilan API dari tombol ini, penambahan boolean `isSending` akan membantu mencegah banyaknya panggilan API yang terjadi di waktu yang bersamaan, dengan menonaktifkan interaksi pengguna pada `SendButton` Anda hingga permintaan selesai.*

```
// App.jsx / App.tsx

// ...

const [isSending, setIsSending] = useState(false);

// ...

const onMessageSend = () => {
  setIsSending(true);
  setMessageToSend('');
};

// ...

const isSendDisabled = connectionState !== 'connected' || isSending;

// ...
```

Siapkan permintaan dengan membuat instans `SendMessageRequest` baru, dengan meneruskan konten pesan ke konstruktor. Setelah mengatur status `isSending` dan `messageToSend`, panggil metode `sendMessage`, yang mengirimkan permintaan ke ruang obrolan. Terakhir, hapus bendera `isSending` saat menerima konfirmasi atau penolakan permintaan.

------
#### [ TypeScript ]

```
// App.tsx

// ...
import { ChatMessage, ChatRoom, ConnectionState, SendMessageRequest } from 'amazon-ivs-chat-messaging'
// ...

const onMessageSend = async () => {
  const request = new SendMessageRequest(messageToSend);
  setIsSending(true);
  setMessageToSend('');

  try {
    const response = await room.sendMessage(request);
  } catch (e) {
    console.log(e);
    // handle the chat error here...
  } finally {
    setIsSending(false);
  }
};

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...
import { ChatRoom, SendMessageRequest } from 'amazon-ivs-chat-messaging'
// ...

const onMessageSend = async () => {
  const request = new SendMessageRequest(messageToSend);
  setIsSending(true);
  setMessageToSend('');

  try {
    const response = await room.sendMessage(request);
  } catch (e) {
    console.log(e);
    // handle the chat error here...
  } finally {
    setIsSending(false);
  }
};

// ...
```

------

Jalankan Chatterbox: coba mengirim pesan dengan menyusun pesan menggunakan `MessageInput` Anda dan mengetuk `SendButton` Anda. Anda akan melihat pesan terkirim Anda di-render dalam `MessageList` yang dibuat sebelumnya.

### Menghapus Pesan
<a name="chat-js-room-actions-deleting-message"></a>

Untuk menghapus pesan dari ruang obrolan, Anda harus memiliki kemampuan yang tepat. Kemampuan diberikan selama inisialisasi token obrolan yang Anda gunakan saat mengautentikasi ke ruang obrolan. Untuk keperluan bagian ini, `ServerApp` dari [Menyiapkan Server Autentikasi/Otorisasi Lokal](chat-sdk-js-tutorial-chat-rooms.md#chat-js-rooms-auth-server) (di Bagian 1 tutorial ini) memungkinkan Anda menentukan kemampuan moderator. Hal ini dilakukan di aplikasi Anda menggunakan objek `tokenProvider` yang Anda buat di [Membangun Penyedia Token](chat-sdk-js-tutorial-chat-rooms.md#chat-js-rooms-token-provider) (juga di Bagian 1).

Di sini, Anda memodifikasi `Message` dengan menambahkan fungsi untuk menghapus pesan.

Pertama, buka `App.tsx` dan tambahkan kemampuan `DELETE_MESSAGE`. (`capabilities` adalah parameter kedua dari fungsi `tokenProvider` Anda.)

Catatan: Ini adalah cara Anda `ServerApp` menginformasikan Obrolan IVS APIs bahwa pengguna yang dikaitkan dengan token obrolan yang dihasilkan dapat menghapus pesan di ruang obrolan. Dalam situasi dunia nyata, Anda mungkin akan memiliki logika backend yang lebih kompleks untuk mengelola kemampuan pengguna di infrastruktur aplikasi server Anda.

------
#### [ TypeScript ]

```
// App.tsx

// ...

const [room] = useState( () =>
    new ChatRoom({
      regionOrUrl: process.env.REGION as string,
      tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE', 'DELETE_MESSAGE']),
    }),
);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const [room] = useState( () =>
  new ChatRoom({
    regionOrUrl: process.env.REGION,
    tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE', 'DELETE_MESSAGE']),
  }),
);

// ...
```

------

Pada langkah berikutnya, Anda memperbarui `Message` untuk menampilkan tombol hapus.

Buka `Message` dan tentukan status boolean baru yang bernama `isDeleting` menggunakan hook `useState` dengan nilai awal `false`. Dengan menggunakan status ini, perbarui konten `Button` Anda menjadi berbeda, tergantung status `isDeleting` saat ini. Nonaktifkan tombol Anda ketika `isDeleting` true; hal ini mencegah Anda dari mencoba untuk membuat dua permintaan penghapusan pesan di waktu yang bersamaan. 

------
#### [ TypeScript ]

```
// Message.tsx

import React, { useState } from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  const { userId } = useUserContext();
  const [isDeleting, setIsDeleting] = useState(false);

  const isMine = message.sender.userId === userId;

  return (
    <div style={{ backgroundColor: isMine ? 'lightblue' : 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
      <button disabled={isDeleting}>Delete</button>
    </div>
  );
};
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { useUserContext } from './UserContext';

export const Message = ({ message }) => {
  const { userId } = useUserContext();
  const [isDeleting, setIsDeleting] = useState(false);

  return (
    <div style={{ backgroundColor: isMine ? 'lightblue' : 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
      <button disabled={isDeleting}>Delete</button>
    </div>
  );
};
```

------

Tentukan fungsi baru bernama `onDelete`, yang menerima string sebagai salah satu parameternya dan mengembalikan `Promise`. Di dalam isi penutupan tindakan `Button` Anda, gunakan `setIsDeleting` untuk mengalihkan boolean `isDeleting` sebelum dan sesudah panggilan ke `onDelete`. Untuk parameter string, berikan ID pesan komponen Anda.

------
#### [ TypeScript ]

```
// Message.tsx

import React, { useState } from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export type Props = {
  message: ChatMessage;
  onDelete(id: string): Promise<void>;
};

export const Message = ({ message onDelete }: Props) => {
  const { userId } = useUserContext();
  const [isDeleting, setIsDeleting] = useState(false);
  const isMine = message.sender.userId === userId;
  const handleDelete = async () => {
    setIsDeleting(true);
    try {
      await onDelete(message.id);
    } catch (e) {
      console.log(e);
      // handle chat error here...
    } finally {
      setIsDeleting(false);
    }
  };

  return (
    <div style={{ backgroundColor: isMine ? 'lightblue' : 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{content}</p>
      <button onClick={handleDelete} disabled={isDeleting}>
        Delete
      </button>
    </div>
  );
};
```

------
#### [ JavaScript ]

```
// Message.jsx

import React, { useState } from 'react';
import { useUserContext } from './UserContext';

export const Message = ({ message, onDelete }) => {
  const { userId } = useUserContext();
  const [isDeleting, setIsDeleting] = useState(false);
  const isMine = message.sender.userId === userId;
  const handleDelete = async () => {
    setIsDeleting(true);
    try {
      await onDelete(message.id);
    } catch (e) {
      console.log(e);
      // handle the exceptions here...
    } finally {
      setIsDeleting(false);
    }
  };

  return (
    <div style={{ backgroundColor: 'silver', padding: 6, borderRadius: 10, margin: 10 }}>
      <p>{message.content}</p>
      <button onClick={handleDelete} disabled={isDeleting}>
        Delete
      </button>
    </div>
  );
};
```

------

Selanjutnya, Anda memperbarui `MessageList` untuk merefleksikan perubahan terbaru pada komponen `Message` Anda.

Buka `MessageList` dan tentukan fungsi baru yang disebut `onDelete`, yang menerima string sebagai parameter dan mengembalikan `Promise`. Perbarui `Message` Anda dan teruskan melalui properti `Message`. Parameter string dalam penutupan baru Anda akan menjadi ID pesan yang ingin dihapus, yang diteruskan dari `Message` Anda.

------
#### [ TypeScript ]

```
// MessageList.tsx

import * as React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { Message } from './Message';

interface Props {
  messages: ChatMessage[];
  onDelete(id: string): Promise<void>;
}

export const MessageList = ({ messages, onDelete }: Props) => {
  return (
    <>
      {messages.map((message) => (
        <Message key={message.id} onDelete={onDelete} content={message.content} id={message.id} />
      ))}
    </>
  );
};
```

------
#### [ JavaScript ]

```
// MessageList.jsx

import * as React from 'react';
import { Message } from './Message';

export const MessageList = ({ messages, onDelete }) => {
  return (
    <>
      {messages.map((message) => (
        <Message key={message.id} onDelete={onDelete} content={message.content} id={message.id} />
      ))}
    </>
  );
};
```

------

Selanjutnya, Anda memperbarui `App` untuk menunjukkan perubahan terbaru pada `MessageList` Anda.

Di `App`, tentukan fungsi bernama `onDeleteMessage` dan teruskan ke properti `MessageList onDelete`:

------
#### [ TypeScript ]

```
// App.tsx

// ...

const onDeleteMessage = async (id: string) => {};

return (
  <div style={{ display: 'flex', flexDirection: 'column', padding: 10 }}>
    <h4>Connection State: {connectionState}</h4>
    <MessageList onDelete={onDeleteMessage} messages={messages} />
    <div style={{ flexDirection: 'row', display: 'flex', width: '100%' }}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onSendPress={onMessageSend} />
    </div>
  </div>
);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const onDeleteMessage = async (id) => {};

return (
  <div style={{ display: 'flex', flexDirection: 'column', padding: 10 }}>
    <h4>Connection State: {connectionState}</h4>
    <MessageList onDelete={onDeleteMessage} messages={messages} />
    <div style={{ flexDirection: 'row', display: 'flex', width: '100%' }}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onSendPress={onMessageSend} />
    </div>
  </div>
);

// ...
```

------

Siapkan permintaan dengan membuat instans `DeleteMessageRequest` baru, dengan meneruskan ID pesan yang relevan ke parameter konstruktor, dan panggil `deleteMessage` yang menerima permintaan yang disiapkan di atas:

------
#### [ TypeScript ]

```
// App.tsx

// ...

const onDeleteMessage = async (id: string) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const onDeleteMessage = async (id) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------

Selanjutnya, Anda memperbarui status `messages` untuk merefleksikan daftar pesan baru yang menghilangkan pesan yang baru saja Anda hapus.

Di hook `useEffect`, dengarkan peristiwa `messageDelete` dan perbarui array status `messages` Anda dengan menghapus pesan yang mempunyai ID yang sama dengan parameter `message`.

Catatan: Peristiwa `messageDelete` dapat dimunculkan untuk pesan yang dihapus oleh pengguna saat ini atau pengguna lain di ruang. Dengan menanganinya di handler peristiwa (bukan di samping permintaan `deleteMessage`), Anda dapat menyatukan penanganan hapus-pesan.

```
// App.jsx / App.tsx

// ...

const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteMessageEvent) => {
  setMessages((prev) => prev.filter((message) => message.id !== deleteMessageEvent.id));
});

return () => {
  // ...

  unsubscribeOnMessageDeleted();
};

// ...
```

Sekarang Anda dapat menghapus pengguna dari ruang obrolan di aplikasi obrolan Anda.

## Langkah Berikutnya
<a name="chat-js-messages-events-next-steps"></a>

Sebagai percobaan, coba implementasikan tindakan lain di suatu ruang, seperti memutus koneksi pengguna lain.

# SDK Pesan Klien Obrolan IVS: Tutorial React Native Bagian 1: Ruang Obrolan
<a name="chat-sdk-react-tutorial-chat-rooms"></a>

Ini adalah bagian pertama dari tutorial dua bagian. Anda akan mempelajari hal-hal penting dalam bekerja dengan Amazon IVS Chat Client Messaging JavaScript SDK dengan membangun aplikasi yang berfungsi penuh menggunakan React Native. Kami menyebut aplikasi itu *Chatterbox*.

Audiens yang dimaksudkan adalah para developer berpengalaman yang baru mengenal SDK Perpesanan Obrolan Amazon IVS. Anda harus merasa nyaman dengan TypeScript atau bahasa JavaScript pemrograman dan perpustakaan React Native.

Untuk singkatnya, kita akan merujuk ke Amazon IVS Chat Client Messaging JavaScript SDK sebagai Chat JS SDK.

**Catatan**: Dalam beberapa kasus, contoh kode untuk JavaScript dan TypeScript identik, sehingga digabungkan.

Bagian pertama dari tutorial ini dipecah menjadi beberapa bagian:

1. [Mengatur Authentication/Authorization Server Lokal](#chat-react-rooms-auth-server)

1. [Buat Proyek Chatterbox](#chat-react-rooms-chatterbox)

1. [Menghubungkan ke Ruang Obrolan](#chat-react-rooms-connect)

1. [Membangun Penyedia Token](#chat-react-rooms-token-provider)

1. [Mengamati Pembaruan Koneksi](#chat-react-rooms-connection-state)

1. [Membuat Komponen Tombol Kirim](#chat-react-rooms-send-button)

1. [Buat Input Pesan](#chat-react-rooms-message-input)

1. [Langkah Berikutnya](#chat-react-rooms-next-steps)

## Prasyarat
<a name="chat-react-rooms-prerequisites"></a>
+ Biasakan diri dengan TypeScript atau JavaScript dan library React Native. Jika Anda tidak paham dengan React Native, pelajari dasar-dasarnya di [Pengantar React Native](https://reactnative.dev/docs/tutorial).
+ Baca dan pahami [Memulai Obrolan Amazon IVS](getting-started-chat.md).
+ Buat pengguna AWS IAM dengan CreateRoom kemampuan CreateChatToken dan yang ditentukan dalam kebijakan IAM yang ada. (Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md)).
+ Pastikan secret/access kunci untuk pengguna ini disimpan dalam file kredensial AWS. Untuk instruksinya, lihat [Panduan Pengguna AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) (terutama [Pengaturan file konfigurasi dan kredensial](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)).
+ Buat ruang obrolan dan simpan ARN-nya. Lihat [Memulai Obrolan Amazon IVS](getting-started-chat.md). (Jika Anda tidak menyimpan ARN, Anda dapat mencarinya nanti dengan konsol atau API Obrolan.)
+ Instal lingkungan Node.js 14\$1 dengan NPM atau manajer paket Yarn.

## Mengatur Authentication/Authorization Server Lokal
<a name="chat-react-rooms-auth-server"></a>

Aplikasi backend Anda bertanggung jawab untuk membuat ruang obrolan dan menghasilkan token obrolan yang diperlukan untuk SDK JS Obrolan guna mengautentikasi dan mengotorisasi klien Anda untuk ruang obrolan Anda. Anda harus menggunakan backend milik sendiri karena Anda tidak dapat menyimpan kunci AWS dengan aman di aplikasi seluler; penyerang canggih dapat mengekstraknya dan mendapatkan akses ke akun AWS Anda.

Lihat [Buat Token Obrolan](getting-started-chat-auth.md) di *Memulai Obrolan Amazon IVS*. Seperti yang ditunjukkan pada diagram alur di sana, aplikasi sisi server Anda bertanggung jawab untuk membuat token obrolan. Hal ini berarti aplikasi Anda harus menyediakan caranya sendiri untuk menghasilkan token obrolan dengan memintanya dari aplikasi sisi server Anda.

Di bagian ini, Anda akan mempelajari dasar-dasar membuat penyedia token di backend Anda. Kami memanfaatkan kerangka kerja ekspres untuk membuat server lokal langsung yang mengelola pembuatan token obrolan menggunakan lingkungan AWS lokal Anda.

Buat proyek `npm` kosong menggunakan NPM. Buat direktori untuk menyimpan aplikasi Anda, dan jadikan sebagai direktori kerja Anda:

```
$ mkdir backend & cd backend
```

Gunakan `npm init` untuk membuat file `package.json` pada aplikasi Anda:

```
$ npm init
```

Perintah ini meminta beberapa hal pada Anda, termasuk nama dan versi aplikasi Anda. Saat ini, cukup tekan **KEMBALI** untuk menerima default sebagian besar perintah tersebut, dengan pengecualian berikut:

```
entry point: (index.js)
```

Tekan **KEMBALI** untuk menerima nama file default yang disarankan `index.js` atau masukkan nama apa pun yang Anda inginkan untuk file utama.

Sekarang instal dependensi yang diperlukan:

```
$ npm install express aws-sdk cors dotenv
```

`aws-sdk` memerlukan variabel lingkungan konfigurasi, yang memuat secara otomatis dari file bernama `.env` yang terletak di direktori root. Untuk mengonfigurasikannya, buat file baru bernama `.env` dan isi informasi konfigurasi yang hilang:

```
# .env

# The region to send service requests to.
AWS_REGION=us-west-2

# Access keys use an access key ID and secret access key
# that you use to sign programmatic requests to AWS.

# AWS access key ID.
AWS_ACCESS_KEY_ID=...

# AWS secret access key.
AWS_SECRET_ACCESS_KEY=...
```

Sekarang kita buat file titik masuk di direktori root dengan nama yang Anda masukkan di atas dalam perintah `npm init`. Dalam hal ini, kami menggunakan `index.js`, dan mengimpor semua paket yang diperlukan:

```
// index.js
import express from 'express';
import AWS from 'aws-sdk';
import 'dotenv/config';
import cors from 'cors';
```

Sekarang, buat instans baru `express`:

```
const app = express();
const port = 3000;

app.use(express.json());
app.use(cors({ origin: ['http://127.0.0.1:5173'] }));
```

Setelah itu, Anda dapat membuat metode POST titik akhir pertama Anda untuk penyedia token. Ambil parameter yang diperlukan dari isi permintaan (`roomId`, `userId`, `capabilities`, dan `sessionDurationInMinutes`):

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};
});
```

Tambahkan validasi bidang yang wajib diisi:

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`' });
    return;
  }
});
```

Setelah menyiapkan metode POST, kita mengintegrasikan `createChatToken` dengan `aws-sdk` untuk fungsionalitas inti autentikasi/otorisasi:

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId || !capabilities) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`, `capabilities`' });
    return;
  }

  ivsChat.createChatToken({ roomIdentifier, userId, capabilities, sessionDurationInMinutes }, (error, data) => {
    if (error) {
      console.log(error);
      res.status(500).send(error.code);
    } else if (data.token) {
      const { token, sessionExpirationTime, tokenExpirationTime } = data;
      console.log(`Retrieved Chat Token: ${JSON.stringify(data, null, 2)}`);

      res.json({ token, sessionExpirationTime, tokenExpirationTime });
    }
  });
});
```

Di akhir file, tambahkan pendengar port untuk aplikasi `express` Anda:

```
app.listen(port, () => {
  console.log(`Backend listening on port ${port}`);
});
```

Sekarang Anda dapat menjalankan server dengan perintah berikut dari root proyek:

```
$ node index.js
```

**Tip**: Server ini menerima permintaan URL di https://localhost:3000.

## Buat Proyek Chatterbox
<a name="chat-react-rooms-chatterbox"></a>

Pertama, Anda membuat proyek React Native yang disebut `chatterbox`. Jalankan perintah ini:

```
npx create-expo-app
```

Atau buat proyek expo dengan TypeScript template.

```
npx create-expo-app -t expo-template-blank-typescript
```

Anda dapat mengintegrasikan SDK JS Perpesanan Klien Obrolan melalui [Manajer Paket Simpul](https://www.npmjs.com/) atau [Manajer Paket Yarn](https://yarnpkg.com/):
+ Npm: `npm install amazon-ivs-chat-messaging`
+ Yarn: `yarn add amazon-ivs-chat-messaging`

## Menghubungkan ke Ruang Obrolan
<a name="chat-react-rooms-connect"></a>

Di sini Anda membuat `ChatRoom` dan menghubungkannya menggunakan metode asinkron. Kelas `ChatRoom` mengelola koneksi pengguna Anda ke SDK JS Obrolan. Agar berhasil terhubung ke ruang obrolan, Anda harus menyediakan instans `ChatToken` dalam aplikasi React Anda. 

Arahkan ke file `App` yang dibuat dalam proyek `chatterbox` default dan hapus semua yang dikembalikan oleh komponen fungsional. Tidak memerlukan kode yang telah diisi sebelumnya. Saat ini, `App` kami cukup kosong.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

import * as React from 'react';
import { Text } from 'react-native';

export default function App() {
  return <Text>Hello!</Text>;
}
```

Buat instans `ChatRoom` baru dan teruskan ke status menggunakan hook `useState`. Ini memerlukan `regionOrUrl` penerusan (wilayah AWS tempat ruang obrolan Anda di-host) dan `tokenProvider` (digunakan untuk authentication/authorization aliran backend yang dibuat pada langkah selanjutnya).

**Penting**: Anda harus menggunakan wilayah AWS yang sama dengan wilayah tempat Anda membuat ruang di [Memulai Obrolan Amazon IVS](getting-started-chat-create-room.md). API adalah layanan regional AWS. Untuk daftar wilayah yang didukung dan titik akhir layanan HTTPS Obrolan Amazon IVS, lihat halaman [wilayah Obrolan Amazon IVS](https://docs.aws.amazon.com/general/latest/gr/ivs.html#ivs_region).

**TypeScript/JavaScript**:

```
// App.jsx / App.tsx

import React, { useState } from 'react';
import { Text } from 'react-native';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [room] = useState(() =>
    new ChatRoom({
      regionOrUrl: process.env.REGION,
      tokenProvider: () => {},
    }),
  );

  return <Text>Hello!</Text>;
}
```

## Membangun Penyedia Token
<a name="chat-react-rooms-token-provider"></a>

Sebagai langkah berikutnya, kita perlu membangun fungsi `tokenProvider` tanpa parameter yang diperlukan oleh konstruktor `ChatRoom`. Pertama, kita akan membuat fungsi `fetchChatToken` yang akan membuat permintaan POST ke aplikasi backend yang Anda siapkan di [Mengatur Authentication/Authorization Server Lokal](#chat-react-rooms-auth-server). Token obrolan berisi informasi yang diperlukan agar SDK berhasil membuat koneksi ruang obrolan. API Obrolan menggunakan token ini sebagai cara aman untuk memvalidasi identitas pengguna, kemampuan dalam ruang obrolan, dan durasi sesi.

Di navigator Proyek, buat TypeScript/JavaScript file baru bernama`fetchChatToken`. Bangun permintaan pengambilan ke aplikasi `backend` dan kembalikan objek `ChatToken` dari respons. Tambahkan properti isi permintaan yang diperlukan untuk membuat token obrolan. Gunakan aturan yang ditentukan untuk [Amazon Resource Names (ARNs)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html). Properti ini didokumentasikan dalam [CreateChatToken](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html#API_CreateChatToken_RequestBody)operasi.

**Catatan**: URL yang Anda gunakan di sini adalah URL yang sama dengan yang dibuat oleh server lokal Anda saat menjalankan aplikasi backend.

------
#### [ TypeScript ]

```
// fetchChatToken.ts

import { ChatToken } from 'amazon-ivs-chat-messaging';

type UserCapability = 'DELETE_MESSAGE' | 'DISCONNECT_USER' | 'SEND_MESSAGE';

export async function fetchChatToken(
  userId: string,
  capabilities: UserCapability[] = [],
  attributes?: Record<string, string>,
  sessionDurationInMinutes?: number,
): Promise<ChatToken> {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------
#### [ JavaScript ]

```
// fetchChatToken.js

export async function fetchChatToken(
  userId,
  capabilities = [],
  attributes,
  sessionDurationInMinutes) {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------

## Mengamati Pembaruan Koneksi
<a name="chat-react-rooms-connection-state"></a>

Bereaksi terhadap perubahan status koneksi ruang obrolan adalah bagian penting dalam membuat aplikasi obrolan. Mari kita mulai dengan berlangganan peristiwa yang relevan:

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {});
    const unsubscribeOnConnected = room.addListener('connect', () => {});
    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {});

    return () => {
      // Clean up subscriptions.
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <Text>Hello!</Text>;
}
```

Selanjutnya, kita perlu menyediakan kemampuan untuk membaca status koneksi. Kita menggunakan hook `useState` untuk membuat beberapa status lokal di `App` dan mengatur status koneksi di dalam setiap pendengar.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {  
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setConnectionState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setConnectionState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setConnectionState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <Text>Hello!</Text>;
}
```

Setelah berlangganan status koneksi, tampilkan status koneksi dan hubungkan ke ruang obrolan dengan menggunakan metode `room.connect` di dalam hook `useEffect`:

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

useEffect(() => {
  const unsubscribeOnConnecting = room.addListener('connecting', () => {
    setConnectionState('connecting');
  });

  const unsubscribeOnConnected = room.addListener('connect', () => {
    setConnectionState('connected');
  });

  const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
    setConnectionState('disconnected');
  });

  room.connect();

  return () => {
    unsubscribeOnConnecting();
    unsubscribeOnConnected();
    unsubscribeOnDisconnected();
  };
}, [room]);

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  }
});

// ...
```

Anda telah berhasil mengimplementasikan koneksi ruang obrolan.

## Membuat Komponen Tombol Kirim
<a name="chat-react-rooms-send-button"></a>

Di bagian ini, Anda membuat tombol kirim yang memiliki desain berbeda untuk setiap status koneksi. Tombol kirim memfasilitasi pengiriman pesan di ruang obrolan. Ini juga berfungsi sebagai indikator visual whether/when pesan dapat dikirim; misalnya, dalam menghadapi koneksi terputus atau sesi obrolan yang kedaluwarsa.

Pertama, buat file baru di direktori `src` proyek Chatterbox Anda dan beri nama `SendButton`. Kemudian, buat komponen yang akan menampilkan tombol untuk aplikasi obrolan Anda. Ekspor `SendButton` Anda dan impor ke `App`. Di `<View></View>` yang kosong, tambahkan `<SendButton />`.

------
#### [ TypeScript ]

```
// SendButton.tsx

import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';

interface Props {
  onPress?: () => void;
  disabled: boolean;
  loading: boolean;
}

export const SendButton = ({ onPress, disabled, loading }: Props) => {
  return (
    <TouchableOpacity style={styles.root} disabled={disabled} onPress={onPress}>
      {loading ? <Text>Send</Text> : <ActivityIndicator />}
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  root: {
    width: 50,
    height: 50,
    borderRadius: 30,
    marginLeft: 10,
    justifyContent: 'center',
    alignContent: 'center',
  }
});

// App.tsx

import { SendButton } from './SendButton';

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton />
  </SafeAreaView>
);
```

------
#### [ JavaScript ]

```
// SendButton.jsx

import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';

export const SendButton = ({ onPress, disabled, loading }) => {
  return (
    <TouchableOpacity style={styles.root} disabled={disabled} onPress={onPress}>
      {loading ? <Text>Send</Text> : <ActivityIndicator />}
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  root: {
    width: 50,
    height: 50,
    borderRadius: 30,
    marginLeft: 10,
    justifyContent: 'center',
    alignContent: 'center',
  }
});

// App.jsx

import { SendButton } from './SendButton';

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton />
  </SafeAreaView>
);
```

------

Selanjutnya di `App`, tentukan fungsi bernama `onMessageSend` dan berikan fungsi tersebut ke properti `SendButton onPress`. Tentukan variabel lain bernama `isSendDisabled` (yang mencegah pengiriman pesan ketika ruang tidak terhubung) dan berikan variabel tersebut ke properti `SendButton disabled`.

**TypeScript/JavaScript**:

```
// App.jsx / App.tsx

// ...

const onMessageSend = () => {};

const isSendDisabled = connectionState !== 'connected';

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </SafeAreaView>
);

// ...
```

## Buat Input Pesan
<a name="chat-react-rooms-message-input"></a>

Bilah pesan Chatterbox adalah komponen yang akan berinteraksi dengan Anda untuk mengirim pesan ke ruang obrolan. Biasanya, bilah ini berisi input teks untuk menulis pesan dan tombol untuk mengirim pesan Anda.

Untuk membuat komponen `MessageInput`, pertama buat file baru di direktori `src` dan beri nama `MessageInput`. Kemudian, buat komponen yang akan menampilkan input untuk aplikasi obrolan Anda. Ekspor `MessageInput` Anda dan impor ke `App` (di atas `<SendButton />`).

Buat status baru bernama `messageToSend` menggunakan hook `useState`, dengan string kosong sebagai nilai default-nya. Di bagian utama aplikasi Anda, berikan `messageToSend` ke `value` dari `MessageInput` dan berikan `setMessageToSend` ke properti `onMessageChange`:

------
#### [ TypeScript ]

```
// MessageInput.tsx

import * as React from 'react';

interface Props {
  value?: string;
  onValueChange?: (value: string) => void;
}

export const MessageInput = ({ value, onValueChange }: Props) => {
  return (
    <TextInput style={styles.input} value={value} onChangeText={onValueChange} placeholder="Send a message" />
  );
};

const styles = StyleSheet.create({
  input: {
    fontSize: 20,
    backgroundColor: 'rgb(239,239,240)',
    paddingHorizontal: 18,
    paddingVertical: 15,
    borderRadius: 50,
    flex: 1,
  }
})

// App.tsx

// ...

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  messageBar: {
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: 'rgb(160,160,160)',
    flexDirection: 'row',
    padding: 16,
    alignItems: 'center',
    backgroundColor: 'white',
  }
});
```

------
#### [ JavaScript ]

```
// MessageInput.jsx

import * as React from 'react';

export const MessageInput = ({ value, onValueChange }) => {
  return (
    <TextInput style={styles.input} value={value} onChangeText={onValueChange} placeholder="Send a message" />
  );
};

const styles = StyleSheet.create({
  input: {
    fontSize: 20,
    backgroundColor: 'rgb(239,239,240)',
    paddingHorizontal: 18,
    paddingVertical: 15,
    borderRadius: 50,
    flex: 1,
  }
})

// App.jsx

// ...

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  messageBar: {
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: 'rgb(160,160,160)',
    flexDirection: 'row',
    padding: 16,
    alignItems: 'center',
    backgroundColor: 'white',
  }
});
```

------

## Langkah Berikutnya
<a name="chat-react-rooms-next-steps"></a>

Karena Anda telah selesai membuat bilah pesan untuk Chatterbox, lanjutkan ke Bagian 2 tutorial React Native, [Pesan dan Peristiwa](chat-sdk-react-tutorial-messages-events.md).

# IVS Chat Client Messaging SDK: React Native Tutorial Bagian 2: Pesan dan Acara
<a name="chat-sdk-react-tutorial-messages-events"></a>

Bagian kedua (dan terakhir) dari tutorial ini dipecah menjadi beberapa bagian:

1. [Berlangganan Peristiwa Pesan Obrolan](#chat-react-messages-events-subscribe)

1. [Menampilkan Pesan yang Diterima](#chat-react-messages-events-show)

   1.  [Membuat Komponen Pesan](#chat-react-messages-create-component)

   1. [Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini](#chat-react-messages-recognize)

   1. [Me-render Daftar Pesan Obrolan](#chat-react-messages-render-list)

1. [Melakukan Tindakan di Ruang Obrolan](#chat-react-messages-events-room-actions)

   1. [Mengirim Pesan](#chat-react-room-actions-sending-message)

   1. [Menghapus Pesan](#chat-react-room-actions-deleting-message)

1. [Langkah Berikutnya](#chat-react-messages-events-next-steps)

**Catatan**: Dalam beberapa kasus, contoh kode untuk JavaScript dan TypeScript identik, sehingga digabungkan.

## Prasyarat
<a name="chat-react-messages-events-prerequisite"></a>

Pastikan Anda telah menyelesaikan Bagian 1 dari tutorial ini, [Ruang Obrolan](chat-sdk-react-tutorial-chat-rooms.md).

## Berlangganan Peristiwa Pesan Obrolan
<a name="chat-react-messages-events-subscribe"></a>

Instans `ChatRoom` menggunakan peristiwa untuk berkomunikasi ketika peristiwa terjadi di ruang obrolan. Untuk mulai mengimplementasikan pengalaman obrolan, Anda harus menunjukkan kepada pengguna saat orang lain mengirim pesan di ruang yang terhubung dengan mereka.

Di sini, Anda berlangganan peristiwa pesan obrolan. Selanjutnya, kami akan menunjukkan cara memperbarui daftar pesan yang Anda buat, yang diperbarui dengan setiap pesan/peristiwa.

Di `App` Anda, di dalam hook `useEffect`, berlangganan semua peristiwa pesan:

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

useEffect(() => {
  // ...
  const unsubscribeOnMessageReceived = room.addListener('message', (message) => {});

  return () => {
    // ...
    unsubscribeOnMessageReceived();
  };
}, []);
```

## Menampilkan Pesan yang Diterima
<a name="chat-react-messages-events-show"></a>

Menerima pesan adalah bagian inti dari pengalaman obrolan. Dengan SDK JS Obrolan, Anda dapat menyiapkan kode agar dapat dengan mudah menerima peristiwa dari pengguna lain yang terhubung ke ruang obrolan.

Selanjutnya, kami akan menunjukkan cara melakukan tindakan di ruang obrolan yang memanfaatkan komponen yang Anda buat di sini.

Di `App` Anda, tentukan status bernama `messages` dengan tipe array `ChatMessage` yang bernama `messages`:

------
#### [ TypeScript ]

```
// App.tsx

// ...

import { ChatRoom, ChatMessage, ConnectionState } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);

  //...
}
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [messages, setMessages] = useState([]);

  //...
}
```

------

Selanjutnya, di fungsi pendengar `message`, tambahkan `message` ke array `messages`:

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
  setMessages((msgs) => [...msgs, message]);
});

// ...
```

Di bawah ini, kita akan menjalankan langkah demi langkah untuk menampilkan pesan yang diterima:

1.  [Membuat Komponen Pesan](#chat-react-messages-create-component)

1. [Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini](#chat-react-messages-recognize)

1. [Me-render Daftar Pesan Obrolan](#chat-react-messages-render-list)

### Membuat Komponen Pesan
<a name="chat-react-messages-create-component"></a>

Komponen `Message` bertanggung jawab untuk me-render konten pesan yang diterima oleh ruang obrolan Anda. Di bagian ini, Anda membuat komponen pesan untuk me-render pesan obrolan individu di `App`.

Buat file baru di direktori `src` dan beri nama `Message`. Berikan tipe `ChatMessage` untuk komponen ini, dan berikan string `content` dari properti `ChatMessage` untuk menampilkan teks pesan yang diterima dari pendengar pesan ruang obrolan. Di Navigator Proyek, buka `Message`.

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  return (
    <View style={styles.root}>
      <Text>{message.sender.userId}</Text>
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export const Message = ({ message }) => {
  return (
    <View style={styles.root}>
      <Text>{message.sender.userId}</Text>
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
});
```

------

**Tip**: Gunakan komponen ini untuk menyimpan properti berbeda yang ingin Anda render di baris pesan; misalnya, avatar URLs, nama pengguna, dan cap waktu saat pesan dikirim.

### Mengenali Pesan yang Dikirim oleh Pengguna Saat Ini
<a name="chat-react-messages-recognize"></a>

Untuk mengenali pesan yang dikirim oleh pengguna saat ini, kita mengubah kode dan membuat konteks React untuk menyimpan `userId` pengguna saat ini.

Buat file baru di direktori `src` dan beri nama `UserContext`:

------
#### [ TypeScript ]

```
// UserContext.tsx

import React from 'react';

const UserContext = React.createContext<string | undefined>(undefined);

export const useUserContext = () => {
  const context = React.useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

export const UserProvider = UserContext.Provider;
```

------
#### [ JavaScript ]

```
// UserContext.jsx

import React from 'react';

const UserContext = React.createContext(undefined);

export const useUserContext = () => {
  const context = React.useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

export const UserProvider = UserContext.Provider;
```

------

Catatan: Di sini kita menggunakan hook `useState` untuk menyimpan nilai `userId`. Ke depannya, Anda dapat memanfaatkan `setUserId` untuk mengubah konteks pengguna atau untuk tujuan login.

Selanjutnya, ganti `userId` pada parameter pertama yang diberikan ke `tokenProvider`, dengan menggunakan konteks yang dibuat sebelumnya. Pastikan untuk menambahkan kemampuan `SEND_MESSAGE` ke penyedia token Anda, seperti yang ditentukan di bawah ini; kemampuan ini diperlukan untuk mengirim pesan.:

------
#### [ TypeScript ]

```
// App.tsx

// ...

import { useUserContext } from './UserContext';

// ...


export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const userId = useUserContext();
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']),
      }),
  );

  // ...
}
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

import { useUserContext } from './UserContext';

// ...


export default function App() {
  const [messages, setMessages] = useState([]);
  const userId = useUserContext();
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']),
      }),
  );

  // ...
}
```

------

Dalam komponen `Message` Anda, gunakan `UserContext` yang dibuat sebelumnya, nyatakan variabel `isMine`, cocokkan `userId` pengirim dengan `userId` dari konteks, dan terapkan gaya pesan yang berbeda untuk pengguna saat ini.

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export const Message = ({ message }) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------

### Me-render Daftar Pesan Obrolan
<a name="chat-react-messages-render-list"></a>

Sekarang, buat daftar pesan dengan memanfaatkan komponen `FlatList` dan `Message`:

------
#### [ TypeScript ]

```
// App.tsx

// ...

const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => {
  return (
    <Message key={item.id} message={item} />
  );
}, []);

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <FlatList inverted data={messages} renderItem={renderItem} />
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const renderItem = useCallback(({ item }) => {
  return (
    <Message key={item.id} message={item} />
  );
}, []);

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <FlatList inverted data={messages} renderItem={renderItem} />
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

// ...
```

------

Semua potongan puzzle sekarang sudah siap sehingga `App` Anda dapat mulai me-render pesan yang diterima oleh ruang obrolan Anda. Lanjutkan langkah di bawah ini untuk melihat cara melakukan tindakan di ruang obrolan yang memanfaatkan komponen yang telah Anda buat.

## Melakukan Tindakan di Ruang Obrolan
<a name="chat-react-messages-events-room-actions"></a>

Mengirim pesan dan melakukan tindakan moderator adalah beberapa cara utama Anda dalam berinteraksi dengan ruang obrolan. Di sini, Anda akan belajar cara menggunakan berbagai objek permintaan obrolan untuk melakukan tindakan umum di Chatterbox, seperti mengirim pesan, menghapus pesan, dan memutus koneksi pengguna lain.

Semua tindakan di ruang obrolan mengikuti pola umum: untuk setiap tindakan yang Anda lakukan di ruang obrolan, ada objek permintaan yang sesuai. Untuk setiap permintaan, ada objek respons yang sesuai yang Anda terima setelah konfirmasi permintaan.

Selama pengguna Anda diberi kemampuan yang benar saat Anda membuat token obrolan, mereka akan berhasil melakukan tindakan yang sesuai menggunakan objek permintaan untuk melihat permintaan apa yang dapat Anda lakukan di ruang obrolan.

Di bawah ini, kami menjelaskan cara [mengirim pesan](#chat-react-room-actions-sending-message) dan [menghapus pesan](#chat-react-room-actions-deleting-message).

### Mengirim Pesan
<a name="chat-react-room-actions-sending-message"></a>

Kelas `SendMessageRequest` memungkinkan pengiriman pesan di ruang obrolan. Di sini, Anda memodifikasi `App` untuk mengirim permintaan pesan menggunakan komponen yang Anda buat di [Buat Input Pesan](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-message-input) (di Bagian 1 tutorial ini).

Untuk memulai, tentukan properti boolean baru bernama `isSending` dengan hook `useState`. Gunakan properti baru ini untuk mengalihkan status nonaktif elemen `button` Anda, menggunakan konstanta `isSendDisabled`. Di handler peristiwa untuk `SendButton` Anda, kosongkan nilai untuk `messageToSend` dan atur `isSending` ke true.

*Karena Anda akan melakukan panggilan API dari tombol ini, penambahan boolean `isSending` akan membantu mencegah banyaknya panggilan API yang terjadi di waktu yang bersamaan, dengan menonaktifkan interaksi pengguna pada `SendButton` Anda hingga permintaan selesai.*

Catatan: Pengiriman pesan hanya akan berhasil jika Anda menambahkan kemampuan `SEND_MESSAGE` ke penyedia token, seperti yang dibahas di atas dalam [Mengenali Pesan yang Dikirim oleh Pengguna Saat ini](#chat-react-messages-recognize).

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const [isSending, setIsSending] = useState(false);

// ...

const onMessageSend = () => {
  setIsSending(true);
  setMessageToSend('');
};

// ...

const isSendDisabled = connectionState !== 'connected' || isSending;

// ...
```

Siapkan permintaan dengan membuat instans `SendMessageRequest` baru, dengan meneruskan konten pesan ke konstruktor. Setelah mengatur status `isSending` dan `messageToSend`, panggil metode `sendMessage`, yang mengirimkan permintaan ke ruang obrolan. Terakhir, hapus bendera `isSending` saat menerima konfirmasi atau penolakan permintaan.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...
import { ChatRoom, ConnectionState, SendMessageRequest } from 'amazon-ivs-chat-messaging'
// ...

const onMessageSend = async () => {
  const request = new SendMessageRequest(messageToSend);
  setIsSending(true);
  setMessageToSend('');

  try {
    const response = await room.sendMessage(request);
  } catch (e) {
    console.log(e);
    // handle the chat error here...
  } finally {
    setIsSending(false);
  }
};

// ...
```

Jalankan Chatterbox: coba mengirim pesan dengan menyusun pesan menggunakan `MessageBar` Anda dan mengetuk `SendButton` Anda. Anda akan melihat pesan terkirim Anda di-render dalam `MessageList` yang dibuat sebelumnya.

### Menghapus Pesan
<a name="chat-react-room-actions-deleting-message"></a>

Untuk menghapus pesan dari ruang obrolan, Anda harus memiliki kemampuan yang tepat. Kemampuan diberikan selama inisialisasi token obrolan yang Anda gunakan saat mengautentikasi ke ruang obrolan. Untuk keperluan bagian ini, `ServerApp` dari [Mengatur Authentication/Authorization Server Lokal](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-auth-server) (di Bagian 1 tutorial ini) memungkinkan Anda menentukan kemampuan moderator. Hal ini dilakukan di aplikasi Anda menggunakan objek `tokenProvider` yang Anda buat di [Membangun Penyedia Token](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-token-provider) (juga di Bagian 1).

Di sini, Anda memodifikasi `Message` dengan menambahkan fungsi untuk menghapus pesan.

Pertama, buka `App.tsx` dan tambahkan kemampuan `DELETE_MESSAGE`. (`capabilities` adalah parameter kedua dari fungsi `tokenProvider` Anda.)

Catatan: Ini adalah cara Anda `ServerApp` menginformasikan Obrolan IVS APIs bahwa pengguna yang dikaitkan dengan token obrolan yang dihasilkan dapat menghapus pesan di ruang obrolan. Dalam situasi dunia nyata, Anda mungkin akan memiliki logika backend yang lebih kompleks untuk mengelola kemampuan pengguna di infrastruktur aplikasi server Anda.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const [room] = useState(() =>
    new ChatRoom({
      regionOrUrl: process.env.REGION,
      tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE', 'DELETE_MESSAGE']),
    }),
);

// ...
```

Pada langkah berikutnya, Anda memperbarui `Message` untuk menampilkan tombol hapus.

Tentukan fungsi baru bernama `onDelete`, yang menerima string sebagai salah satu parameternya dan mengembalikan `Promise`. Untuk parameter string, berikan ID pesan komponen Anda.

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export type Props = {
  message: ChatMessage;
  onDelete(id: string): Promise<void>;
};

export const Message = ({ message, onDelete }: Props) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;
  const handleDelete = () => onDelete(message.id);

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <View style={styles.content}>
        <Text style={styles.textContent}>{message.content}</Text>
        <TouchableOpacity onPress={handleDelete}>
          <Text>Delete<Text/>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  content: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export const Message = ({ message, onDelete }) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;
  const handleDelete = () => onDelete(message.id);

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <View style={styles.content}>
        <Text style={styles.textContent}>{message.content}</Text>
        <TouchableOpacity onPress={handleDelete}>
          <Text>Delete<Text/>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  content: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------

Selanjutnya, Anda memperbarui `renderItem` untuk merefleksikan perubahan terbaru pada komponen `FlatList` Anda.

Di `App`, tentukan fungsi bernama `handleDeleteMessage` dan teruskan ke properti `MessageList onDelete`:

------
#### [ TypeScript ]

```
// App.tsx

// ...

const handleDeleteMessage = async (id: string) => {};

const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => {
  return (
    <Message key={item.id} message={item} onDelete={handleDeleteMessage} />
  );
}, [handleDeleteMessage]);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const handleDeleteMessage = async (id) => {};

const renderItem = useCallback(({ item }) => {
  return (
    <Message key={item.id} message={item} onDelete={handleDeleteMessage} />
  );
}, [handleDeleteMessage]);

// ...
```

------

Siapkan permintaan dengan membuat instans `DeleteMessageRequest` baru, dengan meneruskan ID pesan yang relevan ke parameter konstruktor, dan panggil `deleteMessage` yang menerima permintaan yang disiapkan di atas:

------
#### [ TypeScript ]

```
// App.tsx

// ...

const handleDeleteMessage = async (id: string) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const handleDeleteMessage = async (id) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------

Selanjutnya, Anda memperbarui status `messages` untuk merefleksikan daftar pesan baru yang menghilangkan pesan yang baru saja Anda hapus.

Di hook `useEffect`, dengarkan peristiwa `messageDelete` dan perbarui array status `messages` Anda dengan menghapus pesan yang mempunyai ID yang sama dengan parameter `message`.

Catatan: Peristiwa `messageDelete` dapat dimunculkan untuk pesan yang dihapus oleh pengguna saat ini atau pengguna lain di ruang. Dengan menanganinya di handler peristiwa (bukan di samping permintaan `deleteMessage`), Anda dapat menyatukan penanganan hapus-pesan.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteMessageEvent) => {
  setMessages((prev) => prev.filter((message) => message.id !== deleteMessageEvent.id));
});

return () => {
  // ...

  unsubscribeOnMessageDeleted();
};

// ...
```

Sekarang Anda dapat menghapus pengguna dari ruang obrolan di aplikasi obrolan Anda.

## Langkah Berikutnya
<a name="chat-react-messages-events-next-steps"></a>

Sebagai percobaan, coba implementasikan tindakan lain di suatu ruang, seperti memutus koneksi pengguna lain.

# SDK Pesan Klien Obrolan IVS: Praktik Terbaik React & React Native
<a name="chat-sdk-react-best-practices"></a>

Dokumen ini menjelaskan tentang praktik terpenting dalam menggunakan SDK Perpesanan Obrolan Amazon IVS untuk React dan React Native. Informasi ini akan memungkinkan Anda untuk membangun fungsionalitas obrolan umum di dalam aplikasi React, dan memberikan latar belakang yang Anda butuhkan untuk mempelajari lebih dalam tentang bagian SDK Perpesanan Obrolan IVS yang lebih canggih.

## Membuat Hook ChatRoom Initializer
<a name="chatroom-initializer-hook"></a>

Kelas `ChatRoom` berisi metode dan pendengar obrolan inti untuk mengelola status koneksi serta mendengarkan peristiwa seperti pesan diterima dan pesan dihapus. Di sini, kami akan menunjukkan cara menyimpan instans obrolan dengan benar di hook.

### Implementasi
<a name="chatroom-initializer-hook-implementation"></a>

------
#### [ TypeScript ]

```
// useChatRoom.ts

import React from 'react';
import { ChatRoom, ChatRoomConfig } from 'amazon-ivs-chat-messaging';

export const useChatRoom = (config: ChatRoomConfig) => {
  const [room] = React.useState(() => new ChatRoom(config));

  return { room };
};
```

------
#### [ JavaScript ]

```
import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

export const useChatRoom = (config) => {
  const [room] = React.useState(() => new ChatRoom(config));

  return { room };
};
```

------

Catatan: Kami tidak menggunakan metode `dispatch` dari hook `setState` karena Anda tidak dapat memperbarui parameter konfigurasi dengan cepat. SDK membuat instans sekali, dan pembaruan penyedia token tidak dimungkinkan.

**Penting**: Gunakan hook penginisialisasi `ChatRoom` sekali untuk menginisialisasi instans ruang obrolan baru.

### Contoh
<a name="chatroom-initializer-hook-example"></a>

**TypeScript/JavaScript**:

```
// ...

const MyChatScreen = () => {
  const userId = 'Mike';
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(ROOM_ID, ['SEND_MESSAGE']),
  });

  const handleConnect = () => {
    room.connect();
  };

  // ...
};

// ...
```

### Mendengarkan Status Koneksi
<a name="chatroom-initializer-hook-connection-state"></a>

Secara opsional, Anda dapat berlangganan pembaruan status koneksi di hook ruang obrolan Anda.

#### Implementasi
<a name="connection-state-implementation"></a>

------
#### [ TypeScript ]

```
// useChatRoom.ts

import React from 'react';
import { ChatRoom, ChatRoomConfig, ConnectionState } from 'amazon-ivs-chat-messaging';

export const useChatRoom = (config: ChatRoomConfig) => {
  const [room] = useState(() => new ChatRoom(config));

  const [state, setState] = React.useState<ConnectionState>('disconnected');

  React.useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, []);

  return { room, state };
};
```

------
#### [ JavaScript ]

```
// useChatRoom.js

import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

export const useChatRoom = (config) => {
  const [room] = useState(() => new ChatRoom(config));

  const [state, setState] = React.useState('disconnected');

  React.useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, []);

  return { room, state };
};
```

------

## ChatRoom Penyedia Instance
<a name="chatroom-instance-provider"></a>

Untuk menggunakan hook di komponen lain (guna menghindari penggunaan prop drilling), Anda dapat membuat penyedia ruang obrolan dengan menggunakan `context` React.

### Implementasi
<a name="chatroom-instance-provider-implementation"></a>

------
#### [ TypeScript ]

```
// ChatRoomContext.tsx

import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

const ChatRoomContext = React.createContext<ChatRoom | undefined>(undefined);

export const useChatRoomContext = () => {
  const context = React.useContext(ChatRoomContext);

  if (context === undefined) {
    throw new Error('useChatRoomContext must be within ChatRoomProvider');
  }

  return context;
};

export const ChatRoomProvider = ChatRoomContext.Provider;
```

------
#### [ JavaScript ]

```
// ChatRoomContext.jsx

import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

const ChatRoomContext = React.createContext(undefined);

export const useChatRoomContext = () => {
  const context = React.useContext(ChatRoomContext);

  if (context === undefined) {
    throw new Error('useChatRoomContext must be within ChatRoomProvider');
  }

  return context;
};

export const ChatRoomProvider = ChatRoomContext.Provider;
```

------

### Contoh
<a name="chatroom-instance-provider-example"></a>

Setelah membuat `ChatRoomProvider`, Anda dapat menggunakan instans Anda dengan `useChatRoomContext`.

**Penting**: Letakkan penyedia di tingkat root hanya jika Anda memerlukan akses ke `context` di antara layar obrolan dan komponen lain di tengah, untuk menghindari render ulang yang tidak perlu jika Anda mendengarkan koneksi. Jika tidak, letakkan penyedia sedekat mungkin ke layar obrolan.

**TypeScript/JavaScript**:

```
// AppContainer

const AppContainer = () => {
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(ROOM_ID, ['SEND_MESSAGE']),
  });

  return (
    <ChatRoomProvider value={room}>
      <MyChatScreen />
    </ChatRoomProvider>
  );
};

// MyChatScreen

const MyChatScreen = () => {
  const room = useChatRoomContext();

  const handleConnect = () => {
    room.connect();
  };
  // ...
};

// ...
```

## Membuat Pendengar Pesan
<a name="message-listener"></a>

Agar tetap mengetahui semua pesan masuk, Anda harus berlangganan peristiwa `message` dan `deleteMessage`. Berikut adalah beberapa kode yang menyediakan pesan obrolan untuk komponen Anda.

**Penting**: Untuk tujuan performa, kami memisahkan `ChatMessageContext` dari `ChatRoomProvider`, karena kami mungkin mendapatkan banyak render ulang saat pendengar pesan-obrolan memperbarui status pesannya. Ingatlah untuk menerapkan `ChatMessageContext` pada komponen tempat Anda akan menggunakan `ChatMessageProvider`.

### Implementasi
<a name="message-listener-implementation"></a>

------
#### [ TypeScript ]

```
// ChatMessagesContext.tsx

import React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useChatRoomContext } from './ChatRoomContext';

const ChatMessagesContext = React.createContext<ChatMessage[] | undefined>(undefined);

export const useChatMessagesContext = () => {
  const context = React.useContext(ChatMessagesContext);

  if (context === undefined) {
    throw new Error('useChatMessagesContext must be within ChatMessagesProvider);
  }

  return context;
};

export const ChatMessagesProvider = ({ children }: { children: React.ReactNode }) => {
  const room = useChatRoomContext();

  const [messages, setMessages] = React.useState<ChatMessage[]>([]);

  React.useEffect(() => {
    const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
      setMessages((msgs) => [message, ...msgs]);
    });

    const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteEvent) => {
      setMessages((prev) => prev.filter((message) => message.id !== deleteEvent.messageId));
    });

    return () => {
      unsubscribeOnMessageDeleted();
      unsubscribeOnMessageReceived();
    };
  }, [room]);

  return <ChatMessagesContext.Provider value={messages}>{children}</ChatMessagesContext.Provider>;
};
```

------
#### [ JavaScript ]

```
// ChatMessagesContext.jsx

import React from 'react';
import { useChatRoomContext } from './ChatRoomContext';

const ChatMessagesContext = React.createContext(undefined);

export const useChatMessagesContext = () => {
  const context = React.useContext(ChatMessagesContext);

  if (context === undefined) {
    throw new Error('useChatMessagesContext must be within ChatMessagesProvider);
  }

  return context;
};

export const ChatMessagesProvider = ({ children }) => {
  const room = useChatRoomContext();

  const [messages, setMessages] = React.useState([]);

  React.useEffect(() => {
    const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
      setMessages((msgs) => [message, ...msgs]);
    });

    const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteEvent) => {
      setMessages((prev) => prev.filter((message) => message.id !== deleteEvent.messageId));
    });

    return () => {
      unsubscribeOnMessageDeleted();
      unsubscribeOnMessageReceived();
    };
  }, [room]);

  return <ChatMessagesContext.Provider value={messages}>{children}</ChatMessagesContext.Provider>;
};
```

------

### Contoh di React
<a name="message-listener-example-react"></a>

**Penting**: Ingatlah untuk membungkus kontainer pesan Anda dengan `ChatMessagesProvider`. Baris `Message` adalah contoh komponen yang menampilkan konten pesan.

**TypeScript/JavaScript**:

```
// your message list component...

import React from 'react';
import { useChatMessagesContext } from './ChatMessagesContext';

const MessageListContainer = () => {
  const messages = useChatMessagesContext();

  return (
    <React.Fragment>
      {messages.map((message) => (
        <MessageRow message={message} />
      ))}
    </React.Fragment>
  );
};
```

### Contoh di React Native
<a name="message-listener-example-react-native"></a>

Secara default, `ChatMessage` berisi `id`, yang digunakan secara otomatis sebagai kunci React di `FlatList` untuk setiap baris; oleh karena itu, Anda tidak perlu menyediakan `keyExtractor`.

------
#### [ TypeScript ]

```
// MessageListContainer.tsx

import React from 'react';
import { ListRenderItemInfo, FlatList } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useChatMessagesContext } from './ChatMessagesContext';

const MessageListContainer = () => {
  const messages = useChatMessagesContext();

  const renderItem = useCallback(({ item }: ListRenderItemInfo<ChatMessage>) => <MessageRow />, []);

  return <FlatList data={messages} renderItem={renderItem} />;
};
```

------
#### [ JavaScript ]

```
// MessageListContainer.jsx

import React from 'react';
import { FlatList } from 'react-native';
import { useChatMessagesContext } from './ChatMessagesContext';

const MessageListContainer = () => {
  const messages = useChatMessagesContext();

  const renderItem = useCallback(({ item }) => <MessageRow />, []);

  return <FlatList data={messages} renderItem={renderItem} />;
};
```

------

## Beberapa Instans Ruang Obrolan dalam Aplikasi
<a name="multiple-chatroom-instances"></a>

Jika Anda menggunakan beberapa ruang obrolan secara bersamaan di aplikasi Anda, kami mengusulkan untuk membuat setiap penyedia bagi setiap obrolan dan menggunakannya di penyedia obrolan. Di dalam contoh ini, kami sedang membuat obrolan Bot Bantuan dan Bantuan Pelanggan. Kami membuat penyedia untuk keduanya.

------
#### [ TypeScript ]

```
// SupportChatProvider.tsx

import React from 'react';
import { SUPPORT_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';

export const SupportChatProvider = ({ children }: { children: React.ReactNode }) => {
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(SUPPORT_ROOM_ID, ['SEND_MESSAGE']),
  });

  return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};

// SalesChatProvider.tsx

import React from 'react';
import { SALES_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';

export const SalesChatProvider = ({ children }: { children: React.ReactNode }) => {
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(SALES_ROOM_ID, ['SEND_MESSAGE']),
  });

  return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
```

------
#### [ JavaScript ]

```
// SupportChatProvider.jsx

import React from 'react';
import { SUPPORT_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';

export const SupportChatProvider = ({ children }) => {
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(SUPPORT_ROOM_ID, ['SEND_MESSAGE']),
  });

  return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};

// SalesChatProvider.jsx

import React from 'react';
import { SALES_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';

export const SalesChatProvider = ({ children }) => {
  const { room } = useChatRoom({
    regionOrUrl: SOCKET_URL,
    tokenProvider: () => tokenProvider(SALES_ROOM_ID, ['SEND_MESSAGE']),
  });

  return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
```

------

### Contoh di React
<a name="multiple-chatroom-instances-example-react"></a>

Sekarang Anda dapat memanfaatkan penyedia obrolan berbeda yang menggunakan `ChatRoomProvider` yang sama. Kemudian, Anda dapat menggunakan kembali `useChatRoomContext` yang sama di dalam setiap layar/tampilan.

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

const App = () => {
  return (
    <Routes>
      <Route
        element={
          <SupportChatProvider>
            <SupportChatScreen />
          </SupportChatProvider>
        }
      />
      <Route
        element={
          <SalesChatProvider>
            <SalesChatScreen />
          </SalesChatProvider>
        }
      />
    </Routes>
  );
};
```

### Contoh di React Native
<a name="multiple-chatroom-instances-example-react-native"></a>

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

const App = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="SupportChat">
        <SupportChatProvider>
          <SupportChatScreen />
        </SupportChatProvider>
      </Stack.Screen>
      <Stack.Screen name="SalesChat">
        <SalesChatProvider>
          <SalesChatScreen />
        </SalesChatProvider>
      </Stack.Screen>
    </Stack.Navigator>
  );
};
```

**TypeScript/JavaScript**:

```
// SupportChatScreen.tsx / SupportChatScreen.jsx

// ...

const SupportChatScreen = () => {
  const room = useChatRoomContext();

  const handleConnect = () => {
    room.connect();
  };

  return (
    <>
      <Button title="Connect" onPress={handleConnect} />
      <MessageListContainer />
    </>
  );
};

// SalesChatScreen.tsx / SalesChatScreen.jsx

// ...

const SalesChatScreen = () => {
  const room = useChatRoomContext();

  const handleConnect = () => {
    room.connect();
  };

  return (
    <>
      <Button title="Connect" onPress={handleConnect} />
      <MessageListContainer />
    </>
  );
};
```