currently I have this WET code caused by the fact that NotificationCompat does not support setSmallIcon for a Icon and not a resource-id:
val notification = if (Build.VERSION.SDK_INT < 23) {
NotificationCompat.Builder(this)
.setLargeIcon(bitmap)
.setSmallIcon(R.drawable.ic_launcher)
.setContentText(intentDescriber!!.userFacingIntentDescription)
.setContentTitle(label)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.build()
} else {
Notification.Builder(this)
.setSmallIcon(Icon.createWithBitmap(bitmap))
.setLargeIcon(bitmap)
.setContentText(intentDescriber!!.userFacingIntentDescription)
.setContentTitle(label)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.build()
}
Is there a way to make this nicer ( DRY ? ) - the problem is that both builder-classes are different ..
If you are comfortable using reflection, then rather than setting the small icon in the builder, set it in the built notification itself. You can check there for SDK 23, and call setSmallIcon using reflection (it is a public method, but is hidden. I doubt it will change), otherwise set the icon field in the notification.
Short of reflection I suggest creating your own builder interface with two implementations: one for NotificationCompat.Builder and one for Notification.Builder. You might be repeating "android" but you won't be repeating yourself. e.g.:
interface NotificationFacadeBuilder<out T> {
/* facade builder method declarations go here */
fun build(): T
}
class SupportAppNotificationCompatFacadeBuilder(context: Context)
: NotificationFacadeBuilder<NotificationCompat> {
val builder = NotificationCompat.Builder(context)
/* facade builder method implementations go here and delegate to `builder` */
override fun build(): NotificationCompat = TODO()
}
class AppNotificationFacadeBuilder(context: Context)
: NotificationFacadeBuilder<Notification> {
val builder = Notification.Builder(context)
/* facade builder method implementations go here and delegate to `builder` */
override fun build(): Notification = TODO()
}
NotificationFacadeBuilder (or whatever you decide to call it) will have to declare each common builder method you need and then each implementing class will simply delegate those to their respective, actual builder implementations.
Related
I am trying to create an Android app which plays a sound every few seconds. I want this to work even when the phone is idle. At the moment everything works fine even when the phone screen is off. But after about a minute, the timer stops working. As soon as the screen is turned back on, the missed sounds are played in quick succession. I struggle to find the right terms and concepts to properly find a solution with Google.
When I first encountered this issue, I made sure that my service was running in the background. As it seems, the service is also enabled in the background because everything works fine as long as the screen is not turned off.
Code for running the service in the background:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
timer.scheduleAtFixedRate(TimeTask(), 0, 100);
return START_STICKY
}
private inner class TimeTask() : TimerTask() {
override fun run() {
sendBroadcast(Intent(TIMER_UPDATED))
}
}
Since this didn't work, I tried to make the service a foreground service. But this didn't work either. (I tried to do it as shown here)
Code for running the service in foreground:
private fun runInBackground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("service", "something")
} else {
""
}
val notification: Notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("title")
.setContentText("text")
.setSmallIcon(R.drawable.alert_dark_frame)
.build()
startForeground(1, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
return channelId
}
I read something about scheduling tasks in Android. And found the AlarmManager, but I don't think this would really work the way I want it to because I would have to schedule an alarm for every 100ms. The official doc also states that this shouldn't be used in that way and that "handlers" are more suited, but I struggle to understand how I could replace my current timer with such a handler. I have tried to implement something, but failed.
val updateHandler = Handler()
val runnable = Runnable {
// some action
}
updateHandler.looper(runnable, 100)
Finally solved my problem. Something that I didn't understand before was a "wakelock". It looks something like this:
val wakeLock: PowerManager.WakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ShuttleRun::DefaultWakeLock").apply {
acquire(30*60*1000L)
}
}
It basically just keeps the device awake, which doesn't mean that the screen is turned on, but rather that the service can run in the background without being disturbed.
I did implement a notification feature in android using the Notification.Builder in Android OREO+. I need to cancel the notification after a certain time frame, if the user has not clicked on the notification. which i completed using the setTimeOutAfter method.
https://developer.android.com/reference/android/app/Notification.Builder.html#setTimeoutAfter(long).
Now, i need to send a message to server that the notification wasn't clicked/timeout has occured. How can i implement this? Is there any notificationTimeout Listener?
There's nothing like a timeout listener but you can use a delete intent for your purpose. You'll need a Broadcast Receiver in order to do something (like calling your server) when the notification gets dismissed.
In code:
class NotificationDismissedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// call your server here
}
}
private fun getNotificationWithDeleteIntent() : Notification{
val deleteIntent = Intent(context, NotificationDismissedReceiver::class.java)
deleteIntent.action = "notification_cancelled"
val onDismissPendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setTimeoutAfter(TIMEOUT)
.setDeleteIntent(onDismissPendingIntent)
return builder.build()
}
I have got a periodic work request running foreground. Using the following version of WorkManager
androidx.work:work-runtime-ktx:2.5.0-alpha03
Here is the work request.
val workRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
setConstraints(constraints).build()
And on the worker class, I set it to foreground.
class MyWorker(context, params) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(createForegroundInfo())
//rest of the logic
}
private void createForegroundInfo() {
val cancelIntent = WorkManager.getInstance(context).createCancelPendingIntent(id) //work request id
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle("Test Title")
.addAction(actionIconRes, "Cancel", cancelIntent).build()
return ForegroundInfo(notificationId, notification)
}
}
When I run it, I can see the notification coming up with cancel action. When clicked, the notification disappears momentarily and appears again. Nothing happens on further clicks on cancel action. There is no cancel signalled at all either. What could potentially be a stopper? Any clue?
I think I found the problem. I had nested suspending functions which were causing the Worker to cancel completely. Now that I have changed those functions, it's all working now.
I'm trying to implement an implicit deep link handling in my application, but the following code doesn't work out and onNewIntent in my single activity isn't calling, but always startDestination from navigation graph is opening.
In my navigation graph, I have the following deep link for a fragment
<deepLink
android:id="#+id/deepLink"
app:uri="people/{uuid}" />
Then I added the nav. graph to manifest file between the activity tag
<nav-graph android:value="#navigation/app_graph" />
After I put onNewIntent implementation to MainActivity and it looks like
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
findNavController(R.id.fragmentContainer).handleDeepLink(intent)
}
Creating of a pending intent is happening like:
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
data = Uri.parse("people/$uuid")
}
val pendingIntent = PendingIntent.getActivity(
context,
PENDING_INTENT_REQUEST_CODE,
intent,
0
)
And finally the dialog creation
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
// not important
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
NotificationManagerCompat
.from(context)
.notify(Random.nextInt(), notification)
I think first your scheme is wrong it should be something like <scheme_name>://people/{uuid}
I am receiving the following crash report from some of my users on devices like the Google Pixel 2, LG Nexus 5X and Nokia 6, 7 or 8 on Android 8+. I am not able to reproduce this crash on an LG V30 with 8.0, Google Pixel with Android 9.0 or emulators with 8.1.
Fatal Exception: android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=null pri=-2 contentView=null vibrate=null sound=null smartAlertCount=0x0 defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6501)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
The only notification for startForeground in my code is a notification that is being displayed during audio playback with android.media.MediaPlayer. I suspect this notification to be the one from the exception, since many users tell me that the MediaPlayer is not working on their phone - but I am not totally sure, since the crash log is not telling exactly where this error occurs.
The Kotlin code for creating this notification looks like this:
class MediaPlayerService : Service() {
private var mediaPlayer: MediaPlayer? = null
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
mediaPlayer = MediaPlayer()
mediaPlayer?.let { mediaPlayer ->
mediaPlayer.setOnPreparedListener(MediaPlayer.OnPreparedListener {
mediaPlayer.start()
showNotification()
})
try {
mediaPlayer.setDataSource(URL)
mediaPlayer.prepareAsync()
} catch (exception: IOException) {
exception.printStackTrace()
}
}
return Service.START_NOT_STICKY
}
fun showNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
val channel = NotificationChannel(
"channel_id",
"Channel",
android.app.NotificationManager.IMPORTANCE_LOW
).apply {
lightColor = Color.GREEN
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
setSound(Settings.System.DEFAULT_NOTIFICATION_URI, null)
setShowBadge(false)
}
notificationManager?.createNotificationChannel(channel)
}
NotificationCompat.Builder(context, "channel_id")
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup("channel_id")
.setGroupSummary(false)
.setColor(Color.GREEN)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.setAutoCancel(true)
.setSmallIcon(R.drawable.icon_small)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.icon_large))
.setContentTitle("Title")
.setContentText("Text")
.setTicker("Text")
.setStyle(NotificationCompat.BigTextStyle().bigText("Text"))
.setNumber(1)
.build()
}
I am using Notification Channels for all of my notifications, including the one from above. I am not able to reproduce any problem with these notifications, as they all seem to work as expected for push notifications - and for the most part for the foreground service, too.
Is there something I am missing?
'mysun' had an observation that reduced the complaints for my foreground service:
The system can not find the specified resources
This is generally due to setSmallIcon(R.drawable.icon_small), which is not yet ready for some circumstances (such as when you start a restart system to send a notification), resulting in an App exception.
Instead, get the application icon from ApplicationInfo; e.g. setSmallIcon(context.getApplicationInfo().icon)
see mysun's fatalerrors.org post here
I had a similar problem, it turned out that my context was null, then I passed the context from the .MyApp main class
PendingIntent broadcast = PendingIntent.getActivity
(context, 222, intent1, PendingIntent.FLAG_MUTABLE);
everything worked.
Just try to find out what value your context has, maybe you have null too