Android FCM Topic Grouping Message In Kotlin

In this Previous tutorial we have seen example FCM Push Notification , In this tutorial we will see how to do grouping of  messages using FCM Topic Messaging . 

FCM Topic Messaging is based on the publish/subscribe model, FCM topic messaging allows you to send a message to segment or group of device that have subscribed for a particular topic , while composing push notification when you add topic message FCM handles routing and delivering the message reliably to the right devices .

For Example , Let say news app which sends news notification for 3 different languages (English , Spanish , Russian ) , users can opt for language of his interest by subscribing from the client and while composing notification when you select the particular language only these users we will get message who have subscribed for that topic .

  • One app instance can be subscribed to no more than 2000 topics.  
  • The frequency of new subscriptions is rate-limited per project. 
  • If you send too many subscription requests in a short period of time, FCM servers will respond with a 429 RESOURCE_EXHAUSTED ("quota exceeded") response. Retry with exponential backoff.

1. Storing Data In App's SharedPreference 


To Store the topic name with status using sharedPreference .

file : PreferenceManager.kt
package com.tutorialsbuzz.fcmdemo

class PreferenceManager internal constructor(context: Context) {

    private val mPrefs: SharedPreferences

    init {
        mPrefs = context.getSharedPreferences(PREFERENCES, 0)
    }

    companion object {
        private const val PREFERENCES = "settings"
        private var mPreferenceManager: PreferenceManager? = null
        fun getInstance(context: Context): PreferenceManager? {
            if (mPreferenceManager == null) {
                mPreferenceManager = PreferenceManager(context)
            }
            return mPreferenceManager
        }
    }

    fun getStoredJSONOptimization(): List<Model>? {
        val gson = Gson()
        val response = mPrefs.getString(
            Name.JSON_DATA,
            null
        )
            ?: return null
        return gson.fromJson(response, Array<Model>::class.java).toList()
    }

    fun storeJSONOptimization(value: String?) {
        mPrefs.edit().putString(
            Name.JSON_DATA,
            value
        ).apply()
    }

    object Name {
        const val JSON_DATA = "JSON_DATA"
    }

}


1. XML Layout For RecyclerView Row Item


XML Layout for RecyclerView Items

file : row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/subscribeLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">

    <TextView
        android:id="@+id/subscribeLabel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginStart="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:layout_weight="1"
        android:text="English"
        android:textSize="22sp" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:visibility="gone" />

    <TextView
        android:id="@+id/subscribe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="20dp"
        android:text="@string/subscribe"
        android:textSize="16sp"
        android:textStyle="bold" />

</RelativeLayout>


1. Adapter


Adapter For RecyclerView For displaying list of topics user subscribed and unsubscribed and updating the status when user updates the status .

file : SubscriptionAdapter.kt
package com.tutorialsbuzz.fcmdemo

class SubscriptionAdapter(val modelList: List<Model>, val context: Context) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val itemViewHolder = (holder as ViewHolder)
        itemViewHolder.bind(modelList.get(position));
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        return ViewHolder(layoutInflater.inflate(R.layout.row_item, parent, false))
    }

    override fun getItemCount(): Int {
        return modelList.size;
    }


    lateinit var mClickListener: ClickListener

    fun setOnItemClickListener(aClickListener: ClickListener) {
        mClickListener = aClickListener
    }

    interface ClickListener {
        fun onClick(pos: Int, aView: View)
    }


    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
        View.OnClickListener {

        fun bind(model: Model): Unit {
            itemView.subscribeLabel.text = "${(adapterPosition + 1)} ) ${model.name}"

            if (model.status) {
                itemView.subscribe.setText(R.string.subscribed)
                itemView.subscribe.setTextColor(ContextCompat.getColor(context, R.color.subscribed))
            } else {
                itemView.subscribe.setText(R.string.subscribe)
                itemView.subscribe.setTextColor(ContextCompat.getColor(context, R.color.subscribe))
            }

            itemView.subscribe.visibility = View.VISIBLE
            itemView.progressBar.visibility = View.GONE
            itemView.subscribe.setOnClickListener(this)
        }

        override fun onClick(view: View?) {

            if (view?.id == itemView.subscribe.id) {

                itemView.subscribe.visibility = View.GONE
                itemView.progressBar.visibility = View.VISIBLE

                mClickListener.onClick(adapterPosition, itemView)
            }

        }
    }

}



1. XML Layout for Activity


file : activity_subscribe.xml
<?xml version="1.0" encoding="utf-8"?>

<androidx.recyclerview.widget.RecyclerView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/subscriptionList"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />


1. Activity


Subscribe the client app to a topic.

// subscribe for notification topic
fun subscribeToTopics(topic: String, position: Int) {
 FirebaseMessaging.getInstance().subscribeToTopic(topic)
	.addOnCompleteListener { task -> //String msg = getString(R.string.msg_subscribed);
		if (task.isSuccessful) {
			//Topic Subscribed Succesfully Todo Task
		}
	}
}

UnSubscribe the client app to a topic.

// Unsubscribe for notification topic
fun UnSubscribeToTopics(topic: String, position: Int) {
 FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
	.addOnCompleteListener { task -> //String msg = getString(R.string.msg_subscribed);
		if (task.isSuccessful) {
			//Topic Unsubscribed Succesfully Todo Task
		}
	}
}

Complete code for Subscribe Notification Activity

file : SubscribeNotificationTopicsActivity.kt
package com.tutorialsbuzz.fcmdemo

class SubscribeNotificationTopicsActivity : AppCompatActivity() {

    private lateinit var adapter: SubscriptionAdapter

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

        setContentView(R.layout.activity_subscribe)

        subscriptionList.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)

        subscriptionList.addItemDecoration(SimpleDividerItemDecoration(this))

        adapter = SubscriptionAdapter(readFromAsset(), this)
        subscriptionList.adapter = adapter

        adapter.setOnItemClickListener(object : SubscriptionAdapter.ClickListener {
            override fun onClick(pos: Int, aView: View) {

                // status false user not subscribed
                if (!adapter.modelList.get(pos).status) {
                    subscribeToTopics(adapter.modelList.get(pos).name, pos)
                } else {
                    UnSubscribeToTopics(adapter.modelList.get(pos).name, pos)
                }

            }
        })
    }


    fun updateList(position: Int, status: Boolean) {

        adapter.modelList.get(position).status = status
        adapter.notifyItemChanged(position)

        val preferenceManager =
            PreferenceManager.getInstance(this@SubscribeNotificationTopicsActivity)
        preferenceManager?.storeJSONOptimization(Gson().toJson(adapter.modelList))

    }

    // subscribe for notification topic
    fun subscribeToTopics(topic: String, position: Int) {
        FirebaseMessaging.getInstance().subscribeToTopic(topic)
            .addOnCompleteListener { task -> //String msg = getString(R.string.msg_subscribed);
                if (task.isSuccessful) {
                    updateList(position, true)
                    showToast("Subscribe To " + topic)
                }
            }
    }

    // Unsubscribe for notification topic
    fun UnSubscribeToTopics(topic: String, position: Int) {
        FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
            .addOnCompleteListener { task -> //String msg = getString(R.string.msg_subscribed);
                if (task.isSuccessful) {
                    updateList(position, false)
                    showToast("UnSubscribed For " + topic)
                }
            }
    }

    fun showToast(msg: String) {
        Toast.makeText(
            this@SubscribeNotificationTopicsActivity,
            msg,
            Toast.LENGTH_SHORT
        ).show()
    }


    private fun readFromAsset(): List<Model> {
            /** Find Code for Read from Asset from 
                Below Download Code Link**/
    }

}



Composing Notification From Firebase Console 






No comments:

Post a Comment