I've been experimenting with the new Bubbles API recently. No matter what I do, the notifications that I expect to appear as a bubble always appear in the system tray as a normal notification.
I've written my own toy app, which I'll add here. I have also pulled down a couple of other apps from tutorials (here and here) that I have studied. In every single case, no bubble, just a system tray notification.
Since the sample apps assert that they can present bubbles, I assume that the problem must be somewhere in my emulator environment. I'm running an emulator that uses Android API R
. And I have enabled bubbles in the developer options:
Here is the relevant code from the app that I have developed:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bubbles">
<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/AppTheme">
<activity
android:name=".BubbleActivity"
android:allowEmbedded="true"
android:documentLaunchMode="always"
android:resizeableActivity="true" />
<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>
MainActivity.kt
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var bubbleViewModel: BubbleViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bubbleViewModel = ViewModelProvider(
this, BubbleViewModelFactory(this.application))
.get(BubbleViewModel::class.java)
}
fun blowBubble(view: View) {
bubbleViewModel.showBubble()
}
}
BubbleViewModel.kt
class BubbleViewModel(application: Application): AndroidViewModel(application) {
private val notificationHelper = NotificationHelper(getApplication())
init {
notificationHelper.createNotificationChannels()
}
fun showBubble() {
viewModelScope.launch{
withContext(Dispatchers.Main) {
with (notificationHelper) {
if (canBubble())
showNotification()
}
}
}
}
}
NotificationHelper.kt
class NotificationHelper(private val context: Context) {
private val notificationManager = context.getSystemService(NotificationManager::class.java)
private lateinit var channel: NotificationChannel
fun createNotificationChannels() {
channel = NotificationChannel(
CHANNEL_NEW_MESSAGES,
context.getString(R.string.channel_name),
NotificationManager.IMPORTANCE_HIGH)
with(channel) {
enableVibration(true)
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
description = context.getString(R.string.channel_description)
setAllowBubbles(true)
}
Log.d("bubble", "Can Bubble: $channel.canBubble()")
notificationManager?.let {
it.createNotificationChannel(channel)
}
}
@WorkerThread
fun showNotification() {
val bubbleIntent = PendingIntent.getActivity(
context,
REQUEST_BUBBLE,
Intent(context, BubbleActivity::class.java).setAction(Intent.ACTION_VIEW),
PendingIntent.FLAG_UPDATE_CURRENT)
val bubbleMetaData = Notification.BubbleMetadata.Builder()
.setDesiredHeight(600)
.createIntentBubble(bubbleIntent, Icon.createWithResource(context, R.drawable.baseball))
.setAutoExpandBubble(false)
.setSuppressNotification(false)
.build()
val person = Person.Builder()
.setIcon(Icon.createWithResource(context, R.drawable.baseball))
.setName("Bubbles...")
.setImportant(true)
.build()
val style = Notification.MessagingStyle(person)
.addMessage("...are the Best!", System.currentTimeMillis(), person)
val builder = Notification.Builder(context, CHANNEL_NEW_MESSAGES)
.setBubbleMetadata(bubbleMetaData)
.setContentIntent(bubbleIntent)
// .setContentTitle("Title")
// .setContentText("Hello this is a notification")
.setSmallIcon(R.drawable.baseball)
.setShowWhen(true)
.setAutoCancel(true)
.setStyle(style)
// .addPerson(person.uri)
notificationManager?.notify(0, builder.build())
}
fun canBubble(): Boolean {
notificationManager?.let {
val channel = it.getNotificationChannel(CHANNEL_NEW_MESSAGES)
return it.areBubblesAllowed() && channel.canBubble()
}
return false
}
companion object {
private const val CHANNEL_NEW_MESSAGES = "new_messages"
const val REQUEST_BUBBLE = 2
}
}
And finally, the destination activity, which I don't think really matters too much since it only fires if the bubble were available to click: BubbleActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class BubbleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bubble)
}
}
And that's really all there is to this. But when run this and click the button to display a bubble, I get this result: