- First of all your are confident that you have implemented the dependency & the sdk
implementation("com.google.firebase:firebase-database:20.3.1")
- First of all you have to create data model class like this
data class FirebaseChatUserModel(
var name: String? = "",
var id: Int? = 0,
var image: String? = "",
var token:String?=""
):Serializable
data class FirebaseChatModel(
var message: String? = "",
var createdAt: Long? = 0,
var media: String? = "",
var from: FirebaseChatUserModel? = null,
var to: FirebaseChatUserModel? = null,
)
- In your fragment or activity initialize this
private lateinit var database: FirebaseDatabase
private var receiverDetails: FirebaseChatUserModel? = null
private var chatList: ArrayList<FirebaseChatModel> = ArrayList()
private var senderChatReference: DatabaseReference? = null
private var messaageAdapter: MessageAdapter? = null
private var receiverChatReference: DatabaseReference? = null
- This will come when you come from another activity or fragment to current fragment , which consists of that user to want chat.
requireArguments().getSerializable(CommonUtils.USER_MODEL)?.let {
receiverDetails = it as FirebaseChatUserModel
}
- In onCreate , first of all , call this function.
private fun doFirebaseSetup() {
database = FirebaseDatabase.getInstance()
senderChatReference =
database.reference.child(CommonUtils.MESSAGES).child(model.user!!.id.toString())
.child(receiverDetails?.id.toString())
receiverChatReference =
database.reference.child(CommonUtils.MESSAGES).child(receiverDetails?.id.toString())
.child(model.user!!.id.toString())
firebaseDataListeners()
}
- then paste the code of firebasedatalisteners
private fun firebaseDataListeners() {
senderChatReference?.addChildEventListener(chatAddedListener)
receiverChatReference?.addChildEventListener(chatReadListener)
}
- Then paste the variable chataddedlistner & chatreadlistener
private val chatReadListener = object : ChildEventListener {
@SuppressLint("SuspiciousIndentation")
override fun onChildAdded(
snapshot: DataSnapshot, previousChildName: String?,
) {
val chatOtherUser = snapshot.getValue(FirebaseChatModel::class.java)
if (chatOtherUser?.to?.id.toString() == model.user?.id.toString()) receiverChatReference?.child(
snapshot.key ?: ""
)
}
override fun onChildChanged(
snapshot: DataSnapshot, previousChildName: String?,
) {
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(
snapshot: DataSnapshot, previousChildName: String?,
) {
}
override fun onCancelled(error: DatabaseError) {
}
}
private val chatAddedListener = object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
try {
val chat = snapshot.getValue(FirebaseChatModel::class.java)
chat?.let {
val position = chatList.size
chatList.add(chat)
messaageAdapter?.notifyItemInserted(position)
}
binding.rvChatView.smoothScrollToPosition(chatList.size - 1)
binding.progressBar.visibility=View.GONE
} catch (e: Exception) {
showToast(e.localizedMessage ?: "")
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
TODO("Not yet implemented")
}
override fun onChildRemoved(snapshot: DataSnapshot) {
TODO("Not yet implemented")
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
TODO("Not yet implemented")
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
}
- add this method in onDestroy lifecycle
override fun onDestroy() {
senderChatReference?.removeEventListener(chatAddedListener)
receiverChatReference?.removeEventListener(chatReadListener)
super.onDestroy()
}
- set adapter if you have and call in onCreate method
private fun setAdapter() {
messaageAdapter = MessageAdapter(
requireContext(), model.user?.id!!.toInt(), chatList
)
binding.rvChatView.adapter = messaageAdapter
}
- on send button paste this method
private fun createAndSendChatMessage() {
binding.progressBar.visibility=View.VISIBLE
senderChatReference?.push()?.setValue(getChatModel())?.addOnCompleteListener {
receiverChatReference?.push()?.setValue(getChatModel())?.addOnCompleteListener {
val firebaseMessageReq = FirebaseMessageReq().apply {
to = receiverDetails?.token
data = Data(
title = receiverDetails?.name,
body = binding.edtMessage.text.toString(),
type = "Chat",
receiver = FirebaseChatUserModel(
name = model.user?.name,
id=model.user?.id,
image = model.user?.image,
token = model.sharedPreference.getString(CommonUtils.FCM_TOKEN)
)
)
}
model.sendNotification(firebaseMessageReq){}
binding.progressBar.visibility=View.GONE
binding.edtMessage.text = null
binding.edtMessage.isEnabled = true
}
}
}
- here i have taken model.sendnotification for api call and request for push notification. Comment down if you dont want.
- Here is the adapter class
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.infoicon.epcproject.databinding.MessageIncomingBinding
import com.infoicon.epcproject.databinding.MessageOutgoingBinding
import com.infoicon.epcproject.models.firebasedatabase.FirebaseChatModel
import com.infoicon.epcproject.utils.CommonUtils
import com.infoicon.epcproject.utils.TimeAgo2
class MessageAdapter(
private val context: Context,
private var currentUserId: Int,
private val chatlist: List<FirebaseChatModel> // Changed to List for better performance
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var timeConverted: String
private val itemSend = 1
private val itemReceive = 2
private lateinit var myFinalValue: String
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
itemSend -> {
val binding = MessageOutgoingBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
OutgoingMessageViewHolder(binding)
}
else -> {
val binding = MessageIncomingBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
IncomingMessageViewHolder(binding)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = chatlist[position]
when (holder) {
is OutgoingMessageViewHolder -> holder.bind(item)
is IncomingMessageViewHolder -> holder.bind(item)
}
}
override fun getItemCount(): Int {
return chatlist.size
}
override fun getItemViewType(position: Int): Int {
val item = chatlist[position]
return if (item.from?.id == currentUserId) itemSend else itemReceive
}
inner class OutgoingMessageViewHolder(private val binding: MessageOutgoingBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: FirebaseChatModel) {
binding.txtMessage.text = item.message
timeConverted = CommonUtils.convertLongToTime(item.createdAt!!)
myFinalValue = TimeAgo2.convertTimeToText(context,timeConverted).toString()
binding.txtTimeStamp.text = myFinalValue
}
}
inner class IncomingMessageViewHolder(private val binding: MessageIncomingBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: FirebaseChatModel) {
val context=binding.root.context
binding.txtMessage.text = item.message
binding.txtTimeStamp.text =
TimeAgo2.convertTimeToText(context,CommonUtils.convertLongToTime(item.createdAt!!))
.toString()
}
}
}
- here is the message_outgoing layout
<?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:paddingEnd="@dimen/_12sdp"
android:paddingStart="@dimen/_35sdp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_5sdp"
android:layout_marginBottom="@dimen/_5sdp"
android:background="@drawable/background_outgoing_message"
android:fontFamily="@font/segoeui"
android:padding="@dimen/_12sdp"
android:textAlignment="textEnd"
app:layout_constraintHorizontal_bias="1"
android:text="Hello, this is an outgoing message. afasasfasf"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTimeStamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="timestamp"
android:fontFamily="@font/seguisb"
android:layout_marginEnd="@dimen/_3sdp"
android:textSize="@dimen/_10sdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/txtMessage"
app:layout_constraintTop_toBottomOf="@+id/txtMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>
- here is the message_incoming layout
<?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:paddingStart="@dimen/_12sdp"
android:paddingEnd="@dimen/_35sdp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0"
android:background="@drawable/background_incoming_message"
android:fontFamily="@font/segoeui"
android:padding="@dimen/_12sdp"
android:text="Hello, this is an incoming message."
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTimeStamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/seguisb"
android:text="timestamp"
android:textSize="@dimen/_10sdp"
android:layout_marginStart="@dimen/_3sdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtMessage"
app:layout_constraintTop_toBottomOf="@+id/txtMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>
- here is the mainlayout
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".fragments.FacilityMessageFragment">
<!-- RecyclerView for displaying chat messages -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvChatView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="@dimen/_4sdp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/btnSend"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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"
app:layout_constraintVertical_bias="0.5" />
<ImageView
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/custom_trans_background"
android:src="@drawable/sendbtn"
app:layout_constraintBottom_toBottomOf="@+id/edtMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/edtMessage"
app:layout_constraintTop_toTopOf="@+id/edtMessage" />
<EditText
android:id="@+id/edtMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_7sdp"
android:layout_marginBottom="@dimen/_7sdp"
android:background="@drawable/background_green_corner"
android:hint="@string/enter_your_message"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingVertical="@dimen/_14sdp"
android:singleLine="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnSend"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rvChatView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- here is the api call if you want push notification
fun sendNotification(
firebaseMessageReq: FirebaseMessageReq,
result: (FirebaseResponse) -> Unit
) {
viewModelScope.launch {
executeApiRequest({ getFirebaseClient().sendNotification(firebaseMessageReq) },
{ result(it) },
{ throwable ->
val errorMessage = throwable.localizedMessage ?: "Unknown error occurred"
CommonUtils.showToast(errorMessage)
retrofitError.postValue(errorRetrofit(throwable))
})
}
}
data class FirebaseResponse(
val failure: Int?,
val success: Int?
)
data class FirebaseMessageReq(
var `data`: Data?=null,
var to: String?=null
)
data class Data(
var body: String?=null,
var title: String?=null,
var type: String?=null,
var receiver: FirebaseChatUserModel?=null
)
- in your api calling interface add this
@POST(ApiEndPoints.FIREBASE_END_POINTS)
suspend fun sendNotification(
@Body firebaseMessageReq: FirebaseMessageReq
): Response<FirebaseResponse>
- your retrofit function will be
const val FIREBASE_URL = "https://fcm.googleapis.com/"
fun getFirebaseClient(): RetrofitUrl {
val client = getOkHttpClient(false)
return Retrofit.Builder().baseUrl(FIREBASE_URL).client(client)
.addConverterFactory(GsonConverterFactory.create()).build()
.create(RetrofitUrl::class.java)
}
- in your header, in interceptor
add this,
header(CommonUtils.AUTHORIZATION, BEARER + ApiEndPoints.SERVER_KEY)
//here server key will be from firebase and bearer is "Bearer"
- your messagingservice file will be
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.RingtoneManager
import android.os.Build
import android.os.PowerManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.infoicon.epcproject.R
import com.infoicon.epcproject.activity.HomeActivity
import com.infoicon.epcproject.models.NotificationData
import com.infoicon.epcproject.models.firebasedatabase.FirebaseChatUserModel
import com.infoicon.epcproject.utils.CommonUtils
import org.json.JSONObject
import java.util.Locale
class MyFirebaseMessagingService : FirebaseMessagingService() {
private var wakeLock: PowerManager.WakeLock? = null
override fun onNewToken(token: String) {
super.onNewToken(token)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val notificationData = message.data.let { data ->
val receiverData = data["receiver"]
Log.e(CommonUtils.TAG, "onMessageReceived: $receiverData")
val receiverModel = if (receiverData != null) {
// Parse receiver data here and create FirebaseChatUserModel object
// For example:
val receiverJson = JSONObject(receiverData)
FirebaseChatUserModel(
id = receiverJson.getInt("id"),
name = receiverJson.getString("name"),
token = receiverJson.getString("token")
// Add other fields as needed
)
} else {
null
}
NotificationData(
type = data["type"] ?: "default",
title = data["title"] ?: "No Title",
body = data["body"] ?: "No Body",
receiver = receiverModel
)
}
Log.e(CommonUtils.TAG, "onMessageReceived: $notificationData")
if (notificationData.type.isNotEmpty()) {
createNotificationChannel(notificationData.type)
createNotification(notificationData, applicationContext)
}
}
private fun createNotificationChannel(channelId: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Channel ${
channelId.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
}
}"
val descriptionText = "Description for channel $channelId"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(channelId, name, importance).apply {
description = descriptionText
enableVibration(true)
enableLights(true)
lightColor = Color.GREEN
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
}
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotification(notificationData: NotificationData, context: Context) {
val notificationIntent = Intent(context, HomeActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("type", notificationData.type)
putExtra("receiver",notificationData.receiver)
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
//val vibrationPattern = longArrayOf(0, 1000, 500, 1000) // Define your custom vibration pattern here
val bigTextStyle = NotificationCompat.BigTextStyle()
.bigText(notificationData.body)
// Acquire wake lock
acquireWakeLock(context)
val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(context, notificationData.type)
.setSmallIcon(R.drawable.logo1)
.setContentTitle(notificationData.title)
.setContentText(notificationData.body)
.setLights(Color.GREEN, 1000, 300)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.logo1))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSound(alarmSound)
.setStyle(bigTextStyle)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder.setChannelId(notificationData.type)
}
val notification = notificationBuilder.build()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationData.type.hashCode(), notification)
}
private fun acquireWakeLock(context: Context) {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
powerManager.newWakeLock(
PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
"MyApp::MyWakelockTag"
)
} else {
powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
"MyApp::MyWakelockTag"
)
}
wakeLock?.acquire(10 * 1000L /*10 seconds*/)
}
override fun onDestroy() {
super.onDestroy()
// Release wake lock
wakeLock?.release()
}
}
- add permission in this mainfest
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
- also add your service file in application body in manifest.xml
<service
android:name=".receiver.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
- now handle the received notification and redirect to that activity and pass data through intent or bundle
fun checkNotificationTap() {
lifecycleScope.launch {
val dataFromNotification = intent.getStringExtra("type")
// Log.e(TAG, "checkNotificationTap: $dataFromNotification", )
val receiverDetails = intent?.getSerializableExtra("receiver") as? FirebaseChatUserModel
if (dataFromNotification != null) {
when (dataFromNotification) {
"Chat" -> {
updateFragmentLabel(
R.id.facilityMessageFragment,
receiverDetails?.name ?: ""
)
navController.navigate(
R.id.facilityMessageFragment,
bundleOf(CommonUtils.USER_MODEL to receiverDetails)
)
}
"review" -> {
navController.navigate(R.id.ratingReviewsFragment)
}
"default" -> {
navController.navigate(R.id.notficationFragment)
}
}
intent.removeExtra("type")
}
}
}
- want to get firebase token use this
private fun initFirebaseToken() {
lifecycleScope.launch {
FirebaseMessaging.getInstance().token
.addOnSuccessListener { token ->
// Log.e(TAG, "FCM Token: $token")
homeViewModel.sharedPreference.putString(FCM_TOKEN,token)
// Handle the token here
}
}
}
0 Comments