The setTimeOutAfter method for notification seems not t be working. My application meets the requirement for minSdk but I do not know why the heads-up-notification dismises from the screen before time elapses.
#Singleton
class NotificationHelper #Inject constructor(
#ApplicationContext private var context: Context,
) {
companion object {
private const val NOTIFICATION_CHANNEL_ID = "NOTIFICATION_CHANNEL"
private const val NOTIFICATION_CHANNEL_NAME = "NOTIFICATION_CHANNEL_NAME"
private const val BROADCAST_REQUEST_CODE = 0
private const val NOTIFICATION_TIME_OUT = 30000L
}
fun getNotificationManager() =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
fun createNotification(
title: String,
subtitle: String = "",
): Notification {
createNotificationChannel()
return createNotificationBase(title, subtitle)
.build()
}
fun <T> createNotification(
title: String,
actionTitle: String,
buttonAction: String,
broadcastReceiver: Class<T>,
subtitle: String = ""
): Notification {
createNotificationChannel()
val intent = Intent(context, broadcastReceiver)
.apply {
action = buttonAction
}
val pendingIntent = pendingIntentWithBroadcast(intent)
return createNotificationBase(title, subtitle)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_amazon_blue_logo, actionTitle, pendingIntent)
.build()
}
fun sendNotification(notification: Notification, notificationId: Int) {
getNotificationManager().notify(notificationId, notification)
}
private fun pendingIntentWithBroadcast(intent: Intent): PendingIntent =
PendingIntent.getBroadcast(
context,
BROADCAST_REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
private fun createNotificationChannel() {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
)
.apply {
lockscreenVisibility = Notification.VISIBILITY_PRIVATE
}
getNotificationManager().createNotificationChannel(channel)
}
private fun createNotificationBase(
title: String,
subtitle: String,
): NotificationCompat.Builder {
return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_alexa_notification)
.setContentTitle(title)
.setContentText(subtitle)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setTimeoutAfter(NOTIFICATION_TIME_OUT)
.setOnlyAlertOnce(true)
}
fun cancelNotification(notificationId:Int) = getNotificationManager().cancel(notificationId)
}
How can I make this work?
do you want to keep heads-up-always-visible Notification present on screen for TimeOutAfter duration? thats not possible, duration of head-up layout visible on screen is configured in system and you won't override this
setTimeOutAfter value will make Notification auto-dismiss after this period, but only in API26+ (when introduced). There is no guarantee that AppCompat/AndroidX version will handle this method in older system versions, common approach is that compat library just won't call setTimeOutAfter under the hood on APIs below 26 protecting your app from NoSuchMethodException crash. still this isn't same as bringing this feature to older OS versions. but you can do by yourself with e.g.
new Handler(Looper.getMainLooper()).postDelayed (() -> {
getNotificationManager().cancel(notificationId);
}, NOTIFICATION_TIME_OUT);
placed just after getNotificationManager().notify(... call
I was able to achieve the desired result with adding setOngoing(true) to the notification builder.
Related
I'm creating a countdown timer and I want to notify the user all the time that the countdown timer runs. Therefore I have created a service which is started by a ViewModel. I use Hilt for dependency injection as I want to inject the service into the ViewModel. Additionally the UI library is jetpack compose. Following is my approach.
This is my service.
#AndroidEntryPoint
class TimerService: Service(){
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Notifications.createNotification(applicationContext)
return super.onStartCommand(intent, flags, startId)
}
}
This is how notifications are created.
object Notifications {
private var notificationId = UUID.randomUUID().hashCode()
fun createNotification(context: Context){
val notification = NotificationCompat.Builder(context, "ChannelId")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Timer started")
.setContentText("Timer started and running...")
.build()
NotificationManagerCompat.from(context).notify(notificationId, notification)
}
fun createNotificationChannel(context: Context){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val name = "timerNotifyChannel"
val description = "Timer Notification Channel"
val importance = NotificationManager.IMPORTANCE_HIGH
// The notification channel
val channel = NotificationChannel("ChannelId", name, importance).apply {
description
}
val notificationManager : NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
And a manager for starting and stopping the service.
class TimerServiceManager #Inject constructor(
#ApplicationContext private val applicationContext: Context,
){
private val serviceIntent = Intent(applicationContext, TimerService::class.java)
fun startTimerService(){
ContextCompat.startForegroundService(applicationContext, serviceIntent)
}
fun stopTimerService(){
applicationContext.stopService(serviceIntent)
}
}
The Application.
#HiltAndroidApp
class TimerApp: Application(){}
The TimerViewModel, which starts the service whenever the service is started.
#HiltViewModel
class TimerViewModel #Inject constructor(
private val timerServiceManager: TimerServiceManager,
): ViewModel() {
//...
fun startcountDown(){
//...
countDownTimer = object : CountDownTimer(...){...}
countDownTimer?.start()
timerServiceManage.startTimerService()
}
private fun cancelTimer(){
countDownTimer?.cancel()
_isRunning.postValue(false)
timerServiceManager.stopTimerService()
}
}
And the MainActivity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
// private val timerViewModel by viewModels<TimerViewModel>()
private val timeViewModel by viewModels<TimeViewModel>()
// val timeViewModel: TimeViewModel = ViewModelProvider(checkNotNull(LocalViewModelStoreOwner.current))[TimeViewModel::class.java]
override fun onCreate(savedInstanceState: Bundle?) {
//...
val timerViewModel = hiltViewModel<TimerViewModel>()
callUI(timerViewModel = timerViewModel, timeViewModel, localConfig = LocalConfiguration.current)
}
}
The problem is when I debug I can see that it calls the ContextCompact.startForegrounService(...) withing theTimerServiceManager.startTimerService() function. But it doesn't start the service. I say it doesn't start because when I put a break point in the onCreated(...) method in the TimerService it's never reached. Why is this failing? What's wrong?
After weeks of trial-and-errors I could show notifications from my app. I was being dumb at the first place for not allowing the application to access notifications in the emulator. So if you're also having this issue make sure your application has access to the notifications.
However I have also made some changes. I don't know whether the app working because of these changes or not.
I removed the Notificationobject and then add that functionality to the TimerService.
#AndroidEntryPoint
class TimerService: Service() {
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var notification: Notification
override fun onCreate() {
// the notification channel creates when the Service is created
super.onCreate()
notificationManager = NotificationManagerCompat.from(this)
createNotificationChannel()
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// when the service starts, a notification will be created
notification = NotificationCompat.Builder(this, TIMER_SERVICE_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Timer service")
.setContentText("Timer running")
.setSmallIcon(R.drawable.ic_launcher_background)
.setOngoing(true) // an ongoing notification means can't dismiss by the user.
.setOnlyAlertOnce(true)
.build()
startForeground(TIMER_SERVICE_NOTIFICATION_ID, notification)
return START_STICKY
}
private fun createNotificationChannel(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val serviceChannel = NotificationChannel(
TIMER_SERVICE_NOTIFICATION_CHANNEL_ID,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(serviceChannel)
}
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onDestroy() {
super.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
}
override fun onBind(p0: Intent?): IBinder? = null // We don't need a binder
companion object {
public const val TIMER_SERVICE_NOTIFICATION_CHANNEL_ID = "TimerServiceChannel"
public const val TIMER_SERVICE_NOTIFICATION_ID = 69
}
}
I'm making a note app that has an option for pinning notes in the notifications.
I'm using foreground service but the problem is when I want to pin more than one note, the notification for the second one replaces the first one.
I'm using each note's unique ID as notificationId. Here's my code :
class MyService : Service() {
lateinit var note: Note
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
note = intent.getParcelableExtra(NOTIFICATION_MESSAGE_EXTRA)!!
showNotification()
return START_STICKY
}
private fun showNotification() {
val notificationIntent = Intent(this, MyService::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Title")
.setContentText(note.noteText)
.setContentIntent(pendingIntent)
.setGroup(CHANNEL_GROUP_KEY)
.build()
startForeground(note.id, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager =
getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
}
}
You can done this by using
startForeground(notifyId.toInt(), notificationBuilder) in onCreate method of service and then in onStartCommand use
notificationManager.notify(notifyId.toInt(), notificationBuilder);
Basically you need to only use startForeground once and then you need to use notification manager for showing notification. That way you are able to show all the notifications and all are using foreground service
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 spent some time now trying to get a notification with the battery temperature in it.
At the moment I have managed to make the notification appear. Only when I want to run the code below I get an error with the showNotification() part.
This error indicates:
No value passed for parameter 'context' No value passed for parameter
'intent'
But how do I know what parameters to add to showNotification()?
Anyone who can help me with this?
class MyWorker(context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {
companion object {
const val CHANNEL_ID = "channel_id"
const val NOTIFICATION_ID = 1
}
override fun doWork(): Result {
Log.d("success",
"doWork: Success function called")
showNotification(HERE DO I GET THE PARAMETER MESSAGE)
return Result.success()
}
private fun showNotification(context: Context, intent: Intent) {
if (intent?.action == "android.intent.action.BATTERY_CHANGED") {
val temp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) / 10.0
Toast.makeText(context, "${temp}" + "°C", Toast.LENGTH_SHORT).show()
}
val intent = Intent(applicationContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
applicationContext,
0, intent, 0
)
val notification = NotificationCompat.Builder(
applicationContext,
CHANNEL_ID
)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("New Task")
.setContentText("Subscribe on the channel")
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelName = "Channel Name"
val channelDescription = "Channel Description"
val channelImportance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, channelName, channelImportance).apply {
description = channelDescription
}
val notificationManager = applicationContext.getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
with(NotificationManagerCompat.from(applicationContext)) {
notify(NOTIFICATION_ID, notification.build())
}
}
}
There is the MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// simpleWork()
myWorkManager()
}
private fun myWorkManager() {
val constraints = Constraints.Builder()
.setRequiresCharging(false)
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.setRequiresCharging(false)
.setRequiresBatteryNotLow(true)
.build()
val myRequest = PeriodicWorkRequest.Builder(
MyWorker::class.java,
15,
TimeUnit.MINUTES
).setConstraints(constraints)
.build()
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork(
"my_id",
ExistingPeriodicWorkPolicy.KEEP,
myRequest
)
}
private fun simpleWork() {
val mRequest: WorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
.build()
WorkManager.getInstance(this)
.enqueue(mRequest)
}
}
You can pass arguments when you create the Request for your WorkManager like this and pass the arguments.
val builder = Data.Builder()
val intent = Intent() // for example, you get your intent here
val data = workDataOf("mydata" to intent)
builder.putAll(data)
val request = OneTimeWorkRequestBuilder<YourWorker>()
.setInputData(builder.build())
.build()
and then receive arguments via
override fun doWork(): Result {
var intent: Intent? = null
intent = inputData.keyValueMap["mydata"] as Intent
if(intent != null) {
showNotification(context, intent)
}
return Result.success()
}
Do tell if that worked for you, you already have the context inside your Worker.
I'm trying to make a simple notification service sent from server via postman work, I think I set everything in the right way within the activities.
FirebaseNotificationActivity :
class FirebasePushNotificationActivity : BaseActivity<FirebasePushNotificationContract.FirebasePushNotificationView, FirebasePushNotificationContract.FirebasePushNotificationPresenter>(),
FirebasePushNotificationContract.FirebasePushNotificationView {
private val TAG = "MyFirebaseToken"
override val layoutResId: Int
get() = R.layout.activity_firebase_push_notification
override fun createPresenter(): FirebasePushNotificationContract.FirebasePushNotificationPresenter {
return FirebasePushNotificationContract.FirebasePushNotificationPresenter()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_firebase_push_notification)
initView()
}
private fun initView() {
//This method will use for fetching Token
Thread(Runnable {
try {
Log.i(TAG, FirebaseInstanceId.getInstance().getToken(getString(R.string.SENDER_ID), "FCM"))
} catch (e: IOException) {
e.printStackTrace()
}
}).start()
}
}
MyFirebaseMessagingService
class MyFirebaseMessagingService: FirebaseMessagingService() {
private val TAG = "MyFirebaseToken"
private lateinit var notificationManager: NotificationManager
private val ADMIN_CHANNEL_ID = "Android4Dev"
override fun onNewToken(token: String?) {
super.onNewToken(token)
Log.i(TAG, token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage?) {
super.onMessageReceived(remoteMessage)
remoteMessage?.let { message ->
Log.i(TAG, message.getData().get("message"))
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//Setting up Notification channels for android O and above
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
setupNotificationChannels()
}
val notificationId = Random().nextInt(60000)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, ADMIN_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher) //a resource for your custom small icon
.setContentTitle(message.data["title"]) //the "title" value you sent in your notification
.setContentText(message.data["message"]) //ditto
.setAutoCancel(true) //dismisses the notification on click
.setSound(defaultSoundUri)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId /* ID of notification */, notificationBuilder.build())
}
}
#RequiresApi(api = Build.VERSION_CODES.O)
private fun setupNotificationChannels() {
val adminChannelName = getString(R.string.notifications_admin_channel_name)
val adminChannelDescription = getString(R.string.notifications_admin_channel_description)
val adminChannel: NotificationChannel
adminChannel = NotificationChannel(ADMIN_CHANNEL_ID, adminChannelName, NotificationManager.IMPORTANCE_LOW)
adminChannel.description = adminChannelDescription
adminChannel.enableLights(true)
adminChannel.lightColor = Color.RED
adminChannel.enableVibration(true)
notificationManager.createNotificationChannel(adminChannel)
}
}
The problem is that following the guide I have not well understood what to insert in the field "to :" within the definition of the notification in Postman, or rather, I understand that it is necessary to insert the token of the device but I do not know how to get it.
{
"to":
"Add your device token",
"data": {
"title": "Android4Dev",
"message": "Learn Firebase PushNotification From Android4Dev Blog"
}
}
Problem solved, I added in my main activity this :
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener(this#SplashActivity,
OnSuccessListener<InstanceIdResult> { instanceIdResult ->
val newToken = instanceIdResult.token
Log.e("newToken", newToken)
})