I'm new to android development and I'm trying to create a notification which pops up according to sensor data but before doing this I wanted to get a notification when I open the app(just to check whether the notification I created is working as I expected or not). I've tried running the code but I'm not getting any notification
Here's the main Activity
package com.example.test
import android.app.Notification.PRIORITY_DEFAULT
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.media.app.NotificationCompat
import android.app.PendingIntent
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import com.google.firebase.database.*
import com.jjoe64.graphview.GraphView
import com.jjoe64.graphview.GridLabelRenderer
import com.jjoe64.graphview.Viewport
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
import java.lang.System.currentTimeMillis
import android.app.NotificationChannel
import android.app.NotificationManager
class MainActivity : AppCompatActivity() {
lateinit private var firebasedatabase: FirebaseDatabase
lateinit private var databaseReference: DatabaseReference
lateinit private var dbref : DatabaseReference
private var channelId : String = "12345"
fun notification(notificationManager : NotificationManager, description : String)
{
val intent = Intent(this, notification::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val contentView = RemoteViews(packageName, R.layout.activity_notification)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.GREEN
notificationChannel.enableVibration(false)
notificationManager.createNotificationChannel(notificationChannel)
val builder = androidx.core.app.NotificationCompat.Builder(this, channelId)
.setContent(contentView)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
notificationManager.notify(1234, builder.build())
}
else{
val builder = androidx.core.app.NotificationCompat.Builder(this)
.setContent(contentView)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
notificationManager.notify(1234, builder.build())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
firebasedatabase = FirebaseDatabase.getInstance();
databaseReference = firebasedatabase.getReference("temp")
dbref = firebasedatabase.getReference("humidity")
val temp_graph : GraphView = findViewById(R.id.Temp)
//Basic Config of Graph
temp_graph.title = "TEMPERATURE"
val viewport :Viewport = temp_graph.viewport
viewport.setScalable(true)
viewport.setScrollable(true)
viewport.setScalableY(true)
viewport.setScrollableY(true)
viewport.setMinX(0.0)
viewport.setMaxX(10.0)
val gridLabel: GridLabelRenderer = temp_graph.gridLabelRenderer
gridLabel.horizontalAxisTitle = "Time"
// gridLabel.verticalAxisTitle = "Temp."
// val series = LineGraphSeries(
// arrayOf(
// DataPoint(0.0, 1.0), DataPoint(10.0, 5.0), DataPoint(
// 20.0,
// 10.0
// )
// )
// );
// temp_graph.addSeries(series)
var count : Double = 0.0
val series = LineGraphSeries(arrayOf<DataPoint>())
series.setColor(Color.GREEN)
val series2 = LineGraphSeries(arrayOf<DataPoint>())
temp_graph.addSeries(series)
temp_graph.addSeries(series2)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val description = "Test notification"
notification(notificationManager, description)
databaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val value = dataSnapshot.getValue(Float::class.java)
println(value)
val time = currentTimeMillis()
series.appendData(value?.toDouble()?.let { DataPoint(count, it) }, true, 40)
count += 1;
}
override fun onCancelled(databaseError: DatabaseError) {
Toast.makeText(this#MainActivity, "Error fetching data", Toast.LENGTH_LONG)
.show()
}
})
var c2 : Double = 0.0
dbref.addValueEventListener(object : ValueEventListener{
override fun onDataChange(dataSnapshot: DataSnapshot) {
val value = dataSnapshot.getValue(Float::class.java)
println(value)
val time = currentTimeMillis()
series2.appendData(value?.toDouble()?.let { DataPoint(c2, it) }, true, 40)
c2 += 1;
}
override fun onCancelled(databaseError: DatabaseError) {
Toast.makeText(this#MainActivity, "Error fetching data", Toast.LENGTH_LONG)
.show()
}
})
}
}
Here's the app gradle :
plugins {
id 'com.android.application'
id 'kotlin-android'
}
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.example.test"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.core:core:1.5.0#aar'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation("com.android.volley:volley:1.1.1")
implementation "com.google.firebase:firebase-database:11.8.0"
implementation "com.google.firebase:firebase-core:11.8.0"
implementation "com.google.firebase:firebase-storage:11.8.0"
implementation "com.google.firebase:firebase-auth:11.8.0"
implementation 'com.firebaseui:firebase-ui-database:3.2.2'
implementation platform('com.google.firebase:firebase-bom:28.0.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.jjoe64:graphview:4.2.2'
implementation 'com.android.support:multidex:1.0.3'
}
Here's the notification 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".notification">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temperature Value Exceeded "
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here's the manifest :
<?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.example.test">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Test"
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity android:name=".notification2"></activity>
<activity android:name=".notification"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Please help me out.. Thanks in Advance :)
For notifications, you need a class that extrend BroadcastReceiver().
Example AlarmReceiver class:
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationChannelId = "MY_APP_NAME_OR_ID"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(notificationChannelId, ctx.resources.getString(R.string.alarm_body_message), NotificationManager.IMPORTANCE_HIGH)
notificationChannel.description = ctx.resources.getString(R.string.alarm_description)
notificationManager.createNotificationChannel(notificationChannel)
}
val notificationBuilder = NotificationCompat.Builder(ctx, notificationChannelId)
notificationBuilder.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.NOTIFICATION_ICON)
.setPriority(Notification.PRIORITY_MAX)
.setContentTitle(ctx.resources.getString(R.string.alarm_title))
.setContentText(ctx.resources.getString(R.string.alarm_body_message))
.setContentInfo(ctx.resources.getString(R.string.alarm_button_text))
val notificationIntent = Intent(ctx, NotificationActivity::class.java)
val pendingIntent = PendingIntent.getActivity(ctx, ALARM_SN, notificationIntent, 0)
notificationBuilder.addAction(android.R.drawable.btn_star, ctx.resources.getString(R.string.alarm_button_text), pendingIntent)
notificationBuilder.setContentIntent(pendingIntent)
notificationManager.notify(ALARM_SN, notificationBuilder.build())
}
companion object {
const val ALARM_SN = 007
}
}
you might also need to have an activity to receive the notification's action:
class NotificationActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AlarmReceiver.clearNotification(this)
// If this activity is the root activity of the task, the app is not running
if (isTaskRoot) {
// Start the app before finishing
val startAppIntent = Intent(applicationContext, ActivityToOpen::class.java)
startAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(startAppIntent)
}
finish()
}
Finally, your function to create the notification should have something like this:
fun notification(duration: Int) { // duration in ms
val sendNotificationIntent = Intent(this, AlarmReceiver::class.java)
val delayedIntent = PendingIntent.getBroadcast(this, ALARM_CODE, sendNotificationIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val alarms = this.applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarms.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + duration, delayedIntent)
} else {
alarms.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + duration, delayedIntent)
}
}
Related
I'm having troubles with a class inside "utils" package on my Kotlin code
Classic issues I guess, but I have been looking for solutions on forums and nothing works, I'm just trying the bindPreview method with CameraX
This is my main activity
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.util.Size
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import com.fablab.tensorflowcamerax.databinding.ActivityMainBinding
import com.fablab.tensorflowcamerax.utils.Draw
import com.google.common.util.concurrent.ListenableFuture
import com.google.mlkit.common.model.LocalModel
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.objects.ObjectDetection
import com.google.mlkit.vision.objects.ObjectDetector
import com.google.mlkit.vision.objects.custom.CustomObjectDetectorOptions
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var objectDetector: ObjectDetector
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider = cameraProvider)
}, ContextCompat.getMainExecutor(this))
val localModel = LocalModel.Builder()
.setAssetFilePath("object_detection.tflite")
// \\assets\object_detection.tflite
.build()
val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel)
.setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
.enableClassification()
.setClassificationConfidenceThreshold(0.5f)
.setMaxPerObjectLabelCount(3)
.build()
objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
}
#SuppressLint("UnsafeOptInUsageError")
private fun bindPreview (cameraProvider: ProcessCameraProvider) {
val preview = Preview.Builder().build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(binding.previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1200,720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this)
) { imageProxy ->
val rotationDegreesValue = imageProxy.imageInfo.rotationDegrees
val image = imageProxy.image
if (image != null) {
val processImage = InputImage.fromMediaImage(image, rotationDegreesValue)
objectDetector
.process(processImage)
.addOnSuccessListener { objects ->
for (i in objects) {
if (binding.parentLayout.childCount > 1) binding.parentLayout.removeViewAt(1)
val context = binding.root.context
val element = Draw(
context = context,
rect = i.boundingBox,
text = i.labels.firstOrNull()?.text ?: "Undefined"
)
binding.parentLayout.addView(element)
}
imageProxy.close()
}.addOnFailureListener {
Log.v("MainActivity", "Error - ${it.message}")
imageProxy.close()
}
}
}
cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageAnalysis)
}
}
My custom view "Draw"
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
class Draw(context: Context?, var rect: Rect, var text: String): View(context) {
lateinit var boundaryPaint: Paint
lateinit var textPaint: Paint
init {
init()
}
private fun init(){
boundaryPaint = Paint()
boundaryPaint.color = Color.BLACK
boundaryPaint.strokeWidth = 10f
boundaryPaint.style = Paint.Style.STROKE
textPaint = Paint()
textPaint.color = Color.BLACK
textPaint.textSize = 50f
textPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText(
text,
rect.centerX().toFloat(),
rect.centerY().toFloat(),
textPaint)
canvas?.drawRect(
rect.left.toFloat(),
rect.top.toFloat(),
rect.right.toFloat(),
rect.bottom.toFloat(),
boundaryPaint
)
}
}
And the gradle (I know there are deprecated dependencies, I deliveratedly choose them because compile and work with the method that I'm replicating)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
compileSdk 33
//buildToolsVersion "30.0.1"
defaultConfig {
minSdk 21
//noinspection ExpiredTargetSdkVersion
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
aaptOptions {
noCompress "tflite"
}
dataBinding {
enabled = true
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Object detection
implementation 'com.google.mlkit:object-detection-custom:16.3.0'
// CameraX
implementation "androidx.camera:camera-camera2:1.3.0-alpha02"
implementation "androidx.camera:camera-lifecycle:1.3.0-alpha02"
implementation "androidx.camera:camera-view:1.3.0-alpha02"
}
The "Draw" class is inside utils package and the IDE gives a warning that says:
"Custom view Draw is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int)"
I tried with and without declaring context value on the objectDetector for, and nothing happens
I also tried converting to secondary constuctor, but did not working either
The project compiles, when I build an APK and execute the app, I got access to native camera (giving manually hardware permission, but I will work on that detail later) but nothing about object detection and canvas drawing.
Please guide me to find a solution without changing again the dependencies version, and I need to know if the assetFilePath is correct too.
Thank you.
i'll Check id's and Everything Was Right!! but I don't know what is the problem.
errors :
Unresolved reference: synthetic
Unresolved reference: btnLogin
Unresolved reference: btnSignUp
Unresolved reference: etEmailAddress
Unresolved reference: etPassword
My LoginActivity
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
firebaseAuth = FirebaseAuth.getInstance()
btnLogin.setOnClickListener {
login()
}
btnSignUp.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login(){
val email = etEmailAddress.text.toString()
val password = etPassword.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this){
if(it.isSuccessful){
Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
else{
Toast.makeText(this, "Authentication Failed", Toast.LENGTH_SHORT).show()
}
}
}
}
My AndroidManifest
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppThemeNoActionBar">
<activity android:name=".activities.ProfileActivity"/>
<activity android:name=".activities.ResultActivity" />
<activity
android:name=".activities.QuestionActivity"
android:theme="#style/AppTheme" />
<activity android:name=".activities.LoginActivity" />
<activity android:name=".activities.SignupActivity" />
<activity android:name=".activities.LoginIntro" />
<activity android:name=".activities.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
my Build.Gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 32
defaultConfig {
applicationId "com.cheezycode.quizzed"
minSdkVersion 21
targetSdkVersion 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
namespace 'com.cheezycode.quizzed'
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.firebase:firebase-auth:19.3.2'
implementation 'com.google.firebase:firebase-firestore:22.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
}
Try this once-
You missed the initialization.
Suppose you don't want to do initialization use View binding
USING VIEW BINDING AS PER your gradle use
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
private var _binding: ActivityLoginBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
binding.btnLogin.setOnClickListener {
login()
}
binding.btnSignUp.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login(){
val email = binding.etEmailAddress.text.toString()
val password = binding.etPassword.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
Option 2-
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
private var btnLogin: Button? = null
private var btnSignUp: Button? = null
private var etEmailAddress: EditText? = null
private var etPassword: EditText? = null
lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
firebaseAuth = FirebaseAuth.getInstance()
btnLogin = findViewById(R.id.btnLogin)
btnSignUp = findViewById(R.id.btnSignUp)
etEmailAddress = findViewById(R.id.etEmailAddress)
etPassword = findViewById(R.id.etPassword)
btnLogin?.setOnClickListener {
login()
}
btnSignUp?.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login() {
val email = etEmailAddress?.text.toString()
val password = etPassword?.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this) {
if (it.isSuccessful) {
Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, "Authentication Failed", Toast.LENGTH_SHORT).show()
}
}
}
I am using Firebase Cloud Messaging to implement notifications in android app.
I'm new to android. Please help me.
Problem
When I receive a notification in the foreground state, there is unexpected movement.
Expectation: Nothing is done at the timing when the notification is received. I want to process the notification when I tap it.
Actual: BroadcastReceiver's onReceive is called at the timing when the notification is received. It doesn't respond when I tap it ("test onCreate" is not displayed in logcat).
Note: This app is entirely operated by a fragment on MainActivity.
Files
PushNotificationListenerService.kt
class PushNotificationListenerService: FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val channelId = message.notification?.channelId.toString()
val title: String = message.notification?.title.toString()
val text: String = message.notification?.body.toString()
sendNotification(
title,
text,
channelId
)
var broadcast = Intent()
broadcast.action = "FCM_RECEIVE_FOREGROUND"
sendBroadcast(broadcast)
}
private fun sendNotification(
title: String,
text: String,
receivedChannelId: String,
) {
val intent = Intent(this, HomeFragment::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)
.apply {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val notificationBuilder = NotificationCompat.Builder(this, receivedChannelId)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.ic_menu_logo)
.setContentIntent(pendingIntent)
.setPriority(PRIORITY_MAX)
.setCategory(CATEGORY_CALL)
.setAutoCancel(true)
.setSound(null)
.setVibrate(null)
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val uuid = UUID.randomUUID().hashCode()
notificationManager.notify(uuid, notificationBuilder.build())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder.setChannelId(receivedChannelId)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private var mReceiver: BroadcastReceiver? = null
private val mIntentFilter = IntentFilter("FCM_RECEIVE_FOREGROUND")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("test onCreate")
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
mReceiver = (object: BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Timber.d("test onReceive called")
if (p1 == null) { return }
}
})
setupChannels()
}
override fun onResume() {
super.onResume()
registerReceiver(mReceiver, mIntentFilter)
}
override fun onPause() {
if (mReceiver != null) {
unregisterReceiver(mReceiver)
mReceiver = null
}
super.onPause()
}
private fun setupChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val attribute = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
var uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val channel = NotificationChannel(
"channelId",
"channelName",
NotificationManager.IMPORTANCE_HIGH
).apply {
vibrationPattern = createVibrate()
lightColor = Color.BLUE
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
setSound(uri, attribute)
}
manager.createNotificationChannel(channel)
}
}
}
payload on notification from Firebase Cloud Messaging
const fcmTokenMessage: admin.messaging.TokenMessage = {
android: {
data: {},
notification: {
body: "message",
title: "title",
defaultSound: true,
channelId: "channelId",
sound: 'default',
priority: 'high',
notificationCount: fcmPushData.notificationCount
}
},
token: fcmPushData.fcmPushToken
};
Versions
build.gradle
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
classpath "org.jetbrains.kotlin:kotlin-serialization:1.4.32"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
classpath 'com.google.gms:google-services:4.3.8'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
That's all. I apologize for the inconvenience, and thank you for your cooperation.
Resolved.
The problem was that Fragment was specified as the Intent, and when Activity was specified, it worked.
I've the below code trying to work with SMS Token, i got the token code, but once i sent it in SMS I got no responce!
MainActivity.kt
package com.sms
import android.app.PendingIntent
import android.content.Intent
import android.os.Bundle
import android.telephony.SmsManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
import com.sms.ui.theme.SmsTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// val smsManager: SmsManager = SmsManager.getDefault()
val smsManager: SmsManager = getSystemService(SmsManager::class.java)
val appSmsToken = smsManager.createAppSpecificSmsToken(createSmsTokenPendingIntent())
print(appSmsToken)
setContent {
SmsTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Greeting(appSmsToken)
}
}
}
}
private fun createSmsTokenPendingIntent(): PendingIntent? {
return PendingIntent.getActivity(
this, 1234,
Intent(this, SmsTokenResultVerificationActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) // setting the mutability flag
}
}
#Composable
fun Greeting(name: String) {
Text(text = "Token is: $name")
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
SmsTheme {
Greeting("Android")
}
}
And SmsTokenResultVerificationActivity.kt is:
package com.sms
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.sms.ui.theme.SmsTheme
class SmsTokenResultVerificationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SmsTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Greeting("Welcome")
}
}
}
}
#Composable
fun Greeting(name: String) {
Text(text = "hi: $name")
}
}
As another option, I found using SMS retriever API in Android could solve it, but I still interested in using the Token as i want my app to depend on the user device token rather than the app token.
BaseApplication.kt
package com.shishirthedev.smsretriverapi
import android.app.Application
import android.util.Log
import android.widget.Toast
class BaseApplication : Application() {
private val TAG = BaseApplication::class.java.simpleName
override fun onCreate() {
super.onCreate()
// Generate Hash Key >>>>>
val appSignatureHashHelper = AppSignatureHashHelper(this)
Log.e(TAG, "HashKey: " + appSignatureHashHelper.appSignatures[0])
var msg = "HashKey: " + appSignatureHashHelper.appSignatures[0]
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
// Storing data into SharedPreferences
val sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE)
// Creating an Editor object to edit(write to the file)
val myEdit = sharedPreferences.edit()
// Storing the key and its value as the data fetched from edittext
myEdit.putString("token", appSignatureHashHelper.appSignatures[0])
// myEdit.putInt("age", age.getText().toString().toInt())
// Once the changes have been made,
// we need to commit to apply those changes made,
// otherwise, it will throw an error
myEdit.commit()
}
}
AppSignatureHashHelper.kt
package com.shishirthedev.smsretriverapi
import android.annotation.TargetApi
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Log
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
class AppSignatureHashHelper(context: Context?) :
ContextWrapper(context) {// Get all package details
/**
* Get all the app signatures for the current package
*
* #return
*/
val appSignatures: ArrayList<String>
get() {
val appSignaturesHashs = ArrayList<String>()
try {
// Get all package details
val packageName = packageName
val packageManager = packageManager
val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES
).signingInfo.apkContentsSigners
} else {
TODO("VERSION.SDK_INT < P")
}
for (signature in signatures) {
val hash = hash(packageName, signature.toCharsString())
if (hash != null) {
appSignaturesHashs.add(String.format("%s", hash))
}
}
} catch (e: Exception) {
Log.e(TAG, "Package not found", e)
}
return appSignaturesHashs
}
companion object {
val TAG = AppSignatureHashHelper::class.java.simpleName
private const val HASH_TYPE = "SHA-256"
const val NUM_HASHED_BYTES = 9
const val NUM_BASE64_CHAR = 11
#TargetApi(19)
private fun hash(packageName: String, signature: String): String? {
val appInfo = "$packageName $signature"
try {
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
var hashSignature = messageDigest.digest()
// truncated into NUM_HASHED_BYTES
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
// encode into Base64
var base64Hash =
Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR)
return base64Hash
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "No Such Algorithm Exception", e)
}
return null
}
}
}
SMSReceiver.kt`
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import java.util.regex.Pattern
class SMSReceiver : BroadcastReceiver() {
private var otpListener: OTPReceiveListener? = null
fun setOTPListener(otpListener: OTPReceiveListener?) {
this.otpListener = otpListener
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SmsRetriever.SMS_RETRIEVED_ACTION) {
val extras = intent.extras
val status = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status!!.statusCode) {
CommonStatusCodes.SUCCESS -> {
val sms = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
sms?.let {
// val p = Pattern.compile("[0-9]+") check a pattern with only digit
val p = Pattern.compile("\\d+")
val m = p.matcher(it)
if (m.find()) {
val otp = m.group()
if (otpListener != null) {
otpListener!!.onOTPReceived(otp)
}
}
}
}
}
}
}
interface OTPReceiveListener {
fun onOTPReceived(otp: String?)
}
}
MainActivity.kt
package com.shishirthedev.smsretriverapi
import android.content.IntentFilter
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.shishirthedev.smsretriverapi.SMSReceiver.OTPReceiveListener
class MainActivity : AppCompatActivity() {
private var intentFilter: IntentFilter? = null
private var smsReceiver: SMSReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var token = findViewById<TextView>(R.id.textView)
// Retrieving the value using its keys the file name
// must be same in both saving and retrieving the data
val sh = getSharedPreferences("MySharedPref", MODE_PRIVATE)
// The value will be default as empty string because for
// the very first time when the app is opened, there is nothing to show
val t = sh.getString("token", "")
// val a = sh.getInt("age", 0)
// We can then use the data
token.text = t
// age.setText(a.toString())
// Init Sms Retriever >>>>
initSmsListener()
initBroadCast()
}
private fun initBroadCast() {
intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
smsReceiver = SMSReceiver()
smsReceiver?.setOTPListener(object : OTPReceiveListener {
override fun onOTPReceived(otp: String?) {
showToast("OTP Received: $otp")
}
})
}
private fun initSmsListener() {
val client = SmsRetriever.getClient(this)
client.startSmsRetriever()
}
override fun onResume() {
super.onResume()
registerReceiver(smsReceiver, intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(smsReceiver)
}
override fun onDestroy() {
super.onDestroy()
smsReceiver = null
}
private fun showToast(msg: String?) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}
manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shishirthedev.smsretriverapi">
<application
android:name=".BaseApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.SmsRetriverAPi">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle (Module)
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.shishirthedev.smsretriverapi"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0'
}
``build.gradle (Project)`
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Credit goes to
SHISHIR
I have a class that extends Firebase Messaging service:
internal class The8AppGCMListenerService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
//save token
}
override fun onMessageReceived(message: RemoteMessage) {
val from = message.from
val data = message.data
sendNotification(data)
}
private fun sendNotification(data: Map<String, String>) {
if (Interactors.preferences.notificationsEnabled == true) {
Timber.d(data.toString())
val msg = data[KEY_MSG]
val url = data[KEY_URL]
sendNotification(msg, type, url)
}
}
private fun sendNotification(message: String?, url: String?) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = "Main"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(channelId, "My Notifications", NotificationManager.IMPORTANCE_HIGH)
notificationChannel.description = "Channel description"
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.RED
notificationChannel.vibrationPattern = longArrayOf(0, 1000, 500, 1000)
notificationChannel.enableVibration(true)
notificationManager.createNotificationChannel(notificationChannel)
}
val pendingIntent = getNotificationIntent(url)//Build intent with data received from notifcation
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
notificationBuilder.setSmallIcon(R.drawable.ic_notification)
notificationBuilder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.notif))
notificationBuilder.setContentTitle(getString(R.string.app_name))
notificationBuilder.setContentText(message)
notificationBuilder.setAutoCancel(true)
notificationBuilder.setSound(defaultSoundUri)
notificationBuilder.setContentIntent(pendingIntent)
notificationManager.notify(0, notificationBuilder.build())
}
private fun getNotificationIntent(Long, url: String?): PendingIntent {
val useState = UseState(UseState.COME_FROM_NOTIFICATION)
val intent = getNotificationIntent(this, type, useState, offerId, url)
return PendingIntent.getActivity(this, 0, intent, Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
companion object {
private const val KEY_MSG = "Text"
private const val KEY_URL = "pushUrl"
internal fun getNotificationIntent(context: Context, useState: UseState, url: String?): Intent =
openSponsorTree(context,useState,ASponsorTree.TAB_FEED,url)
}.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).apply {
if (The8CloudSdk.isSdkReady()) {
RxBus.post(SponsorHubEvents.UpdateNotifications())
SDKInternal.notifyBadgeListener()
}
}
private fun openSponsorTree(context: Context, useState: UseState, startTab: Int, url: String?): Intent {
val intent = ASponsorTree.newInstance(context, useState, startTab,url=url)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
}
}
I've declared it in the manifest like so:
<service android:name="com.weare8.android.network.gcm.The8AppGCMListenerService">
<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_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/app_dark_blue" />
And imported these firebase packages:
implementation('com.google.firebase:firebase-core:18.0.2')
implementation platform('com.google.firebase:firebase-bom:26.4.0')
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
However, when the notification is clicked when the app is in the background all it does is open the app and does not parse the data returned from message.data in onMessageReceived(). I can confirm this when I attempt to get the URL passed into the ASponsorTree activity and it is null.
How do I pass data into the app when bringing it from the background via a notification?
Edit: newInstance() method of ASponsorTree activity:
companion object {
const val KEY_URL = "pushUrl"
fun newInstance(
context: Context,
useState: UseState,
startTab: Int = TAB_FEED,
url:String?,
noPost: String? = null
): Intent {
val intent = Intent(context, ASponsorTree::class.java).putExtra(KEY_URL,url)
return intent
}
}
I retrieved it in that activity's oncreate with
val pushUrl = intent.getStringExtra(KEY_URL)
which evaluates to null.
I learned that onMessageReceived() is not called from the background unless the a data message is used, and that the receiving activity must be tagged with a click action in the manifest.