Aplikasi Pesan Singkat Sederhana Menggunakan Rainbow SDK – Menerima dan Membalas Pesan
Halo teman-teman,
Pada tutorial sebelumnya kita telah berhasil melakukan inisialisasi Rainbow SDK pada project kita.
💻 Mulai Belajar Pemrograman
Belajar pemrograman di Dicoding Academy dan mulai perjalanan Anda sebagai developer profesional.
Daftar SekarangSelanjutnya, kita akan membuat aplikasi kita dapat menerima dan membalas pesan singkat antar kontak secara realtime. Jika belum menginisialisasi Rainbow SDK pada project, silakan cek tutorial sebelumnya yaitu Aplikasi Pesan Singkat Sederhana Menggunakan Rainbow SDK – Inisialisasi Komponen Rainbow API.
Komponen Adapter
Pertama kita membutuhkan sebuah adapter untuk menampilkan data tiap kontak, untuk itu kita akan membuat sebuah layout terlebih dahulu seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/item_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/img_user_photo" android:layout_width="64dp" android:layout_height="64dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/img_user_status" android:layout_width="17dp" android:layout_height="17dp" app:layout_constraintBottom_toBottomOf="@+id/img_user_photo" app:layout_constraintEnd_toEndOf="@+id/img_user_photo" app:srcCompat="@color/design_default_color_primary_dark" /> <TextView android:id="@+id/txt_user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:textAppearance="@style/TextAppearance.AppCompat.Title" app:layout_constraintStart_toEndOf="@id/img_user_photo" app:layout_constraintTop_toTopOf="parent" tools:text="User name" /> <TextView android:id="@+id/txt_user_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:layout_marginBottom="4dp" android:textAppearance="@style/TextAppearance.AppCompat.Caption" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@id/txt_user_name" app:layout_constraintTop_toBottomOf="@id/txt_user_name" tools:text="Offline" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Pada berkas layout di atas memiliki beberapa view yaitu CircleImageView untuk menampilkan gambar pengguna dan status pengguna, dan juga TextView untuk menampilkan nama pengguna dan status pengguna.
Selanjutnya kita membuat kelas Adapter yang di dalamnya terdapat kelas holder yang akan digunakan untuk me-render tampilan aplikasi menjadi seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
class ContactListAdapter( private val context: Context ) : ListAdapter<IRainbowContact, ContactListAdapter.ContactViewHolder>( object : DiffUtil.ItemCallback<IRainbowContact>() { override fun areItemsTheSame(oldItem: IRainbowContact, newItem: IRainbowContact): Boolean { return oldItem == newItem } override fun areContentsTheSame( oldItem: IRainbowContact, newItem: IRainbowContact ): Boolean { return oldItem.id == newItem.id } } ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder { return ContactViewHolder( LayoutInflater .from(context) .inflate(R.layout.item_contact, parent, false) ) } override fun onBindViewHolder(holder: ContactViewHolder, position: Int) { holder.bind(getItem(position)) } inner class ContactViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val tvUserName = view.findViewById<TextView>(R.id.txt_user_name) private val tvUserStatus = view.findViewById<TextView>(R.id.txt_user_status) private val imgUserStatus = view.findViewById<CircleImageView>(R.id.img_user_status) private val imgUserPhoto = view.findViewById<CircleImageView>(R.id.img_user_photo) private val layout = view.findViewById<ConstraintLayout>(R.id.item_layout) fun bind(contact: IRainbowContact) { val userFullName = "${contact.firstName} ${contact.lastName}" val userPhoto = contact.photo tvUserName.text = userFullName imgUserPhoto.setImageBitmap(userPhoto) layout.setOnClickListener { view -> val intent = Intent(view.context, ChatRoomActivity::class.java) intent.putExtra(ARG_CONTACT_ID, contact.jid) intent.putExtra(ARG_USERNAME, userFullName) view.context.startActivity(intent) } when (contact.presence) { RainbowPresence.ONLINE -> { tvUserStatus.text = context.getString(R.string.status_online) imgUserStatus.setImageDrawable(ColorDrawable(Color.GREEN)) } RainbowPresence.OFFLINE -> { tvUserStatus.text = context.getString(R.string.status_offline) imgUserStatus.setImageDrawable(ColorDrawable(Color.GRAY)) } RainbowPresence.MOBILE_ONLINE -> { tvUserStatus.text = context.getString(R.string.status_on_mobile) imgUserStatus.setImageDrawable(ColorDrawable(Color.BLUE)) } RainbowPresence.AWAY -> { tvUserStatus.text = context.getString(R.string.status_away) imgUserStatus.setImageDrawable(ColorDrawable(Color.YELLOW)) } RainbowPresence.BUSY -> { tvUserStatus.text = context.getString(R.string.status_busy) imgUserStatus.setImageDrawable(ColorDrawable(Color.CYAN)) } RainbowPresence.DND -> { tvUserStatus.text = context.getString(R.string.status_do_not_distrub) imgUserStatus.setImageDrawable(ColorDrawable(Color.RED)) } RainbowPresence.DND_PRESENTATION -> { tvUserStatus.text = context.getString(R.string.status_presenting) imgUserStatus.setImageDrawable(ColorDrawable(Color.RED)) } else -> { tvUserStatus.text = context.getString(R.string.status_unknown) imgUserStatus.setImageDrawable(ColorDrawable(Color.GRAY)) } } } } } |
Fungsi switch statement di atas adalah untuk mengecek status kontak apakah sedang online, offline atau yang lainya.
Kelas adapter sendiri akan digunakan untuk menjembatani data yang akan ditampilkan dengan antarmuka aplikasi.
Halaman List Kontak
Pada halaman kontak hanya terdapat satu buah widget, yaitu widget RecyclerView dan akan digunakan untuk menampilkan daftar kontak seperti berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.listroom.ContactListActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_user_list" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/item_contact" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Kemudian pada kelas ContactListActivity terdapat inisialisasi ContactListAdapter, RecyclerView dan pemanggilan beberapa static method dari kelas RainbowConnection yang sebelumnya sudah dibuat seperti berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
class ContactListActivity : AppCompatActivity(), RainbowConnectionListener.Connection, RainbowConnectionListener.Login { private lateinit var contactListAdapter: ContactListAdapter private lateinit var contacts: List<IRainbowContact> private val contactChangeListener = IItemListChangeListener { RainbowConnection.registerContactChangeListener(contactListener) } private val contactListener = object : RainbowConnectionListener.Contact() { override fun contactUpdated() { contacts = RainbowConnection.rainbowContacts contactListAdapter.submitList(contacts) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_contact_list) contactListAdapter = ContactListAdapter(this) rv_user_list.layoutManager = LinearLayoutManager(this) rv_user_list.adapter = contactListAdapter RainbowConnection.startConnection(this) } override fun onDestroy() { super.onDestroy() RainbowConnection.unregisterAllRainbowContact(contactChangeListener) RainbowConnection.unregisterContactChangeListener(contactListener) } override fun onSignInSuccess() { Toast.makeText( this, "Sign in to Rainbow API success!", Toast.LENGTH_SHORT ).show() RainbowConnection.registerAllRainbowContact(contactChangeListener) } override fun onSignInFailed(error: String) { Toast.makeText( this@ContactListActivity, "Sign in to Rainbow API failed: $error", Toast.LENGTH_SHORT ).show() } /** email = "user1@email.com" password = "Test1_1234" email = "user2@email.com" password = "Test2_1234" */ override fun onConnectionSuccess() { val emailRainbow = "user1@email.com" val passwordRainbow = "Test1_1234" RainbowConnection.startSignIn(emailRainbow, passwordRainbow, this) } override fun onConnectionFailed(error: String) { Toast.makeText( this, "Connection to Rainbow API failed: $error", Toast.LENGTH_SHORT ).show() } } |
Setelah selesai kamu bisa langsung mencobanya. Jika tidak terjadi eror maka hasilnya adalah seperti berikut :
Oke sekarang kita akan melanjutkan project dengan membuat kelas ChatRoomActivity, di mana pada kelas ini kita akan menampilkan daftar pesan antara kita dan pengguna lain yang akan kita hubungi. Pada kelas ini akan menerima JID dari kontak yang kita pilih dari daftar kontak dan kemudian menampilkan semua daftar pesan antara kontak tersebut dan pengguna yang sedang terhubung ke aplikasi. Kelas ChatRoomActivity akan mengimplementasi IItemChaneListener agar jika terjadi perubahan pada daftar pesan akan langsung diperbaharui secara real time. Kelas ChatRoomActivity akan seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
class ChatRoomActivity : AppCompatActivity(), IItemListChangeListener { private lateinit var contactJId: String private lateinit var username: String private lateinit var chatListAdapter: ChatListAdapter private lateinit var conversation: IRainbowConversation override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_chat_room) contactJId = intent.getStringExtra(ARG_CONTACT_ID) as String username = intent.getStringExtra(ARG_USERNAME) as String supportActionBar?.title = username chatListAdapter = ChatListAdapter(mutableListOf()) rv_chat_list.layoutManager = LinearLayoutManager(this) rv_chat_list.adapter = chatListAdapter conversation = RainbowConnection.getConversationFromContact(contactJId) conversation.messages.registerChangeListener(this) RainbowConnection.getMessagesFromConversation(conversation) btn_send_message.setOnClickListener { val message = edt_message_input.text.toString() RainbowConnection.sendMessageToConversation(conversation, message) edt_message_input.setText("") } } override fun dataChanged() { runOnUiThread { chatListAdapter.submitList(conversation.messages.copyOfDataList) rv_chat_list.smoothScrollToPosition(conversation.messages.copyOfDataList.lastIndex) } } override fun onDestroy() { super.onDestroy() conversation.messages.unregisterChangeListener(this) } companion object { const val ARG_CONTACT_ID = "contact_id" const val ARG_USERNAME = "username" } } |
Fungsi Rainbow SDK di dalam Kelas ChatRoomActivity
Di dalam kelas ChatRoomActivity terdapat beberapa fungsi dari Rainbow SDK yang memiliki kegunaan sebagai berikut :
- getConversationFromContact(contactJId)
Fungsi ini digunakan untuk mendapatkan data percakapan berdasarkan JID kontak yang telah kita berikan. - getMessagesFromConversation(conversation)
Fungsi ini digunkaan untuk mendapatkan daftar pesan yang ada pada data percakapan yang telah kita ambil sebelumnya. - sendMessageToConversation(conversation, message)
Fungsi ini digunakan untuk mengirimkan pesan baru ke data percakapan.
Untuk layout halaman chat room kita membutuhkan RecyclerView, EditText dan sebuah Button yang akan digunakan untuk menampilkan daftar kontak dan mengirim pesan seperti berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.chatroom.ChatRoomActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_chat_list" android:layout_width="match_parent" android:layout_height="0dp" android:clipToPadding="false" android:overScrollMode="never" android:paddingTop="20dp" android:paddingBottom="20dp" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/item_chat_send" /> <EditText android:id="@+id/edt_message_input" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:background="@drawable/rounded_message_field" android:ems="10" android:hint="Type message here" android:inputType="textMultiLine" android:padding="12dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="@id/guideline" app:layout_constraintEnd_toStartOf="@+id/btn_send_message" app:layout_constraintStart_toStartOf="parent" /> <ImageButton android:id="@+id/btn_send_message" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginEnd="10dp" android:background="@drawable/rounded_rectangle_received" android:src="@drawable/ic_send" app:layout_constraintBottom_toBottomOf="@id/edt_message_input" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/edt_message_input" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_end="75dp" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Tahap selanjutnya adalah kita perlu membuat berkas layout dengan nama item_chat_received dan item_chat_send untuk menampilkan setiap item pesan sebagai berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tv_received_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="12dp" android:layout_marginEnd="64dp" android:layout_marginBottom="5dp" android:maxWidth="290dp" android:textSize="17sp" android:background="@drawable/rounded_rectangle_received" android:paddingStart="16dp" android:paddingTop="8dp" android:paddingEnd="16dp" android:paddingBottom="8dp" android:textColor="#272727" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.04" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="hi man, how are you?" /> <TextView android:id="@+id/tv_received_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:textSize="12sp" android:visibility="gone" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@id/tv_received_message" app:layout_constraintTop_toBottomOf="@id/tv_received_message" tools:text="11:40" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Berikut merupakan kode pada berkas item_chat_send.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="4dp" android:paddingTop="4dp" android:paddingEnd="4dp"> <TextView android:id="@+id/tv_sender_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="64dp" android:layout_marginEnd="16dp" android:layout_marginBottom="5dp" android:textSize="17sp" android:background="@drawable/rounded_rectangle_sender" android:maxWidth="290dp" android:paddingStart="16dp" android:paddingTop="8dp" android:paddingEnd="16dp" android:paddingBottom="8dp" android:textColor="#ffffff" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="hello, hello!" /> <TextView android:id="@+id/tv_sender_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:layout_marginEnd="4dp" android:layout_marginBottom="5dp" android:textSize="12sp" android:visibility="gone" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/tv_sender_message" app:layout_constraintTop_toBottomOf="@id/tv_sender_message" tools:text="11:40" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Layout item_chat_received akan digunakan untuk menampilkan pesan yang kita terima dari pengguna lain dan layout item_chat_send akan digunakan untuk menampilkan pesan yang kita kirimkan. Selanjutnya kita memerlukan kelas Adapter yang akan kita beri nama ChatListAdapter yang berguna untuk menampilkan setiap item menjadi list. Kelas ChatListAdapter akan seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
class ChatListAdapter(private val messages: MutableList<IMMessage>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { private const val VIEW_TYPE_MESSAGE_SENT = 1 private const val VIEW_TYPE_MESSAGE_RECEIVED = 2 } fun submitList(updatedMessage: MutableList<IMMessage>){ messages.clear() messages.addAll(updatedMessage) notifyDataSetChanged() } override fun getItemViewType(position: Int): Int { val message = messages[position] return if (message.isMsgSent) { VIEW_TYPE_MESSAGE_SENT } else { VIEW_TYPE_MESSAGE_RECEIVED } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { VIEW_TYPE_MESSAGE_RECEIVED -> { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_chat_received, parent, false) ChatReceivedViewHolder(view) } VIEW_TYPE_MESSAGE_SENT -> { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_chat_send, parent, false) ChatSenderViewHolder(view) } else -> throw IllegalArgumentException("Unknown View Type") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(holder){ is ChatSenderViewHolder -> { holder.bindSenderContent(messages[position]) } is ChatReceivedViewHolder -> { holder.bindReceivedContent(messages[position]) } else -> throw IllegalArgumentException("Unknown View Type") } } override fun getItemCount() = messages.size } |
Pada kelas Adapter ini kita akan menampilkan pesan menjadi dua tipe yang berbeda, yaitu pesan yang kita terima dan pesan yang kita kirimkan. Untuk itu, kita membuat 2 variable konstan sebagai tipe dari tampilan yang akan kita gunakan. Kita juga perlu melakukkan override pada fungsi getItemViewType dan memberikan kondisi jenis pesan tersebut dan mengembalikan nilai yang sesuai, sehingga adapter mengetahui dan menggunakan layout yang diperlukan pada fungsi onCreateViewHolder.
Setelah selesai, jalankan project Anda. Jika tidak terjadi eror maka tampilan aplikasi akan seperti berikut:
Sekian tutorial mengirimkan pesan antar pengguna melalui kontak secara real time. Semoga artikel ini bermanfaat buatmu.
Jika ada pertanyaan, silakan tinggalkan komentar di halaman blog ini ya. Kami akan coba membantumu!
Aplikasi Pesan Singkat Sederhana Menggunakan Rainbow SDK – Menerima dan Membalas Pesan-end
Simak artikel terkait: