Enables you to show Android Conversation Bubbles from your Flutter app.
Android Conversation Bubbles are floating contact points of applications. Bubbles are draggable on any part of the screen, can expand into an app's view, and can be collapsed back. Many bubbles from different apps can be displayed at the same time. They all display over other apps. Bubbles are available on devices running Android 11+.
- More About Conversation Bubbles
- Android Requirements
- How To Use
- Example
To understand how to use this package, you need to first understand how the Android OS expects you to integrate bubbles in Android apps.
Bubbles are integrated into the Android OS as part of the notification system. A bubble is attached to notifications. In Kotlin, when you want to show a bubble, you show a notification as usual but then you have to add bubble metadata and fulfill some conversation requirements set by Android. This will now make your notification to show in the system app bar with the option to bubble.
When the user bubbles a notification, the system will call a dedicated activity that you've set up to handle the bubble. Android expects us to handle our app's UI and UX differences for users when they access our apps from within bubbles.
This package has handled a good amount of the Kotlin code you should write to show bubbles. To use this package to show bubbles, you will make few changes to the Android native side of your project together with some integrating Flutter code.
Before we delve into how you to integrate this package, let us look at the technical Kotlin requirements for bubbles. Not for you to redo them, but for you to understand what the package expects you to provide.
Following are the Java/Kotlin requirements for bubbles:
It all starts with a notification. You provide notification data as usual.
Such data include your appIcon (e.g. @mipmap/ic_launcher) and
NotificationChannel details (id, name, and optional description).
NotificationChannel is like notification groups for your app in Android Settings.
When a user taps on a bubble, the system expands the bubble and show an
activity
that you had set up to handle that bubble. Android requires that this activity should
be set as embedded and resizeable in the AndroidManifest.xml file. We will see how
to do this later.
These are classes in native Android that we attach to the notification to fulfill the conversation requirements set by Android.
BubbleMetadata sets the bubble's height, the bubble's icon (user profile picture), whether the
bubble should not notify the system, the data the system should provide to your app if
in case the user taps on it (intentUri), etc.
Person class references the context of a bubble. It tells who the user is
chatting with in a conversation. The Android system provides this class.
Person contains the name, icon, contacts, etc. You usually will use the icon
attached to the provided person as the icon for the bubble. Android uses the person's
name as the bubble's title.
Dynamic Shortcuts are also required to make our notifications to bubble. The
shortcuts are contextual links that show when users long-press on your app in
the app menu. Bubbles have to be linked to a shortcut.
As mentioned earlier, this conversation_bubbles flutter package handles these
native Android integrations. To use the package, you only provide the minimum required
to show a bubble.
To integrate this package, you will have to make changes to your project in both the Android Native and the Flutter sides.
In your project, go to android/app/src/main/AndroidManifest.xml.
After the opening manifest XML tag and before the opening
application XML tag, paste the following:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>You should have something like this:
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>That's generally the place where you put permissions your app uses. So if you have any other permission declaration, it will be there too.
When a user taps a notification, Android opens your app.
Most, if not all the time, you need to know which exact notification the user tapped. Because when your app is finally opened, you need to show your apps's UI in context of the tapped notification.
For example, let's say you show chat notifications from different persons (e.g. Cat, Dog, Sheep, etc.) When a user taps the notification of a particular person (let's say Cat), you want to open into the Chat Screen of that particular person.
For Android to tell you whose notification was opened, you need to have had
provided data to that notification when you initially showed it. For
conversations and bubbles, an intentUri suffices.
When showing the notification, you can set a URI
in the Intent
that Android will use to open your app when the user taps the notification.
In turn, when Android eventually opens your app, it will provide you with
this intent alongside the originally provided uri.
Conventionally, you choose the host or domain name of the URI based on your company's or app's name. You also have to set this URI in the AndroidManifest.xml file so that the Android system can appropriately call your app on tap of an involved bubble or notification.
This Flutter conversation_bubbles package has handled the process of adding the
intentUri to the notification data in the Kotlin side. You will provide the
intentUri to the show method when the time comes.
However, you have to add an intent-filter XML tag for your app's URI in the
MainActivity's activity tag in AndroidManifest file. This will allow the
Android system to appropriately open your app based on the retrieved intentUri.
Go to MainActivity's activity XML tag in AndroidManifest.xml file.
There should already be an existing intent-filter XML tag for
MainActivity with action as MAIN and category as LAUNCHER within the
activity tag.
This time, you will add a new intent-filter whose action will be VIEW and
category BROWSABLE. Next to the already declared intent-filter for
MAIN/LAUNCHER, add the following intent-filter.
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="example.com"
android:pathPattern="/chat/*"
android:scheme="https" />
</intent-filter>Replace example.com with your chosen domain name. Replace the value of the
android:pathPattern attribute with what you intend using for the screen that
you will show in bubbles. The /chat/* can work for you if you are building
a chat-related application.
You should have something like this:
<activity
android:name=".MainActivity"
...>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="example.com"
android:pathPattern="/chat/*"
android:scheme="https" />
</intent-filter>
</activity>You could declare more intent-filters based on your app's needs. The above
is simply the minimum needed to show bubbles.
In your Flutter project, locate where your MainActivity.kt is found and
create a BubbleActivity.kt file along with (or in the same directory as)
MainActivity.kt. The file name "BubbleActivity" is conventional. You
could have different file names or multiple activities for bubbles for
various purposes.
Paste the following contents into this file.
package com.example // TODO: Set your package name
import io.flutter.embedding.android.FlutterActivity
class BubbleActivity: FlutterActivity()Change the package name to what your app uses and delete the TODO.
In Android apps, all declared activity classes must be specified in the
AndroidManifest.xml file.
In this step, you will add the just created BubbleActivity to the
AndroidManifest.xml file. In addition, you will specify the VIEW/BROWSABLE intent-filter for this BubbleActivity, just as with did with MainActivity
in step 1b above.
Go to android/app/src/main/AndroidManifest.xml.
After the closing activity XML tag of MainActivity and before
the closing application XML tag, paste the following code:
<activity
android:name=".BubbleActivity"
android:exported="true"
android:documentLaunchMode="always"
android:allowEmbedded="true"
android:resizeableActivity="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="example.com"
android:pathPattern="/chat/*"
android:scheme="https" />
</intent-filter>
</activity>This section guides through how to actually use this Flutter conversation_bubbles
package to show bubbles in your Flutter app.
It assumes that you are good in Flutter and that you have a working Flutter project or at least you've handle state management or similar concepts in your Flutter codebase.
In your Flutter project, you will need to be sure that the user has granted your application the permissions to show notifications before you proceed to use this project to show bubbles.
You can use the permission_handler
package to request for notification permissions.
If you don't do this, the bubbles will not show.
Add this package to your pubspec.yaml file. But this time, you are using the git
method as this package is not yet published to pub.dev.
Add the following to the dependencies section of your pubspec.yaml file.
conversation_bubbles:
git:
url: https://github.com/keepdeploying/conversation_bubblesRun flutter pub get to install the package.
To initialize this plugin, call the .init method with your appIcon and the
fully qualified class name of the BubbleActivity you created in step 1c above.
The appIcon should is what you already use in your Android app. It shows in
notifications. It is usually in the form of @mipmap/ic_launcher.
The fully qualified class name of the BubbleActivity comes with the package name
you had set in step 1c above. For example, if your package name is com.example,
the fully qualified class name of the BubbleActivity will be
com.example.BubbleActivity.
import 'package:conversation_bubbles/conversation_bubbles.dart';
final _conversationBubblesPlugin = ConversationBubbles();
_conversationBubblesPlugin.init(
appIcon: '@mipmap/ic_launcher',
fqBubbleActivity: 'com.example.BubbleActivity',
);To show bubbles, call the .show method of the plugin. The method signature
for .show is as follows:
Future<void> show({
required int notificationId,
required String body,
required String contentUri,
required NotificationChannel channel,
required Person person,
bool? isFromUser,
bool? shouldMinimize,
String? appIcon,
String? fqBubbleActivity,
});| Parameter | Description |
|---|---|
notificationId |
The unique identifier for the notification. You should use the same notificationId when you want Android to show a notification in an already existing bubble. |
body |
The message you want to show in the notification. |
contentUri |
The intentUri that you want Android to provide to your app when the user taps the notification. This is the constructed string equivalent of the URI parts you had set in the AndroidManifest.xml file. |
channel |
The notification channel you want to use. Remember that bubbles are attached to notifications. Provide the channel that will be used for the notification. This package exposes a NotificationChannel class. |
person |
The context of the bubble. It tells who the user is chatting with in a conversation. This is compulsory for Bubbles to show. This package exposes a Person class. The bubble's icon and name will be that of person. |
isFromUser |
A boolean that tells if the message is from the user. If true, the bubble will simply show without triggering a notification. This is useful when you want a "Show Bubble" button in your app. |
shouldMinimize |
A boolean that tells if your app should minimize when the bubble gets expanded. This is useful when isFromUser is true. So that if the user clicked on some "Show Bubble" button in your app, your app will minimize and the bubble will show and expand. |
appIcon |
Same as the appIcon you provided when initializing the plugin. If you provide this, it will override the appIcon you provided when initializing the plugin; for that particular bubble's notification. Useful when you want to use a different appIcon. |
fqBubbleActivity |
Same as the fqBubbleActivity you provided when initializing the plugin. If you provide this, it will override the fqBubbleActivity you provided when initializing the plugin; for that particular bubble's notification. Useful when you want to use a different BubbleActivity. |
Here is an example of how to show a bubble. We will assume we are in Chat application. We will show a bubble for a chat message from a person named Cat. The icon for the bubble will be a picture of a cat that we have in our assets. This package expects the icon to be in bytes.
final id = 1;
final bytesData = await rootBundle.load('assets/Cat.jpg');
final iconBytes = bytesData.buffer.asUint8List();
await _conversationBubblesPlugin.show(
notificationId: id,
body: 'Meow',
contentUri: 'https://example.com/chat/$id',
channel: const NotificationChannel(id: 'chat', name: 'Chat'),
person: Person(id: '$id', name: 'Cat', icon: iconBytes),
);Use the getIntentUri method of the plugin to get the intentUri that Android
provided to your app when the user tapped on a bubble. The method returns a
Future<String?>. You can parse the results and extract the parts you need.
final intentUri = await _conversationBubblesPlugin.getIntentUri();
if (intentUri != null) {
final uri = Uri.tryParse(intentUri);
if (uri != null) {
final id = int.tryParse(uri.pathSegments.last);
if (id != null) {
// Show the chat screen for the user with id
}
}
}This intentUri getter is useful when the user tapped on the notification
without opening the bubble. Android will open your app in full (and not in
a bubble) and will still provide the intentUri to your application. You
should just show the necessary UI. That's why handling intentUri on launch
of the Flutter app is separate from handling isInBubble.
When the user taps on a bubble, the system will open your app. You have to handle the UI changes that you want to make when the user opens your app from a bubble.
This step is extremely important. You want bubbles to be localised to a given screen in your app. You also want to prevent the user from accessing some screens when they are in bubbles.
Use the isInBubble method of the plugin to know if the user is
opening your app from a bubble. The method returns a Future<bool>.
final isInBubble = await _conversationBubblesPlugin.isInBubble();
if (isInBubble) {
// Handle UI changes for bubbles
}You can find a complete example of how to use this package in the example directory.
The example is a simple chat application named "People". It is a chat with animals that have different ways of replying you after 5 seconds. It is built the same way as the "People" app that the Android Docs used to demonstrate bubbles.
The example uses Isar for data persistence. It also keeps singleton services as an abstraction layer for the plugin.
