Android Foreground service is not starting with jetpack compose - android

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
}
}

Related

Can we still launch a foreground service with extended Service class or we should move to workmanager?

I am getting issue regarding foreground service is that some time foreground being killed by os so should i resolve Service class issue or move to workmanager . i have completed the application and only getting a single os issue suggestion required thanks .
// this is the service class
class SmsService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private lateinit var smsScheduler : SmsScdeduler
private var isServiceStarted = false
private var notificationBuilder : NotificationCompat.Builder?=null
private var serviceScope = CoroutineScope(Dispatchers.Main)
private var notificationManager : NotificationManager?=null
fun isServiceRunning():Boolean{
return isServiceStarted }
companion object{
const val MESSAGE_ID: String ="messageId"
const val USER_ID:String ="userId"
const val DELIVERED: String ="sb.app.messageschedular.sms_schedulers.DELIVERYKEY"
const val ADD_SERVICE = "sb.spp.message_scheduler.add"
const val DELETE_SERVICE ="sb.spp.message_scheduler.delete"
const val Add_kEY ="ADD_KEY"
const val SENT ="sb.app.messageschedular.sms_schedulers.SENT"
private const val channelId = "default_notification_channel_id"
private const val NOTIFICATION_ID =1995L
}
/************* Delivery BroaddCast ENd **************/
override fun onCreate() {
super.onCreate()
smsScheduler = SmsScdeduler.getInstance(this )
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(sb.app.messageschedular.R.mipmap.ic_launcher)
.setContentTitle("Message Scheduled")
// .setContentIntent(pendingIntent)
.setAutoCancel(true)
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showNotification(NOTIFICATION_ID)
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire()
}
}
println("intent ${intent }")
isServiceStarted =true
if(intent!=null) {
val action = intent?.action
val sms = intent?.getParcelableExtra<Sms>(Add_kEY)
schedule(sms!!)
}else{
serviceScope.launch(Dispatchers.IO) {
smsScheduler.reSchedule()
}
}
return START_STICKY }
#RequiresApi(Build.VERSION_CODES.O)
private fun showNotification(messageid:Long ) {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "test1", NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "test otificaiton"
notificationManager?.createNotificationChannel(channel)
}
this.startForeground(messageid.toInt() ,notificationBuilder!!.build())
}
override fun onBind(intent: Intent?): IBinder? {
return LocalBinder() }
inner class LocalBinder : Binder(){
fun getService(): SmsService = this#SmsService
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted =false
if( serviceScope.isActive){
println("Service Scope is cancelled ")
serviceScope.cancel() }
}
}
fun finish() {
println("service finished ")
println("Stop self")
println("Stop foreground service")
stopForeground(true )
println("service state false ")
isServiceStarted =false
stopSelf()
}
}
// calling service
fun scheduleService(sms: Sms) {
if(!mService.isServiceRunning()){
val intent = Intent(this , SmsService::class.java)
intent.action = SmsService.ADD_SERVICE
intent.putExtra(SmsService.Add_kEY, sms)
ContextCompat.startForegroundService(this.applicationContext,intent)
}
/// Permission of Service
<service android:name=".service.SmsService"
android:foregroundServiceType="dataSync"
/>

How to handle services for Android older than O?

Is the following the correct way to create a service for Android version O and newer, as well as handle older versions? For O and newer I'd like to create a foreground service. For older versions, I assume a regular service needs to be created? Is the following code correct to handle this or is there a better way?
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Intent(this, ForegroundService::class.java).also { intent ->
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
}
}
ForegroundService.kt
class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("channel_service", "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
val notification: Notification = Notification.Builder(this, "channel_service")
.setContentTitle("Title")
.setContentText("Text")
.setSmallIcon(R.drawable.fancy_small_icon)
.build()
startForeground(1, notification)
}
/**
* Do work
*/
stopSelf()
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}

Handler() works slower in Service when sreen is off

I made a Service that is actually a simple background counter.
It just pluses 1 to a last number and then it goes to UI.
My previous problem was about the fact that Handler() sometimes worked very slow when smartphone was turned off or if it wasn't charging. Recently I found the same problem in this forum.
I added PowerManager.WakeLock to my Service and everything worked fine...
But I decided to test it for a longer time and started the app simultaneously on three smartphones and leave them for about an hour and a half. When I returned I have seen a complete difference between three of them.
The first shows 5100 (1 h 25 mins), the second - 2800 (46 mins) and the third - 5660 (1 h 34 mins).
I was pretty sure that wakelock will do the job correctly but now I don't know what happened there.
Here is a code of my Service:
class Timer_Service : Service() {
companion object {
val PARAM_OUT_MSG = "0"
}
var i = 0
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var mHandler: Handler
private lateinit var mRunnable: Runnable
override fun onBind(p0: Intent?): IBinder? {
TODO("not implemented")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock"
)
wakeLock.acquire()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.infocell.action.RESPONSE"
mHandler = Handler()
mRunnable = Runnable {
showOrderNumber()
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
broadcastIntent.putExtra(PARAM_OUT_MSG, i.toString())
sendBroadcast(broadcastIntent)
}
mHandler.postDelayed(mRunnable, 1000)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacks(mRunnable)
}
private fun showOrderNumber() {
i += 1
mHandler.postDelayed(mRunnable, 1000)
}
}
Manifest also contains <uses-permission android:name="android.permission.WAKE_LOCK" />
Finally after various tests I got the most precise way to make a simple counter. Instead of relatively reliable Handle() method I would recommend to use Timer(). It worked absolutely equal on all of my four smartphones. Wakelock is also required for that. I would also test JobScheduler() and CountDownTimer() for getting all testing results but I am glad with timer so far.
I will share my code if someone is looking for solution for such tasks.
class Timer_Service : Service() {
companion object {
val PARAM_OUT_MSG = "0"
}
var i = 0
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var timer: Timer
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock"
)
wakeLock.acquire()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.infocell.action.RESPONSE"
timer = Timer()
val task = object : TimerTask() {
override fun run() {
if (Trigger.getTrigger() == 0){
showOrderNumber()
// bring 'i' value to main activity
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
broadcastIntent.putExtra(PARAM_OUT_MSG, i.toString())
sendBroadcast(broadcastIntent)
}
}
}
timer.schedule(task,0, 1000)
return START_STICKY
}
override fun onCreate() {
super.onCreate()
var notification = createNotification()
startForeground(1, notification)
}
private fun createNotification(): Notification {
val notificationChannelId = "ENDLESS SERVICE CHANNEL"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
val channel = NotificationChannel(
notificationChannelId,
"My Service",
NotificationManager.IMPORTANCE_HIGH
).let {
it.description = "Service channel"
it.enableLights(true)
it.lightColor = Color.RED
it.enableVibration(true)
it.vibrationPattern = longArrayOf(100)
it
}
notificationManager.createNotificationChannel(channel)
}
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder(
this,
notificationChannelId
) else Notification.Builder(this)
return builder
.setContentTitle("My Service")
.setContentText("Endless service working...")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker("Ticker text")
.setPriority(Notification.PRIORITY_HIGH) // for under android 26 compatibility
.build()
}
override fun onDestroy() {
super.onDestroy()
// Trigger is a separate kotlin class with variables
if (Trigger.getTrigger() == 1){
timer.cancel()
timer.purge()
}
}
private fun showOrderNumber() {
i += 1
}
}

Force stop loop function in IntentService

There a function in my intent service that works like a countdown. It is called counter.
What should be added to IntentService or directly into counter to stop this loop after some action in MainActivity?
class IntentServiceExample : IntentService("Loop_test") {
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object {
val PARAM_OUT_MSG = "None"
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText("kylsha")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return super.onStartCommand(intent, flags, startId)
}
override fun onHandleIntent(p0: Intent?) {
Toast.makeText(this,"Service started",Toast.LENGTH_LONG).show()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.intenttest.action.RESPONSE"
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
sendBroadcast(broadcastIntent)
counter(broadcastIntent)
}
fun counter(bc: Intent){
for (i in 1..100){
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
}
}
create a variable in the class.
Create a setter to set the variable true.
In you rcounter routine, check for the variable being set.
private val cancelCounter = false
public fun setToCancel() {
cancelCounter = true
}
/*Stuff*/
fun counter(bc: Intent){
for (i in 1..100){
if (cancelCounter) {
cancelCounter = false
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
You may not have direct access to the object from main - if not then you should create this class with a singleton pattern ;)
I don't code in kotlin enough to now the "right way" to do it, but some links to the right way:
https://blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin
https://medium.com/swlh/singleton-class-in-kotlin-c3398e7fd76b
Both of these links have some information about why they make the decisions they make in the structure pattern, and some of how the code behind for the implementations works too ;)
For someone who also learn Kotlin I will post my solution as well. It looks pretty simple, however, there probably could be more solutions.
I created a simple Kotlin object like:
object Trigger {
var triggerStop = 0
fun getTrigger(): Int{
return triggerStop
}
}
As you can see variable triggerStop can be changed and called with function getTrigger()
So I added this object into MainActivity to buttons' setOnClickListeners:
class MainActivity : AppCompatActivity() {
lateinit var i:Intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
i = Intent(this, IntentServiceExample::class.java)
buttonStart.setOnClickListener{
Trigger.triggerStop = 0 // this variable will be checked in IntentService
startService(i)
}
buttonEnd.setOnClickListener{
Trigger.triggerStop = 1 // this variable will be checked in IntentService
stopService(i)
}
}
}
Then I put this object into my IntentService. In a loop that I want to be stopped by user interaction I put a check like in #Watachiaieto's answer.
fun counter(bc: Intent){
for (i in 1..100){
val stopIt = Trigger.getTrigger() // get trigger value
if (stopIt == 1) {
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
sendBroadcast(bc)
}
}

Pass data to Service in Kotlin

I have an app that uses Service class to perform task in foreground.
This service also contains a Handler object to run same function multiple times. I want to change attributes in my activity_main.xml while functions are running in Service. For example when function calculates something in Service the result prints in TextView.
How it would be correct access activity_main's objects to retrieve and change their values and attributes?
Here is what I have:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private var notificationManager: NotificationManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonStart.setOnClickListener{
buttonStart.isEnabled = false
buttonStop.isEnabled = true
IdListener.startService(this, "Foreground Service is running...")
}
}
}
IdListener.kt:
class IdListener : Service() {
private val CHANNEL_ID = "ForegroundService Kotlin"
private lateinit var mainHandler: Handler
private lateinit var mRunnable: Runnable
companion object {
fun startService(context: Context, message: String) {
val startIntent = Intent(context, IdListener::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, IdListener::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mainHandler = Handler()
mRunnable = Runnable { showRandomNumber(tm) }
mainHandler.postDelayed(mRunnable, 1000)
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText(input)
.setSmallIcon(R.drawable.ic_notofication)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
mainHandler.removeCallbacks(mRunnable)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
/// function in which I want elements from activity_main.xml to be changed
fun showRandomNumber(manager: TelephonyManager){
myTextView.text = "Working..."
mainHandler.postDelayed(mRunnable, 1000)
}
}
Here's how I'd probably handle your case. I don't know exactly what you're doing, but I'm just having the text view show "Working..." when it starts the service until there's an ID available. I haven't tested this and haven't worked with services in a long time, so you might want other input.
object IdServiceData {
val id = MutableLiveData<String>()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...
myTextView.text = "Working..." // should really use string resource here.
IdServiceData.id.observe(this) {
myTextView.text = it.value
}
}
}
When an Activity or Fragment observes a LiveData, they automatically stop observing when they are destroyed, so they are not leaked. So your Activity can be destroyed and recreated multiple times while the Service is running and it will keep getting the proper updates.
class IdListener : Service() {
//...
private fun broadcastNewId(id: String){
mainHandler.post {
IdServiceData.id.value = id
}
}
}
If you want better encapsulation, I suppose you could abstract out the MutableLiveData by creating a separate IdServiceDataProvider that has the MutableLiveData and is used by the service, and the IdServiceData would reference the data like this: val id: LiveData<String> = IdServiceDataProvider.id

Categories

Resources