Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
How can you support the project?
If you're even looking at this page, thank you! We appreciate any donations made to this effort, large or small! Any donations made will go directly to the main developers, split evenly. If more developers choose to contribute, we will happily split the donations with them.
Our developers have put hundreds of hours into the research, design, and creation of the BlueBubbles App ecosystem. All donations will help compensate the developers for their time. All developers are doing this as a side project, in their free time.
This app is in no way affiliated with or endorsed by Apple and / or their affiliate(s), it provided is for educational purposes only. Apple, macOS, and iMessage are trademarks of Apple Inc., registered in the U.S. and other countries.
Tools and some image resources are under the Apache license, version 2.0.
3rd party library licenses can be found within the app, under settings > about > licenses.
Info on how the clients were coded, what features they have, how to contribute, and more.
All clients are coded in Flutter and Dart. Our first stable release was published June 15th, 2021, after extensive closed alpha and public beta testing since October of 2020. Along this journey we have learned quite a bit about development in Flutter, and we'd like to share that, along with other things, in the form of this documentation.
We were choosing between Flutter and React Native to be our framework for the project. Initially, we ended up going with React Native because most of the developers were familiar with JavaScript and React development.
However, React proved to be difficult for us when creating the UI. We wanted our app to look like iMessage, but with a bit of our own twist - this meant a lot of blur, opacity, and bouncy scrolling. At the time, React did not have great support for these types of things, while with Flutter, it was as simple as providing decoration
to a Container
, or different ScrollPhysics
to a scrollable widget.
We did not consider native development because we figured that development would be slow and clunky, even if the app might be more performant and memory efficient - the tradeoff would not be worth it.
Send & receive texts, media, and location
View tapbacks/reactions, stickers, and replies
Create new chats (macOS 11+ has limited support while macOS 10 has full support)
View read/delivered timestamps
Mute or archive conversations
Robust theming engine
Choose between an iOS or Android-style interface
Lots of customizations and options to personalize your experience
Send and receive typing indicators
Mark chats read on the host macOS device
Create chats
Rename group chats
Add or remove participants from group chats
Send messages, replies, message effects, mentions, and tapbacks
Android 5.0+
Android Auto (GitHub build required at this time)
WearOS (currently in alpha)
Windows 10 / 11 & Linux
Any modern browser
We appreciate y'all!
How to install the desktop app from Github Releases
Download bluebubbles_standalone.exe
from GitHub releases (linked above)
Run the installer
You may receive a warning from Windows Defender about an unrecognized publisher, simply click "More Info", and then "Run anyways" at the bottom.
Complete the installation
Download bluebubbles_linux.zip
from GitHub Releases (linked above)
Unzip bluebubbles_linux.zip
Move libobjectbox.so
from bundle/lib
in bundle to /usr/lib
Run the executable bluebubbles_app
inside bundle
Note: You might need to install the following dependencies:
For notifications - libnotify-dev
For video playback - vlc
, libvlc-dev
For the system tray - appindicator3-0.1
, libappindicator3-dev
You can use the following command:
This page will show you how you can use BlueBubbles with Adguard, if you are having connection issues.
Adguard is a local VPN on your Android device that you can use for simple DNS blocking.
Many of you are using one of BlueBubble's built in proxy services such as Cloudflare or Ngrok. Sometimes, Adguard will block these domains, and can cause connection issues between your BlueBubbles Android app and the BlueBubbles Server.
The fix is simple. Just open the Adguard app's settings, and disable Adguard for the BlueBubbles App.
See the screenshot below for an example of this (note the Adguard protection
toggle is disabled):
How to build the app on your own system, or how to contribute to the project
This guide is tailored towards the Android Studio IDE. You can also build in VSCode, but some tools may not exist or may work differently.
Install , following the guide for your OS all the way up to the "Test Drive" page
Clone the repository to your system
We recommend building off the development
branch! It has the more recent code that is more likely to build without errors.
Add a file named .env
to the root of the project directory. Inside it, place GIPHY_API_KEY = ""
. This is to prevent a build error.
Find android > app > build.gradle
(not to be confused with android > build.gradle
), and scroll down to signingConfig signingConfigs.release
at line 111. Change this to signingConfig signingConfigs.debug
.
In a terminal window at the root of the project directory, run flutter build apk --release --flavor prod
You may need to accept licenses and perform other tasks since you are building Flutter for the first time. The terminal output will guide you through this process.
The output APK file path will be given to you, simply transfer it to your phone and install it!
In /lib/repository/models/html
add a new file called giphy.dart
. Inside this, put only const GIPHY_API_KEY = "";
. If you have your own API key for GIPHY, you can place it inside the quotes, otherwise leave it as is.
In the terminal window at the root of the project directory, run flutter build web --web-renderer=canvaskit
. It will output the build files to build/web
to be hosted on your server.
Install NuGet package manager
Go to Visual Studio Installer -> Modify Build Tools -> Individual Components and install the latest Windows 10 SDK
Run the following commands:
Under construction...
Hey there! We're glad you want to contribute. We only ask for these three things:
Write clean code, and comment it!
Follow Flutter & Dart best practices
Avoid making large formatting changes to files, unless that is the goal of your PR. It makes it easier for us to review the changes this way.
Fork the repo, and then clone the fork to your system
Open the files in the IDE of your choice
Make your changes
Run the app using flutter run
or the green play button at the top of your IDE
Test your changes
Commit and PR!
Make sure you don't commit your changes to comment out onContentCommit
.
The client apps have a lot of variables that need to be tested. For example, if you're making a UI change, please make sure it looks good in all the default themes, and all the skins as well.
If you wish to make a backend change, we suggest you consult with the main developers before writing code. This is so we can come up with a plan of attack and make sure we don't degrade existing functionality or create bugs.
Please create all PRs targeting the development
branch, not the master
branch.
If you're new to Flutter development, look out for the good-first-issue
or the Difficulty: Easy
label on GitHub. These will be easier issues to help you start learning Flutter, without dealing with an overly-complex bug or feature.
Chat List | Message View | Private API Features |
---|
Make sure you've completed .
Complete step 2 of
Details on how BlueBubbles integrates with Tasker
Tasker integration requires BlueBubbles App v1.12.0 or greater!
If you make any cool integrations, feel free to share in our Discord!
To fetch the server URL in Tasker, follow the below process:
Create the Send Intent
task
Set the action
as com.bluebubbles.external.GET_SERVER_URL
Set one extra
as password:<your server password>
. This is required so that apps cannot abuse this ability without your consent.
Set the package
as com.bluebubbles.messaging
.
Ensure the target
is set as Broadcast Receiver
.
Create the Intent Received
event
Set the action
as net.dinglisch.android.taskerm.BB_SERVER_URL
Create your own task to perform once this is received. The server URL can be accessed via the %url
variable.
To listen for server events (new message, chat read status change, etc), follow the below process:
Enable the option within BlueBubbles app settings > Tasker Integration
Create the Intent Received
intent
Set the action
as net.dinglisch.android.taskerm.BB_EVENT
Create your own task to perform once this is received.
The intent sends a few pieces of data:
The server URL, which can be accessed via the %url
variable
The type of event, which can be accessed via the %event
variable
The event data, which can be accessed via the %data
variable
The event type is defined by the server event types, which can be found here.
The event data is a JSON string. You can use Tasker's built-in JSON parser to convert this to a real JSON object and access the individual data inside.
If you are using GitHub releases for one reason or another, whether it's because of Android Auto support or something else, follow this guide to receive automatic updates.
Install the Obtainium
app found here
Open the app
Tap on the Add App
tab
In the 2nd field labelled, "Search", enter bluebubbles-app
and click the "Search" button
Select the first result labelled, BlueBubblesApp/bluebubbles-app
and then confirm using the "Pick" button
Set any custom configuration/filtering options you may want
Lastly, confirm/save the configuration using the "Add" button at the top
Before installing updates, make sure to confirm the version you are updating to, making sure it is newer.
This page will show you how you can set up Unified Push to receive notifications so you don't have to use Google Firebase or the Background Service
Unified Push is an open-source protocol that enables users to choose how they receive push notifications, breaking the dependency on proprietary services. It empowers users by giving them control over their notifications, promoting privacy, and allowing for a more decentralized and customizable notification experience.
Unified Push can be self-hosted, or can be used with third-party "distributors". You can learn more about Unified Push and common distributors here:
Once Unified Push is set up, you can configure your BlueBubbles Server with a new Webhook to send notifications/messages to your device.
Since Unified Push is essentially just a protocol, there can and will be many distributors that support it. You just have to choose one that works best for you. In our recommended setup, we will be using ntfy.sh as our provider. It is a free and easy to use service that can be used directly with the BlueBubbles App (v1.15.0+).
Ensure you have the BlueBubbles App v1.15.0 or newer installed
Install the ntfy.sh app
Open the nfty.sh app and allow notification permissions
Open the Settings
page in the BlueBubbles App
Navigate to the Notification Providers
settings page and open the Unified Push
configuration.
Enable Unified Push
Within a few seconds, you should see an ntfy.sh
URL generated below the toggle
Switch to your BlueBubbles Server (Mac)
Open the API & Webhooks
tab
Click Manage
-> Add New Webhook
Copy/Paste the URL provided in the BlueBubbles App into the new webhook popup
Enable the webhook to for All Events
Save the Webhook
Now, when you are not actively using the BlueBubbles App, you should still receive notifications via Unified Push + ntfy.sh
This section aims to provide extensive documentation and how-tos for all of the complex things we have managed to implement while working within the constraints of a Flutter app. We hope that this can be a useful guide / tool for other developers working on Flutter apps, especially Flutter-based chat apps.
Check the sidebar for all the documentation! (WIP)
As mentioned in #why-flutter, we chose Flutter over building something in React Native, or in Java itself. When deciding our framework, we never had full cross platform in mind, and rather we were focusing on the ease of development. While native Android would definitely provide the best overall user experience, we decided to make a small tradeoff on this aspect to utilize Flutter's ease of use, scalability, and quick turnaround times.
By far and away, the best thing about Flutter has to be the ease of development. Flutter layout design and UI is ridiculously easy to understand, and creating great UI is a simple task. We also really like Dart syntax, it makes code very readable and has a lot of great features to make coding easier.
Pub has been a great package repo - There are so many high quality packages available to drop into your project and eliminate the need for a ton of boilerplate code. We use everything from small UI packages to larger state / framework / dependency management packages.
There is definitely much more to like, but it isn't important to list all that here.
We would like to preface this section by saying we are not the most experienced developers in Flutter. Many of the issues listed here might be resolved with advanced code, but so far we haven't been able to find workarounds for them.
One of the biggest issues we face with Flutter is performance and memory usage. Before Flutter 2.8, our app would often surpass 2 GB memory when loading up more than 10-15 JPEGs or MP4s. This functionality is crucial for a messaging app where multimedia is a high priority. We also had many issues with lag in our transitions, animations, and system events such as the keyboard going up and down. Most of the performance issues were resolved with painstaking optimizations in code and the Flutter 2.8 upgrade, however.
Another large issue in Flutter is text input. Out of the box, Flutter doesn't support any sort of image / multimedia paste into textfields. This also means users can't insert GIFs or screenshots from Gboard on Android. To solve this, we ended up creating a fork of the Flutter SDK and engine, which we discuss in more detail at Image Insertion. For Web, we ended up using a Dart / JS interop function which is also discussed in the same section.
Our final major downside is just how Flutter interfaces with native. Things like creating notifications, supporting features such as conversation bubbles, or using the default copy/paste menu, are difficult to implement or simply not possible at this time. Many of these issues have open tickets on GitHub but they are either P3, P4, or P5 which doesn't bode well for having any chance these features are added in the near future.
With all that said, we believe the pros vastly outweigh the cons. Flutter has been a godsend for creating beautiful UI easily, and Dart is a very easy language with great tools to create mobile apps. The package support is also excellent, we love being able to just drop in packages and quickly add new features using them. Above all else, Flutter allowed us to extend not only to mobile, but also Web and Desktop with very minimal changes to our initial mobile-only codebase.
If you are creating a chat app specifically, keep the cons in mind. Be prepared to have to jump through hoops to integrate some important functionalities - however in the end it will be worth it.
How we added the ability to insert images via keyboard or copy/paste into Flutter
One of the most crucial requirements for a chat app is to allow users to insert rich content directly from their keyboard. On Android, this can be things such as stickers, GIFs, bitmoji, and even AI-based context-aware items (e.g. the keyboard offering to paste a recent screenshot). This functionality is sorely missing in Flutter, and we were forced to modify the Flutter Engine and Framework ourselves to implement it.
The basic content commit API is described at https://developer.android.com/guide/topics/text/image-keyboard. The Java code required to interface with Flutter's `TextInputClient` was added in the Flutter Engine.
Our PR to the Flutter Engine has been merged! The code essentially attaches the TextInputChannel
to the contentCommit
API, and sends the file data through the PlatformChannel
back to the Flutter Framework.
Our PR to the Flutter Framework is currently pending review. The framework code organizes the data received from the PlatformChannel
into a Dart class, and then this data can be manipulated however the developer chooses.
Flutter currently does not have any way to detect image content when a paste action is performed on a text field. We implemented this with the great interoperability that Dart and JS have. We created a simple function that uses the JS
package to call JS code from Dart:
This is called when we detect a CTRL-V event on the text field using a RawKeyboardListener
. The JS function unfortunately only works in Chrome, but it uses the browser Clipboard
APIs to get any media content:
This function is placed inside the web/index.html
file as a <script>
.
We use the pasteboard package on Desktop to get the most recent item (image) from the clipboard. This is also called when we detect a CTRL-V event on the text field using a RawKeyboardListener
.
What we've learned and had to deal with while using ObjectBox as our database of choice
Initially, our app used the package as our database (i.e. SQLite). The iMessage database on macOS is an SQLite database, so it made the most sense for our app to emulate the same structure (which it did, we copied the CREATE statements for each table and used them to build our own internal database).
This worked well for the most part, but we hit a major roadblock when we decided to port our app to desktop platforms using Flutter and the package - The speeds decreased dramatically. A simple DB call could take upwards of 5-10 seconds, and this was causing significant delay for actions like loading chats, message threads, etc which should normally take up to 1 second.
As a result, we decided to look into alternative databases. We landed on two potential ones: and . We first tried sqlite3
to avoid a huge migration of code, but it was the same issue - speeds were too slow on desktop. Then, we moved to objectbox
. The speed issue was resolved, but we discovered a whole host of other issues that required some hacky workarounds and advanced Dart coding to resolve, and this page will discuss that in depth.
It was very tricky to get our code to compile for web while using ObjectBox. The issue is that it uses some dart:io
exclusive code, such as Pointer
s and other things.
As a result, we need to create two separate files for the same model, with very slightly different code. Here's a quick example:
When importing this model into other files, you want to use the following import directive:
This ensures that when compiling for web platforms, it does not import ObjectBox libraries, which would prevent the app from compiling.
The same process can be used for any functions that call ObjectBox code, create an io
version which has the function actually perform a task, and the web
version is empty:
Then use a similar import directive as above, and if you wish, you can call this function on io
only by using if (!kIsWeb) openObjectbox();
in your code.
The other big issue with ObjectBox is a lack of an asynchronous API. If you use synchronous code for larger reads in your app, it could cause heavy jittering and lag on page load. This was a huge problem for us, but thankfully there are two ways to mitigate it.
Requires Flutter 2.5 (Dart 2.14) and higher!
Do not use this method if you plan to use ObjectBox Relations! It will not work!
The first thing to do is store a reference to the ObjectBox Store
object by encoding it into a base64 string:
You'll want to do this right after you initialize your Store
in the app.
Next, let's take an example class:
As you can see, we are trying to get chats from ObjectBox using a given limit and offset. We use the Flutter compute
function which handles all the boilerplate code for creating an isolate, sending, and receiving arguments.
getChatsIsolate
must be a top level function, and is defined like so:
We get the arguments from the list sent to the function. The most important part is to use the same Store
reference as in the main thread!
This code will get you the best performance possible in the app, and we highly recommend doing it this way. What if you must absolutely use relations though?
async_task
PackageWe only recommend using this method if you absolutely need to use ObjectBox Relations! This method is not as performant, but is still definitely better than using the synchronous API.
When using the async_task
package, the example shown above would look something like this:
And GetChatAttachments
is defined as follows:
Finally, we also must add the createAsyncTask
function:
The parallelism: 0
is very important here. It makes sure the async_task
package doesn't try to use an isolate (which would break due to the Relations), and instead use an async zone to run the task.
Oftentimes jank will happen on animations - you will see dropped frames or stutters. To solve this, there are some things you should check and try doing:
Use transactions to group larger reads / writes into a single block
Reduce the number of reads / writes by simplifying your code
Run database code after page animations are complete:
You may have a background isolate that runs Dart code when an external event happens, e.g. when you receive an FCM notification. This is the proper way to use the Store
inside your isolate:
Every time you initialize a new Store
, you should store the reference inside some sort of SharedPreferences, like so:
This ensures that your code retains concurrency if your app happens to be active but also receives a background event that starts the secondary isolate.