Trying to build an android application using kotlin where I am connecting, sending and reading data from a BLE (Bluetooth Low Energy) device. I have designed an activity which will show me connection stats and data that is being received from BLE device.And I have a foreground service running to keep the connection with the BLE device alive and listen to the stats. I have used pending intent to open this activity from my foreground service notification.
Following code shows the method to create notification
private fun getServiceNotification(textToShowInNotification: String)
{
val pendingIntent = Intent(<Static context from application class>,ActivityName::class.java)
pendingIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
val contentIntent = PendingIntent.getActivity(<static_context_from_application_class>, 0, pendingIntent, 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
val notificationChannel = NotificationChannel("DATA_CHANNEL", <Static context from application class>.resources.getString(R.string.app_name),NotificationManager.IMPORTANCE_DEFAULT)
notificationManager!!.createNotificationChannel(notificationChannel)
}
notification = notificationBuilder!!.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentText(textToShowInNotification)
.setContentIntent(contentIntent)
.build()
notificationManager!!.notify(NOTIFICATION_ID, notification)
}
Since I have the use case to open the activity where BLE connections and data receiving is shown. Multiple instances of the activity is created and previous once is not destroyed and because of the same I am unable to get the context of the running activity and due to this I am facing a problem to show alert dialogs in the activity.
Actual Usecase - When the connection with device is established and data is listened from service at that time when I kill the app and open it from foreground service notification, the dialog which has to show up saying that my device is still connected and data is being received is not shown and exceptions occurs when dialog is to be displayed
Following errors/ exceptions occurs when trying to show alert dialogs
D/DialogExecption: Unable to add window -- token null is not valid; is your activity running?
D/DialogExecption: Unable to add window token android.os.BinderProxy@4250d6d8 is not valid; is your activity running?
I have used below code to show the dialog
private fun showAlertDialog()
{
val dialogAlertDialog = AlertDialog.Builder(<Activity Context>)
val inflater: LayoutInflater = layoutInflater
val dialogAlertView: View = inflater.inflate(R.layout.activity_xml_file,null)
dialogAlertDialog.setView(dialogAlertView)
dialogAlertDialog.setCancelable(false)
val builderAlertDialog : AlertDialog = dialogAlertDialog.create()
try
{
builderAlertDialog.show()
}
catch(exception: Exception)
{
Log.d("DialogExecption",""+exception.message)
}
}
I have also tried the way
if (!this.isFinishing)
{
builderAlertDialogCycleCancelled.show()
}
but this too is not helping. Above code will supress the execption but I don't want to do it instead I want to show the dialog at any cost.
To give a pov I have tried the below in my manifest file to keep a single instance of the activity possible but this is not working out.
<activity android:name=".ActivityName"
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/AppThemeGeneral">
</activity>
I have to use alert dialog in the best possible way and skipping it isn't a choice. Any help would be great.
Important Note - I am using Coroutines with scope Dispatchers.IO in my activity to fetch data from the BLE Device.
notification = notificationBuilder!!.setOngoing(true) .setOnlyAlertOnce(true) .setContentText(textToShowInNotification) .setContentIntent(contentIntent) .build() notificationManager!!.notify(NOTIFICATION_ID, notification)
Clicking on this notification may not start an Activity
because notification
is missing setSmallIcon
or similar setIcon
call. To fix it, set some icon as below:
notification = notificationBuilder!!.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentText(textToShowInNotification)
.setContentIntent(contentIntent)
.setSmallIcon(R.drawable.ic_launcher_background)
.build()
notificationManager!!.notify(NOTIFICATION_ID, notification)
previous activity is not destroyed
That's doubtful because you're using flags Intent.FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK
when starting an Activity
,and that will finish all previous Activities. Otherwise, please show the relevant code.
When the connection with device is established and data is listened from service at that time when I kill the app and open it from foreground service notification, the dialog which has to show up saying that my device is still connected and data is being received is not shown
This sounds quite confusing. It sounds like you're showing it from your Activity
, but then it's unlikely to fail, especially, if you show it from onCreate
of your Activity
:
override fun onCreate(savedInstanceState: Bundle?) {
...
showAlertDialog()
}
D/DialogExecption: Unable to add window -- token null is not valid; is your activity running? D/DialogExecption: Unable to add window token android.os.BinderProxy@4250d6d8 is not valid; is your activity running?
The error message is quite clear - you're trying to show a dialog based on an invalid Context
here AlertDialog.Builder(<context>)
. For instance, if you use applicationContext
or a Service
context
there, it will fail with such exception.
You claim to start your dialog with an Activity
context as below:
...
val dialogAlertDialog = AlertDialog.Builder(<Activity Context>)
...
However, it's not clear from your code from where showAlertDialog()
is called, and the context object is not shown either. So, I created a sample project to test the described behaviour.
I tried reproducing the issue by building a minimalistic project based on the information that you provided in your question. Please note that I'm not using any BLE functionality for this example, even though Bluetooth is used as a prefix for each component.
I created a foreground service BluetoothDeviceService
responsible for starting an Activity
, when a notification is clicked.
class BluetoothDeviceService: Service() {
private val SERVICE_NOTIFICATION_ID = 123
private val SERVICE_NOTIFICATION_CHANNEL_ID = "channel_01"
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
SERVICE_NOTIFICATION_CHANNEL_ID,
"Bluetooth service",
NotificationManager.IMPORTANCE_DEFAULT)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
val pendingIntent = Intent()
pendingIntent.setClass(this,BluetoothDeviceActivity::class.java)
pendingIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
val contentIntent = PendingIntent.getActivity(this, 0,
pendingIntent, 0)
val notification = NotificationCompat.Builder(this, SERVICE_NOTIFICATION_CHANNEL_ID)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setContentText("Bluetooth service running...")
.setContentIntent(contentIntent)
.setSmallIcon(R.drawable.ic_launcher_background)
.build()
startForeground(SERVICE_NOTIFICATION_ID, notification)
}
}
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
}
I created a MainActivity
that had to start the foreground service.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startServiceIntent = Intent(this, BluetoothDeviceService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(startServiceIntent)
} else {
startService(startServiceIntent)
}
finish()
}
}
Please note that the Service
could also be started by BroadcastReceiver
, and that would be more appropriate, but I used the Activity
for simplicity.
Also, I introduced a BluetoothDeviceActivity
that was started by the service with help of PendingIntent
:
class BluetoothDeviceActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showAlertDialog()
}
}
private fun showAlertDialog() {
val dialogAlertDialog = AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("This is a test")
.setTitle("Information")
.create()
dialogAlertDialog.show()
}
Just in case, I also put my manifest.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.stackoverflowquestion2">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".MainApplication"
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.StackOverflowQuestion2"
android:fullBackupContent="@xml/backup_descriptor">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.StackOverflowQuestion2.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".BluetoothDeviceActivity"/>
<service android:name=".BluetoothDeviceService"/>
</application>
</manifest>
This worked as expected without any problems.
One other idea - you could convert your AlertDialog
to Activity
and use it as a Dialog
. For this, you need to do 2 things:
Create a new Àctivity
as below:
class AlertDialogActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dialogAlertDialog = AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("This is a test")
.setTitle("Information")
.setPositiveButton("OK") { dialog, which -> finish() }
.create()
dialogAlertDialog.show()
}
}
Add it to your manifest and set its theme as Dialog
:
<activity android:name=".AlertDialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>
Then, create a method in your Service
, and use it any time it's needed:
private fun showAlertDialog() {
val intent = Intent(applicationContext, AlertDialogActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
applicationContext.startActivity(intent)
}
That's what it looks like: