├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── flutter_format.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── dev │ └── steenbakker │ └── flutter_ble_peripheral │ ├── FlutterBlePeripheralManager.kt │ ├── FlutterBlePeripheralPlugin.kt │ ├── callbacks │ ├── PeripheralAdvertisingCallback.kt │ └── PeripheralAdvertisingSetCallback.kt │ ├── exceptions │ ├── PeripheralException.kt │ └── PermissionNotFoundException.kt │ ├── handlers │ ├── DataReceivedHandler.kt │ ├── MtuChangedHandler.kt │ └── StateChangedHandler.kt │ └── models │ ├── PeripheralState.kt │ ├── PermissionState.kt │ └── State.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── proguard-rules.pro │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── ble_status_screen.dart │ └── main.dart ├── macos │ ├── .gitignore │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.yaml └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterBlePeripheralManager.swift │ ├── FlutterBlePeripheralPlugin.h │ ├── FlutterBlePeripheralPlugin.m │ ├── Models │ │ ├── PeripheralData.swift │ │ └── PeripheralState.swift │ ├── SwiftFlutterBlePeripheralPlugin.swift │ ├── callbacks │ │ └── PeripheralManagerDelegate.swift │ └── handlers │ │ ├── DataReceivedHandler.swift │ │ ├── MtuChangedHandler.swift │ │ └── StateChangedHandler.swift └── flutter_ble_peripheral.podspec ├── lib ├── flutter_ble_peripheral.dart └── src │ ├── flutter_ble_peripheral.dart │ └── models │ ├── advertise_data.dart │ ├── advertise_data.g.dart │ ├── advertise_set_parameters.dart │ ├── advertise_set_parameters.g.dart │ ├── advertise_settings.dart │ ├── advertise_settings.g.dart │ ├── constants.dart │ ├── enums │ ├── advertise_mode.dart │ ├── advertise_tx_power.dart │ └── bluetooth_peripheral_state.dart │ ├── map_uint8list_converter.dart │ ├── periodic_advertise_settings.dart │ ├── periodic_advertise_settings.g.dart │ ├── peripheral_state.dart │ ├── permission_state.dart │ ├── uint8list_converter.dart │ └── uint8list_map_string_converter.dart ├── macos ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterBlePeripheralManager.swift │ ├── FlutterBlePeripheralPlugin.h │ ├── FlutterBlePeripheralPlugin.m │ ├── Models │ │ ├── PeripheralData.swift │ │ └── PeripheralState.swift │ ├── SwiftFlutterBlePeripheralPlugin.swift │ ├── callbacks │ │ └── PeripheralManagerDelegate.swift │ └── handlers │ │ ├── DataReceivedHandler.swift │ │ ├── MtuChangedHandler.swift │ │ └── StateChangedHandler.swift └── flutter_ble_peripheral.podspec ├── pubspec.yaml ├── test └── flutter_ble_peripheral_test.dart └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter_ble_peripheral_plugin.cpp ├── flutter_ble_peripheral_plugin.h ├── flutter_ble_peripheral_plugin_c_api.cpp └── include └── flutter_ble_peripheral └── flutter_ble_peripheral_plugin_c_api.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [juliansteenbakker] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | reviewers: 8 | - "juliansteenbakker" 9 | - package-ecosystem: gradle 10 | directory: "/android" 11 | schedule: 12 | interval: "weekly" 13 | reviewers: 14 | - "juliansteenbakker" 15 | - package-ecosystem: gradle 16 | directory: "/example/android" 17 | schedule: 18 | interval: "weekly" 19 | reviewers: 20 | - "juliansteenbakker" 21 | -------------------------------------------------------------------------------- /.github/workflows/flutter_format.yml: -------------------------------------------------------------------------------- 1 | name: code analysis & formatting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | analysis: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-java@v4 11 | with: 12 | distribution: 'temurin' 13 | java-version: '11' 14 | - uses: subosito/flutter-action@v2.16.0 15 | - name: Version 16 | run: flutter doctor -v 17 | - name: Install dependencies 18 | run: flutter pub get 19 | - name: Linter 20 | run: flutter analyze 21 | formatting: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-java@v4 26 | with: 27 | distribution: 'temurin' 28 | java-version: '11' 29 | - uses: subosito/flutter-action@v2.16.0 30 | - name: Format 31 | run: dart format --set-exit-if-changed . 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .last_build_id 13 | /.cxx/ 14 | example/android/app/.cxx 15 | 16 | # IntelliJ related 17 | *.iml 18 | *.ipr 19 | *.iws 20 | .idea/ 21 | 22 | # Visual Studio Code related 23 | .classpath 24 | .project 25 | .settings/ 26 | .vscode/ 27 | 28 | # Flutter repo-specific 29 | /bin/cache/ 30 | /bin/mingit/ 31 | /dev/benchmarks/mega_gallery/ 32 | /dev/bots/.recipe_deps 33 | /dev/bots/android_tools/ 34 | /dev/docs/doc/ 35 | /dev/docs/flutter.docs.zip 36 | /dev/docs/lib/ 37 | /dev/docs/pubspec.yaml 38 | /dev/integration_tests/**/xcuserdata 39 | /dev/integration_tests/**/Pods 40 | /packages/flutter/coverage/ 41 | version 42 | 43 | # packages file containing multi-root paths 44 | .packages.generated 45 | 46 | # Flutter/Dart/Pub related 47 | **/doc/api/ 48 | .dart_tool/ 49 | .flutter-plugins 50 | .flutter-plugins-dependencies 51 | .packages 52 | .pub-cache/ 53 | .pub/ 54 | build/ 55 | flutter_*.png 56 | linked_*.ds 57 | unlinked.ds 58 | unlinked_spec.ds 59 | 60 | # Android related 61 | **/android/**/gradle-wrapper.jar 62 | **/android/.gradle 63 | **/android/captures/ 64 | **/android/gradlew 65 | **/android/gradlew.bat 66 | **/android/local.properties 67 | **/android/**/GeneratedPluginRegistrant.java 68 | **/android/key.properties 69 | **/android/property* 70 | *.jks 71 | 72 | # iOS/XCode related 73 | **/ios/**/*.mode1v3 74 | **/ios/**/*.mode2v3 75 | **/ios/**/*.moved-aside 76 | **/ios/**/*.pbxuser 77 | **/ios/**/*.perspectivev3 78 | **/ios/**/*sync/ 79 | **/ios/**/.sconsign.dblite 80 | **/ios/**/.tags* 81 | **/ios/**/.vagrant/ 82 | **/ios/**/DerivedData/ 83 | **/ios/**/Icon? 84 | **/ios/**/Pods/ 85 | **/ios/**/.symlinks/ 86 | **/ios/**/profile 87 | **/ios/**/xcuserdata 88 | **/ios/.generated/ 89 | **/ios/Flutter/App.framework 90 | **/ios/Flutter/Flutter.framework 91 | **/ios/Flutter/Flutter.podspec 92 | **/ios/Flutter/Generated.xcconfig 93 | **/ios/Flutter/app.flx 94 | **/ios/Flutter/app.zip 95 | **/ios/Flutter/flutter_assets/ 96 | **/ios/Flutter/flutter_export_environment.sh 97 | **/ios/ServiceDefinitions.json 98 | **/ios/Runner/GeneratedPluginRegistrant.* 99 | 100 | # macOS 101 | **/macos/Flutter/GeneratedPluginRegistrant.swift 102 | **/macos/Flutter/Flutter-Debug.xcconfig 103 | **/macos/Flutter/Flutter-Release.xcconfig 104 | **/macos/Flutter/Flutter-Profile.xcconfig 105 | 106 | # Coverage 107 | coverage/ 108 | 109 | # Symbols 110 | app.*.symbols 111 | 112 | # Exceptions to above rules. 113 | !**/ios/**/default.mode1v3 114 | !**/ios/**/default.mode2v3 115 | !**/ios/**/default.pbxuser 116 | !**/ios/**/default.perspectivev3 117 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 118 | !/dev/ci/**/Gemfile.lock -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 17 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 18 | - platform: android 19 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 20 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 21 | - platform: ios 22 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 23 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 24 | - platform: macos 25 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 26 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 27 | - platform: windows 28 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 29 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 30 | 31 | # User provided section 32 | 33 | # List of Local paths (relative to this file) that should be 34 | # ignored by the migrate tool. 35 | # 36 | # Files that are not part of the templates will be ignored by default. 37 | unmanaged_files: 38 | - 'lib/main.dart' 39 | - 'ios/Runner.xcodeproj/project.pbxproj' 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2.6 2 | - [Android] Fixes error on start broadcasting 3 | 4 | ## 1.2.5 5 | - [Android] Added advertiseSet parameter to AdvertiseSettings to enable advertiseSet on Android o and higher devices. 6 | 7 | ## 1.2.4 8 | - [iOS & macOS] Fixed an issue which caused the first advertisement not to be broadcast. 9 | 10 | ## 1.2.3 11 | - [Android] Fixed requestPermission not working correctly. 12 | 13 | ## 1.2.2 14 | - [Android] Fixed serviceUuid not working. (thanks @Shik1266 !) 15 | - [Android] Updated compileSdk to 34. 16 | 17 | ## 1.2.1 18 | - Fix build errors & crash on Windows 19 | - Upgrade gradle to 8.1 20 | 21 | ## 1.2.0 22 | Improvements: 23 | - Added support for windows 24 | - Updated bluetooth permissions system for Android, no need for permission handler anymore. 25 | - Updated dependencies and several other small improvements. 26 | 27 | ## 1.1.1 28 | Bugs fixed: 29 | - Fixed an issue which caused the enableBluetooth function to reply twice. 30 | - Fixed analyzer issues 31 | - Upgraded dependencies 32 | 33 | ## 1.1.0 34 | Upgraded android sdk to 33. 35 | Added permission check on enableBluetooth function. 36 | 37 | ## 1.0.0 38 | Stable release including the changes noted in the beta releases. 39 | This release also updates Android dependencies. 40 | 41 | ## 1.0.0-beta.2 42 | Fixed macOS version not working 43 | 44 | ## 1.0.0-beta.1 45 | BREAKING CHANGES: 46 | You now define the data to be advertised using the AdvertiseData() constructor. 47 | AdvertiseData is the only supported object in iOS. AdvertiseSettings and other objects are only 48 | supported on Android. 49 | 50 | NEW: 51 | * You can now make use of the new startAdvertisingSet parameter on Android 26+ 52 | 53 | ## 0.6.0 54 | * Refactored large parts of the code for both Android & iOS. 55 | * Upgraded Android to Android 12 permission system. 56 | * Other minor improvements 57 | 58 | ## 0.5.0+1 59 | Changes of 0.5.0 weren't visible on pub.dev 60 | 61 | ## 0.5.0 62 | Added isSupported function to check if BLE advertising is supported by the device. 63 | 64 | ## 0.4.2 65 | Fixed typo causing deviceName not to broadcast on iOS 66 | 67 | ## 0.4.1 68 | Fixed bug on iOS which led to crash 69 | Added local name to advertising in iOS 70 | Updated Android dependencies 71 | 72 | ## 0.4.0 73 | Added new options to AdvertiseData 74 | Removed embedding V1 for Android 75 | 76 | ## 0.3.0 77 | Upgraded to null-safety 78 | Updated dependencies 79 | Changed to pedantic 80 | 81 | Bug fixes 82 | * Fixed null-pointer when bluetooth adapter isn't found 83 | 84 | ## 0.2.0 85 | Add support for MacOS 86 | 87 | ## 0.1.0 88 | Fixed several parts for Android: 89 | * Advertising local name 90 | * Advertising Manufacturer Data 91 | * Advertising Service Data 92 | 93 | ## 0.0.4 94 | Fixed iOS advertising not working 95 | 96 | ## 0.0.3 97 | Fixed callback on Android 98 | 99 | ## 0.0.2 100 | Fixed flutter v2 embedding 101 | 102 | ## 0.0.1 103 | Initial version of the library. This version includes: 104 | * broadcasting a custom UUID 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2020 Julian Steenbakker 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![pub package](https://img.shields.io/pub/v/flutter_ble_peripheral?include_prereleases)](https://pub.dartlang.org/packages/flutter_ble_peripheral) 3 | [![Join the chat](https://img.shields.io/discord/827432913896341534)](https://discord.gg/XeyJZhaczm) 4 | [![Workflow](https://github.com/juliansteenbakker/flutter_ble_peripheral/actions/workflows/flutter_format.yml/badge.svg?branch=master)](https://github.com/juliansteenbakker/flutter_ble_peripheral/actions) 5 | [![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint) 6 | [![GitHub Sponsors](https://img.shields.io/github/sponsors/juliansteenbakker?label=sponsor%20me)](https://github.com/sponsors/juliansteenbakker) 7 | 8 | # FlutterBlePeripheral 9 | 10 | This Flutter plugin allows a device to be used in Peripheral mode, and advertise data over BLE to central devices. 11 | 12 | ## Help develop this plugin! 13 | 14 | If you want to contribute to this plugin, feel free to make issues and pull-requests. 15 | 16 | ### Not stable 17 | 18 | Since this plugin is currently being developed, limited functionality will be available. Check the release page for the most recent release. 19 | 20 | | Functionality | Android | iOS | Description | 21 | |----------------------------|:------------------:|:------------------:|------------------------------------------------------| 22 | | Advertise UUID | :white_check_mark: | :white_check_mark: | Set and advertise a custom uuid. | 23 | | Advertise ManufacturerData | :white_check_mark: | :x: | Set and advertise custom data. *not possible on iOS* | 24 | | Advertise custom service | :white_check_mark: | :x: | Advertise a custom service. *not possible on iOS* | 25 | 26 | ## How to use 27 | Please check the example app to see how to broadcast data. 28 | Note that iOS does not support a lot of options. Please see `AdvertiseData` to see which options are supported by iOS & Android. 29 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options_package.yaml 2 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'dev.steenbakker.flutter_ble_peripheral' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.9.20' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.6.1' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdk 35 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_17 32 | targetCompatibility JavaVersion.VERSION_17 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '17' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | defaultConfig { 43 | minSdkVersion 21 44 | } 45 | lintOptions { 46 | disable 'InvalidPackage' 47 | } 48 | namespace 'dev.steenbakker.flutter_ble_peripheral' 49 | } 50 | 51 | dependencies { 52 | 53 | } 54 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 18 13:29:08 CET 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_ble_peripheral' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/callbacks/PeripheralAdvertisingCallback.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.callbacks 2 | 3 | import android.bluetooth.le.AdvertiseCallback 4 | import android.bluetooth.le.AdvertiseSettings 5 | import dev.steenbakker.flutter_ble_peripheral.handlers.StateChangedHandler 6 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralState 7 | import io.flutter.Log 8 | import io.flutter.plugin.common.MethodChannel 9 | 10 | class PeripheralAdvertisingCallback(private val result: MethodChannel.Result, private val stateChangedHandler: StateChangedHandler): AdvertiseCallback() { 11 | override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { 12 | super.onStartSuccess(settingsInEffect) 13 | Log.i("FlutterBlePeripheral", "onStartSuccess() mode: ${settingsInEffect.mode}, txPOWER ${settingsInEffect.txPowerLevel}") 14 | result.success(null) 15 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 16 | } 17 | 18 | override fun onStartFailure(errorCode: Int) { 19 | super.onStartFailure(errorCode) 20 | val statusText: String 21 | when (errorCode) { 22 | ADVERTISE_FAILED_ALREADY_STARTED -> { 23 | statusText = "ADVERTISE_FAILED_ALREADY_STARTED" 24 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 25 | } 26 | ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> { 27 | statusText = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED" 28 | stateChangedHandler.publishPeripheralState(PeripheralState.unsupported) 29 | } 30 | ADVERTISE_FAILED_INTERNAL_ERROR -> { 31 | statusText = "ADVERTISE_FAILED_INTERNAL_ERROR" 32 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 33 | } 34 | ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> { 35 | statusText = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS" 36 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 37 | } 38 | ADVERTISE_FAILED_DATA_TOO_LARGE -> { 39 | statusText = "ADVERTISE_FAILED_DATA_TOO_LARGE" 40 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 41 | } 42 | else -> { 43 | statusText = "UNDOCUMENTED" 44 | stateChangedHandler.publishPeripheralState(PeripheralState.unknown) 45 | } 46 | } 47 | result.error(errorCode.toString(), statusText, "startAdvertising") 48 | } 49 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/callbacks/PeripheralAdvertisingSetCallback.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.callbacks 2 | 3 | import android.bluetooth.le.AdvertisingSet 4 | import android.bluetooth.le.AdvertisingSetCallback 5 | import android.bluetooth.le.BluetoothLeAdvertiser 6 | import android.os.Build 7 | import androidx.annotation.RequiresApi 8 | import dev.steenbakker.flutter_ble_peripheral.handlers.StateChangedHandler 9 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralState 10 | import io.flutter.Log 11 | import io.flutter.plugin.common.MethodChannel 12 | 13 | @RequiresApi(Build.VERSION_CODES.O) 14 | class PeripheralAdvertisingSetCallback(private val result: MethodChannel.Result, private val stateChangedHandler: StateChangedHandler): AdvertisingSetCallback() { 15 | /** 16 | * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} 17 | * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet 18 | * contains the started set and it is advertising. If error occurred, advertisingSet is 19 | * null, and status will be set to proper error code. 20 | * 21 | * @param advertisingSet The advertising set that was started or null if error. 22 | * @param txPower tx power that will be used for this set. 23 | * @param status Status of the operation. 24 | */ 25 | 26 | override fun onAdvertisingSetStarted( 27 | advertisingSet: AdvertisingSet?, 28 | txPower: Int, 29 | status: Int 30 | ) { 31 | Log.i("FlutterBlePeripheral", "onAdvertisingSetStarted() status: $advertisingSet, txPOWER $txPower, status $status") 32 | super.onAdvertisingSetStarted(advertisingSet, txPower, status) 33 | var statusText = "" 34 | when (status) { 35 | ADVERTISE_SUCCESS -> { 36 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 37 | } 38 | ADVERTISE_FAILED_ALREADY_STARTED -> { 39 | statusText = "ADVERTISE_FAILED_ALREADY_STARTED" 40 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 41 | } 42 | ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> { 43 | statusText = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED" 44 | stateChangedHandler.publishPeripheralState(PeripheralState.unsupported) 45 | } 46 | ADVERTISE_FAILED_INTERNAL_ERROR -> { 47 | statusText = "ADVERTISE_FAILED_INTERNAL_ERROR" 48 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 49 | } 50 | ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> { 51 | statusText = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS" 52 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 53 | } 54 | ADVERTISE_FAILED_DATA_TOO_LARGE -> { 55 | statusText = "ADVERTISE_FAILED_DATA_TOO_LARGE" 56 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 57 | } 58 | else -> { 59 | statusText = "UNDOCUMENTED" 60 | stateChangedHandler.publishPeripheralState(PeripheralState.unknown) 61 | } 62 | 63 | } 64 | if (status != ADVERTISE_SUCCESS) { 65 | result.error(status.toString(), statusText, "startAdvertisingSet") 66 | } else { 67 | result.success(0) 68 | } 69 | 70 | } 71 | 72 | /** 73 | * Callback triggered in response to [BluetoothLeAdvertiser.stopAdvertisingSet] 74 | * indicating advertising set is stopped. 75 | * 76 | * @param advertisingSet The advertising set. 77 | */ 78 | override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet?) { 79 | Log.i("FlutterBlePeripheral", "onAdvertisingSetStopped() status: $advertisingSet") 80 | super.onAdvertisingSetStopped(advertisingSet) 81 | stateChangedHandler.publishPeripheralState(PeripheralState.idle) 82 | } 83 | 84 | /** 85 | * Callback triggered in response to [BluetoothLeAdvertiser.startAdvertisingSet] 86 | * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is 87 | * advertising. 88 | * 89 | * @param advertisingSet The advertising set. 90 | * @param status Status of the operation. 91 | */ 92 | override fun onAdvertisingEnabled( 93 | advertisingSet: AdvertisingSet?, 94 | enable: Boolean, 95 | status: Int 96 | ) { 97 | Log.i("FlutterBlePeripheral", "onAdvertisingEnabled() status: $advertisingSet, enable $enable, status $status") 98 | super.onAdvertisingEnabled(advertisingSet, enable, status) 99 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 100 | } 101 | 102 | /** 103 | * Callback triggered in response to [AdvertisingSet.setAdvertisingData] indicating 104 | * result of the operation. If status is ADVERTISE_SUCCESS, then data was changed. 105 | * 106 | * @param advertisingSet The advertising set. 107 | * @param status Status of the operation. 108 | */ 109 | override fun onAdvertisingDataSet(advertisingSet: AdvertisingSet?, status: Int) { 110 | Log.i("FlutterBlePeripheral", "onAdvertisingDataSet() status: $advertisingSet, status $status") 111 | super.onAdvertisingDataSet(advertisingSet, status) 112 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 113 | } 114 | 115 | /** 116 | * Callback triggered in response to [AdvertisingSet.setAdvertisingData] indicating 117 | * result of the operation. 118 | * 119 | * @param advertisingSet The advertising set. 120 | * @param status Status of the operation. 121 | */ 122 | override fun onScanResponseDataSet(advertisingSet: AdvertisingSet?, status: Int) { 123 | Log.i("FlutterBlePeripheral", "onScanResponseDataSet() status: $advertisingSet, status $status") 124 | super.onAdvertisingDataSet(advertisingSet, status) 125 | stateChangedHandler.publishPeripheralState(PeripheralState.advertising) 126 | } 127 | 128 | /** 129 | * Callback triggered in response to [AdvertisingSet.setAdvertisingParameters] 130 | * indicating result of the operation. 131 | * 132 | * @param advertisingSet The advertising set. 133 | * @param txPower tx power that will be used for this set. 134 | * @param status Status of the operation. 135 | */ 136 | override fun onAdvertisingParametersUpdated( 137 | advertisingSet: AdvertisingSet?, 138 | txPower: Int, status: Int 139 | ) { 140 | Log.i("FlutterBlePeripheral", "onAdvertisingParametersUpdated() status: $advertisingSet, txPOWER $txPower, status $status") 141 | } 142 | 143 | /** 144 | * Callback triggered in response to [AdvertisingSet.setPeriodicAdvertisingParameters] 145 | * indicating result of the operation. 146 | * 147 | * @param advertisingSet The advertising set. 148 | * @param status Status of the operation. 149 | */ 150 | override fun onPeriodicAdvertisingParametersUpdated( 151 | advertisingSet: AdvertisingSet?, 152 | status: Int 153 | ) { 154 | Log.i("FlutterBlePeripheral", "onPeriodicAdvertisingParametersUpdated() status: $advertisingSet, status $status") 155 | } 156 | 157 | /** 158 | * Callback triggered in response to [AdvertisingSet.setPeriodicAdvertisingData] 159 | * indicating result of the operation. 160 | * 161 | * @param advertisingSet The advertising set. 162 | * @param status Status of the operation. 163 | */ 164 | override fun onPeriodicAdvertisingDataSet( 165 | advertisingSet: AdvertisingSet?, 166 | status: Int 167 | ) { 168 | Log.i("FlutterBlePeripheral", "onPeriodicAdvertisingDataSet() status: $advertisingSet, status $status") 169 | } 170 | 171 | /** 172 | * Callback triggered in response to [AdvertisingSet.setPeriodicAdvertisingEnabled] 173 | * indicating result of the operation. 174 | * 175 | * @param advertisingSet The advertising set. 176 | * @param status Status of the operation. 177 | */ 178 | override fun onPeriodicAdvertisingEnabled( 179 | advertisingSet: AdvertisingSet?, enable: Boolean, 180 | status: Int 181 | ) { 182 | Log.i("FlutterBlePeripheral", "onPeriodicAdvertisingEnabled() status: $advertisingSet, enable $enable, status $status") 183 | } 184 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/exceptions/PeripheralException.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.exceptions 2 | 3 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralState 4 | import java.lang.Exception 5 | 6 | class PeripheralException(val state: PeripheralState) : Exception(state.name) -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/exceptions/PermissionNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.exceptions 2 | 3 | import java.lang.Exception 4 | 5 | class PermissionNotFoundException(message: String) : Exception(message) 6 | -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/handlers/DataReceivedHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.handlers 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import dev.steenbakker.flutter_ble_peripheral.FlutterBlePeripheralManager 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.plugin.common.EventChannel 8 | 9 | class DataReceivedHandler : EventChannel.StreamHandler { 10 | private var eventSink: EventChannel.EventSink? = null 11 | 12 | fun register(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, flutterBlePeripheralManager: FlutterBlePeripheralManager) { 13 | val eventChannel = EventChannel( 14 | flutterPluginBinding.binaryMessenger, 15 | "dev.steenbakker.flutter_ble_peripheral/ble_data_received" 16 | ) 17 | 18 | eventChannel.setStreamHandler(this) 19 | 20 | // flutterBlePeripheralManager.onDataReceived = { 21 | // 22 | // Handler(Looper.getMainLooper()).post { 23 | // eventSink?.success(it) 24 | // } 25 | // } 26 | } 27 | 28 | override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { 29 | this.eventSink = eventSink 30 | } 31 | 32 | override fun onCancel(event: Any?) { 33 | this.eventSink = null 34 | } 35 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/handlers/MtuChangedHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.handlers 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import dev.steenbakker.flutter_ble_peripheral.FlutterBlePeripheralManager 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.plugin.common.EventChannel 8 | 9 | class MtuChangedHandler(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, flutterBlePeripheralManager: FlutterBlePeripheralManager) : EventChannel.StreamHandler { 10 | private var eventSink: EventChannel.EventSink? = null 11 | 12 | 13 | init { 14 | val eventChannel = EventChannel( 15 | flutterPluginBinding.binaryMessenger, 16 | "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed" 17 | ) 18 | 19 | eventChannel.setStreamHandler(this) 20 | 21 | // flutterBlePeripheralManager.onMtuChanged = { mtu -> 22 | // Handler(Looper.getMainLooper()).post { 23 | // eventSink?.success(mtu) 24 | // } 25 | // } 26 | } 27 | 28 | fun register(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, flutterBlePeripheralManager: FlutterBlePeripheralManager) { 29 | val eventChannel = EventChannel( 30 | flutterPluginBinding.binaryMessenger, 31 | "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed" 32 | ) 33 | 34 | eventChannel.setStreamHandler(this) 35 | 36 | // flutterBlePeripheralManager.onMtuChanged = { mtu -> 37 | // Handler(Looper.getMainLooper()).post { 38 | // eventSink?.success(mtu) 39 | // } 40 | // } 41 | } 42 | 43 | override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { 44 | this.eventSink = eventSink 45 | } 46 | 47 | override fun onCancel(event: Any?) { 48 | this.eventSink = null 49 | } 50 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/handlers/StateChangedHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.handlers 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralState 6 | import io.flutter.Log 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin 8 | import io.flutter.plugin.common.EventChannel 9 | 10 | class StateChangedHandler(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) : EventChannel.StreamHandler { 11 | private val tag: String = "BLE Peripheral state " 12 | 13 | private var eventSink: EventChannel.EventSink? = null 14 | 15 | var state = PeripheralState.idle 16 | 17 | private val eventChannel = EventChannel( 18 | flutterPluginBinding.binaryMessenger, 19 | "dev.steenbakker.flutter_ble_peripheral/ble_state_changed" 20 | ) 21 | 22 | init { 23 | eventChannel.setStreamHandler(this) 24 | } 25 | 26 | // fun register(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 27 | // val eventChannel = EventChannel( 28 | // flutterPluginBinding.binaryMessenger, 29 | // "dev.steenbakker.flutter_ble_peripheral/ble_state_changed" 30 | // ) 31 | // eventChannel.setStreamHandler(this) 32 | // } 33 | 34 | fun publishPeripheralState(state: PeripheralState) { 35 | Log.i(tag, state.name) 36 | this.state = state 37 | Handler(Looper.getMainLooper()).post { 38 | eventSink?.success(state.ordinal) 39 | } 40 | } 41 | 42 | override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { 43 | this.eventSink = eventSink 44 | publishPeripheralState(state) 45 | } 46 | 47 | override fun onCancel(event: Any?) { 48 | this.eventSink = null 49 | } 50 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/models/PeripheralState.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.models 2 | 3 | enum class PeripheralState { 4 | /// Status is not (yet) determined. 5 | unknown, 6 | 7 | /// BLE is not supported on this device. 8 | unsupported, 9 | 10 | /// BLE usage is not authorized for this app. 11 | unauthorized, 12 | 13 | /// BLE is turned off. 14 | poweredOff, 15 | 16 | // /// Android only: Location services are disabled. 17 | // locationServicesDisabled, 18 | 19 | /// BLE is fully operating for this app. 20 | idle, 21 | 22 | /// BLE is advertising data. 23 | advertising, 24 | 25 | /// BLE is connected to a device. 26 | connected, 27 | 28 | shouldShowRequestPermissionRationale, 29 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/models/PermissionState.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.models 2 | 3 | enum class PermissionState(val nr: Int) { 4 | /// Status is not (yet) determined. 5 | Granted(0), 6 | 7 | /// BLE is not supported on this device. 8 | Denied(1), 9 | 10 | /// BLE usage is not authorized for this app. 11 | PermanentlyDenied(2), 12 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/models/State.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.models 2 | 3 | /* 4 | * Copyright (c) 2023. Julian Steenbakker. 5 | * All rights reserved. Use of this source code is governed by a 6 | * BSD-style license that can be found in the LICENSE file. 7 | */ 8 | 9 | enum class State(val value: Int) { 10 | /// The user granted access to the requested feature. 11 | Granted(1), /// The user denied access to the requested feature, permission needs to be asked first. 12 | Denied(2), /// Permission to the requested feature is permanently denied, 13 | 14 | /// the permission dialog will not be shown when requesting this permission. 15 | /// The user may still change the permission status in the settings. 16 | PermanentlyDenied(3), /// The status is unknown 17 | 18 | 19 | /// The user cannot change this app's status, possibly due to active restrictions such as parental controls being in place. 20 | /// 21 | /// Only supported on iOS. 22 | Restricted(4), /// User has authorized this application for limited access. 23 | 24 | /// Only supported on iOS (iOS14+). 25 | Limited(5), /// Bluetooth is turned off 26 | TurnedOff(6), 27 | Unsupported(7), 28 | Unknown(8), 29 | Ready(9), 30 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_ble_peripheral_example 2 | 3 | Demonstrates how to use the flutter_ble_peripheral plugin. 4 | 5 | ## Example 6 | 7 | ```dart 8 | /* 9 | * Copyright (c) 2020. Julian Steenbakker. 10 | * All rights reserved. Use of this source code is governed by a 11 | * BSD-style license that can be found in the LICENSE file. 12 | */ 13 | 14 | import 'package:flutter/material.dart'; 15 | import 'package:flutter_ble_peripheral/flutter_ble_peripheral.dart'; 16 | 17 | void main() => runApp(FlutterBlePeripheralExample()); 18 | 19 | class FlutterBlePeripheralExample extends StatefulWidget { 20 | @override 21 | _FlutterBlePeripheralExampleState createState() => 22 | _FlutterBlePeripheralExampleState(); 23 | } 24 | 25 | class _FlutterBlePeripheralExampleState 26 | extends State { 27 | final FlutterBlePeripheral blePeripheral = FlutterBlePeripheral(); 28 | final AdvertiseData _data = AdvertiseData(); 29 | bool _isBroadcasting = false; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | setState(() { 35 | _data.includeDeviceName = false; 36 | _data.uuid = 'bf27730d-860a-4e09-889c-2d8b6a9e0fe7'; 37 | _data.manufacturerId = 1234; 38 | _data.manufacturerData = [1, 2, 3, 4, 5, 6]; 39 | _data.txPowerLevel = AdvertisePower.ADVERTISE_TX_POWER_ULTRA_LOW; 40 | _data.advertiseMode = AdvertiseMode.ADVERTISE_MODE_LOW_LATENCY; 41 | }); 42 | initPlatformState(); 43 | } 44 | 45 | Future initPlatformState() async { 46 | var isAdvertising = await blePeripheral.isAdvertising(); 47 | setState(() { 48 | _isBroadcasting = isAdvertising; 49 | }); 50 | } 51 | 52 | void _toggleAdvertise() async { 53 | if (await blePeripheral.isAdvertising()) { 54 | await blePeripheral.stop(); 55 | setState(() { 56 | _isBroadcasting = false; 57 | }); 58 | } else { 59 | await blePeripheral.start(_data); 60 | setState(() { 61 | _isBroadcasting = true; 62 | }); 63 | } 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return MaterialApp( 69 | home: Scaffold( 70 | appBar: AppBar( 71 | title: const Text('Flutter BLE Peripheral'), 72 | ), 73 | body: Center( 74 | child: Column( 75 | mainAxisAlignment: MainAxisAlignment.center, 76 | crossAxisAlignment: CrossAxisAlignment.center, 77 | children: [ 78 | Text('Is advertising: $_isBroadcasting'), 79 | StreamBuilder( 80 | stream: blePeripheral.getAdvertisingStateChange(), 81 | initialData: 'Advertisement not started.', 82 | builder: 83 | (BuildContext context, AsyncSnapshot snapshot) { 84 | return Text('Is advertising stream: ${snapshot.data}'); 85 | }), 86 | Text('Current uuid is ${_data.uuid}'), 87 | MaterialButton( 88 | onPressed: _toggleAdvertise, 89 | child: Text( 90 | 'Toggle advertising', 91 | style: Theme.of(context) 92 | .primaryTextTheme 93 | .button! 94 | .copyWith(color: Colors.blue), 95 | )), 96 | ])), 97 | ), 98 | ); 99 | } 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | 26 | 27 | android { 28 | compileSdkVersion 35 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | 34 | defaultConfig { 35 | applicationId "dev.steenbakker.flutter_ble_peripheral_example" 36 | minSdkVersion 21 37 | targetSdkVersion 35 38 | versionCode flutterVersionCode.toInteger() 39 | versionName flutterVersionName 40 | } 41 | 42 | buildTypes { 43 | release { 44 | // TODO: Add your own signing config for the release build. 45 | // Signing with the debug keys for now, so `flutter run --release` works. 46 | signingConfig signingConfigs.debug 47 | } 48 | } 49 | lint { 50 | disable 'InvalidPackage' 51 | } 52 | namespace 'dev.steenbakker.flutter_ble_peripheral_example' 53 | } 54 | 55 | flutter { 56 | source '../..' 57 | } 58 | 59 | dependencies { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 36 | 40 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.defaults.buildfeatures.buildconfig=true 5 | android.nonTransitiveRClass=false 6 | android.nonFinalResIds=false 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 13 13:19:01 CET 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /example/android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class android.bluetooth.le.** { *; } -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version '8.9.0' apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.20" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_ble_peripheral_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSBluetoothAlwaysUsageDescription 26 | Bluetooth is required in order to advertise via BLE 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/ble_status_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_ble_peripheral/flutter_ble_peripheral.dart'; 3 | 4 | class BleStatusScreen extends StatelessWidget { 5 | const BleStatusScreen({required this.status, Key? key}) : super(key: key); 6 | 7 | final PeripheralState status; 8 | // idle, advertising, connected, unsupported, unauthorized } 9 | String determineText(PeripheralState status) { 10 | switch (status) { 11 | case PeripheralState.unsupported: 12 | return "This device does not support Bluetooth"; 13 | case PeripheralState.unauthorized: 14 | return "Authorize the BlePeripheral example app to use Bluetooth and location"; 15 | case PeripheralState.poweredOff: 16 | return "Bluetooth is powered off on your device turn it on"; 17 | // case PeripheralState.unauthorized: 18 | // return "Enable location services"; 19 | case PeripheralState.idle: 20 | return "Bluetooth is up and running"; 21 | default: 22 | return "Waiting to fetch Bluetooth status $status"; 23 | } 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) => Scaffold( 28 | body: Center( 29 | child: Text(determineText(status)), 30 | ), 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = flutter_ble_peripheral_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.flutterBlePeripheralExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 dev.steenbakker. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.device.bluetooth 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSBluetoothAlwaysUsageDescription 6 | This app needs bluetooth in order to advertise data. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | $(PRODUCT_COPYRIGHT) 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.bluetooth 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ble_peripheral_example 2 | description: Demonstrates how to use the flutter_ble_peripheral plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=2.15.0 <3.0.0' 7 | flutter: ">=1.10.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | flutter_ble_peripheral: 13 | path: ../ 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | lint: ^2.0.1 19 | 20 | flutter: 21 | uses-material-design: true -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(flutter_ble_peripheral_example LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "flutter_ble_peripheral_example") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | # Generated plugin build rules, which manage building the plugins and adding 56 | # them to the application. 57 | include(flutter/generated_plugins.cmake) 58 | 59 | 60 | # === Installation === 61 | # Support files are copied into place next to the executable, so that it can 62 | # run in place. This is done instead of making a separate bundle (as on Linux) 63 | # so that building and running from within Visual Studio will work. 64 | set(BUILD_BUNDLE_DIR "$") 65 | # Make the "install" step default, as it's required to run. 66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 69 | endif() 70 | 71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 73 | 74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 78 | COMPONENT Runtime) 79 | 80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 81 | COMPONENT Runtime) 82 | 83 | if(PLUGIN_BUNDLED_LIBRARIES) 84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 86 | COMPONENT Runtime) 87 | endif() 88 | 89 | # Fully re-copy the assets directory on each build to avoid having stale files 90 | # from a previous install. 91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 92 | install(CODE " 93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 94 | " COMPONENT Runtime) 95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 97 | 98 | # Install the AOT library on non-Debug builds only. 99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 100 | CONFIGURATIONS Profile;Release 101 | COMPONENT Runtime) 102 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | FlutterBlePeripheralPluginCApiRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("FlutterBlePeripheralPluginCApi")); 14 | } 15 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_ble_peripheral 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "dev.steenbakker" "\0" 93 | VALUE "FileDescription", "flutter_ble_peripheral_example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "flutter_ble_peripheral_example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 dev.steenbakker. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "flutter_ble_peripheral_example.exe" "\0" 98 | VALUE "ProductName", "flutter_ble_peripheral_example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"flutter_ble_peripheral_example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responsponds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FlutterBlePeripheralManager.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | 8 | import Foundation 9 | import CoreBluetooth 10 | import CoreLocation 11 | 12 | class FlutterBlePeripheralManager : NSObject { 13 | 14 | let stateChangedHandler: StateChangedHandler 15 | var peripheralManager : CBPeripheralManager! 16 | 17 | init(stateChangedHandler: StateChangedHandler) { 18 | self.stateChangedHandler = stateChangedHandler 19 | super.init() 20 | peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: [CBPeripheralManagerOptionShowPowerAlertKey : true]) 21 | } 22 | 23 | // var peripheralData: NSDictionary! 24 | 25 | // min MTU before iOS 10 26 | // var mtu: Int = 158 { 27 | // didSet { 28 | // onMtuChanged?(mtu) 29 | // } 30 | // } 31 | 32 | // var dataToBeAdvertised: [String: Any]! 33 | // 34 | // var txCharacteristic: CBMutableCharacteristic? 35 | // var txSubscribed = false { 36 | // didSet { 37 | // if txSubscribed { 38 | // state = .connected 39 | // } else if isAdvertising() { 40 | // state = .advertising 41 | // } 42 | // } 43 | // } 44 | // var rxCharacteristic: CBMutableCharacteristic? 45 | // 46 | // var txSubscriptions = Set() 47 | 48 | func start(advertiseData: PeripheralData) { 49 | var dataToBeAdvertised: [String: Any]! = [:] 50 | if (advertiseData.uuids != nil) { 51 | dataToBeAdvertised[CBAdvertisementDataServiceUUIDsKey] = advertiseData.uuids!.map { CBUUID(string: $0) } 52 | } else if (advertiseData.uuid != nil) { 53 | dataToBeAdvertised[CBAdvertisementDataServiceUUIDsKey] = [CBUUID(string: advertiseData.uuid!)] 54 | } 55 | 56 | if (advertiseData.localName != nil) { 57 | dataToBeAdvertised[CBAdvertisementDataLocalNameKey] = advertiseData.localName 58 | } 59 | 60 | print("[flutter_ble_peripheral] start advertising data: \(String(describing: dataToBeAdvertised))") 61 | 62 | peripheralManager.startAdvertising(dataToBeAdvertised) 63 | 64 | // TODO: Add service to advertise 65 | // if peripheralManager.state == .poweredOn { 66 | // addService() 67 | // } 68 | } 69 | 70 | // TODO: Add service to advertise 71 | // private func addService() { 72 | // // Add service and characteristics if needed 73 | // if txCharacteristic == nil || rxCharacteristic == nil { 74 | // 75 | // let mutableTxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.txCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 76 | // let mutableRxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.rxCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 77 | // 78 | // let service = CBMutableService(type: CBUUID(string: PeripheralData.serviceUUID), primary: true) 79 | // service.characteristics = [mutableTxCharacteristic, mutableRxCharacteristic]; 80 | // 81 | // peripheralManager.add(service) 82 | // 83 | // self.txCharacteristic = mutableTxCharacteristic 84 | // self.rxCharacteristic = mutableRxCharacteristic 85 | // } 86 | // 87 | // peripheralManager.startAdvertising(dataToBeAdvertised) 88 | // } 89 | // 90 | // func send(data: Data) { 91 | // 92 | // print("[flutter_ble_peripheral] Send data: \(data)") 93 | // 94 | // guard let characteristic = txCharacteristic else { 95 | // return 96 | // } 97 | // 98 | // peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: nil) 99 | // } 100 | } 101 | -------------------------------------------------------------------------------- /ios/Classes/FlutterBlePeripheralPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterBlePeripheralPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FlutterBlePeripheralPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterBlePeripheralPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "flutter_ble_peripheral-Swift.h" 9 | #endif 10 | 11 | @implementation FlutterBlePeripheralPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftFlutterBlePeripheralPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/Models/PeripheralData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralData.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 06/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class PeripheralData { 11 | var uuid: String? 12 | var uuids: [String]? 13 | var localName: String? //CBAdvertisementDataLocalNameKey 14 | 15 | 16 | // TODO: add service data 17 | static let serviceUUID: String = "8ebdb2f3-7817-45c9-95c5-c5e9031aaa47" 18 | static let txCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D4" 19 | static let rxCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D5" 20 | 21 | init(uuid: String?, localName: String?, uuids: [String]?) { 22 | self.uuid = uuid //uuid; 23 | self.localName = localName 24 | self.uuids = uuids 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ios/Classes/Models/PeripheralState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralStatus.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 26/11/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | enum PeripheralState : Int{ 11 | // case idle, unauthorized, unsupported, advertising, connected 12 | /// Status is not (yet) determined. 13 | case unknown 14 | 15 | /// BLE is not supported on this device. 16 | case unsupported 17 | 18 | /// BLE usage is not authorized for this app. 19 | case unauthorized 20 | 21 | /// BLE is turned off. 22 | case poweredOff 23 | 24 | // /// Android only: Location services are disabled. 25 | // locationServicesDisabled, 26 | 27 | /// BLE is fully operating for this app. 28 | case idle 29 | 30 | /// BLE is advertising data. 31 | case advertising 32 | 33 | /// BLE is connected to a device. 34 | case connected 35 | 36 | // var index: Int { PeripheralState..firstIndex(of: self) ?? 0 } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFlutterBlePeripheralPlugin.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import Flutter 8 | import UIKit 9 | import CoreLocation 10 | 11 | public class SwiftFlutterBlePeripheralPlugin: NSObject, FlutterPlugin { 12 | 13 | private let flutterBlePeripheralManager: FlutterBlePeripheralManager 14 | 15 | private let stateChangedHandler: StateChangedHandler 16 | // private let mtuChangedHandler = MtuChangedHandler() 17 | // private let dataReceivedHandler = DataReceivedHandler() 18 | init(stateChangedHandler: StateChangedHandler) { 19 | self.stateChangedHandler = stateChangedHandler 20 | flutterBlePeripheralManager = FlutterBlePeripheralManager(stateChangedHandler: stateChangedHandler) 21 | super.init() 22 | } 23 | 24 | public static func register(with registrar: FlutterPluginRegistrar) { 25 | let instance = SwiftFlutterBlePeripheralPlugin(stateChangedHandler: StateChangedHandler(registrar: registrar)) 26 | 27 | // Method channel 28 | let methodChannel = FlutterMethodChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state", binaryMessenger: registrar.messenger()) 29 | registrar.addMethodCallDelegate(instance, channel: methodChannel) 30 | 31 | // Event channels 32 | // instance.mtuChangedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 33 | // instance.dataReceivedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 34 | } 35 | 36 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 37 | switch (call.method) { 38 | case "start": 39 | startPeripheral(call, result) 40 | case "stop": 41 | stopPeripheral(result) 42 | case "isAdvertising": 43 | result(stateChangedHandler.state == PeripheralState.advertising) 44 | case "isSupported": 45 | isSupported(result) 46 | case "isConnected": 47 | result(stateChangedHandler.state == PeripheralState.connected) 48 | case "openBluetoothSettings": 49 | openAppSettings() 50 | result(nil) 51 | // case "sendData": 52 | // sendData(call, result) 53 | default: 54 | result(FlutterMethodNotImplemented) 55 | } 56 | } 57 | 58 | private func startPeripheral(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 59 | let map = call.arguments as? Dictionary 60 | let advertiseData = PeripheralData( 61 | uuid: map?["serviceUuid"] as? String , 62 | localName: map?["localName"] as? String, 63 | uuids: map?["serviceUuids"] as? [String] , 64 | ) 65 | flutterBlePeripheralManager.start(advertiseData: advertiseData) 66 | result(nil) 67 | } 68 | 69 | private func stopPeripheral(_ result: @escaping FlutterResult) { 70 | flutterBlePeripheralManager.peripheralManager.stopAdvertising() 71 | stateChangedHandler.publishPeripheralState(state: PeripheralState.idle) 72 | result(nil) 73 | } 74 | 75 | // We can check if advertising is supported by checking if the ios device supports iBeacons since that uses BLE. 76 | private func isSupported(_ result: @escaping FlutterResult) { 77 | if (CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self)){ 78 | result(true) 79 | } else { 80 | result(false) 81 | } 82 | } 83 | 84 | private func openAppSettings() { 85 | if let url = URL(string: UIApplication.openSettingsURLString) { 86 | if UIApplication.shared.canOpenURL(url) { 87 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 88 | } 89 | } 90 | } 91 | 92 | // private func sendData(_ call: FlutterMethodCall, 93 | // _ result: @escaping FlutterResult) { 94 | // 95 | // if let flutterData = call.arguments as? FlutterStandardTypedData { 96 | // flutterBlePeripheralManager.send(data: flutterData.data) 97 | // } 98 | // result(nil) 99 | // } 100 | } 101 | -------------------------------------------------------------------------------- /ios/Classes/callbacks/PeripheralManagerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralManagerDelegate.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 25/03/2022. 6 | // 7 | 8 | import Foundation 9 | import CoreBluetooth 10 | import CoreLocation 11 | 12 | extension FlutterBlePeripheralManager: CBPeripheralManagerDelegate { 13 | 14 | func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 15 | var state: PeripheralState 16 | switch peripheral.state { 17 | case .poweredOn: 18 | state = .idle 19 | // addService() TODO: add service 20 | case .poweredOff: 21 | state = .poweredOff 22 | case .resetting: 23 | state = .idle 24 | case .unsupported: 25 | state = .unsupported 26 | case .unauthorized: 27 | state = .unauthorized 28 | case .unknown: 29 | state = .unknown 30 | @unknown default: 31 | state = .unknown 32 | } 33 | stateChangedHandler.publishPeripheralState(state: state) 34 | } 35 | 36 | func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: (any Error)?) { 37 | print("[flutter_ble_peripheral] didStartAdvertising:", error ?? "success") 38 | 39 | guard error == nil else { 40 | return 41 | } 42 | 43 | stateChangedHandler.publishPeripheralState(state: .advertising) 44 | 45 | // Immediately set to connected if the tx Characteristic is already subscribed 46 | // if txSubscribed { 47 | // state = .connected 48 | // } 49 | } 50 | 51 | // func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 52 | // print("[flutter_ble_peripheral] didReceiveRead:", request) 53 | // 54 | // // Only answer to requests if not idle 55 | // guard state != .idle else { 56 | // print("[flutter_ble_peripheral] state = .idle -> not answering read request") 57 | // return 58 | // } 59 | // 60 | // // Not supported 61 | // peripheralManager.respond(to: request, withResult: .requestNotSupported) 62 | // } 63 | // 64 | // func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { 65 | // print("[flutter_ble_peripheral] didReceiveWrite:", requests) 66 | // 67 | // // Only answer to requests if not idle 68 | // guard state != .idle else { 69 | // print("[flutter_ble_peripheral] state = .idle -> not answering write request") 70 | // return 71 | // } 72 | // 73 | // for request in requests { 74 | // 75 | // print("[flutter_ble_peripheral] write request:", request); 76 | // 77 | // let characteristic = request.characteristic 78 | // guard let data = request.value else { 79 | // print("[flutter_ble_peripheral] request.value is nil"); 80 | // return 81 | // } 82 | // 83 | // // Write only supported in rxCharacteristic 84 | // guard characteristic == self.rxCharacteristic else { 85 | // peripheralManager.respond(to: request, withResult: .requestNotSupported) 86 | // print("[flutter_ble_peripheral] respond requestNotSupported (only supported in rxCharacteristic)") 87 | // return 88 | // } 89 | // 90 | // print("[flutter_ble_peripheral] request.value:", request.value!) 91 | // print("[flutter_ble_peripheral] characteristic.value:", characteristic.value!) 92 | // 93 | // if data.count > 0 { 94 | // print("[flutter_ble_peripheral] Receive data: \(data)") 95 | // onDataReceived?(data) 96 | // } 97 | // 98 | // // Respond with success 99 | // peripheralManager.respond(to: request, withResult: .success) 100 | // } 101 | // } 102 | // 103 | // func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { 104 | // 105 | // if characteristic == txCharacteristic { 106 | // 107 | // print("[flutter_ble_peripheral] didSubscribeTo:", central, characteristic) 108 | // 109 | // // Update MTU 110 | // self.mtu = central.maximumUpdateValueLength; 111 | // 112 | // // Add to subscriptions 113 | // txSubscriptions.insert(central.identifier) 114 | // 115 | // txSubscribed = !txSubscriptions.isEmpty 116 | // 117 | // print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 118 | // } 119 | // } 120 | // 121 | // func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { 122 | // 123 | // if characteristic == txCharacteristic { 124 | // 125 | // print("[flutter_ble_peripheral] didUnsubscribeFrom:", central, characteristic) 126 | // 127 | // // Remove from txSubscriptions 128 | // txSubscriptions.remove(central.identifier) 129 | // 130 | // txSubscribed = !txSubscriptions.isEmpty 131 | // 132 | // print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 133 | // } 134 | // } 135 | } 136 | -------------------------------------------------------------------------------- /ios/Classes/handlers/DataReceivedHandler.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// StateChangedHandler.swift 3 | //// flutter_ble_peripheral 4 | //// 5 | //// Created by Julian Steenbakker on 25/03/2022. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //public class DataReceivedHandler: NSObject, FlutterStreamHandler { 11 | // 12 | // private var eventSink: FlutterEventSink? 13 | // 14 | // fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 15 | // 16 | // let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_data_received", 17 | // binaryMessenger: registrar.messenger()) 18 | // eventChannel.setStreamHandler(self) 19 | // 20 | // peripheral.onDataReceived = { data in 21 | // if let eventSink = self.eventSink { 22 | // eventSink(FlutterStandardTypedData(bytes: data)) 23 | // } 24 | // } 25 | // } 26 | // 27 | // public func onListen(withArguments arguments: Any?, 28 | // eventSink: @escaping FlutterEventSink) -> FlutterError? { 29 | // self.eventSink = eventSink 30 | // return nil 31 | // } 32 | // 33 | // public func onCancel(withArguments arguments: Any?) -> FlutterError? { 34 | // eventSink = nil 35 | // return nil 36 | // } 37 | //} 38 | -------------------------------------------------------------------------------- /ios/Classes/handlers/MtuChangedHandler.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// StateChangedHandler.swift 3 | //// flutter_ble_peripheral 4 | //// 5 | //// Created by Julian Steenbakker on 25/03/2022. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //public class MtuChangedHandler: NSObject, FlutterStreamHandler { 11 | // 12 | // private var eventSink: FlutterEventSink? 13 | // 14 | // fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 15 | // 16 | // let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed", 17 | // binaryMessenger: registrar.messenger()) 18 | // eventChannel.setStreamHandler(self) 19 | // 20 | // peripheral.onMtuChanged = { mtuSize in 21 | // if let eventSink = self.eventSink { 22 | // eventSink(mtuSize) 23 | // } 24 | // } 25 | // } 26 | // 27 | // public func onListen(withArguments arguments: Any?, 28 | // eventSink: @escaping FlutterEventSink) -> FlutterError? { 29 | // self.eventSink = eventSink 30 | // return nil 31 | // } 32 | // 33 | // public func onCancel(withArguments arguments: Any?) -> FlutterError? { 34 | // eventSink = nil 35 | // return nil 36 | // } 37 | //} 38 | -------------------------------------------------------------------------------- /ios/Classes/handlers/StateChangedHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StateChangedHandler.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 25/03/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public class StateChangedHandler: NSObject, FlutterStreamHandler { 11 | 12 | private var eventSink: FlutterEventSink? 13 | 14 | var state: PeripheralState = PeripheralState.idle 15 | 16 | private let eventChannel: FlutterEventChannel 17 | 18 | init(registrar: FlutterPluginRegistrar) { 19 | eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state_changed", 20 | binaryMessenger: registrar.messenger()) 21 | super.init() 22 | eventChannel.setStreamHandler(self) 23 | } 24 | 25 | func publishPeripheralState(state: PeripheralState) { 26 | self.state = state 27 | if let eventSink = self.eventSink { 28 | eventSink(state.rawValue) 29 | } 30 | } 31 | 32 | public func onListen(withArguments arguments: Any?, 33 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 34 | self.eventSink = eventSink 35 | if let eventSink = self.eventSink { 36 | eventSink(state.rawValue) 37 | } 38 | return nil 39 | } 40 | 41 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 42 | eventSink = nil 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ios/flutter_ble_peripheral.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_ble_peripheral.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_ble_peripheral' 7 | s.version = '0.0.3' 8 | s.summary = 'This plugin enables a device to be set into peripheral mode, and advertise custom 9 | services and characteristics.' 10 | s.description = <<-DESC 11 | This plugin enables a device to be set into peripheral mode, and advertise custom 12 | services and characteristics. 13 | DESC 14 | s.homepage = 'https://steenbakker.dev' 15 | s.license = { :file => '../LICENSE' } 16 | s.author = { 'Your Company' => 'email@example.com' } 17 | s.source = { :path => '.' } 18 | s.source_files = 'Classes/**/*' 19 | s.dependency 'Flutter' 20 | s.platform = :ios, '8.0' 21 | 22 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 23 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 24 | s.swift_version = '5.0' 25 | end 26 | -------------------------------------------------------------------------------- /lib/flutter_ble_peripheral.dart: -------------------------------------------------------------------------------- 1 | export 'src/flutter_ble_peripheral.dart'; 2 | export 'src/models/advertise_data.dart'; 3 | export 'src/models/advertise_set_parameters.dart'; 4 | export 'src/models/advertise_settings.dart'; 5 | export 'src/models/constants.dart'; 6 | export 'src/models/enums/advertise_mode.dart'; 7 | export 'src/models/enums/advertise_tx_power.dart'; 8 | export 'src/models/enums/bluetooth_peripheral_state.dart'; 9 | export 'src/models/peripheral_state.dart'; 10 | export 'src/models/permission_state.dart'; 11 | -------------------------------------------------------------------------------- /lib/src/flutter_ble_peripheral.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import 'dart:async'; 8 | import 'dart:io'; 9 | // ignore: unnecessary_import 10 | import 'dart:typed_data'; 11 | 12 | import 'package:flutter/services.dart'; 13 | import 'package:flutter_ble_peripheral/src/models/advertise_data.dart'; 14 | import 'package:flutter_ble_peripheral/src/models/advertise_set_parameters.dart'; 15 | import 'package:flutter_ble_peripheral/src/models/advertise_settings.dart'; 16 | import 'package:flutter_ble_peripheral/src/models/enums/bluetooth_peripheral_state.dart'; 17 | import 'package:flutter_ble_peripheral/src/models/periodic_advertise_settings.dart'; 18 | import 'package:flutter_ble_peripheral/src/models/peripheral_state.dart'; 19 | 20 | class FlutterBlePeripheral { 21 | /// Singleton instance 22 | static final FlutterBlePeripheral _instance = 23 | FlutterBlePeripheral._internal(); 24 | 25 | /// Singleton factory 26 | factory FlutterBlePeripheral() { 27 | return _instance; 28 | } 29 | 30 | /// Singleton constructor 31 | FlutterBlePeripheral._internal(); 32 | 33 | /// Method Channel used to communicate state with 34 | static const MethodChannel _methodChannel = 35 | MethodChannel('dev.steenbakker.flutter_ble_peripheral/ble_state'); 36 | 37 | /// Event Channel for MTU state 38 | final EventChannel _mtuChangedEventChannel = const EventChannel( 39 | 'dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed', 40 | ); 41 | 42 | /// Event Channel used to changed state 43 | final EventChannel _stateChangedEventChannel = const EventChannel( 44 | 'dev.steenbakker.flutter_ble_peripheral/ble_state_changed', 45 | ); 46 | 47 | Stream? _mtuState; 48 | Stream? _peripheralState; 49 | 50 | //TODO Event Channel used to received data 51 | // final EventChannel _dataReceivedEventChannel = const EventChannel( 52 | // 'dev.steenbakker.flutter_ble_peripheral/ble_data_received'); 53 | 54 | /// Start advertising. Takes [AdvertiseData] as an input. 55 | Future start({ 56 | required AdvertiseData advertiseData, 57 | AdvertiseSettings? advertiseSettings, 58 | AdvertiseSetParameters? advertiseSetParameters, 59 | AdvertiseData? advertiseResponseData, 60 | AdvertiseData? advertisePeriodicData, 61 | PeriodicAdvertiseSettings? periodicAdvertiseSettings, 62 | }) async { 63 | final parameters = advertiseData.toJson(); 64 | parameters["manufacturerDataBytes"] = advertiseData.manufacturerData; 65 | final settings = advertiseSettings ?? AdvertiseSettings(); 66 | final jsonSettings = settings.toJson(); 67 | for (final key in jsonSettings.keys) { 68 | parameters[key] = jsonSettings[key]; 69 | } 70 | 71 | if (advertiseData.serviceUuids != null) { 72 | parameters['serviceUuids'] = advertiseData.serviceUuids; 73 | } 74 | 75 | // ignore: deprecated_member_use_from_same_package 76 | // if (advertiseData.serviceUuid == null && 77 | // advertiseData.serviceUuids != null) { 78 | // try { 79 | // final firstUuid = advertiseData.serviceUuids!.first; 80 | // parameters['serviceUuid'] = firstUuid; 81 | // } catch (e) { 82 | // // no service uuid present 83 | // } 84 | // } 85 | parameters.addAll(advertiseData.toJson()); 86 | 87 | if (advertiseSetParameters != null) { 88 | final json = advertiseSetParameters.toJson(); 89 | for (final key in json.keys) { 90 | parameters['set$key'] = json[key]; 91 | } 92 | parameters.addAll(advertiseData.toJson()); 93 | } 94 | 95 | if (advertiseResponseData != null) { 96 | final json = advertiseData.toJson(); 97 | for (final key in json.keys) { 98 | parameters['response$key'] = json[key]; 99 | } 100 | parameters.addAll(advertiseData.toJson()); 101 | } 102 | 103 | final response = 104 | await _methodChannel.invokeMethod('start', parameters); 105 | return response == null 106 | ? BluetoothPeripheralState.unknown 107 | : BluetoothPeripheralState.values[response]; 108 | } 109 | 110 | /// Stop advertising 111 | Future stop() async { 112 | final response = await _methodChannel.invokeMethod('stop'); 113 | return response == null 114 | ? BluetoothPeripheralState.unknown 115 | : BluetoothPeripheralState.values[response]; 116 | } 117 | 118 | /// Returns `true` if advertising or false if not advertising 119 | Future get isAdvertising async { 120 | return await _methodChannel.invokeMethod('isAdvertising') ?? false; 121 | } 122 | 123 | /// Returns `true` if advertising over BLE is supported 124 | Future get isSupported async => 125 | await _methodChannel.invokeMethod('isSupported') ?? false; 126 | 127 | /// Returns `true` if device is connected 128 | Future get isConnected async => 129 | await _methodChannel.invokeMethod('isConnected') ?? false; 130 | 131 | /// Start advertising. Takes [AdvertiseData] as an input. 132 | Future sendData(Uint8List data) async { 133 | await _methodChannel.invokeMethod('sendData', data); 134 | } 135 | 136 | /// Stop advertising 137 | /// 138 | /// [askUser] ONLY AVAILABLE ON ANDROID SDK < 33 139 | /// If set to false, it will enable bluetooth without asking user. 140 | Future enableBluetooth({bool askUser = true}) async { 141 | if (!Platform.isAndroid) return false; 142 | return await _methodChannel.invokeMethod( 143 | 'enableBluetooth', 144 | askUser, 145 | ) ?? 146 | false; 147 | } 148 | 149 | Future requestPermission() async { 150 | if (!Platform.isAndroid) return BluetoothPeripheralState.unknown; 151 | final response = 152 | await _methodChannel.invokeMethod('requestPermission'); 153 | return response == null 154 | ? BluetoothPeripheralState.unknown 155 | : BluetoothPeripheralState.values[response]; 156 | } 157 | 158 | Future hasPermission() async { 159 | if (!Platform.isAndroid) return BluetoothPeripheralState.unknown; 160 | final response = await _methodChannel.invokeMethod('hasPermission'); 161 | return response == null 162 | ? BluetoothPeripheralState.unknown 163 | : BluetoothPeripheralState.values[response]; 164 | } 165 | 166 | Future openBluetoothSettings() async { 167 | await _methodChannel.invokeMethod('openBluetoothSettings'); 168 | } 169 | 170 | Future openAppSettings() async { 171 | await _methodChannel.invokeMethod('openAppSettings'); 172 | } 173 | 174 | /// Returns Stream of MTU updates. 175 | Stream get onMtuChanged { 176 | _mtuState ??= _mtuChangedEventChannel 177 | .receiveBroadcastStream() 178 | .cast() 179 | .distinct() 180 | .map((dynamic event) => event as int); 181 | return _mtuState!; 182 | } 183 | 184 | /// Returns Stream of state. 185 | /// 186 | /// After listening to this Stream, you'll be notified about changes in peripheral state. 187 | Stream? get onPeripheralStateChanged { 188 | if (Platform.isWindows) return null; 189 | _peripheralState ??= _stateChangedEventChannel 190 | .receiveBroadcastStream() 191 | .map((dynamic event) => PeripheralState.values[event as int]); 192 | return _peripheralState!; 193 | } 194 | 195 | // /// Returns Stream of data. 196 | // /// 197 | // /// 198 | // Stream getDataReceived() { 199 | // return _dataReceivedEventChannel.receiveBroadcastStream().cast(); 200 | // } 201 | } 202 | -------------------------------------------------------------------------------- /lib/src/models/advertise_data.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import 'dart:typed_data'; 8 | 9 | import 'package:flutter_ble_peripheral/src/models/uint8list_converter.dart'; 10 | import 'package:json_annotation/json_annotation.dart'; 11 | 12 | part 'advertise_data.g.dart'; 13 | 14 | /// Model of the data to be advertised. 15 | @JsonSerializable() 16 | class AdvertiseData { 17 | /// Android & iOS 18 | /// 19 | /// Specifies a single service UUIDs to be advertised 20 | // @Deprecated( 21 | // 'Please use serviceUuids, where you can also define a single service uuid.', 22 | // ) 23 | final String? serviceUuid; 24 | 25 | /// Android & iOS 26 | /// 27 | /// Specifies multiple service UUIDs to be advertised 28 | /// Multiple serviceUuids is only supported on iOS for now. 29 | /// If specified, [serviceUuid] will not be used. 30 | final List? serviceUuids; 31 | 32 | /// Android only 33 | /// 34 | /// Specifies a manufacturer id 35 | /// Manufacturer ID assigned by Bluetooth SIG. 36 | final int? manufacturerId; 37 | 38 | /// Android only 39 | /// 40 | /// Specifies manufacturer data. 41 | @Uint8ListConverter() 42 | final Uint8List? manufacturerData; 43 | 44 | /// Android only 45 | /// 46 | /// Specifies service data UUID 47 | final String? serviceDataUuid; 48 | 49 | /// Android only 50 | /// 51 | /// Specifies service data 52 | final List? serviceData; 53 | 54 | /// Android only 55 | /// 56 | /// Set to true if device name needs to be included with advertisement 57 | /// Default: false 58 | final bool includeDeviceName; 59 | 60 | /// iOS only 61 | /// 62 | /// Set the deviceName to be broadcasted. Can be 10 bytes. 63 | final String? localName; 64 | 65 | /// Android only 66 | /// 67 | /// set to true if you want to include the power level in the advertisement 68 | /// Default: false 69 | final bool? includePowerLevel; 70 | 71 | /// Android > SDK 31 only 72 | /// 73 | /// A service solicitation UUID to advertise data. 74 | final String? serviceSolicitationUuid; 75 | 76 | AdvertiseData({ 77 | // @Deprecated( 78 | // 'Please use serviceUuids, where you can also define a single service uuid.', 79 | // ) 80 | this.serviceUuid, 81 | this.serviceUuids, 82 | this.manufacturerId, 83 | this.manufacturerData, 84 | this.serviceDataUuid, 85 | this.serviceData, 86 | this.includeDeviceName = false, 87 | this.localName, 88 | this.includePowerLevel = false, 89 | this.serviceSolicitationUuid, 90 | }); 91 | 92 | factory AdvertiseData.fromJson(Map json) => 93 | _$AdvertiseDataFromJson(json); 94 | 95 | Map toJson() => _$AdvertiseDataToJson(this); 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/models/advertise_data.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'advertise_data.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | AdvertiseData _$AdvertiseDataFromJson(Map json) => 10 | AdvertiseData( 11 | serviceUuid: json['serviceUuid'] as String?, 12 | manufacturerId: (json['manufacturerId'] as num?)?.toInt(), 13 | manufacturerData: const Uint8ListConverter() 14 | .fromJson(json['manufacturerData'] as List?), 15 | serviceDataUuid: json['serviceDataUuid'] as String?, 16 | serviceData: (json['serviceData'] as List?) 17 | ?.map((e) => (e as num).toInt()) 18 | .toList(), 19 | includeDeviceName: json['includeDeviceName'] as bool? ?? false, 20 | localName: json['localName'] as String?, 21 | includePowerLevel: json['includePowerLevel'] as bool? ?? false, 22 | serviceSolicitationUuid: json['serviceSolicitationUuid'] as String?, 23 | ); 24 | 25 | Map _$AdvertiseDataToJson(AdvertiseData instance) => 26 | { 27 | 'serviceUuid': instance.serviceUuid, 28 | 'manufacturerId': instance.manufacturerId, 29 | 'manufacturerData': 30 | const Uint8ListConverter().toJson(instance.manufacturerData), 31 | 'serviceDataUuid': instance.serviceDataUuid, 32 | 'serviceData': instance.serviceData, 33 | 'includeDeviceName': instance.includeDeviceName, 34 | 'localName': instance.localName, 35 | 'includePowerLevel': instance.includePowerLevel, 36 | 'serviceSolicitationUuid': instance.serviceSolicitationUuid, 37 | }; 38 | -------------------------------------------------------------------------------- /lib/src/models/advertise_set_parameters.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_ble_peripheral/flutter_ble_peripheral.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'advertise_set_parameters.g.dart'; 5 | 6 | /// Model of the data to be advertised. 7 | @JsonSerializable() 8 | class AdvertiseSetParameters { 9 | final int? anonymous; 10 | 11 | /// Android only 12 | /// 13 | /// Set whether the advertisement type should be connectable or non-connectable. 14 | /// Default: false 15 | final bool connectable; 16 | 17 | final bool? includeTxPowerLevel; 18 | 19 | final int? interval; 20 | 21 | final bool? legacyMode; 22 | 23 | final int? primaryPhy; 24 | 25 | final bool? scannable; 26 | 27 | final int? secondaryPhy; 28 | 29 | /// Android only 30 | /// 31 | /// Set advertise TX power level to control the transmission power level for the advertising. 32 | /// Default: AdvertisePower.ADVERTISE_TX_POWER_HIGH 33 | final int txPowerLevel; 34 | 35 | final int? duration; 36 | 37 | final int? maxExtendedAdvertisingEvents; 38 | 39 | AdvertiseSetParameters({ 40 | this.connectable = false, 41 | this.txPowerLevel = txPowerHigh, 42 | this.interval = intervalHigh, 43 | this.legacyMode = false, 44 | this.primaryPhy, 45 | this.scannable, 46 | this.secondaryPhy, 47 | this.anonymous, 48 | this.includeTxPowerLevel = false, 49 | this.duration, 50 | this.maxExtendedAdvertisingEvents, 51 | }); 52 | 53 | factory AdvertiseSetParameters.fromJson(Map json) => 54 | _$AdvertiseSetParametersFromJson(json); 55 | 56 | Map toJson() => _$AdvertiseSetParametersToJson(this); 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/models/advertise_set_parameters.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'advertise_set_parameters.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | AdvertiseSetParameters _$AdvertiseSetParametersFromJson( 10 | Map json, 11 | ) => 12 | AdvertiseSetParameters( 13 | connectable: json['connectable'] as bool? ?? false, 14 | txPowerLevel: (json['txPowerLevel'] as num?)?.toInt() ?? txPowerHigh, 15 | interval: (json['interval'] as num?)?.toInt() ?? intervalHigh, 16 | legacyMode: json['legacyMode'] as bool? ?? false, 17 | primaryPhy: (json['primaryPhy'] as num?)?.toInt(), 18 | scannable: json['scannable'] as bool?, 19 | secondaryPhy: (json['secondaryPhy'] as num?)?.toInt(), 20 | anonymous: (json['anonymous'] as num?)?.toInt(), 21 | includeTxPowerLevel: json['includeTxPowerLevel'] as bool? ?? false, 22 | duration: (json['duration'] as num?)?.toInt(), 23 | maxExtendedAdvertisingEvents: 24 | (json['maxExtendedAdvertisingEvents'] as num?)?.toInt(), 25 | ); 26 | 27 | Map _$AdvertiseSetParametersToJson( 28 | AdvertiseSetParameters instance, 29 | ) => 30 | { 31 | 'anonymous': instance.anonymous, 32 | 'connectable': instance.connectable, 33 | 'includeTxPowerLevel': instance.includeTxPowerLevel, 34 | 'interval': instance.interval, 35 | 'legacyMode': instance.legacyMode, 36 | 'primaryPhy': instance.primaryPhy, 37 | 'scannable': instance.scannable, 38 | 'secondaryPhy': instance.secondaryPhy, 39 | 'txPowerLevel': instance.txPowerLevel, 40 | 'duration': instance.duration, 41 | 'maxExtendedAdvertisingEvents': instance.maxExtendedAdvertisingEvents, 42 | }; 43 | -------------------------------------------------------------------------------- /lib/src/models/advertise_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_ble_peripheral/src/models/enums/advertise_mode.dart'; 2 | import 'package:flutter_ble_peripheral/src/models/enums/advertise_tx_power.dart'; 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | part 'advertise_settings.g.dart'; 6 | 7 | /// Model of the data to be advertised. 8 | @JsonSerializable() 9 | class AdvertiseSettings { 10 | /// Android only 11 | /// 12 | /// Set the advertise mode to use when using android >= o 13 | final bool advertiseSet; 14 | 15 | /// Android only 16 | /// 17 | /// Set advertise mode to control the advertising power and latency. 18 | /// Default: AdvertiseMode.ADVERTISE_MODE_LOW_LATENCY 19 | final AdvertiseMode advertiseMode; 20 | 21 | /// Android only 22 | /// 23 | /// Set whether the advertisement type should be connectable or non-connectable. 24 | /// Default: false 25 | final bool connectable; 26 | 27 | /// Android only 28 | /// 29 | /// Limit advertising to a given amount of time. 30 | /// May not exceed 180000 milliseconds. 31 | /// Default: 400 milliseconds 32 | final int timeout; 33 | 34 | /// Android only 35 | /// 36 | /// Set advertise TX power level to control the transmission power level for the advertising. 37 | /// Default: AdvertisePower.ADVERTISE_TX_POWER_HIGH 38 | final AdvertiseTxPower txPowerLevel; 39 | 40 | AdvertiseSettings({ 41 | this.advertiseSet = true, 42 | this.connectable = false, 43 | this.timeout = 400, 44 | this.advertiseMode = AdvertiseMode.advertiseModeLowLatency, 45 | this.txPowerLevel = AdvertiseTxPower.advertiseTxPowerLow, 46 | }); 47 | 48 | factory AdvertiseSettings.fromJson(Map json) => 49 | _$AdvertiseSettingsFromJson(json); 50 | 51 | Map toJson() => _$AdvertiseSettingsToJson(this); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/models/advertise_settings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'advertise_settings.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | AdvertiseSettings _$AdvertiseSettingsFromJson(Map json) => 10 | AdvertiseSettings( 11 | advertiseSet: json['advertiseSet'] as bool? ?? true, 12 | connectable: json['connectable'] as bool? ?? false, 13 | timeout: (json['timeout'] as num?)?.toInt() ?? 400, 14 | advertiseMode: 15 | $enumDecodeNullable(_$AdvertiseModeEnumMap, json['advertiseMode']) ?? 16 | AdvertiseMode.advertiseModeLowLatency, 17 | txPowerLevel: $enumDecodeNullable( 18 | _$AdvertiseTxPowerEnumMap, 19 | json['txPowerLevel'], 20 | ) ?? 21 | AdvertiseTxPower.advertiseTxPowerLow, 22 | ); 23 | 24 | Map _$AdvertiseSettingsToJson(AdvertiseSettings instance) => 25 | { 26 | 'advertiseSet': instance.advertiseSet, 27 | 'advertiseMode': _$AdvertiseModeEnumMap[instance.advertiseMode], 28 | 'connectable': instance.connectable, 29 | 'timeout': instance.timeout, 30 | 'txPowerLevel': _$AdvertiseTxPowerEnumMap[instance.txPowerLevel], 31 | }; 32 | 33 | const _$AdvertiseModeEnumMap = { 34 | AdvertiseMode.advertiseModeLowPower: 0, 35 | AdvertiseMode.advertiseModeBalanced: 1, 36 | AdvertiseMode.advertiseModeLowLatency: 2, 37 | }; 38 | 39 | const _$AdvertiseTxPowerEnumMap = { 40 | AdvertiseTxPower.advertiseTxPowerUltraLow: 0, 41 | AdvertiseTxPower.advertiseTxPowerLow: 1, 42 | AdvertiseTxPower.advertiseTxPowerMedium: 2, 43 | AdvertiseTxPower.advertiseTxPowerHigh: 3, 44 | }; 45 | -------------------------------------------------------------------------------- /lib/src/models/constants.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | // TODO: Docs 7 | const int intervalLow = 160; 8 | const int intervalHigh = 1600; 9 | const int intervalMax = 16777215; 10 | const int intervalMedium = 400; 11 | const int intervalMin = 160; 12 | 13 | const int txPowerHigh = 1; 14 | const int txPowerLow = -15; 15 | const int txPowerMax = 1; 16 | const int txPowerMedium = -7; 17 | const int txPowerMin = -127; 18 | const int txPowerUltraLow = -21; 19 | -------------------------------------------------------------------------------- /lib/src/models/enums/advertise_mode.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | enum AdvertiseMode { 10 | /// Perform Bluetooth LE advertising in low power mode. This is the default and preferred 11 | /// advertising mode as it consumes the least power. 12 | @JsonValue(0) 13 | advertiseModeLowPower, 14 | 15 | /// Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising 16 | /// frequency and power consumption. 17 | @JsonValue(1) 18 | advertiseModeBalanced, 19 | 20 | /// Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power 21 | /// consumption and should not be used for continuous background advertising. 22 | @JsonValue(2) 23 | advertiseModeLowLatency 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/models/enums/advertise_tx_power.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | enum AdvertiseTxPower { 10 | /// Advertise using the lowest transmission (TX) power level. Low transmission power can be used 11 | /// to restrict the visibility range of advertising packets. 12 | @JsonValue(0) 13 | advertiseTxPowerUltraLow, 14 | 15 | /// Advertise using low TX power level. 16 | @JsonValue(1) 17 | advertiseTxPowerLow, 18 | 19 | /// Advertise using medium TX power level. 20 | @JsonValue(2) 21 | advertiseTxPowerMedium, 22 | 23 | /// Advertise using high TX power level. This corresponds to largest visibility range of the 24 | /// advertising packet. 25 | @JsonValue(3) 26 | advertiseTxPowerHigh 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/models/enums/bluetooth_peripheral_state.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | enum BluetoothPeripheralState { 10 | /// The user granted access to the requested feature. 11 | @JsonValue(0) 12 | granted, 13 | 14 | /// The user denied access to the requested feature, permission needs to be asked first. 15 | @JsonValue(1) 16 | denied, 17 | 18 | /// Permission to the requested feature is permanently denied, 19 | /// the permission dialog will not be shown when requesting this permission. 20 | /// The user may still change the permission status in the settings. 21 | @JsonValue(2) 22 | permanentlyDenied, 23 | 24 | /// The OS denied access to the requested feature. 25 | /// The user cannot change this app's status, possibly due to active restrictions such as parental controls being in place. 26 | /// 27 | /// Only supported on iOS. 28 | /// 29 | @JsonValue(3) 30 | restricted, 31 | 32 | /// User has authorized this application for limited access. 33 | /// Only supported on iOS (iOS14+). 34 | @JsonValue(4) 35 | limited, 36 | 37 | /// Bluetooth is turned off 38 | @JsonValue(5) 39 | turnedOff, 40 | @JsonValue(6) 41 | unsupported, 42 | 43 | /// The status is unknown 44 | @JsonValue(7) 45 | unknown, 46 | @JsonValue(8) 47 | ready, 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/models/map_uint8list_converter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | class Uint8ListMapIntConverter 5 | implements JsonConverter?, Map?> { 6 | const Uint8ListMapIntConverter(); 7 | 8 | @override 9 | Map? fromJson(Map? json) { 10 | if (json == null) { 11 | return null; 12 | } 13 | 14 | final Map map = {}; 15 | for (final key in json.keys) { 16 | map[int.parse(key)] = Uint8List.fromList((json[key] as List).cast()); 17 | } 18 | 19 | return map; 20 | } 21 | 22 | @override 23 | Map? toJson(Map? object) { 24 | if (object == null) { 25 | return null; 26 | } 27 | final Map map = {}; 28 | for (final key in object.keys) { 29 | map[key.toString()] = object[key]!.toList(); 30 | } 31 | return map; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/models/periodic_advertise_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'periodic_advertise_settings.g.dart'; 4 | 5 | /// Model of the data to be advertised. 6 | @JsonSerializable() 7 | class PeriodicAdvertiseSettings { 8 | final int? interval; 9 | 10 | final bool? includeTxPowerLevel; 11 | 12 | PeriodicAdvertiseSettings({ 13 | this.interval = 100, 14 | this.includeTxPowerLevel = false, 15 | }); 16 | 17 | factory PeriodicAdvertiseSettings.fromJson(Map json) => 18 | _$PeriodicAdvertiseSettingsFromJson(json); 19 | 20 | Map toJson() => _$PeriodicAdvertiseSettingsToJson(this); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/models/periodic_advertise_settings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'periodic_advertise_settings.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | PeriodicAdvertiseSettings _$PeriodicAdvertiseSettingsFromJson( 10 | Map json, 11 | ) => 12 | PeriodicAdvertiseSettings( 13 | interval: (json['interval'] as num?)?.toInt() ?? 100, 14 | includeTxPowerLevel: json['includeTxPowerLevel'] as bool? ?? false, 15 | ); 16 | 17 | Map _$PeriodicAdvertiseSettingsToJson( 18 | PeriodicAdvertiseSettings instance, 19 | ) => 20 | { 21 | 'interval': instance.interval, 22 | 'includeTxPowerLevel': instance.includeTxPowerLevel, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/src/models/peripheral_state.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | enum PeripheralState { 8 | /// Status is not (yet) determined. 9 | unknown, 10 | 11 | /// BLE is not supported on this device. 12 | unsupported, 13 | 14 | /// BLE usage is not authorized for this app. 15 | unauthorized, 16 | 17 | /// BLE is turned off. 18 | poweredOff, 19 | 20 | // /// Android only: Location services are disabled. 21 | // locationServicesDisabled, 22 | 23 | /// BLE is fully operating for this app. 24 | idle, 25 | 26 | /// BLE is advertising data. 27 | advertising, 28 | 29 | /// BLE is connected to a device. 30 | connected, 31 | 32 | shouldShowRequestPermissionRationale, 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/models/permission_state.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | enum PermissionState { 8 | /// The user granted access to the requested feature. 9 | granted, 10 | 11 | /// The user denied access to the requested feature, permission needs to be asked first. 12 | denied, 13 | 14 | /// Permission to the requested feature is permanently denied, 15 | /// the permission dialog will not be shown when requesting this permission. 16 | /// The user may still change the permission status in the settings. 17 | permanentlyDenied, 18 | 19 | /// The status is unknown 20 | unknown, 21 | 22 | /// The OS denied access to the requested feature. 23 | /// The user cannot change this app's status, possibly due to active restrictions such as parental controls being in place. 24 | /// 25 | /// Only supported on iOS. 26 | restricted, 27 | 28 | /// User has authorized this application for limited access. 29 | /// Only supported on iOS (iOS14+). 30 | limited, 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/models/uint8list_converter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | class Uint8ListConverter implements JsonConverter?> { 5 | const Uint8ListConverter(); 6 | 7 | @override 8 | Uint8List? fromJson(List? json) { 9 | if (json == null) { 10 | return null; 11 | } 12 | 13 | return Uint8List.fromList(json.cast()); 14 | } 15 | 16 | @override 17 | List? toJson(Uint8List? object) { 18 | if (object == null) { 19 | return null; 20 | } 21 | 22 | return object.toList(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/models/uint8list_map_string_converter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | class Uint8ListMapStringConverter 5 | implements JsonConverter?, Map?> { 6 | const Uint8ListMapStringConverter(); 7 | 8 | @override 9 | Map? fromJson(Map? json) { 10 | if (json == null) { 11 | return null; 12 | } 13 | 14 | final Map map = {}; 15 | for (final String key in json.keys) { 16 | map[key] = Uint8List.fromList((json[key] as List).cast()); 17 | } 18 | 19 | return map; 20 | } 21 | 22 | @override 23 | Map>? toJson(Map? object) { 24 | if (object == null) { 25 | return null; 26 | } 27 | final Map map = {}; 28 | for (final key in object.keys) { 29 | map[key] = object[key]!.toList(); 30 | } 31 | return object; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /macos/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliansteenbakker/flutter_ble_peripheral/ac20d56287487cf2576daa1c4886bf05e1e84a7b/macos/Assets/.gitkeep -------------------------------------------------------------------------------- /macos/Classes/FlutterBlePeripheralManager.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | 8 | import Foundation 9 | import CoreBluetooth 10 | import CoreLocation 11 | 12 | class FlutterBlePeripheralManager : NSObject { 13 | 14 | let stateChangedHandler: StateChangedHandler 15 | var peripheralManager : CBPeripheralManager! 16 | 17 | init(stateChangedHandler: StateChangedHandler) { 18 | self.stateChangedHandler = stateChangedHandler 19 | super.init() 20 | peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: [CBPeripheralManagerOptionShowPowerAlertKey : true]) 21 | } 22 | 23 | // var peripheralData: NSDictionary! 24 | 25 | // min MTU before iOS 10 26 | // var mtu: Int = 158 { 27 | // didSet { 28 | // onMtuChanged?(mtu) 29 | // } 30 | // } 31 | 32 | // var dataToBeAdvertised: [String: Any]! 33 | // 34 | // var txCharacteristic: CBMutableCharacteristic? 35 | // var txSubscribed = false { 36 | // didSet { 37 | // if txSubscribed { 38 | // state = .connected 39 | // } else if isAdvertising() { 40 | // state = .advertising 41 | // } 42 | // } 43 | // } 44 | // var rxCharacteristic: CBMutableCharacteristic? 45 | // 46 | // var txSubscriptions = Set() 47 | 48 | func start(advertiseData: PeripheralData) { 49 | 50 | var dataToBeAdvertised: [String: Any]! = [:] 51 | if (advertiseData.uuid != nil) { 52 | dataToBeAdvertised[CBAdvertisementDataServiceUUIDsKey] = [CBUUID(string: advertiseData.uuid!)] 53 | } 54 | 55 | if (advertiseData.localName != nil) { 56 | dataToBeAdvertised[CBAdvertisementDataLocalNameKey] = advertiseData.localName 57 | } 58 | 59 | // print("[flutter_ble_peripheral] start advertising data: \(String(describing: dataToBeAdvertised))") 60 | 61 | peripheralManager.startAdvertising(dataToBeAdvertised) 62 | 63 | // TODO: Add service to advertise 64 | // if peripheralManager.state == .poweredOn { 65 | // addService() 66 | // } 67 | } 68 | 69 | // TODO: Add service to advertise 70 | // private func addService() { 71 | // // Add service and characteristics if needed 72 | // if txCharacteristic == nil || rxCharacteristic == nil { 73 | // 74 | // let mutableTxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.txCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 75 | // let mutableRxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.rxCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 76 | // 77 | // let service = CBMutableService(type: CBUUID(string: PeripheralData.serviceUUID), primary: true) 78 | // service.characteristics = [mutableTxCharacteristic, mutableRxCharacteristic]; 79 | // 80 | // peripheralManager.add(service) 81 | // 82 | // self.txCharacteristic = mutableTxCharacteristic 83 | // self.rxCharacteristic = mutableRxCharacteristic 84 | // } 85 | // 86 | // peripheralManager.startAdvertising(dataToBeAdvertised) 87 | // } 88 | // 89 | // func send(data: Data) { 90 | // 91 | // print("[flutter_ble_peripheral] Send data: \(data)") 92 | // 93 | // guard let characteristic = txCharacteristic else { 94 | // return 95 | // } 96 | // 97 | // peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: nil) 98 | // } 99 | } 100 | -------------------------------------------------------------------------------- /macos/Classes/FlutterBlePeripheralPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterBlePeripheralPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /macos/Classes/FlutterBlePeripheralPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterBlePeripheralPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "flutter_ble_peripheral-Swift.h" 9 | #endif 10 | 11 | @implementation FlutterBlePeripheralPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftFlutterBlePeripheralPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /macos/Classes/Models/PeripheralData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralData.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 06/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class PeripheralData { 11 | var uuid: String? 12 | var localName: String? //CBAdvertisementDataLocalNameKey 13 | 14 | // TODO: add service data 15 | static let serviceUUID: String = "8ebdb2f3-7817-45c9-95c5-c5e9031aaa47" 16 | static let txCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D4" 17 | static let rxCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D5" 18 | 19 | init(uuid: String?, localName: String?) { 20 | self.uuid = uuid //uuid; 21 | self.localName = localName 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /macos/Classes/Models/PeripheralState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralStatus.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 26/11/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | enum PeripheralState : Int{ 11 | // case idle, unauthorized, unsupported, advertising, connected 12 | /// Status is not (yet) determined. 13 | case unknown 14 | 15 | /// BLE is not supported on this device. 16 | case unsupported 17 | 18 | /// BLE usage is not authorized for this app. 19 | case unauthorized 20 | 21 | /// BLE is turned off. 22 | case poweredOff 23 | 24 | // /// Android only: Location services are disabled. 25 | // locationServicesDisabled, 26 | 27 | /// BLE is fully operating for this app. 28 | case idle 29 | 30 | /// BLE is advertising data. 31 | case advertising 32 | 33 | /// BLE is connected to a device. 34 | case connected 35 | 36 | // var index: Int { PeripheralState..firstIndex(of: self) ?? 0 } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /macos/Classes/SwiftFlutterBlePeripheralPlugin.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Julian Steenbakker. 3 | * All rights reserved. Use of this source code is governed by a 4 | * BSD-style license that can be found in the LICENSE file. 5 | */ 6 | 7 | import FlutterMacOS 8 | import CoreLocation 9 | 10 | public class SwiftFlutterBlePeripheralPlugin: NSObject, FlutterPlugin { 11 | 12 | private let flutterBlePeripheralManager: FlutterBlePeripheralManager 13 | 14 | private let stateChangedHandler: StateChangedHandler 15 | // private let mtuChangedHandler = MtuChangedHandler() 16 | // private let dataReceivedHandler = DataReceivedHandler() 17 | init(stateChangedHandler: StateChangedHandler) { 18 | self.stateChangedHandler = stateChangedHandler 19 | flutterBlePeripheralManager = FlutterBlePeripheralManager(stateChangedHandler: stateChangedHandler) 20 | super.init() 21 | } 22 | 23 | public static func register(with registrar: FlutterPluginRegistrar) { 24 | let instance = SwiftFlutterBlePeripheralPlugin(stateChangedHandler: StateChangedHandler(registrar: registrar)) 25 | 26 | // Method channel 27 | let methodChannel = FlutterMethodChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state", binaryMessenger: registrar.messenger) 28 | registrar.addMethodCallDelegate(instance, channel: methodChannel) 29 | 30 | // Event channels 31 | // instance.mtuChangedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 32 | // instance.dataReceivedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 33 | } 34 | 35 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 36 | switch (call.method) { 37 | case "start": 38 | startPeripheral(call, result) 39 | case "stop": 40 | stopPeripheral(result) 41 | case "isAdvertising": 42 | result(stateChangedHandler.state == PeripheralState.advertising) 43 | case "isSupported": 44 | isSupported(result) 45 | case "isConnected": 46 | result(stateChangedHandler.state == PeripheralState.connected) 47 | // case "sendData": 48 | // sendData(call, result) 49 | default: 50 | result(FlutterMethodNotImplemented) 51 | } 52 | } 53 | 54 | private func startPeripheral(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 55 | let map = call.arguments as? Dictionary 56 | let advertiseData = PeripheralData( 57 | uuid: map?["serviceUuid"] as? String , 58 | localName: map?["localName"] as? String 59 | ) 60 | flutterBlePeripheralManager.start(advertiseData: advertiseData) 61 | result(nil) 62 | } 63 | 64 | private func stopPeripheral(_ result: @escaping FlutterResult) { 65 | flutterBlePeripheralManager.peripheralManager.stopAdvertising() 66 | stateChangedHandler.publishPeripheralState(state: PeripheralState.idle) 67 | result(nil) 68 | } 69 | 70 | // We can check if advertising is supported by checking if the ios device supports iBeacons since that uses BLE. 71 | private func isSupported(_ result: @escaping FlutterResult) { 72 | if #available(macOS 10.15, *) { 73 | if (CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self)){ 74 | result(true) 75 | } else { 76 | result(false) 77 | } 78 | } else { 79 | result(true) 80 | } 81 | } 82 | 83 | // private func sendData(_ call: FlutterMethodCall, 84 | // _ result: @escaping FlutterResult) { 85 | // 86 | // if let flutterData = call.arguments as? FlutterStandardTypedData { 87 | // flutterBlePeripheralManager.send(data: flutterData.data) 88 | // } 89 | // result(nil) 90 | // } 91 | } 92 | -------------------------------------------------------------------------------- /macos/Classes/callbacks/PeripheralManagerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralManagerDelegate.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 25/03/2022. 6 | // 7 | 8 | import Foundation 9 | import CoreBluetooth 10 | import CoreLocation 11 | 12 | extension FlutterBlePeripheralManager: CBPeripheralManagerDelegate { 13 | 14 | func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 15 | var state: PeripheralState 16 | switch peripheral.state { 17 | case .poweredOn: 18 | state = .idle 19 | // addService() TODO: add service 20 | case .poweredOff: 21 | state = .poweredOff 22 | case .resetting: 23 | state = .idle 24 | case .unsupported: 25 | state = .unsupported 26 | case .unauthorized: 27 | state = .unauthorized 28 | case .unknown: 29 | state = .unknown 30 | @unknown default: 31 | state = .unknown 32 | } 33 | stateChangedHandler.publishPeripheralState(state: state) 34 | } 35 | 36 | func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) { 37 | // print("[flutter_ble_peripheral] didStartAdvertising:", error ?? "success") 38 | 39 | guard error == nil else { 40 | return 41 | } 42 | 43 | stateChangedHandler.publishPeripheralState(state: .advertising) 44 | 45 | // Immediately set to connected if the tx Characteristic is already subscribed 46 | // if txSubscribed { 47 | // state = .connected 48 | // } 49 | } 50 | 51 | // func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 52 | // print("[flutter_ble_peripheral] didReceiveRead:", request) 53 | // 54 | // // Only answer to requests if not idle 55 | // guard state != .idle else { 56 | // print("[flutter_ble_peripheral] state = .idle -> not answering read request") 57 | // return 58 | // } 59 | // 60 | // // Not supported 61 | // peripheralManager.respond(to: request, withResult: .requestNotSupported) 62 | // } 63 | // 64 | // func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { 65 | // print("[flutter_ble_peripheral] didReceiveWrite:", requests) 66 | // 67 | // // Only answer to requests if not idle 68 | // guard state != .idle else { 69 | // print("[flutter_ble_peripheral] state = .idle -> not answering write request") 70 | // return 71 | // } 72 | // 73 | // for request in requests { 74 | // 75 | // print("[flutter_ble_peripheral] write request:", request); 76 | // 77 | // let characteristic = request.characteristic 78 | // guard let data = request.value else { 79 | // print("[flutter_ble_peripheral] request.value is nil"); 80 | // return 81 | // } 82 | // 83 | // // Write only supported in rxCharacteristic 84 | // guard characteristic == self.rxCharacteristic else { 85 | // peripheralManager.respond(to: request, withResult: .requestNotSupported) 86 | // print("[flutter_ble_peripheral] respond requestNotSupported (only supported in rxCharacteristic)") 87 | // return 88 | // } 89 | // 90 | // print("[flutter_ble_peripheral] request.value:", request.value!) 91 | // print("[flutter_ble_peripheral] characteristic.value:", characteristic.value!) 92 | // 93 | // if data.count > 0 { 94 | // print("[flutter_ble_peripheral] Receive data: \(data)") 95 | // onDataReceived?(data) 96 | // } 97 | // 98 | // // Respond with success 99 | // peripheralManager.respond(to: request, withResult: .success) 100 | // } 101 | // } 102 | // 103 | // func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { 104 | // 105 | // if characteristic == txCharacteristic { 106 | // 107 | // print("[flutter_ble_peripheral] didSubscribeTo:", central, characteristic) 108 | // 109 | // // Update MTU 110 | // self.mtu = central.maximumUpdateValueLength; 111 | // 112 | // // Add to subscriptions 113 | // txSubscriptions.insert(central.identifier) 114 | // 115 | // txSubscribed = !txSubscriptions.isEmpty 116 | // 117 | // print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 118 | // } 119 | // } 120 | // 121 | // func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { 122 | // 123 | // if characteristic == txCharacteristic { 124 | // 125 | // print("[flutter_ble_peripheral] didUnsubscribeFrom:", central, characteristic) 126 | // 127 | // // Remove from txSubscriptions 128 | // txSubscriptions.remove(central.identifier) 129 | // 130 | // txSubscribed = !txSubscriptions.isEmpty 131 | // 132 | // print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 133 | // } 134 | // } 135 | } 136 | -------------------------------------------------------------------------------- /macos/Classes/handlers/DataReceivedHandler.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// StateChangedHandler.swift 3 | //// flutter_ble_peripheral 4 | //// 5 | //// Created by Julian Steenbakker on 25/03/2022. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //public class DataReceivedHandler: NSObject, FlutterStreamHandler { 11 | // 12 | // private var eventSink: FlutterEventSink? 13 | // 14 | // fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 15 | // 16 | // let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_data_received", 17 | // binaryMessenger: registrar.messenger()) 18 | // eventChannel.setStreamHandler(self) 19 | // 20 | // peripheral.onDataReceived = { data in 21 | // if let eventSink = self.eventSink { 22 | // eventSink(FlutterStandardTypedData(bytes: data)) 23 | // } 24 | // } 25 | // } 26 | // 27 | // public func onListen(withArguments arguments: Any?, 28 | // eventSink: @escaping FlutterEventSink) -> FlutterError? { 29 | // self.eventSink = eventSink 30 | // return nil 31 | // } 32 | // 33 | // public func onCancel(withArguments arguments: Any?) -> FlutterError? { 34 | // eventSink = nil 35 | // return nil 36 | // } 37 | //} 38 | -------------------------------------------------------------------------------- /macos/Classes/handlers/MtuChangedHandler.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// StateChangedHandler.swift 3 | //// flutter_ble_peripheral 4 | //// 5 | //// Created by Julian Steenbakker on 25/03/2022. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //public class MtuChangedHandler: NSObject, FlutterStreamHandler { 11 | // 12 | // private var eventSink: FlutterEventSink? 13 | // 14 | // fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 15 | // 16 | // let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed", 17 | // binaryMessenger: registrar.messenger()) 18 | // eventChannel.setStreamHandler(self) 19 | // 20 | // peripheral.onMtuChanged = { mtuSize in 21 | // if let eventSink = self.eventSink { 22 | // eventSink(mtuSize) 23 | // } 24 | // } 25 | // } 26 | // 27 | // public func onListen(withArguments arguments: Any?, 28 | // eventSink: @escaping FlutterEventSink) -> FlutterError? { 29 | // self.eventSink = eventSink 30 | // return nil 31 | // } 32 | // 33 | // public func onCancel(withArguments arguments: Any?) -> FlutterError? { 34 | // eventSink = nil 35 | // return nil 36 | // } 37 | //} 38 | -------------------------------------------------------------------------------- /macos/Classes/handlers/StateChangedHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StateChangedHandler.swift 3 | // flutter_ble_peripheral 4 | // 5 | // Created by Julian Steenbakker on 25/03/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public class StateChangedHandler: NSObject, FlutterStreamHandler { 11 | 12 | private var eventSink: FlutterEventSink? 13 | 14 | var state: PeripheralState = PeripheralState.idle 15 | 16 | private let eventChannel: FlutterEventChannel 17 | 18 | init(registrar: FlutterPluginRegistrar) { 19 | eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state_changed", 20 | binaryMessenger: registrar.messenger) 21 | super.init() 22 | eventChannel.setStreamHandler(self) 23 | } 24 | 25 | func publishPeripheralState(state: PeripheralState) { 26 | self.state = state 27 | if let eventSink = self.eventSink { 28 | eventSink(state.rawValue) 29 | } 30 | } 31 | 32 | public func onListen(withArguments arguments: Any?, 33 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 34 | self.eventSink = eventSink 35 | if let eventSink = self.eventSink { 36 | eventSink(state.rawValue) 37 | } 38 | return nil 39 | } 40 | 41 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 42 | eventSink = nil 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /macos/flutter_ble_peripheral.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_ble_peripheral.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_ble_peripheral' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'FlutterMacOS' 18 | 19 | s.platform = :osx, '10.11' 20 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 21 | s.swift_version = '5.0' 22 | end 23 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ble_peripheral 2 | description: This plugin enables a device to be set into peripheral mode, and advertise custom 3 | services and characteristics. 4 | version: 1.2.6 5 | homepage: https://github.com/juliansteenbakker/flutter_ble_peripheral 6 | 7 | environment: 8 | sdk: '>=2.12.0 <4.0.0' 9 | flutter: ">=1.10.0" 10 | 11 | # Generating json: flutter pub run build_runner build --delete-conflicting-outputs 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | json_annotation: ^4.8.0 17 | 18 | dev_dependencies: 19 | build_runner: ^2.1.8 20 | flutter_test: 21 | sdk: flutter 22 | json_serializable: ^6.6.1 23 | lint: ^2.0.1 24 | 25 | flutter: 26 | plugin: 27 | platforms: 28 | android: 29 | package: dev.steenbakker.flutter_ble_peripheral 30 | pluginClass: FlutterBlePeripheralPlugin 31 | ios: 32 | pluginClass: FlutterBlePeripheralPlugin 33 | macos: 34 | pluginClass: FlutterBlePeripheralPlugin 35 | windows: 36 | pluginClass: FlutterBlePeripheralPluginCApi 37 | -------------------------------------------------------------------------------- /test/flutter_ble_peripheral_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_ble_peripheral/src/flutter_ble_peripheral.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | const methodChannel = 7 | MethodChannel('dev.steenbakker.flutter_ble_peripheral/ble_state'); 8 | 9 | TestWidgetsFlutterBinding.ensureInitialized(); 10 | late FlutterBlePeripheral blePeripheral; 11 | 12 | setUp(() { 13 | blePeripheral = FlutterBlePeripheral(); 14 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 15 | .setMockMethodCallHandler(methodChannel, (methodCall) async { 16 | if (methodCall.method == 'start' || methodCall.method == 'stop') { 17 | return null; 18 | } else if (methodCall.method == 'isAdvertising') { 19 | return Future.value(true); 20 | } 21 | return null; 22 | }); 23 | }); 24 | 25 | tearDown(() { 26 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 27 | .setMockMethodCallHandler(methodChannel, null); 28 | }); 29 | 30 | test('checking if is advertising returns true', () async { 31 | expect(await blePeripheral.isAdvertising, isTrue); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have a version of Visual Studio 2 | # installed that includes CMake 3.14 or later. You should not increase this 3 | # version, as doing so will cause the plugin to fail to compile for some 4 | # customers of the plugin. 5 | cmake_minimum_required(VERSION 3.14) 6 | 7 | # Project-level configuration. 8 | set(PROJECT_NAME "flutter_ble_peripheral") 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | # This value is used when generating builds using this plugin, so it must 12 | # not be changed 13 | set(PLUGIN_NAME "flutter_ble_peripheral_plugin") 14 | 15 | find_program(NUGET_EXE NAMES nuget) 16 | if(NOT NUGET_EXE) 17 | message("NUGET.EXE not found.") 18 | message(FATAL_ERROR "Please install this executable, and run CMake again.") 19 | endif() 20 | 21 | exec_program(${NUGET_EXE} 22 | ARGS install "Microsoft.Windows.CppWinRT" -Version 2.0.201102.2 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) 23 | 24 | 25 | # Any new source files that you add to the plugin should be added here. 26 | list(APPEND PLUGIN_SOURCES 27 | "flutter_ble_peripheral_plugin.cpp" 28 | "flutter_ble_peripheral_plugin.h" 29 | ) 30 | 31 | # Define the plugin library target. Its name must not be changed (see comment 32 | # on PLUGIN_NAME above). 33 | add_library(${PLUGIN_NAME} SHARED 34 | "include/flutter_ble_peripheral/flutter_ble_peripheral_plugin_c_api.h" 35 | "flutter_ble_peripheral_plugin_c_api.cpp" 36 | ${PLUGIN_SOURCES} 37 | ) 38 | 39 | # Apply a standard set of build settings that are configured in the 40 | # application-level CMakeLists.txt. This can be removed for plugins that want 41 | # full control over build settings. 42 | apply_standard_settings(${PLUGIN_NAME}) 43 | 44 | # Symbols are hidden by default to reduce the chance of accidental conflicts 45 | # between plugins. This should not be removed; any symbols that should be 46 | # exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. 47 | set_target_properties(${PLUGIN_NAME} PROPERTIES 48 | CXX_VISIBILITY_PRESET hidden) 49 | 50 | set_target_properties(${PLUGIN_NAME} PROPERTIES 51 | VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props 52 | ) 53 | 54 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) 55 | 56 | 57 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 58 | 59 | # Source include directories and library dependencies. Add any plugin-specific 60 | # dependencies here. 61 | target_include_directories(${PLUGIN_NAME} INTERFACE 62 | "${CMAKE_CURRENT_SOURCE_DIR}/include") 63 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) 64 | 65 | # List of absolute paths to libraries that should be bundled with the plugin. 66 | # This list could contain prebuilt libraries, or libraries created by an 67 | # external build triggered from this build file. 68 | set(flutter_ble_peripheral_bundled_libraries 69 | "" 70 | PARENT_SCOPE 71 | ) 72 | -------------------------------------------------------------------------------- /windows/flutter_ble_peripheral_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FLUTTER_BLE_CENTRAL_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_FLUTTER_BLE_CENTRAL_PLUGIN_H_ 3 | 4 | // This must be included before many other Windows headers. 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace flutter_ble_peripheral { 30 | 31 | using namespace winrt; 32 | using namespace winrt::Windows::Foundation; 33 | using namespace winrt::Windows::Foundation::Collections; 34 | using namespace winrt::Windows::Storage::Streams; 35 | using namespace winrt::Windows::Devices::Radios; 36 | using namespace winrt::Windows::Devices::Bluetooth; 37 | using namespace winrt::Windows::Devices::Bluetooth::Advertisement; 38 | using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; 39 | using namespace winrt::Windows::Devices::Enumeration; 40 | 41 | using flutter::EncodableMap; 42 | using flutter::EncodableValue; 43 | 44 | 45 | 46 | class FlutterBlePeripheralPlugin : public flutter::Plugin, public flutter::StreamHandler { 47 | public: 48 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); 49 | 50 | FlutterBlePeripheralPlugin(); 51 | 52 | virtual ~FlutterBlePeripheralPlugin(); 53 | 54 | // Disallow copy and assign. 55 | FlutterBlePeripheralPlugin(const FlutterBlePeripheralPlugin&) = delete; 56 | FlutterBlePeripheralPlugin& operator=(const FlutterBlePeripheralPlugin&) = delete; 57 | 58 | private: 59 | winrt::fire_and_forget InitializeAsync(); 60 | 61 | // Called when a method is called on this plugin's channel from Dart. 62 | void HandleMethodCall( 63 | const flutter::MethodCall& method_call, 64 | std::unique_ptr> result); 65 | 66 | std::unique_ptr> OnListenInternal( 67 | const flutter::EncodableValue* arguments, 68 | std::unique_ptr>&& events) override; 69 | std::unique_ptr> OnCancelInternal( 70 | const flutter::EncodableValue* arguments) override; 71 | 72 | std::unique_ptr> scan_result_sink_; 73 | 74 | Radio bluetoothRadio{ nullptr }; 75 | 76 | BluetoothLEAdvertisementWatcher bluetoothLEWatcher{ nullptr }; 77 | winrt::event_token bluetoothLEWatcherReceivedToken; 78 | void BluetoothLEWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args); 79 | 80 | 81 | BluetoothLEAdvertisementPublisher bluetoothLEPublisher{ nullptr }; 82 | 83 | 84 | }; 85 | 86 | } // namespace flutter_ble_peripheral 87 | 88 | #endif // FLUTTER_PLUGIN_FLUTTER_BLE_CENTRAL_PLUGIN_H_ 89 | -------------------------------------------------------------------------------- /windows/flutter_ble_peripheral_plugin_c_api.cpp: -------------------------------------------------------------------------------- 1 | #include "include/flutter_ble_peripheral/flutter_ble_peripheral_plugin_c_api.h" 2 | 3 | #include 4 | 5 | #include "flutter_ble_peripheral_plugin.h" 6 | 7 | void FlutterBlePeripheralPluginCApiRegisterWithRegistrar( 8 | FlutterDesktopPluginRegistrarRef registrar) { 9 | flutter_ble_peripheral::FlutterBlePeripheralPlugin::RegisterWithRegistrar( 10 | flutter::PluginRegistrarManager::GetInstance() 11 | ->GetRegistrar(registrar)); 12 | } 13 | -------------------------------------------------------------------------------- /windows/include/flutter_ble_peripheral/flutter_ble_peripheral_plugin_c_api.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FLUTTER_BLE_PERIPHERAL_PLUGIN_C_API_H_ 2 | #define FLUTTER_PLUGIN_FLUTTER_BLE_PERIPHERAL_PLUGIN_C_API_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void FlutterBlePeripheralPluginCApiRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_FLUTTER_BLE_PERIPHERAL_PLUGIN_C_API_H_ 24 | --------------------------------------------------------------------------------