adds firebase connection and notifications

- notification handler pushes background and
  foreground notifications to the phone directly
- firebase connection pushes remote notifications
  in background and foreground
- firebase unique token currently logged only, should
  be send directly to server via rest
This commit is contained in:
Raphael Maenle 2021-12-30 13:08:52 +01:00
parent 51bf245f00
commit 9ea64fb460
28 changed files with 179 additions and 142 deletions

View File

@ -38,6 +38,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.4' implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.4'
implementation 'com.android.volley:volley:1.2.0' implementation 'com.android.volley:volley:1.2.0'
implementation 'com.google.firebase:firebase-messaging-ktx:21.0.1'
def camerax_version = "1.0.2" def camerax_version = "1.0.2"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
@ -54,3 +55,5 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
} }
apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin

View File

@ -1,9 +1,9 @@
package com.example.bump package com.maenle.bump
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.example.bump.util.LocalData import com.maenle.bump.util.LocalData
import com.example.bump.util.MessageProcessor import com.maenle.bump.util.MessageProcessor
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith

View File

@ -1,10 +1,10 @@
package com.example.bump package com.maenle.bump
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.bump.util.Message import com.maenle.bump.util.Message
import com.example.bump.util.MessageProcessor import com.maenle.bump.util.MessageProcessor
import com.example.bump.util.RestSingleton import com.maenle.bump.util.RestSingleton
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject

View File

@ -7,8 +7,9 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name="com.example.bump.ui.MainApplication" android:name=".ui.MainApplication"
android:allowBackup="true" android:allowBackup="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -16,8 +17,29 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.BumpForAndroid"> android:theme="@style/Theme.BumpForAndroid">
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_launcher_foreground"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/black"/> <!-- [END fcm_default_icon] -->
<!-- [START fcm_default_channel] -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/bump_notification_channel_id" />
<!-- [END fcm_default_channel] -->
<activity <activity
android:name="com.example.bump.ui.MainActivity" android:name=".ui.MainActivity"
tools:node="merge" tools:node="merge"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"

View File

@ -1,28 +0,0 @@
package com.example.bump.receiver
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.core.content.ContextCompat
import com.example.bump.util.sendNotification
import com.maenle.bump.R
class AlarmReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val notificationManager = ContextCompat.getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
notificationManager.sendNotification(
"Hello",
context
)
}
}

View File

@ -1,42 +0,0 @@
package com.example.bump.receiver
import android.app.AlarmManager
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import android.text.format.DateUtils
import androidx.core.app.AlarmManagerCompat
import androidx.core.content.ContextCompat
class SnoozeReceiver: BroadcastReceiver() {
private val REQUEST_CODE = 0
override fun onReceive(context: Context, intent: Intent) {
val triggerTime = SystemClock.elapsedRealtime() + DateUtils.MINUTE_IN_MILLIS
val notifyIntent = Intent(context, AlarmReceiver::class.java)
val notifyPendingIntent = PendingIntent.getBroadcast(
context,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager,
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime,
notifyPendingIntent
)
val notificationManager = ContextCompat.getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
notificationManager.cancelAll()
}
}

View File

@ -0,0 +1,78 @@
package com.maenle.bump
import android.app.NotificationManager
import android.util.Log
import androidx.core.content.ContextCompat
import com.maenle.bump.util.sendNotification
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Log.d(TAG, "From: ${remoteMessage.from}")
// TODO Step 3.5 check messages for data
// Check if message contains a data payload.
remoteMessage.data.let {
Log.d(TAG, "Message data payload: " + remoteMessage.data)
}
// TODO Step 3.6 check messages for notification and call sendNotification
// Check if message contains a notification payload.
remoteMessage.notification?.let {
Log.d(TAG, "Message Notification Body: ${it.body}")
sendNotification(it.body!!)
}
}
// [END receive_message]
//TODO Step 3.2 log registration token
// [START on_new_token]
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token)
}
// [END on_new_token]
/**
* Persist token to third-party servers.
*
* @param token The new token.
*/
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private fun sendNotification(messageBody: String) {
val notificationManager = ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
notificationManager.sendNotification(messageBody, applicationContext)
}
companion object {
private const val TAG = "MyFirebaseMsgService"
}
}

View File

@ -1,4 +1,4 @@
package com.example.bump.ui package com.maenle.bump.ui
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -31,8 +31,8 @@ import kotlin.math.min
import android.view.WindowInsets import android.view.WindowInsets
import android.graphics.Insets import android.graphics.Insets
import com.example.bump.util.BumpProcessor import com.maenle.bump.util.BumpProcessor
import com.example.bump.util.CameraXViewModel import com.maenle.bump.util.CameraXViewModel
class CameraFragment: Fragment() { class CameraFragment: Fragment() {

View File

@ -1,17 +1,20 @@
package com.example.bump.ui package com.maenle.bump.ui
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.example.bump.util.BumpProcessor import com.google.firebase.messaging.FirebaseMessaging
import com.example.bump.util.RestSingleton import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.RestSingleton
import com.maenle.bump.util.sendNotification
import com.maenle.bump.R import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentFirstBinding import com.maenle.bump.databinding.FragmentFirstBinding
import org.json.JSONArray import org.json.JSONArray
@ -30,6 +33,7 @@ class FirstFragment : Fragment() {
private lateinit var rest: RestSingleton private lateinit var rest: RestSingleton
private lateinit var log: JSONArray private lateinit var log: JSONArray
private lateinit var bump: BumpProcessor private lateinit var bump: BumpProcessor
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -40,12 +44,21 @@ class FirstFragment : Fragment() {
bump = BumpProcessor(requireContext()) bump = BumpProcessor(requireContext())
// TODO: Step 1.7 call create channel
createChannel( createChannel(
getString(R.string.bump_notification_channel_id), getString(R.string.bump_notification_channel_id),
getString(R.string.bump_notification_channel_name) getString(R.string.bump_notification_channel_name)
) )
/*
val notificationManager = ContextCompat.getSystemService(
requireContext(),
NotificationManager::class.java
) as NotificationManager
notificationManager.sendNotification(getString(R.string.notification), requireContext())
*/
subscribeTopic()
return binding.root return binding.root
} }
@ -62,13 +75,11 @@ class FirstFragment : Fragment() {
} }
private fun createChannel(channelId: String, channelName: String) { private fun createChannel(channelId: String, channelName: String) {
// TODO: Step 1.6 START create a channel
val notificationChannel = NotificationChannel( val notificationChannel = NotificationChannel(
channelId, channelId,
channelName, channelName,
// TODO: Step 2.4 change importance
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
)// TODO: Step 2.6 disable badges for this channel )
.apply { .apply {
setShowBadge(false) setShowBadge(false)
} }
@ -82,12 +93,24 @@ class FirstFragment : Fragment() {
NotificationManager::class.java NotificationManager::class.java
) )
notificationManager.createNotificationChannel(notificationChannel) notificationManager.createNotificationChannel(notificationChannel)
}
// TODO: Step 1.6 END create a channel private fun subscribeTopic() {
// [START subscribe_topic]
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
.addOnCompleteListener { task ->
var message = getString(R.string.message_subscribed)
if (!task.isSuccessful) {
message = getString(R.string.message_subscribe_failed)
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
// [END subscribe_topics]
} }
companion object { companion object {
fun newInstance() = FirstFragment() fun newInstance() = FirstFragment()
private val TOPIC = "Bump"
} }

View File

@ -1,4 +1,4 @@
package com.example.bump.ui package com.maenle.bump.ui
import android.os.Bundle import android.os.Bundle
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar

View File

@ -1,9 +1,9 @@
package com.example.bump.ui package com.maenle.bump.ui
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import com.example.bump.util.BumpProcessor import com.maenle.bump.util.BumpProcessor
import com.example.bump.util.RestSingleton import com.maenle.bump.util.RestSingleton
// Not object class. AndroidManifest.xml error happen. // Not object class. AndroidManifest.xml error happen.
class MainApplication : Application() { class MainApplication : Application() {

View File

@ -1,11 +1,10 @@
package com.example.bump.ui package com.maenle.bump.ui
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.maenle.bump.databinding.FragmentSecondBinding import com.maenle.bump.databinding.FragmentSecondBinding
/** /**

View File

@ -1,4 +1,4 @@
package com.example.bump.util package com.maenle.bump.util
import android.content.Context import android.content.Context
import android.os.Handler import android.os.Handler

View File

@ -1,4 +1,4 @@
package com.example.bump.util package com.maenle.bump.util
import android.app.Application import android.app.Application
import android.util.Log import android.util.Log

View File

@ -1,4 +1,4 @@
package com.example.bump.util package com.maenle.bump.util
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE

View File

@ -1,8 +1,7 @@
package com.example.bump.util package com.maenle.bump.util
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.*
class Message(var sender: String, var data: String, timestamp: String) { class Message(var sender: String, var data: String, timestamp: String) {
val timestamp = LocalDate.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")) val timestamp = LocalDate.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"))

View File

@ -1,4 +1,4 @@
package com.example.bump.util package com.maenle.bump.util
import java.util.Base64 import java.util.Base64
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
@ -7,7 +7,7 @@ import com.macasaet.fernet.Key
import com.macasaet.fernet.Token import com.macasaet.fernet.Token
import com.macasaet.fernet.StringValidator import com.macasaet.fernet.StringValidator
import com.macasaet.fernet.Validator import com.macasaet.fernet.Validator
import com.example.bump.ui.MainActivity import com.maenle.bump.ui.MainActivity
import java.math.BigInteger import java.math.BigInteger
import java.security.SecureRandom import java.security.SecureRandom
import java.time.Duration import java.time.Duration

View File

@ -1,20 +1,19 @@
package com.example.bump.util package com.maenle.bump.util
import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.example.bump.receiver.SnoozeReceiver import com.maenle.bump.ui.MainActivity
import com.example.bump.ui.MainActivity
import com.maenle.bump.R import com.maenle.bump.R
// Notification ID. // Notification ID.
private val NOTIFICATION_ID = 0 private val NOTIFICATION_ID = 0
private val REQUEST_CODE = 0
private val FLAGS = 0
@SuppressLint("WrongConstant")
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
// Create the content intent for the notification, which launches // Create the content intent for the notification, which launches
// this activity // this activity
@ -33,17 +32,6 @@ fun NotificationManager.sendNotification(messageBody: String, applicationContext
applicationContext.resources, applicationContext.resources,
R.drawable.ic_launcher_foreground R.drawable.ic_launcher_foreground
) )
val bigPicStyle = NotificationCompat.BigPictureStyle()
.bigPicture(eggImage)
.bigLargeIcon(null)
// TODO: Step 2.2 add snooze action
val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java)
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(
applicationContext,
REQUEST_CODE,
snoozeIntent,
FLAGS)
// TODO: Step 1.2 get an instance of NotificationCompat.Builder // TODO: Step 1.2 get an instance of NotificationCompat.Builder
// Build the notification // Build the notification
@ -52,8 +40,6 @@ fun NotificationManager.sendNotification(messageBody: String, applicationContext
applicationContext.getString(R.string.bump_notification_channel_id) applicationContext.getString(R.string.bump_notification_channel_id)
) )
// TODO: Step 1.8 use the new 'breakfast' notification channel
// TODO: Step 1.3 set title, text and icon to builder // TODO: Step 1.3 set title, text and icon to builder
.setSmallIcon(R.drawable.ic_launcher_foreground) .setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(applicationContext .setContentTitle(applicationContext
@ -65,16 +51,8 @@ fun NotificationManager.sendNotification(messageBody: String, applicationContext
.setAutoCancel(true) .setAutoCancel(true)
// TODO: Step 2.1 add style to builder // TODO: Step 2.1 add style to builder
.setStyle(bigPicStyle)
.setLargeIcon(eggImage) .setLargeIcon(eggImage)
// TODO: Step 2.3 add snooze action
.addAction(
R.drawable.ic_launcher_foreground,
applicationContext.getString(R.string.snooze),
snoozePendingIntent
)
// TODO: Step 2.5 set priority // TODO: Step 2.5 set priority
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
// TODO: Step 1.4 call notify // TODO: Step 1.4 call notify

View File

@ -1,11 +1,11 @@
package com.example.bump.util package com.maenle.bump.util
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.android.volley.Request import com.android.volley.Request
import com.android.volley.RequestQueue import com.android.volley.RequestQueue
import com.android.volley.toolbox.* import com.android.volley.toolbox.*
import com.example.bump.ui.MainActivity import com.maenle.bump.ui.MainActivity
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.example.bump.ui.MainActivity"> tools:context="com.maenle.bump.ui.MainActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.example.bump.ui.SecondFragment"> tools:context="com.maenle.bump.ui.SecondFragment">
<!--androidx.camera.view.PreviewView <!--androidx.camera.view.PreviewView
android:id="@+id/viewFinder" android:id="@+id/viewFinder"

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.example.bump.ui.FirstFragment"> tools:context="com.maenle.bump.ui.FirstFragment">
<TextView <TextView
android:id="@+id/textview_first" android:id="@+id/textview_first"

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.example.bump.ui.SecondFragment"> tools:context="com.maenle.bump.ui.SecondFragment">
<androidx.camera.view.PreviewView <androidx.camera.view.PreviewView
android:id="@+id/viewFinder" android:id="@+id/viewFinder"

View File

@ -1,7 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context="com.maenle.bump.com.example.bump.ui.MainActivity"> tools:context="com.maenle.bump.ui.MainActivity">
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:orderInCategory="100" android:orderInCategory="100"

View File

@ -7,7 +7,7 @@
<fragment <fragment
android:id="@+id/FirstFragment" android:id="@+id/FirstFragment"
android:name="com.example.bump.ui.FirstFragment" android:name="com.maenle.bump.ui.FirstFragment"
android:label="@string/first_fragment_label" android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first"> tools:layout="@layout/fragment_first">
@ -17,7 +17,7 @@
</fragment> </fragment>
<fragment <fragment
android:id="@+id/SecondFragment" android:id="@+id/SecondFragment"
android:name="com.example.bump.ui.SecondFragment" android:name="com.maenle.bump.ui.SecondFragment"
android:label="@string/second_fragment_label" android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second"> tools:layout="@layout/fragment_second">
@ -27,7 +27,7 @@
</fragment> </fragment>
<fragment <fragment
android:id="@+id/CameraFragment" android:id="@+id/CameraFragment"
android:name="com.example.bump.ui.CameraFragment" android:name="com.maenle.bump.ui.CameraFragment"
android:label="@string/camera_fragment_label" android:label="@string/camera_fragment_label"
tools:layout="@layout/fragment_camera"> tools:layout="@layout/fragment_camera">

View File

@ -18,4 +18,7 @@
<string name="bump_notification_channel_id">bump_id</string> <string name="bump_notification_channel_id">bump_id</string>
<string name="snooze">Snooze</string> <string name="snooze">Snooze</string>
<string name="bump_notification_channel_name">Bump</string> <string name="bump_notification_channel_name">Bump</string>
<string name="notification">This is a notification</string>
<string name="message_subscribed">Subscribed to Channel</string>
<string name="message_subscribe_failed">Could not subscribe</string>
</resources> </resources>

View File

@ -7,6 +7,8 @@ buildscript {
dependencies { dependencies {
classpath "com.android.tools.build:gradle:7.0.4" classpath "com.android.tools.build:gradle:7.0.4"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0"
classpath 'com.google.gms:google-services:4.3.2' // Google Services plugin
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files