├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterBlePeripheralPlugin.h │ ├── FlutterBlePeripheralPlugin.m │ ├── Models │ │ ├── PeripheralData.swift │ │ └── PeripheralState.swift │ ├── SwiftFlutterBlePeripheralPlugin.swift │ └── FlutterBlePeripheralManager.swift ├── .gitignore └── flutter_ble_peripheral.podspec ├── macos ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterBlePeripheralPlugin.h │ ├── FlutterBlePeripheralPlugin.m │ ├── SwiftFlutterBlePeripheralPlugin.swift │ └── Peripheral.swift └── flutter_ble_peripheral.podspec ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── flutter_format.yml ├── android ├── settings.gradle ├── gradle.properties ├── .gitignore ├── src │ └── main │ │ ├── kotlin │ │ └── dev │ │ │ └── steenbakker │ │ │ └── flutter_ble_peripheral │ │ │ ├── exceptions │ │ │ ├── PeripheralException.kt │ │ │ └── PermissionNotFoundException.kt │ │ │ ├── models │ │ │ ├── PeripheralState.kt │ │ │ └── PeripheralData.kt │ │ │ ├── handlers │ │ │ ├── MtuChangedHandler.kt │ │ │ ├── DataReceivedHandler.kt │ │ │ └── StateChangedHandler.kt │ │ │ ├── FlutterBlePeripheralPlugin.kt │ │ │ └── FlutterBlePeripheralManager.kt │ │ └── AndroidManifest.xml ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── build.gradle ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── 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-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ └── Podfile ├── android │ ├── gradle.properties │ ├── .gitignore │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── 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 │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── macos │ ├── Runner │ │ ├── Configs │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ ├── Warnings.xcconfig │ │ │ └── AppInfo.xcconfig │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── 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 │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Release.entitlements │ │ ├── MainFlutterWindow.swift │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ └── MainMenu.xib │ ├── .gitignore │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── Podfile ├── .metadata ├── pubspec.yaml ├── .gitignore ├── lib │ ├── ble_status_screen.dart │ └── main.dart └── README.md ├── analysis_options.yaml ├── lib ├── flutter_ble_peripheral.dart └── src │ ├── models │ ├── peripheral_state.dart │ └── advertise_data.dart │ └── flutter_ble_peripheral.dart ├── .metadata ├── pubspec.yaml ├── test └── flutter_ble_peripheral_test.dart ├── CHANGELOG.md ├── LICENSE ├── README.md └── .gitignore /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /macos/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [juliansteenbakker] 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_ble_peripheral' 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | constant_identifier_names: false -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Classes/FlutterBlePeripheralPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterBlePeripheralPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /macos/Classes/FlutterBlePeripheralPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterBlePeripheralPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /lib/flutter_ble_peripheral.dart: -------------------------------------------------------------------------------- 1 | export 'src/models/advertise_data.dart'; 2 | export 'src/models/peripheral_state.dart'; 3 | export 'src/flutter_ble_peripheral.dart'; 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/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/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukumijima/flutter_ble_peripheral/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/exceptions/PeripheralException.kt: -------------------------------------------------------------------------------- 1 | package dev.steenbakker.flutter_ble_peripheral.exceptions 2 | 3 | import java.lang.Exception 4 | 5 | class PeripheralException(message: String) : Exception(message) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 25 10:37:01 CET 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/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/macos/Runner.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/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: plugin 11 | -------------------------------------------------------------------------------- /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/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/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/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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/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/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.12.0 <3.0.0' 7 | flutter: ">=1.10.0" 8 | 9 | dependencies: 10 | permission_handler: ^8.3.0 11 | flutter: 12 | sdk: flutter 13 | 14 | flutter_ble_peripheral: 15 | path: ../ 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | uses-material-design: true -------------------------------------------------------------------------------- /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/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/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/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/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.5.31' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.0.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: 0.6.0 5 | homepage: https://github.com/juliansteenbakker/flutter_ble_peripheral 6 | 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | flutter: ">=1.10.0" 10 | 11 | dependencies: 12 | flutter_lints: ^1.0.4 13 | flutter: 14 | sdk: flutter 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | plugin: 22 | platforms: 23 | android: 24 | package: dev.steenbakker.flutter_ble_peripheral 25 | pluginClass: FlutterBlePeripheralPlugin 26 | ios: 27 | pluginClass: FlutterBlePeripheralPlugin 28 | macos: 29 | pluginClass: FlutterBlePeripheralPlugin 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.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@v2 10 | - uses: actions/setup-java@v2 11 | with: 12 | distribution: 'temurin' 13 | java-version: '11' 14 | - uses: subosito/flutter-action@v2.3.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@v2 25 | - uses: actions/setup-java@v2 26 | with: 27 | distribution: 'temurin' 28 | java-version: '11' 29 | - uses: subosito/flutter-action@v2.3.0 30 | - name: Format 31 | run: flutter format -n --set-exit-if-changed . 32 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'dev.steenbakker.flutter_ble_peripheral' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.0' 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 | compileSdkVersion 31 29 | sourceSets { 30 | main.java.srcDirs += 'src/main/kotlin' 31 | } 32 | defaultConfig { 33 | minSdkVersion 21 34 | } 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | } 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /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 | methodChannel.setMockMethodCallHandler((methodCall) async { 15 | if (methodCall.method == 'start' || methodCall.method == 'stop') { 16 | return null; 17 | } else if (methodCall.method == 'isAdvertising') { 18 | return Future.value(true); 19 | } 20 | return null; 21 | }); 22 | }); 23 | 24 | tearDown(() { 25 | methodChannel.setMockMethodCallHandler(null); 26 | }); 27 | 28 | test('checking if is advertising returns true', () async { 29 | expect(await blePeripheral.isAdvertising(), isTrue); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/models/PeripheralData.kt: -------------------------------------------------------------------------------- 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 | package dev.steenbakker.flutter_ble_peripheral.models 8 | 9 | import android.bluetooth.le.AdvertiseSettings 10 | 11 | class PeripheralData( 12 | var uuid: String = "", 13 | var manufacturerId: Int? = null, 14 | var manufacturerData: List = mutableListOf(), 15 | var serviceDataUuid: String = "8ebdb2f3-7817-45c9-95c5-c5e9031aaa47", 16 | var serviceData: List = mutableListOf(), 17 | var txCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D4", 18 | var rxCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D5", 19 | var includeDeviceName: Boolean = false, 20 | var includeTxPowerLevel: Boolean = false, 21 | var advertiseMode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY, 22 | var connectable: Boolean = false, 23 | var timeout: Int = 400, 24 | var txPowerLevel: Int = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH 25 | ) 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.: 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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.0 2 | * Refactored large parts of the code for both Android & iOS. 3 | * Upgraded Android to Android 12 permission system. 4 | * Other minor improvements 5 | 6 | ## 0.5.0+1 7 | Changes of 0.5.0 weren't visible on pub.dev 8 | 9 | ## 0.5.0 10 | Added isSupported function to check if BLE advertising is supported by the device. 11 | 12 | ## 0.4.2 13 | Fixed typo causing deviceName not to broadcast on iOS 14 | 15 | ## 0.4.1 16 | Fixed bug on iOS which led to crash 17 | Added local name to advertising in iOS 18 | Updated Android dependencies 19 | 20 | ## 0.4.0 21 | Added new options to AdvertiseData 22 | Removed embedding V1 for Android 23 | 24 | ## 0.3.0 25 | Upgraded to null-safety 26 | Updated dependencies 27 | Changed to pedantic 28 | 29 | Bug fixes 30 | * Fixed null-pointer when bluetooth adapter isn't found 31 | 32 | ## 0.2.0 33 | Add support for MacOS 34 | 35 | ## 0.1.0 36 | Fixed several parts for Android: 37 | * Advertising local name 38 | * Advertising Manufacturer Data 39 | * Advertising Service Data 40 | 41 | ## 0.0.4 42 | Fixed iOS advertising not working 43 | 44 | ## 0.0.3 45 | Fixed callback on Android 46 | 47 | ## 0.0.2 48 | Fixed flutter v2 embedding 49 | 50 | ## 0.0.1 51 | Initial version of the library. This version includes: 52 | * broadcasting a custom UUID 53 | -------------------------------------------------------------------------------- /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 : 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_mtu_changed" 16 | ) 17 | 18 | eventChannel.setStreamHandler(this) 19 | 20 | flutterBlePeripheralManager.onMtuChanged = { mtu -> 21 | Handler(Looper.getMainLooper()).post { 22 | eventSink?.success(mtu) 23 | } 24 | } 25 | } 26 | 27 | override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { 28 | this.eventSink = eventSink 29 | } 30 | 31 | override fun onCancel(event: Any?) { 32 | this.eventSink = null 33 | } 34 | } -------------------------------------------------------------------------------- /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/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.FlutterBlePeripheralManager 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.plugin.common.EventChannel 8 | 9 | class StateChangedHandler : 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_state_changed" 16 | ) 17 | 18 | eventChannel.setStreamHandler(this) 19 | 20 | flutterBlePeripheralManager.onStateChanged = { state -> 21 | Handler(Looper.getMainLooper()).post { 22 | eventSink?.success(state.ordinal) 23 | } 24 | } 25 | } 26 | 27 | override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { 28 | this.eventSink = eventSink 29 | } 30 | 31 | override fun onCancel(event: Any?) { 32 | this.eventSink = null 33 | } 34 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.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 | -------------------------------------------------------------------------------- /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 | 6 | # FlutterBlePeripheral 7 | 8 | This Flutter plugin allows a device to be used in Peripheral mode, and advertise data over BLE to central devices. 9 | 10 | ## Help develop this plugin! 11 | 12 | If you want to contribute to this plugin, feel free to make issues and pull-requests. 13 | 14 | ### Not stable 15 | 16 | Since this plugin is currently being developed, limited functionality will be available. Check the release page for the most recent release. 17 | 18 | | Functionality | Android | iOS | Description | 19 | | -------------------- |:----------------:|:-----:| --------------| 20 | | Advertise UUID | :white_check_mark: | :white_check_mark: | Set and advertise a custom uuid. | 21 | | Advertise ManufacturerData | :white_check_mark: | :x: | Set and advertise custom data. *not possible on iOS* | 22 | | Advertise custom service | :white_check_mark: | :x: | Advertise a custom service. *not possible on iOS* | 23 | 24 | ## How to use 25 | Please check the example app to see how to broadcast data. 26 | Note that iOS does not support a lot of options. Please see `AdvertiseData` to see which options are supported by iOS & Android. 27 | -------------------------------------------------------------------------------- /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/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 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | applicationId "dev.steenbakker.flutter_ble_peripheral_example" 41 | minSdkVersion 21 42 | targetSdkVersion 31 43 | versionCode flutterVersionCode.toInteger() 44 | versionName flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | } 63 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # Visual Studio Code related 21 | .classpath 22 | .project 23 | .settings/ 24 | .vscode/ 25 | 26 | # Flutter repo-specific 27 | /bin/cache/ 28 | /bin/mingit/ 29 | /dev/benchmarks/mega_gallery/ 30 | /dev/bots/.recipe_deps 31 | /dev/bots/android_tools/ 32 | /dev/docs/doc/ 33 | /dev/docs/flutter.docs.zip 34 | /dev/docs/lib/ 35 | /dev/docs/pubspec.yaml 36 | /dev/integration_tests/**/xcuserdata 37 | /dev/integration_tests/**/Pods 38 | /packages/flutter/coverage/ 39 | version 40 | 41 | # packages file containing multi-root paths 42 | .packages.generated 43 | 44 | # Flutter/Dart/Pub related 45 | **/doc/api/ 46 | .dart_tool/ 47 | .flutter-plugins 48 | .flutter-plugins-dependencies 49 | .packages 50 | .pub-cache/ 51 | .pub/ 52 | build/ 53 | flutter_*.png 54 | linked_*.ds 55 | unlinked.ds 56 | unlinked_spec.ds 57 | 58 | # Android related 59 | **/android/**/gradle-wrapper.jar 60 | **/android/.gradle 61 | **/android/captures/ 62 | **/android/gradlew 63 | **/android/gradlew.bat 64 | **/android/local.properties 65 | **/android/**/GeneratedPluginRegistrant.java 66 | **/android/key.properties 67 | *.jks 68 | 69 | # iOS/XCode related 70 | **/ios/**/*.mode1v3 71 | **/ios/**/*.mode2v3 72 | **/ios/**/*.moved-aside 73 | **/ios/**/*.pbxuser 74 | **/ios/**/*.perspectivev3 75 | **/ios/**/*sync/ 76 | **/ios/**/.sconsign.dblite 77 | **/ios/**/.tags* 78 | **/ios/**/.vagrant/ 79 | **/ios/**/DerivedData/ 80 | **/ios/**/Icon? 81 | **/ios/**/Pods/ 82 | **/ios/**/.symlinks/ 83 | **/ios/**/profile 84 | **/ios/**/xcuserdata 85 | **/ios/.generated/ 86 | **/ios/Flutter/App.framework 87 | **/ios/Flutter/Flutter.framework 88 | **/ios/Flutter/Flutter.podspec 89 | **/ios/Flutter/Generated.xcconfig 90 | **/ios/Flutter/app.flx 91 | **/ios/Flutter/app.zip 92 | **/ios/Flutter/flutter_assets/ 93 | **/ios/Flutter/flutter_export_environment.sh 94 | **/ios/ServiceDefinitions.json 95 | **/ios/Runner/GeneratedPluginRegistrant.* 96 | 97 | # macOS 98 | **/macos/Flutter/GeneratedPluginRegistrant.swift 99 | **/macos/Flutter/Flutter-Debug.xcconfig 100 | **/macos/Flutter/Flutter-Release.xcconfig 101 | **/macos/Flutter/Flutter-Profile.xcconfig 102 | 103 | # Coverage 104 | coverage/ 105 | 106 | # Symbols 107 | app.*.symbols 108 | 109 | # Exceptions to above rules. 110 | !**/ios/**/default.mode1v3 111 | !**/ios/**/default.mode2v3 112 | !**/ios/**/default.pbxuser 113 | !**/ios/**/default.perspectivev3 114 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 115 | !/dev/ci/**/Gemfile.lock -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 9 | 12 | 13 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 32 | 40 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 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 parse_KV_file(file, separator='=') 13 | file_abs_path = File.expand_path(file) 14 | if !File.exists? file_abs_path 15 | return []; 16 | end 17 | pods_ary = [] 18 | skip_line_start_symbols = ["#", "/"] 19 | File.foreach(file_abs_path) { |line| 20 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 21 | plugin = line.split(pattern=separator) 22 | if plugin.length == 2 23 | podname = plugin[0].strip() 24 | path = plugin[1].strip() 25 | podpath = File.expand_path("#{path}", file_abs_path) 26 | pods_ary.push({:name => podname, :path => podpath}); 27 | else 28 | puts "Invalid plugin specification: #{line}" 29 | end 30 | } 31 | return pods_ary 32 | end 33 | 34 | def pubspec_supports_macos(file) 35 | file_abs_path = File.expand_path(file) 36 | if !File.exists? file_abs_path 37 | return false; 38 | end 39 | File.foreach(file_abs_path) { |line| 40 | return true if line =~ /^\s*macos:/ 41 | } 42 | return false 43 | end 44 | 45 | target 'Runner' do 46 | use_frameworks! 47 | use_modular_headers! 48 | 49 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 50 | # referring to absolute paths on developers' machines. 51 | ephemeral_dir = File.join('Flutter', 'ephemeral') 52 | symlink_dir = File.join(ephemeral_dir, '.symlinks') 53 | symlink_plugins_dir = File.join(symlink_dir, 'plugins') 54 | system("rm -rf #{symlink_dir}") 55 | system("mkdir -p #{symlink_plugins_dir}") 56 | 57 | # Flutter Pods 58 | generated_xcconfig = parse_KV_file(File.join(ephemeral_dir, 'Flutter-Generated.xcconfig')) 59 | if generated_xcconfig.empty? 60 | puts "Flutter-Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 61 | end 62 | generated_xcconfig.map { |p| 63 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 64 | symlink = File.join(symlink_dir, 'flutter') 65 | File.symlink(File.dirname(p[:path]), symlink) 66 | pod 'FlutterMacOS', :path => File.join(symlink, File.basename(p[:path])) 67 | end 68 | } 69 | 70 | # Plugin Pods 71 | plugin_pods = parse_KV_file('../.flutter-plugins') 72 | plugin_pods.map { |p| 73 | symlink = File.join(symlink_plugins_dir, p[:name]) 74 | File.symlink(p[:path], symlink) 75 | if pubspec_supports_macos(File.join(symlink, 'pubspec.yaml')) 76 | pod p[:name], :path => File.join(symlink, 'macos') 77 | end 78 | } 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | -------------------------------------------------------------------------------- /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/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/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 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /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 | /// Model of the data to be advertised. 8 | class AdvertiseData { 9 | /// Android & iOS 10 | /// 11 | /// Specifies the UUID to be advertised 12 | final String uuid; 13 | 14 | /// Android only 15 | /// 16 | /// Specifies a manufacturer id 17 | final int? manufacturerId; 18 | 19 | /// Android only 20 | /// 21 | /// Specifies manufacturer data. 22 | final List? manufacturerData; 23 | 24 | /// Android only 25 | /// 26 | /// Specifies service data UUID 27 | final String serviceDataUuid; 28 | 29 | /// Android only 30 | /// 31 | /// Specifies service data 32 | final List? serviceData; 33 | 34 | /// Android only 35 | /// 36 | /// Set to true if device name needs to be included with advertisement 37 | /// Default: false 38 | final bool includeDeviceName; 39 | 40 | /// iOS only 41 | /// 42 | /// Set the deviceName to be broadcasted. Can be 10 bytes. 43 | final String? localName; 44 | 45 | /// Android only 46 | /// 47 | /// set to true if you want to include the power level in the advertisement 48 | /// Default: false 49 | final bool? includePowerLevel; 50 | 51 | /// Android only 52 | /// 53 | /// Set advertise mode to control the advertising power and latency. 54 | /// Default: AdvertiseMode.ADVERTISE_MODE_LOW_LATENCY 55 | final AdvertiseMode advertiseMode; 56 | 57 | /// Android only 58 | /// 59 | /// Set whether the advertisement type should be connectable or non-connectable. 60 | /// Default: false 61 | final bool connectable; 62 | 63 | /// Android only 64 | /// 65 | /// Limit advertising to a given amount of time. 66 | /// May not exceed 180000 milliseconds. 67 | /// Default: 400 milliseconds 68 | final int timeout; 69 | 70 | /// Android only 71 | /// 72 | /// Set advertise TX power level to control the transmission power level for the advertising. 73 | /// Default: AdvertisePower.ADVERTISE_TX_POWER_HIGH 74 | final AdvertisePower txPowerLevel; 75 | 76 | AdvertiseData( 77 | {this.uuid = '', 78 | this.manufacturerId, 79 | this.manufacturerData, 80 | this.serviceDataUuid = '8ebdb2f3-7817-45c9-95c5-c5e9031aaa47', 81 | this.serviceData, 82 | this.includeDeviceName = false, 83 | this.localName, 84 | this.includePowerLevel = false, 85 | this.connectable = false, 86 | this.timeout = 400, 87 | this.advertiseMode = AdvertiseMode.ADVERTISE_MODE_LOW_LATENCY, 88 | this.txPowerLevel = AdvertisePower.ADVERTISE_TX_POWER_HIGH}); 89 | } 90 | 91 | enum AdvertiseMode { 92 | /// Perform Bluetooth LE advertising in low power mode. This is the default and preferred 93 | /// advertising mode as it consumes the least power. 94 | ADVERTISE_MODE_LOW_POWER, 95 | 96 | /// Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising 97 | /// frequency and power consumption. 98 | ADVERTISE_MODE_BALANCED, 99 | 100 | /// Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power 101 | /// consumption and should not be used for continuous background advertising. 102 | ADVERTISE_MODE_LOW_LATENCY 103 | } 104 | 105 | enum AdvertisePower { 106 | /// Advertise using the lowest transmission (TX) power level. Low transmission power can be used 107 | /// to restrict the visibility range of advertising packets. 108 | ADVERTISE_TX_POWER_ULTRA_LOW, 109 | 110 | /// Advertise using low TX power level. 111 | ADVERTISE_TX_POWER_LOW, 112 | 113 | /// Advertise using medium TX power level. 114 | ADVERTISE_TX_POWER_MEDIUM, 115 | 116 | /// Advertise using high TX power level. This corresponds to largest visibility range of the 117 | /// advertising packet. 118 | ADVERTISE_TX_POWER_HIGH 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/flutter_ble_peripheral.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:async'; 8 | import 'dart:typed_data'; 9 | 10 | import 'package:flutter/services.dart'; 11 | 12 | import '../flutter_ble_peripheral.dart'; 13 | 14 | class FlutterBlePeripheral { 15 | /// Singleton instance 16 | static final FlutterBlePeripheral _instance = 17 | FlutterBlePeripheral._internal(); 18 | 19 | /// Singleton factory 20 | factory FlutterBlePeripheral() { 21 | return _instance; 22 | } 23 | 24 | /// Singleton constructor 25 | FlutterBlePeripheral._internal(); 26 | 27 | /// Method Channel used to communicate state with 28 | final MethodChannel _methodChannel = 29 | const MethodChannel('dev.steenbakker.flutter_ble_peripheral/ble_state'); 30 | 31 | /// Event Channel for MTU state 32 | final EventChannel _mtuChangedEventChannel = const EventChannel( 33 | 'dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed'); 34 | 35 | /// Event Channel used to changed state 36 | final EventChannel _stateChangedEventChannel = const EventChannel( 37 | 'dev.steenbakker.flutter_ble_peripheral/ble_state_changed'); 38 | 39 | // Event Channel used to received data 40 | final EventChannel _dataReceivedEventChannel = const EventChannel( 41 | 'dev.steenbakker.flutter_ble_peripheral/ble_data_received'); 42 | 43 | /// Start advertising. Takes [AdvertiseData] as an input. 44 | Future start(AdvertiseData data) async { 45 | Map params = { 46 | 'uuid': data.uuid, 47 | 'manufacturerId': data.manufacturerId, 48 | 'manufacturerData': data.manufacturerData, 49 | 'serviceDataUuid': data.serviceDataUuid, 50 | 'serviceData': data.serviceData, 51 | 'includeDeviceName': data.includeDeviceName, 52 | 'localName': data.localName, 53 | 'transmissionPowerIncluded': data.includePowerLevel, 54 | 'advertiseMode': data.advertiseMode.index, 55 | 'connectable': data.connectable, 56 | 'timeout': data.timeout, 57 | 'txPowerLevel': data.txPowerLevel.index 58 | }; 59 | 60 | await _methodChannel.invokeMethod('start', params); 61 | } 62 | 63 | /// Stop advertising 64 | Future stop() async { 65 | await _methodChannel.invokeMethod('stop'); 66 | } 67 | 68 | /// Returns `true` if advertising or false if not advertising 69 | Future isAdvertising() async { 70 | return await _methodChannel.invokeMethod('isAdvertising'); 71 | } 72 | 73 | /// Returns `true` if advertising over BLE is supported 74 | Future isSupported() async { 75 | return await _methodChannel.invokeMethod('isSupported'); 76 | } 77 | 78 | /// Returns `true` if device is connected 79 | Future isConnected() async { 80 | return await _methodChannel.invokeMethod('isConnected'); 81 | } 82 | 83 | /// Start advertising. Takes [AdvertiseData] as an input. 84 | Future sendData(Uint8List data) async { 85 | await _methodChannel.invokeMethod('sendData', data); 86 | } 87 | 88 | /// Returns Stream of MTU updates. 89 | Stream getMtuChanged() { 90 | return _mtuChangedEventChannel 91 | .receiveBroadcastStream() 92 | .cast() 93 | .distinct() 94 | .map((event) { 95 | return event; 96 | }); 97 | } 98 | 99 | /// Returns Stream of state. 100 | /// 101 | /// After listening to this Stream, you'll be notified about changes in peripheral state. 102 | Stream getStateChanged() { 103 | return _stateChangedEventChannel 104 | .receiveBroadcastStream() 105 | .map((dynamic event) { 106 | return PeripheralState.values[event as int]; 107 | }); 108 | } 109 | 110 | /// Returns Stream of data. 111 | /// 112 | /// 113 | Stream getDataReceived() { 114 | return _dataReceivedEventChannel.receiveBroadcastStream().cast(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /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/lib/main.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 'package:flutter/material.dart'; 8 | import 'package:flutter_ble_peripheral/flutter_ble_peripheral.dart'; 9 | import 'package:permission_handler/permission_handler.dart'; 10 | 11 | void main() => runApp(const FlutterBlePeripheralExample()); 12 | 13 | class FlutterBlePeripheralExample extends StatefulWidget { 14 | const FlutterBlePeripheralExample({Key? key}) : super(key: key); 15 | 16 | @override 17 | _FlutterBlePeripheralExampleState createState() => 18 | _FlutterBlePeripheralExampleState(); 19 | } 20 | 21 | class _FlutterBlePeripheralExampleState 22 | extends State { 23 | final FlutterBlePeripheral blePeripheral = FlutterBlePeripheral(); 24 | final AdvertiseData _data = AdvertiseData( 25 | uuid: 'bf27730d-860a-4e09-889c-2d8b6a9e0fe7', 26 | manufacturerId: 1234, 27 | manufacturerData: [1, 2, 3, 4, 5, 6], 28 | ); 29 | 30 | bool _isSupported = false; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | initPlatformState(); 36 | } 37 | 38 | Future initPlatformState() async { 39 | final isSupported = await blePeripheral.isSupported(); 40 | setState(() { 41 | _isSupported = isSupported; 42 | }); 43 | } 44 | 45 | void _toggleAdvertise() async { 46 | if (await blePeripheral.isAdvertising()) { 47 | await blePeripheral.stop(); 48 | } else { 49 | await blePeripheral.start(_data); 50 | } 51 | } 52 | 53 | void _requestPermissions() async { 54 | await Permission.bluetooth.shouldShowRequestRationale; 55 | 56 | Map statuses = await [ 57 | Permission.bluetooth, 58 | Permission.bluetoothAdvertise, 59 | Permission.bluetoothConnect, 60 | Permission.bluetoothScan, 61 | Permission.location, 62 | ].request(); 63 | for (final status in statuses.keys) { 64 | if (statuses[status] == PermissionStatus.granted) { 65 | debugPrint('$status permission granted'); 66 | } else if (statuses[status] == PermissionStatus.denied) { 67 | debugPrint( 68 | '$status denied. Show a dialog with a reason and again ask for the permission.'); 69 | } else if (statuses[status] == PermissionStatus.permanentlyDenied) { 70 | debugPrint( 71 | '$status permanently denied. Take the user to the settings page.'); 72 | } 73 | } 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return MaterialApp( 79 | home: Scaffold( 80 | appBar: AppBar( 81 | title: const Text('Flutter BLE Peripheral'), 82 | ), 83 | body: Center( 84 | child: Column( 85 | mainAxisAlignment: MainAxisAlignment.center, 86 | crossAxisAlignment: CrossAxisAlignment.center, 87 | children: [ 88 | Text('Is supported: $_isSupported'), 89 | StreamBuilder( 90 | stream: blePeripheral.getStateChanged(), 91 | initialData: PeripheralState.unknown, 92 | builder: 93 | (BuildContext context, AsyncSnapshot snapshot) { 94 | return Text('State: ${snapshot.data}'); 95 | }), 96 | StreamBuilder( 97 | stream: blePeripheral.getDataReceived(), 98 | initialData: 'None', 99 | builder: 100 | (BuildContext context, AsyncSnapshot snapshot) { 101 | return Text('Data received: ${snapshot.data}'); 102 | }), 103 | Text('Current UUID: ${_data.uuid}'), 104 | MaterialButton( 105 | onPressed: _toggleAdvertise, 106 | child: Text( 107 | 'Toggle advertising', 108 | style: Theme.of(context) 109 | .primaryTextTheme 110 | .button! 111 | .copyWith(color: Colors.blue), 112 | )), 113 | MaterialButton( 114 | onPressed: _requestPermissions, 115 | child: Text( 116 | 'Request Permissions', 117 | style: Theme.of(context) 118 | .primaryTextTheme 119 | .button! 120 | .copyWith(color: Colors.blue), 121 | )), 122 | ])), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /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 | 19 | public static func register(with registrar: FlutterPluginRegistrar) { 20 | 21 | let instance = SwiftFlutterBlePeripheralPlugin() 22 | 23 | // Method channel 24 | let methodChannel = FlutterMethodChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state", binaryMessenger: registrar.messenger()) 25 | registrar.addMethodCallDelegate(instance, channel: methodChannel) 26 | 27 | // Event channels 28 | instance.stateChangedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 29 | instance.mtuChangedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 30 | instance.dataReceivedHandler.register(with: registrar, peripheral: instance.flutterBlePeripheralManager) 31 | } 32 | 33 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 34 | switch (call.method) { 35 | case "start": 36 | startPeripheral(call, result) 37 | case "stop": 38 | stopPeripheral(call, result) 39 | case "isAdvertising": 40 | result(flutterBlePeripheralManager.isAdvertising()) 41 | case "isSupported": 42 | isSupported(result) 43 | case "isConnected": 44 | isConnected(call, result) 45 | case "sendData": 46 | sendData(call, result) 47 | default: 48 | result(FlutterMethodNotImplemented) 49 | } 50 | } 51 | 52 | private func startPeripheral(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 53 | let map = call.arguments as? Dictionary 54 | let advertiseData = PeripheralData( 55 | uuid: map?["uuid"] as? String , 56 | localName: map?["localName"] as? String 57 | ) 58 | flutterBlePeripheralManager.start(advertiseData: advertiseData) 59 | result(nil) 60 | } 61 | 62 | private func stopPeripheral(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 63 | flutterBlePeripheralManager.stop() 64 | result(nil) 65 | } 66 | 67 | // We can check if advertising is supported by checking if the ios device supports iBeacons since that uses BLE. 68 | private func isSupported(_ result: @escaping FlutterResult) { 69 | if (CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self)){ 70 | result(true) 71 | } else { 72 | result(false) 73 | } 74 | } 75 | 76 | private func isConnected(_ call: FlutterMethodCall, 77 | _ result: @escaping FlutterResult) { 78 | result(flutterBlePeripheralManager.isConnected()) 79 | } 80 | 81 | private func sendData(_ call: FlutterMethodCall, 82 | _ result: @escaping FlutterResult) { 83 | 84 | if let flutterData = call.arguments as? FlutterStandardTypedData { 85 | flutterBlePeripheralManager.send(data: flutterData.data) 86 | } 87 | result(nil) 88 | } 89 | } 90 | 91 | public class StateChangedHandler: NSObject, FlutterStreamHandler { 92 | 93 | private var eventSink: FlutterEventSink? 94 | 95 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 96 | 97 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state_changed", 98 | binaryMessenger: registrar.messenger()) 99 | eventChannel.setStreamHandler(self) 100 | 101 | peripheral.onStateChanged = { peripheralState in 102 | if let eventSink = self.eventSink { 103 | eventSink(peripheralState.rawValue) 104 | } 105 | } 106 | } 107 | 108 | public func onListen(withArguments arguments: Any?, 109 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 110 | self.eventSink = eventSink 111 | return nil 112 | } 113 | 114 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 115 | eventSink = nil 116 | return nil 117 | } 118 | } 119 | 120 | public class MtuChangedHandler: NSObject, FlutterStreamHandler { 121 | 122 | private var eventSink: FlutterEventSink? 123 | 124 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 125 | 126 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed", 127 | binaryMessenger: registrar.messenger()) 128 | eventChannel.setStreamHandler(self) 129 | 130 | peripheral.onMtuChanged = { mtuSize in 131 | if let eventSink = self.eventSink { 132 | eventSink(mtuSize) 133 | } 134 | } 135 | } 136 | 137 | public func onListen(withArguments arguments: Any?, 138 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 139 | self.eventSink = eventSink 140 | return nil 141 | } 142 | 143 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 144 | eventSink = nil 145 | return nil 146 | } 147 | } 148 | 149 | public class DataReceivedHandler: NSObject, FlutterStreamHandler { 150 | 151 | private var eventSink: FlutterEventSink? 152 | 153 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: FlutterBlePeripheralManager) { 154 | 155 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_data_received", 156 | binaryMessenger: registrar.messenger()) 157 | eventChannel.setStreamHandler(self) 158 | 159 | peripheral.onDataReceived = { data in 160 | if let eventSink = self.eventSink { 161 | eventSink(FlutterStandardTypedData(bytes: data)) 162 | } 163 | } 164 | } 165 | 166 | public func onListen(withArguments arguments: Any?, 167 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 168 | self.eventSink = eventSink 169 | return nil 170 | } 171 | 172 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 173 | eventSink = nil 174 | return nil 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /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 | struct Constants { 11 | static let peripheralStateIdle = 0xF0 12 | static let peripheralStateAdvertising = 0xFA 13 | static let peripheralStateConnected = 0xFB 14 | static let peripheralStateUnsupported = 0xFC 15 | static let peripheralStateUnauthorized = 0xFD 16 | } 17 | 18 | public class SwiftFlutterBlePeripheralPlugin: NSObject, FlutterPlugin { 19 | 20 | private let peripheral = Peripheral() 21 | 22 | private let stateChangedHandler = StateChangedHandler() 23 | private let mtuChangedHandler = MtuChangedHandler() 24 | private let dataReceivedHandler = DataReceivedHandler() 25 | 26 | public static func register(with registrar: FlutterPluginRegistrar) { 27 | 28 | let instance = SwiftFlutterBlePeripheralPlugin() 29 | 30 | // Method channel 31 | let methodChannel = FlutterMethodChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state", binaryMessenger: registrar.messenger) 32 | registrar.addMethodCallDelegate(instance, channel: methodChannel) 33 | 34 | // Event channels 35 | instance.stateChangedHandler.register(with: registrar, peripheral: instance.peripheral) 36 | instance.mtuChangedHandler.register(with: registrar, peripheral: instance.peripheral) 37 | instance.dataReceivedHandler.register(with: registrar, peripheral: instance.peripheral) 38 | } 39 | 40 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 41 | 42 | switch (call.method) { 43 | case "start": 44 | startAdvertising(call, result) 45 | case "stop": 46 | stopAdvertising(call, result) 47 | case "isAdvertising": 48 | isAdvertising(call, result) 49 | case "isSupported": 50 | isSupported(result) 51 | case "isConnected": 52 | isConnected(call, result) 53 | case "sendData": 54 | sendData(call, result) 55 | default: 56 | result(FlutterMethodNotImplemented) 57 | } 58 | } 59 | 60 | private func startAdvertising(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 61 | let map = call.arguments as? Dictionary 62 | let advertiseData = AdvertiseData( 63 | uuid: map?["uuid"] as? String , 64 | localName: map?["localName"] as? String 65 | ) 66 | peripheral.start(advertiseData: advertiseData) 67 | result(nil) 68 | } 69 | 70 | private func stopAdvertising(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 71 | peripheral.stop() 72 | result(nil) 73 | } 74 | 75 | private func isAdvertising(_ call: FlutterMethodCall, 76 | _ result: @escaping FlutterResult) { 77 | result(peripheral.isAdvertising()) 78 | } 79 | 80 | // We can check if advertising is supported by checking if the ios device supports iBeacons since that uses BLE. 81 | private func isSupported(_ result: @escaping FlutterResult) { 82 | if #available(macOS 10.15, *) { 83 | if (CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self)){ 84 | result(true) 85 | } else { 86 | result(false) 87 | } 88 | } else { 89 | // Fallback on earlier versions 90 | result(true) 91 | } 92 | } 93 | 94 | private func isConnected(_ call: FlutterMethodCall, 95 | _ result: @escaping FlutterResult) { 96 | result(peripheral.isConnected()) 97 | } 98 | 99 | private func sendData(_ call: FlutterMethodCall, 100 | _ result: @escaping FlutterResult) { 101 | 102 | if let flutterData = call.arguments as? FlutterStandardTypedData { 103 | peripheral.send(data: flutterData.data) 104 | } 105 | result(nil) 106 | } 107 | } 108 | 109 | public class StateChangedHandler: NSObject, FlutterStreamHandler { 110 | 111 | private var eventSink: FlutterEventSink? 112 | 113 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: Peripheral) { 114 | 115 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_state_changed", 116 | binaryMessenger: registrar.messenger) 117 | eventChannel.setStreamHandler(self) 118 | 119 | peripheral.onStateChanged = { peripheralState in 120 | if let eventSink = self.eventSink { 121 | print("[StateChangedHandler] state: \(peripheralState)") 122 | switch peripheralState { 123 | case .idle: 124 | eventSink(Constants.peripheralStateIdle) 125 | case .unauthorized: 126 | eventSink(Constants.peripheralStateUnauthorized) 127 | case .unsupported: 128 | eventSink(Constants.peripheralStateUnsupported) 129 | case .advertising: 130 | eventSink(Constants.peripheralStateAdvertising) 131 | case .connected: 132 | eventSink(Constants.peripheralStateConnected) 133 | } 134 | } 135 | } 136 | } 137 | 138 | public func onListen(withArguments arguments: Any?, 139 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 140 | self.eventSink = eventSink 141 | return nil 142 | } 143 | 144 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 145 | eventSink = nil 146 | return nil 147 | } 148 | } 149 | 150 | public class MtuChangedHandler: NSObject, FlutterStreamHandler { 151 | 152 | private var eventSink: FlutterEventSink? 153 | 154 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: Peripheral) { 155 | 156 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_mtu_changed", 157 | binaryMessenger: registrar.messenger) 158 | eventChannel.setStreamHandler(self) 159 | 160 | peripheral.onMtuChanged = { mtuSize in 161 | if let eventSink = self.eventSink { 162 | eventSink(mtuSize) 163 | } 164 | } 165 | } 166 | 167 | public func onListen(withArguments arguments: Any?, 168 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 169 | self.eventSink = eventSink 170 | return nil 171 | } 172 | 173 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 174 | eventSink = nil 175 | return nil 176 | } 177 | } 178 | 179 | public class DataReceivedHandler: NSObject, FlutterStreamHandler { 180 | 181 | private var eventSink: FlutterEventSink? 182 | 183 | fileprivate func register(with registrar: FlutterPluginRegistrar, peripheral: Peripheral) { 184 | 185 | let eventChannel = FlutterEventChannel(name: "dev.steenbakker.flutter_ble_peripheral/ble_data_received", 186 | binaryMessenger: registrar.messenger) 187 | eventChannel.setStreamHandler(self) 188 | 189 | peripheral.onDataReceived = { data in 190 | if let eventSink = self.eventSink { 191 | print("[DataReceivedHandler] data: \(data)") 192 | eventSink(FlutterStandardTypedData(bytes: data)) 193 | } 194 | } 195 | } 196 | 197 | public func onListen(withArguments arguments: Any?, 198 | eventSink: @escaping FlutterEventSink) -> FlutterError? { 199 | self.eventSink = eventSink 200 | return nil 201 | } 202 | 203 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 204 | eventSink = nil 205 | return nil 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /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 | lazy var peripheralManager: CBPeripheralManager = CBPeripheralManager(delegate: self, queue: nil) 15 | var peripheralData: NSDictionary! 16 | 17 | var state: PeripheralState = .idle { 18 | didSet { 19 | onStateChanged?(state) 20 | } 21 | } 22 | 23 | // min MTU before iOS 10 24 | var mtu: Int = 158 { 25 | didSet { 26 | onMtuChanged?(mtu) 27 | } 28 | } 29 | 30 | var onStateChanged: ((PeripheralState) -> Void)? 31 | var onMtuChanged: ((Int) -> Void)? 32 | var onDataReceived: ((Data) -> Void)? 33 | 34 | var dataToBeAdvertised: [String: Any]! 35 | 36 | var txCharacteristic: CBMutableCharacteristic? 37 | var txSubscribed = false { 38 | didSet { 39 | if txSubscribed { 40 | state = .connected 41 | } else if isAdvertising() { 42 | state = .advertising 43 | } 44 | } 45 | } 46 | var rxCharacteristic: CBMutableCharacteristic? 47 | 48 | var txSubscriptions = Set() 49 | 50 | func start(advertiseData: PeripheralData) { 51 | 52 | dataToBeAdvertised = [:] 53 | if (advertiseData.uuid != nil) { 54 | dataToBeAdvertised[CBAdvertisementDataServiceUUIDsKey] = [CBUUID(string: advertiseData.uuid!)] 55 | } 56 | 57 | if (advertiseData.localName != nil) { 58 | dataToBeAdvertised[CBAdvertisementDataLocalNameKey] = [advertiseData.localName] 59 | } 60 | 61 | // peripheralManager.startAdvertising(dataToBeAdvertised) 62 | 63 | // TODO: Add service to advertise 64 | if peripheralManager.state == .poweredOn { 65 | addService() 66 | } 67 | } 68 | 69 | func stop() { 70 | peripheralManager.stopAdvertising() 71 | state = .idle 72 | } 73 | 74 | func isAdvertising() -> Bool { 75 | return peripheralManager.isAdvertising 76 | } 77 | 78 | func isConnected() -> Bool { 79 | return state == .connected 80 | } 81 | 82 | // TODO: Add service to advertise 83 | private func addService() { 84 | // Add service and characteristics if needed 85 | if txCharacteristic == nil || rxCharacteristic == nil { 86 | 87 | let mutableTxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.txCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 88 | let mutableRxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: PeripheralData.rxCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 89 | 90 | let service = CBMutableService(type: CBUUID(string: PeripheralData.serviceUUID), primary: true) 91 | service.characteristics = [mutableTxCharacteristic, mutableRxCharacteristic]; 92 | 93 | peripheralManager.add(service) 94 | 95 | self.txCharacteristic = mutableTxCharacteristic 96 | self.rxCharacteristic = mutableRxCharacteristic 97 | } 98 | 99 | peripheralManager.startAdvertising(dataToBeAdvertised) 100 | } 101 | 102 | func send(data: Data) { 103 | 104 | print("[flutter_ble_peripheral] Send data: \(data)") 105 | 106 | guard let characteristic = txCharacteristic else { 107 | return 108 | } 109 | 110 | peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: nil) 111 | } 112 | } 113 | 114 | extension FlutterBlePeripheralManager: CBPeripheralManagerDelegate { 115 | 116 | func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 117 | switch peripheral.state { 118 | case .poweredOn: 119 | addService() // TODO: add service 120 | state = .idle 121 | case .poweredOff: 122 | state = .poweredOff 123 | case .resetting: 124 | state = .idle 125 | case .unsupported: 126 | state = .unsupported 127 | case .unauthorized: 128 | state = .unauthorized 129 | case .unknown: 130 | state = .unknown 131 | @unknown default: 132 | state = .unknown 133 | } 134 | } 135 | 136 | func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) { 137 | print("[flutter_ble_peripheral] didStartAdvertising:", error ?? "success") 138 | 139 | guard error == nil else { 140 | return 141 | } 142 | 143 | state = .advertising 144 | 145 | // Immediately set to connected if the tx Characteristic is already subscribed 146 | if txSubscribed { 147 | state = .connected 148 | } 149 | } 150 | 151 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 152 | print("[flutter_ble_peripheral] didReceiveRead:", request) 153 | 154 | // Only answer to requests if not idle 155 | guard state != .idle else { 156 | print("[flutter_ble_peripheral] state = .idle -> not answering read request") 157 | return 158 | } 159 | 160 | // Not supported 161 | peripheralManager.respond(to: request, withResult: .requestNotSupported) 162 | } 163 | 164 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { 165 | print("[flutter_ble_peripheral] didReceiveWrite:", requests) 166 | 167 | // Only answer to requests if not idle 168 | guard state != .idle else { 169 | print("[flutter_ble_peripheral] state = .idle -> not answering write request") 170 | return 171 | } 172 | 173 | for request in requests { 174 | 175 | print("[flutter_ble_peripheral] write request:", request); 176 | 177 | let characteristic = request.characteristic 178 | guard let data = request.value else { 179 | print("[flutter_ble_peripheral] request.value is nil"); 180 | return 181 | } 182 | 183 | // Write only supported in rxCharacteristic 184 | guard characteristic == self.rxCharacteristic else { 185 | peripheralManager.respond(to: request, withResult: .requestNotSupported) 186 | print("[flutter_ble_peripheral] respond requestNotSupported (only supported in rxCharacteristic)") 187 | return 188 | } 189 | 190 | print("[flutter_ble_peripheral] request.value:", request.value!) 191 | print("[flutter_ble_peripheral] characteristic.value:", characteristic.value!) 192 | 193 | if data.count > 0 { 194 | print("[flutter_ble_peripheral] Receive data: \(data)") 195 | onDataReceived?(data) 196 | } 197 | 198 | // Respond with success 199 | peripheralManager.respond(to: request, withResult: .success) 200 | } 201 | } 202 | 203 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { 204 | 205 | if characteristic == txCharacteristic { 206 | 207 | print("[flutter_ble_peripheral] didSubscribeTo:", central, characteristic) 208 | 209 | // Update MTU 210 | self.mtu = central.maximumUpdateValueLength; 211 | 212 | // Add to subscriptions 213 | txSubscriptions.insert(central.identifier) 214 | 215 | txSubscribed = !txSubscriptions.isEmpty 216 | 217 | print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 218 | } 219 | } 220 | 221 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { 222 | 223 | if characteristic == txCharacteristic { 224 | 225 | print("[flutter_ble_peripheral] didUnsubscribeFrom:", central, characteristic) 226 | 227 | // Remove from txSubscriptions 228 | txSubscriptions.remove(central.identifier) 229 | 230 | txSubscribed = !txSubscriptions.isEmpty 231 | 232 | print("[flutter_ble_peripheral] txSubscriptions:", txSubscriptions) 233 | } 234 | } 235 | } 236 | 237 | 238 | -------------------------------------------------------------------------------- /macos/Classes/Peripheral.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 | 11 | enum PeripheralState { 12 | case idle, unauthorized, unsupported, advertising, connected 13 | } 14 | 15 | class Peripheral : NSObject { 16 | 17 | lazy var peripheralManager: CBPeripheralManager = CBPeripheralManager(delegate: self, queue: nil) 18 | var peripheralData: NSDictionary! 19 | 20 | var state: PeripheralState = .idle { 21 | didSet { 22 | onStateChanged?(state) 23 | } 24 | } 25 | 26 | // min MTU before iOS 10 27 | var mtu: Int = 158 { 28 | didSet { 29 | print("[BLE] mtu:", mtu); 30 | onMtuChanged?(mtu) 31 | } 32 | } 33 | 34 | var onStateChanged: ((PeripheralState) -> Void)? 35 | var onMtuChanged: ((Int) -> Void)? 36 | var onDataReceived: ((Data) -> Void)? 37 | 38 | var dataToBeAdvertised: [String: Any]! 39 | 40 | var shouldStartAdvertising = false 41 | 42 | var txCharacteristic: CBMutableCharacteristic? 43 | var txSubscribed = false { 44 | didSet { 45 | print("[BLE Peripheral] txSubscribed = ", txSubscribed) 46 | if txSubscribed { 47 | state = .connected 48 | } else if isAdvertising() { 49 | state = .advertising 50 | } 51 | } 52 | } 53 | var rxCharacteristic: CBMutableCharacteristic? 54 | 55 | var txSubscriptions = Set() 56 | 57 | func start(advertiseData: AdvertiseData) { 58 | 59 | print("[BLE Peripheral] Start advertising") 60 | 61 | dataToBeAdvertised = [:] 62 | if (advertiseData.uuid != nil) { 63 | dataToBeAdvertised[CBAdvertisementDataServiceUUIDsKey] = [CBUUID(string: advertiseData.uuid!)] 64 | } 65 | 66 | if (advertiseData.localName != nil) { 67 | dataToBeAdvertised[CBAdvertisementDataLocalNameKey] = [advertiseData.localName] 68 | } 69 | 70 | shouldStartAdvertising = true 71 | 72 | if peripheralManager.state == .poweredOn { 73 | addService() 74 | } 75 | } 76 | 77 | func stop() { 78 | 79 | print("[BLE Peripheral] Stop advertising") 80 | 81 | shouldStartAdvertising = false 82 | 83 | peripheralManager.stopAdvertising() 84 | state = .idle 85 | } 86 | 87 | func isAdvertising() -> Bool { 88 | return peripheralManager.isAdvertising 89 | } 90 | 91 | func isConnected() -> Bool { 92 | return state == .connected 93 | } 94 | 95 | private func addService() { 96 | 97 | guard shouldStartAdvertising else { 98 | return 99 | } 100 | 101 | // Add service and characteristics if needed 102 | if txCharacteristic == nil || rxCharacteristic == nil { 103 | 104 | let mutableTxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: AdvertiseData.txCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 105 | let mutableRxCharacteristic = CBMutableCharacteristic(type: CBUUID(string: AdvertiseData.rxCharacteristicUUID), properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable]) 106 | 107 | let service = CBMutableService(type: CBUUID(string: AdvertiseData.serviceUUID), primary: true) 108 | service.characteristics = [mutableTxCharacteristic, mutableRxCharacteristic]; 109 | 110 | peripheralManager.add(service) 111 | 112 | self.txCharacteristic = mutableTxCharacteristic 113 | self.rxCharacteristic = mutableRxCharacteristic 114 | } 115 | 116 | peripheralManager.startAdvertising(dataToBeAdvertised) 117 | 118 | shouldStartAdvertising = false; 119 | } 120 | 121 | func send(data: Data) { 122 | 123 | print("[BLE Peripheral] Send data: \(data)") 124 | 125 | guard let characteristic = txCharacteristic else { 126 | return 127 | } 128 | 129 | peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: nil) 130 | } 131 | } 132 | 133 | extension Peripheral: CBPeripheralManagerDelegate { 134 | 135 | func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 136 | switch peripheral.state { 137 | case .poweredOn: 138 | print("[BLE Peripheral] poweredOn") 139 | addService() 140 | case .poweredOff: 141 | print("[BLE Peripheral] poweredOff") 142 | state = .idle 143 | case .resetting: 144 | print("[BLE Peripheral] resetting") 145 | case .unsupported: 146 | print("[BLE Peripheral] unsupported") 147 | state = .unsupported 148 | case .unauthorized: 149 | print("[BLE Peripheral] unauthorized") 150 | state = .unauthorized 151 | case .unknown: 152 | print("[BLE Peripheral] unknown") 153 | state = .idle 154 | } 155 | } 156 | 157 | func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) { 158 | print("[BLE Peripheral] didStartAdvertising:", error ?? "success") 159 | 160 | guard error == nil else { 161 | return 162 | } 163 | 164 | state = .advertising 165 | 166 | // Immediately set to connected if the tx Characteristic is already subscribed 167 | if txSubscribed { 168 | state = .connected 169 | } 170 | } 171 | 172 | func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) { 173 | print("[BLE Peripheral] didAdd:", service, error ?? "success") 174 | } 175 | 176 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 177 | print("[BLE Peripheral] didReceiveRead:", request) 178 | 179 | // Only answer to requests if not idle 180 | guard state != .idle else { 181 | print("[BLE Peripheral] state = .idle -> not answering read request") 182 | return 183 | } 184 | 185 | // Not supported 186 | peripheralManager.respond(to: request, withResult: .requestNotSupported) 187 | } 188 | 189 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { 190 | print("[BLE Peripheral] didReceiveWrite:", requests) 191 | 192 | // Only answer to requests if not idle 193 | guard state != .idle else { 194 | print("[BLE Peripheral] state = .idle -> not answering write request") 195 | return 196 | } 197 | 198 | for request in requests { 199 | 200 | print("[BLE Peripheral] write request:", request); 201 | 202 | let characteristic = request.characteristic 203 | guard let data = request.value else { 204 | print("[BLE Peripheral] request.value is nil"); 205 | return 206 | } 207 | 208 | // Write only supported in rxCharacteristic 209 | guard characteristic == self.rxCharacteristic else { 210 | peripheralManager.respond(to: request, withResult: .requestNotSupported) 211 | print("[BLE Peripheral] respond requestNotSupported (only supported in rxCharacteristic)") 212 | return 213 | } 214 | 215 | print("[BLE Peripheral] request.value:", request.value) 216 | print("[BLE Peripheral] characteristic.value:", characteristic.value) 217 | 218 | if data.count > 0 { 219 | print("[BLE Peripheral] Receive data: \(data)") 220 | onDataReceived?(data) 221 | } 222 | 223 | // Respond with success 224 | peripheralManager.respond(to: request, withResult: .success) 225 | } 226 | } 227 | 228 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { 229 | 230 | if characteristic == txCharacteristic { 231 | 232 | print("[BLE Peripheral] didSubscribeTo:", central, characteristic) 233 | 234 | // Update MTU 235 | self.mtu = central.maximumUpdateValueLength; 236 | 237 | // Add to subscriptions 238 | if #available(macOS 10.13, *) { 239 | txSubscriptions.insert(central.identifier) 240 | } else { 241 | // Fallback on earlier versions 242 | } 243 | 244 | txSubscribed = !txSubscriptions.isEmpty 245 | 246 | print("[BLE Peripheral] txSubscriptions:", txSubscriptions) 247 | } 248 | } 249 | 250 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { 251 | 252 | if characteristic == txCharacteristic { 253 | 254 | print("[BLE Peripheral] didUnsubscribeFrom:", central, characteristic) 255 | 256 | // Remove from txSubscriptions 257 | if #available(macOS 10.13, *) { 258 | txSubscriptions.remove(central.identifier) 259 | } else { 260 | // Fallback on earlier versions 261 | } 262 | 263 | txSubscribed = !txSubscriptions.isEmpty 264 | 265 | print("[BLE Peripheral] txSubscriptions:", txSubscriptions) 266 | } 267 | } 268 | } 269 | 270 | class AdvertiseData { 271 | var uuid: String? 272 | var localName: String? //CBAdvertisementDataLocalNameKey 273 | 274 | static let serviceUUID: String = "8ebdb2f3-7817-45c9-95c5-c5e9031aaa47" 275 | static let txCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D4" 276 | static let rxCharacteristicUUID: String = "08590F7E-DB05-467E-8757-72F6FAEB13D5" 277 | 278 | init(uuid: String?, localName: String?) { 279 | self.uuid = Self.serviceUUID //uuid; 280 | self.localName = localName 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/FlutterBlePeripheralPlugin.kt: -------------------------------------------------------------------------------- 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 | package dev.steenbakker.flutter_ble_peripheral 8 | 9 | import android.Manifest 10 | import android.content.Context 11 | import android.content.pm.PackageManager 12 | import android.os.Build 13 | import android.os.Handler 14 | import android.os.Looper 15 | import androidx.annotation.NonNull 16 | import androidx.annotation.RequiresApi 17 | import androidx.core.content.ContextCompat 18 | import dev.steenbakker.flutter_ble_peripheral.exceptions.PeripheralException 19 | import dev.steenbakker.flutter_ble_peripheral.exceptions.PermissionNotFoundException 20 | import dev.steenbakker.flutter_ble_peripheral.handlers.DataReceivedHandler 21 | import dev.steenbakker.flutter_ble_peripheral.handlers.MtuChangedHandler 22 | import dev.steenbakker.flutter_ble_peripheral.handlers.StateChangedHandler 23 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralData 24 | import io.flutter.Log 25 | import io.flutter.embedding.engine.plugins.FlutterPlugin 26 | import io.flutter.plugin.common.MethodCall 27 | import io.flutter.plugin.common.MethodChannel 28 | 29 | class FlutterBlePeripheralPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { 30 | 31 | private var methodChannel: MethodChannel? = null 32 | private var context: Context? = null 33 | private val tag: String = "flutter_ble_peripheral" 34 | private var flutterBlePeripheralManager: FlutterBlePeripheralManager = 35 | FlutterBlePeripheralManager() 36 | 37 | private val mtuChangedHandler = MtuChangedHandler() 38 | private val stateChangedHandler = StateChangedHandler() 39 | private val dataReceivedHandler = DataReceivedHandler() 40 | 41 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 42 | methodChannel = MethodChannel( 43 | flutterPluginBinding.binaryMessenger, 44 | "dev.steenbakker.flutter_ble_peripheral/ble_state" 45 | ) 46 | methodChannel?.setMethodCallHandler(this) 47 | 48 | context = flutterPluginBinding.applicationContext 49 | 50 | try { 51 | flutterBlePeripheralManager.init(flutterPluginBinding.applicationContext) 52 | } catch (e: PeripheralException) { 53 | flutterBlePeripheralManager.handlePeripheralException(e, null) 54 | return 55 | } 56 | 57 | mtuChangedHandler.register(flutterPluginBinding, flutterBlePeripheralManager) 58 | stateChangedHandler.register(flutterPluginBinding, flutterBlePeripheralManager) 59 | dataReceivedHandler.register(flutterPluginBinding, flutterBlePeripheralManager) 60 | } 61 | 62 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 63 | methodChannel?.setMethodCallHandler(null) 64 | methodChannel = null 65 | } 66 | 67 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { 68 | when (call.method) { 69 | "start" -> startPeripheral(call, result) 70 | "stop" -> stopPeripheral(result) 71 | "isAdvertising" -> Handler(Looper.getMainLooper()).post { 72 | result.success(flutterBlePeripheralManager.isAdvertising()) 73 | } 74 | "isSupported" -> isSupported(result) 75 | "isConnected" -> isConnected(result) 76 | "sendData" -> sendData(call, result) 77 | else -> Handler(Looper.getMainLooper()).post { 78 | result.notImplemented() 79 | } 80 | } 81 | } 82 | 83 | @Suppress("UNCHECKED_CAST") 84 | private fun startPeripheral(call: MethodCall, result: MethodChannel.Result) { 85 | try { 86 | hasPermissions() 87 | } catch (e: Exception) { 88 | result.error( 89 | "No Permission", 90 | "No permission for ${e.message} Please ask runtime permission.", 91 | "Manifest.permission.${e.message}" 92 | ) 93 | return 94 | } 95 | 96 | if (call.arguments !is Map<*, *>) { 97 | throw IllegalArgumentException("Arguments are not a map! " + call.arguments) 98 | } 99 | 100 | val arguments = call.arguments as Map 101 | val advertiseData = PeripheralData() 102 | (arguments["uuid"] as String?)?.let { advertiseData.uuid = it } 103 | (arguments["manufacturerId"] as Int?)?.let { advertiseData.manufacturerId = it } 104 | (arguments["manufacturerData"] as List?)?.let { advertiseData.manufacturerData = it } 105 | (arguments["serviceDataUuid"] as String?)?.let { advertiseData.serviceDataUuid = it } 106 | (arguments["serviceData"] as List?)?.let { advertiseData.serviceData = it } 107 | (arguments["includeDeviceName"] as Boolean?)?.let { advertiseData.includeDeviceName = it } 108 | (arguments["transmissionPowerIncluded"] as Boolean?)?.let { 109 | advertiseData.includeTxPowerLevel = it 110 | } 111 | (arguments["advertiseMode"] as Int?)?.let { advertiseData.advertiseMode = it } 112 | (arguments["connectable"] as Boolean?)?.let { advertiseData.connectable = it } 113 | (arguments["timeout"] as Int?)?.let { advertiseData.timeout = it } 114 | (arguments["txPowerLevel"] as Int?)?.let { advertiseData.txPowerLevel = it } 115 | 116 | flutterBlePeripheralManager.start(advertiseData, result) 117 | 118 | Handler(Looper.getMainLooper()).post { 119 | Log.i(tag, "Start advertise: $advertiseData") 120 | result.success(null) 121 | } 122 | } 123 | 124 | private fun stopPeripheral(result: MethodChannel.Result) { 125 | try { 126 | hasPermissions() 127 | } catch (e: Exception) { 128 | result.error( 129 | "No Permission", 130 | "No permission for ${e.message} Please ask runtime permission.", 131 | "Manifest.permission.${e.message}" 132 | ) 133 | return 134 | } 135 | 136 | flutterBlePeripheralManager.stop(result) 137 | 138 | Handler(Looper.getMainLooper()).post { 139 | Log.i(tag, "Stop advertise") 140 | result.success(null) 141 | } 142 | } 143 | 144 | private fun isSupported(result: MethodChannel.Result) { 145 | if (context != null) { 146 | val pm: PackageManager = context!!.packageManager 147 | val isSupported = pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) 148 | 149 | Handler(Looper.getMainLooper()).post { 150 | result.success(isSupported) 151 | } 152 | 153 | } else { 154 | Handler(Looper.getMainLooper()).post { 155 | result.error("isSupported", "No context available", null) 156 | } 157 | } 158 | } 159 | 160 | private fun isConnected(result: MethodChannel.Result) { 161 | val isConnected = flutterBlePeripheralManager.isConnected() 162 | 163 | Handler(Looper.getMainLooper()).post { 164 | Log.i(tag, "Is BLE connected: $isConnected") 165 | result.success(isConnected) 166 | } 167 | } 168 | 169 | private fun sendData(call: MethodCall, result: MethodChannel.Result) { 170 | Log.i(tag, "Try send data: ${call.arguments}") 171 | 172 | (call.arguments as? ByteArray)?.let { data -> 173 | flutterBlePeripheralManager.send(data) 174 | Log.i(tag, "Send data: $data") 175 | Handler(Looper.getMainLooper()).post { result.success(null) } 176 | } ?: Handler(Looper.getMainLooper()).post { 177 | Log.i(tag, "Send data error") 178 | result.error("122", "send data", null) 179 | } 180 | } 181 | 182 | private fun hasPermissions(): Boolean { 183 | // Required for API > 31 184 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 185 | if (!hasBluetoothAdvertisePermission()) { 186 | throw PermissionNotFoundException("BLUETOOTH_ADVERTISE") 187 | } 188 | if (!hasBluetoothConnectPermission()) { 189 | throw PermissionNotFoundException("BLUETOOTH_CONNECT") 190 | } 191 | if (!hasBluetoothScanPermission()) { 192 | throw PermissionNotFoundException("BLUETOOTH_SCAN") 193 | } 194 | 195 | // Required for API > 28 196 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 197 | if (!hasLocationFinePermission()) { 198 | throw PermissionNotFoundException("ACCESS_FINE_LOCATION") 199 | } 200 | 201 | // Required for API < 28 202 | } else { 203 | if (!hasLocationCoarsePermission()) { 204 | throw PermissionNotFoundException("ACCESS_COARSE_LOCATION") 205 | } 206 | } 207 | return true 208 | 209 | } 210 | 211 | // Permissions for Bluetooth API > 31 212 | @RequiresApi(Build.VERSION_CODES.S) 213 | private fun hasBluetoothAdvertisePermission(): Boolean { 214 | return (ContextCompat.checkSelfPermission( 215 | context!!, 216 | Manifest.permission.BLUETOOTH_ADVERTISE 217 | ) 218 | == PackageManager.PERMISSION_GRANTED) 219 | } 220 | 221 | 222 | @RequiresApi(Build.VERSION_CODES.S) 223 | private fun hasBluetoothConnectPermission(): Boolean { 224 | return (ContextCompat.checkSelfPermission(context!!, Manifest.permission.BLUETOOTH_CONNECT) 225 | == PackageManager.PERMISSION_GRANTED) 226 | } 227 | 228 | @RequiresApi(Build.VERSION_CODES.S) 229 | private fun hasBluetoothScanPermission(): Boolean { 230 | return (ContextCompat.checkSelfPermission(context!!, Manifest.permission.BLUETOOTH_SCAN) 231 | == PackageManager.PERMISSION_GRANTED) 232 | } 233 | 234 | private fun hasLocationFinePermission(): Boolean { 235 | return (ContextCompat.checkSelfPermission( 236 | context!!, 237 | Manifest.permission.ACCESS_FINE_LOCATION 238 | ) 239 | == PackageManager.PERMISSION_GRANTED) 240 | } 241 | 242 | private fun hasLocationCoarsePermission(): Boolean { 243 | return (ContextCompat.checkSelfPermission( 244 | context!!, 245 | Manifest.permission.ACCESS_COARSE_LOCATION 246 | ) 247 | == PackageManager.PERMISSION_GRANTED) 248 | } 249 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/dev/steenbakker/flutter_ble_peripheral/FlutterBlePeripheralManager.kt: -------------------------------------------------------------------------------- 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 | package dev.steenbakker.flutter_ble_peripheral 8 | 9 | import android.bluetooth.* 10 | import android.bluetooth.le.AdvertiseCallback 11 | import android.bluetooth.le.AdvertiseData 12 | import android.bluetooth.le.AdvertiseSettings 13 | import android.bluetooth.le.BluetoothLeAdvertiser 14 | import android.content.Context 15 | import android.os.ParcelUuid 16 | import dev.steenbakker.flutter_ble_peripheral.exceptions.PeripheralException 17 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralData 18 | import dev.steenbakker.flutter_ble_peripheral.models.PeripheralState 19 | import io.flutter.Log 20 | import io.flutter.plugin.common.MethodChannel 21 | import java.util.* 22 | 23 | 24 | class FlutterBlePeripheralManager { 25 | private val tag: String = "BLE Peripheral" 26 | private lateinit var mBluetoothManager: BluetoothManager 27 | private lateinit var mBluetoothGattServer: BluetoothGattServer 28 | private var mBluetoothLeAdvertiser: BluetoothLeAdvertiser? = null 29 | private var mBluetoothGatt: BluetoothGatt? = null 30 | private var mBluetoothDevice: BluetoothDevice? = null 31 | 32 | private lateinit var context: Context 33 | 34 | private var isAdvertising = false 35 | 36 | var onMtuChanged: ((Int) -> Unit)? = null 37 | 38 | private var state = PeripheralState.idle 39 | var onStateChanged: ((PeripheralState) -> Unit)? = null 40 | private fun updateState(newState: PeripheralState) { 41 | state = newState 42 | onStateChanged?.invoke(newState) 43 | } 44 | 45 | var onDataReceived: ((ByteArray) -> Unit)? = null 46 | 47 | private var txCharacteristic: BluetoothGattCharacteristic? = null 48 | private var rxCharacteristic: BluetoothGattCharacteristic? = null 49 | 50 | fun handlePeripheralException(e: PeripheralException, result: MethodChannel.Result?) { 51 | if (e.message == "Not Supported") { 52 | updateState(PeripheralState.unsupported) 53 | Log.e(tag, "This device does not support bluetooth LE") 54 | result?.error("Not Supported", "This device does not support bluetooth LE", null) 55 | } else { 56 | updateState(PeripheralState.unsupported) 57 | Log.e(tag, "Bluetooth may be turned off or is not supported") 58 | result?.error("Not powered", "Bluetooth may be turned off or is not supported", null) 59 | } 60 | } 61 | 62 | private val mAdvertiseCallback = object : AdvertiseCallback() { 63 | override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { 64 | super.onStartSuccess(settingsInEffect) 65 | isAdvertising = true 66 | updateState(PeripheralState.advertising) 67 | } 68 | 69 | override fun onStartFailure(errorCode: Int) { 70 | super.onStartFailure(errorCode) 71 | val statusText: String 72 | when (errorCode) { 73 | ADVERTISE_FAILED_ALREADY_STARTED -> { 74 | statusText = "ADVERTISE_FAILED_ALREADY_STARTED" 75 | isAdvertising = true 76 | updateState(PeripheralState.advertising) 77 | Log.i(tag, "BLE Advertise $statusText") 78 | } 79 | ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> { 80 | statusText = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED" 81 | isAdvertising = false 82 | updateState(PeripheralState.unsupported) 83 | Log.i(tag, "BLE Advertise $statusText") 84 | } 85 | ADVERTISE_FAILED_INTERNAL_ERROR -> { 86 | statusText = "ADVERTISE_FAILED_INTERNAL_ERROR" 87 | isAdvertising = false 88 | updateState(PeripheralState.idle) 89 | Log.i(tag, "BLE Advertise $statusText") 90 | } 91 | ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> { 92 | statusText = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS" 93 | isAdvertising = false 94 | updateState(PeripheralState.unauthorized) 95 | Log.i(tag, "BLE Advertise $statusText") 96 | } 97 | ADVERTISE_FAILED_DATA_TOO_LARGE -> { 98 | statusText = "ADVERTISE_FAILED_DATA_TOO_LARGE" 99 | isAdvertising = false 100 | updateState(PeripheralState.unsupported) 101 | Log.i(tag, "BLE Advertise $statusText") 102 | } 103 | else -> { 104 | statusText = "UNDOCUMENTED" 105 | updateState(PeripheralState.idle) 106 | Log.i(tag, "BLE Advertise $statusText") 107 | } 108 | } 109 | 110 | Log.e(tag, "ERROR while starting advertising: $errorCode - $statusText") 111 | isAdvertising = false 112 | } 113 | } 114 | 115 | fun init(context: Context) { 116 | this.context = context 117 | 118 | val bluetoothManager: BluetoothManager? = 119 | context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager 120 | 121 | if (bluetoothManager == null) { 122 | throw PeripheralException("Not Supported") 123 | } else { 124 | val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter 125 | mBluetoothManager = bluetoothManager 126 | if (bluetoothAdapter.bluetoothLeAdvertiser == null) { 127 | throw PeripheralException("Not powered") 128 | } else { 129 | mBluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser 130 | } 131 | } 132 | } 133 | 134 | fun start(peripheralData: PeripheralData, result: MethodChannel.Result) { 135 | try { 136 | init(context) 137 | } catch (e: PeripheralException) { 138 | handlePeripheralException(e, result) 139 | return 140 | } 141 | 142 | val advertiseSettings = AdvertiseSettings.Builder() 143 | .setAdvertiseMode(peripheralData.advertiseMode) 144 | .setConnectable(peripheralData.connectable) 145 | .setTimeout(peripheralData.timeout) 146 | .setTxPowerLevel(peripheralData.txPowerLevel) 147 | .build() 148 | 149 | val advertiseData = AdvertiseData.Builder() 150 | .setIncludeDeviceName(peripheralData.includeDeviceName) 151 | .setIncludeTxPowerLevel(peripheralData.includeTxPowerLevel) 152 | .addServiceUuid(ParcelUuid(UUID.fromString(peripheralData.uuid))) 153 | .build() 154 | 155 | mBluetoothLeAdvertiser!!.startAdvertising( 156 | advertiseSettings, 157 | advertiseData, 158 | mAdvertiseCallback 159 | ) 160 | 161 | // TODO: Add service to advertise 162 | addService(peripheralData) 163 | } 164 | 165 | fun isAdvertising(): Boolean { 166 | return isAdvertising 167 | } 168 | 169 | fun isConnected(): Boolean { 170 | return state == PeripheralState.connected 171 | } 172 | 173 | fun stop(result: MethodChannel.Result) { 174 | try { 175 | init(context) 176 | } catch (e: PeripheralException) { 177 | handlePeripheralException(e, result) 178 | return 179 | } 180 | 181 | mBluetoothLeAdvertiser!!.stopAdvertising(mAdvertiseCallback) 182 | isAdvertising = false 183 | 184 | updateState(PeripheralState.idle) 185 | } 186 | 187 | // TODO: Add service to advertise 188 | private fun addService(peripheralData: PeripheralData) { 189 | txCharacteristic = BluetoothGattCharacteristic( 190 | UUID.fromString(peripheralData.txCharacteristicUUID), 191 | BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_NOTIFY, 192 | BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE, 193 | ) 194 | 195 | rxCharacteristic = BluetoothGattCharacteristic( 196 | UUID.fromString(peripheralData.rxCharacteristicUUID), 197 | BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_NOTIFY, 198 | BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE, 199 | ) 200 | 201 | val service = BluetoothGattService( 202 | UUID.fromString(peripheralData.serviceDataUuid), 203 | BluetoothGattService.SERVICE_TYPE_PRIMARY, 204 | ) 205 | 206 | val gattCallback = object : BluetoothGattCallback() { 207 | override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { 208 | onMtuChanged?.invoke(mtu) 209 | } 210 | } 211 | 212 | val serverCallback = object : BluetoothGattServerCallback() { 213 | override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) { 214 | onMtuChanged?.invoke(mtu) 215 | } 216 | 217 | override fun onConnectionStateChange( 218 | device: BluetoothDevice?, 219 | status: Int, 220 | newState: Int 221 | ) { 222 | when (status) { 223 | BluetoothProfile.STATE_CONNECTED -> { 224 | mBluetoothDevice = device 225 | mBluetoothGatt = mBluetoothDevice?.connectGatt(context, true, gattCallback) 226 | updateState(PeripheralState.connected) 227 | Log.i(tag, "Device connected $device") 228 | } 229 | 230 | BluetoothProfile.STATE_DISCONNECTED -> { 231 | updateState(PeripheralState.idle) 232 | Log.i(tag, "Device disconnect $device") 233 | } 234 | } 235 | } 236 | 237 | override fun onCharacteristicReadRequest( 238 | device: BluetoothDevice, 239 | requestId: Int, 240 | offset: Int, 241 | characteristic: BluetoothGattCharacteristic 242 | ) { 243 | Log.i(tag, "BLE Read Request") 244 | 245 | val status = when (characteristic.uuid) { 246 | rxCharacteristic?.uuid -> BluetoothGatt.GATT_SUCCESS 247 | else -> BluetoothGatt.GATT_FAILURE 248 | } 249 | 250 | mBluetoothGattServer.sendResponse(device, requestId, status, 0, null) 251 | } 252 | 253 | override fun onCharacteristicWriteRequest( 254 | device: BluetoothDevice, 255 | requestId: Int, 256 | characteristic: BluetoothGattCharacteristic, 257 | preparedWrite: Boolean, 258 | responseNeeded: Boolean, 259 | offset: Int, 260 | value: ByteArray? 261 | ) { 262 | Log.i(tag, "BLE Write Request") 263 | 264 | val isValid = value?.isNotEmpty() == true && characteristic == rxCharacteristic 265 | 266 | Log.i(tag, "BLE Write Request - Is valid? $isValid") 267 | 268 | if (isValid) { 269 | mBluetoothDevice = device 270 | mBluetoothGatt = mBluetoothDevice?.connectGatt(context, true, gattCallback) 271 | updateState(PeripheralState.connected) 272 | 273 | onDataReceived?.invoke(value!!) 274 | Log.i(tag, "BLE Received Data $peripheralData") 275 | } 276 | 277 | if (responseNeeded) { 278 | Log.i(tag, "BLE Write Request - Response") 279 | mBluetoothGattServer.sendResponse( 280 | device, 281 | requestId, 282 | BluetoothGatt.GATT_SUCCESS, 283 | 0, 284 | null 285 | ) 286 | } 287 | } 288 | } 289 | 290 | service.addCharacteristic(txCharacteristic) 291 | service.addCharacteristic(rxCharacteristic) 292 | 293 | mBluetoothGattServer = mBluetoothManager 294 | .openGattServer(context, serverCallback) 295 | .also { it.addService(service) } 296 | } 297 | 298 | fun send(data: ByteArray) { 299 | txCharacteristic?.let { char -> 300 | char.value = data 301 | mBluetoothGatt?.writeCharacteristic(char) 302 | mBluetoothGattServer.notifyCharacteristicChanged(mBluetoothDevice, char, false) 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /example/macos/Runner/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 066A89293CC45EBD258CC7EE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33C423938E65B7E83742EA06 /* Pods_Runner.framework */; }; 11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 33C423938E65B7E83742EA06 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 351D344A9DEFF23A0C4AA22F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 37 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | AAE3047012183DE4E4C03DE3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 49 | F65ABEC9647285A6E50BD3C7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 066A89293CC45EBD258CC7EE /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 423A63B4AE6C606BAE2CBDAB /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | F65ABEC9647285A6E50BD3C7 /* Pods-Runner.debug.xcconfig */, 68 | AAE3047012183DE4E4C03DE3 /* Pods-Runner.release.xcconfig */, 69 | 351D344A9DEFF23A0C4AA22F /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | path = Pods; 72 | sourceTree = ""; 73 | }; 74 | 9740EEB11CF90186004384FC /* Flutter */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 78 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 79 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 80 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 81 | ); 82 | name = Flutter; 83 | sourceTree = ""; 84 | }; 85 | 97C146E51CF9000F007C117D = { 86 | isa = PBXGroup; 87 | children = ( 88 | 9740EEB11CF90186004384FC /* Flutter */, 89 | 97C146F01CF9000F007C117D /* Runner */, 90 | 97C146EF1CF9000F007C117D /* Products */, 91 | 423A63B4AE6C606BAE2CBDAB /* Pods */, 92 | F2FE5A8AE1A77E475283E9FE /* Frameworks */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 97C146EF1CF9000F007C117D /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 97C146EE1CF9000F007C117D /* Runner.app */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 97C146F01CF9000F007C117D /* Runner */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 108 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 109 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 110 | 97C147021CF9000F007C117D /* Info.plist */, 111 | 97C146F11CF9000F007C117D /* Supporting Files */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 115 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 116 | ); 117 | path = Runner; 118 | sourceTree = ""; 119 | }; 120 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | ); 124 | name = "Supporting Files"; 125 | sourceTree = ""; 126 | }; 127 | F2FE5A8AE1A77E475283E9FE /* Frameworks */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 33C423938E65B7E83742EA06 /* Pods_Runner.framework */, 131 | ); 132 | name = Frameworks; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 97C146ED1CF9000F007C117D /* Runner */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 141 | buildPhases = ( 142 | 696550B8DEB0B0B50383180F /* [CP] Check Pods Manifest.lock */, 143 | 9740EEB61CF901F6004384FC /* Run Script */, 144 | 97C146EA1CF9000F007C117D /* Sources */, 145 | 97C146EB1CF9000F007C117D /* Frameworks */, 146 | 97C146EC1CF9000F007C117D /* Resources */, 147 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 148 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 149 | 0370D003849B38690EDCCF55 /* [CP] Embed Pods Frameworks */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = Runner; 156 | productName = Runner; 157 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 158 | productType = "com.apple.product-type.application"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | 97C146E61CF9000F007C117D /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastUpgradeCheck = 1300; 167 | ORGANIZATIONNAME = ""; 168 | TargetAttributes = { 169 | 97C146ED1CF9000F007C117D = { 170 | CreatedOnToolsVersion = 7.3.1; 171 | LastSwiftMigration = 1100; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 176 | compatibilityVersion = "Xcode 9.3"; 177 | developmentRegion = en; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 97C146E51CF9000F007C117D; 184 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 97C146ED1CF9000F007C117D /* Runner */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | 97C146EC1CF9000F007C117D /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 199 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 200 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 201 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXResourcesBuildPhase section */ 206 | 207 | /* Begin PBXShellScriptBuildPhase section */ 208 | 0370D003849B38690EDCCF55 /* [CP] Embed Pods Frameworks */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputFileListPaths = ( 214 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 215 | ); 216 | name = "[CP] Embed Pods Frameworks"; 217 | outputFileListPaths = ( 218 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | shellPath = /bin/sh; 222 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 223 | showEnvVarsInLog = 0; 224 | }; 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Thin Binary"; 233 | outputPaths = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | shellPath = /bin/sh; 237 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; 238 | }; 239 | 696550B8DEB0B0B50383180F /* [CP] Check Pods Manifest.lock */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputFileListPaths = ( 245 | ); 246 | inputPaths = ( 247 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 248 | "${PODS_ROOT}/Manifest.lock", 249 | ); 250 | name = "[CP] Check Pods Manifest.lock"; 251 | outputFileListPaths = ( 252 | ); 253 | outputPaths = ( 254 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | shellPath = /bin/sh; 258 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 259 | showEnvVarsInLog = 0; 260 | }; 261 | 9740EEB61CF901F6004384FC /* Run Script */ = { 262 | isa = PBXShellScriptBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | inputPaths = ( 267 | ); 268 | name = "Run Script"; 269 | outputPaths = ( 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | shellPath = /bin/sh; 273 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; 274 | }; 275 | /* End PBXShellScriptBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 97C146EA1CF9000F007C117D /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 283 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXSourcesBuildPhase section */ 288 | 289 | /* Begin PBXVariantGroup section */ 290 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | 97C146FB1CF9000F007C117D /* Base */, 294 | ); 295 | name = Main.storyboard; 296 | sourceTree = ""; 297 | }; 298 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | 97C147001CF9000F007C117D /* Base */, 302 | ); 303 | name = LaunchScreen.storyboard; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXVariantGroup section */ 307 | 308 | /* Begin XCBuildConfiguration section */ 309 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_COMMA = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 340 | ENABLE_NS_ASSERTIONS = NO; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_NO_COMMON_BLOCKS = YES; 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 351 | MTL_ENABLE_DEBUG_INFO = NO; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | DEVELOPMENT_TEAM = 75Y2P2WSQQ; 366 | ENABLE_BITCODE = NO; 367 | FRAMEWORK_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "$(PROJECT_DIR)/Flutter", 370 | ); 371 | INFOPLIST_FILE = Runner/Info.plist; 372 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 373 | LD_RUNPATH_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "@executable_path/Frameworks", 376 | ); 377 | LIBRARY_SEARCH_PATHS = ( 378 | "$(inherited)", 379 | "$(PROJECT_DIR)/Flutter", 380 | ); 381 | PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.ble; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 384 | SWIFT_VERSION = 5.0; 385 | VERSIONING_SYSTEM = "apple-generic"; 386 | }; 387 | name = Profile; 388 | }; 389 | 97C147031CF9000F007C117D /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_EMPTY_BODY = YES; 405 | CLANG_WARN_ENUM_CONVERSION = YES; 406 | CLANG_WARN_INFINITE_RECURSION = YES; 407 | CLANG_WARN_INT_CONVERSION = YES; 408 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 409 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 410 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 412 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 413 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 414 | CLANG_WARN_STRICT_PROTOTYPES = YES; 415 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 416 | CLANG_WARN_UNREACHABLE_CODE = YES; 417 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 418 | COPY_PHASE_STRIP = NO; 419 | DEBUG_INFORMATION_FORMAT = dwarf; 420 | ENABLE_STRICT_OBJC_MSGSEND = YES; 421 | ENABLE_TESTABILITY = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu99; 423 | GCC_DYNAMIC_NO_PIC = NO; 424 | GCC_NO_COMMON_BLOCKS = YES; 425 | GCC_OPTIMIZATION_LEVEL = 0; 426 | GCC_PREPROCESSOR_DEFINITIONS = ( 427 | "DEBUG=1", 428 | "$(inherited)", 429 | ); 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 437 | MTL_ENABLE_DEBUG_INFO = YES; 438 | ONLY_ACTIVE_ARCH = YES; 439 | SDKROOT = iphoneos; 440 | TARGETED_DEVICE_FAMILY = "1,2"; 441 | }; 442 | name = Debug; 443 | }; 444 | 97C147041CF9000F007C117D /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ALWAYS_SEARCH_USER_PATHS = NO; 448 | CLANG_ANALYZER_NONNULL = YES; 449 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 450 | CLANG_CXX_LIBRARY = "libc++"; 451 | CLANG_ENABLE_MODULES = YES; 452 | CLANG_ENABLE_OBJC_ARC = YES; 453 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 454 | CLANG_WARN_BOOL_CONVERSION = YES; 455 | CLANG_WARN_COMMA = YES; 456 | CLANG_WARN_CONSTANT_CONVERSION = YES; 457 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 458 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 459 | CLANG_WARN_EMPTY_BODY = YES; 460 | CLANG_WARN_ENUM_CONVERSION = YES; 461 | CLANG_WARN_INFINITE_RECURSION = YES; 462 | CLANG_WARN_INT_CONVERSION = YES; 463 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 464 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 465 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 466 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 467 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 468 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 469 | CLANG_WARN_STRICT_PROTOTYPES = YES; 470 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 471 | CLANG_WARN_UNREACHABLE_CODE = YES; 472 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 473 | COPY_PHASE_STRIP = NO; 474 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 475 | ENABLE_NS_ASSERTIONS = NO; 476 | ENABLE_STRICT_OBJC_MSGSEND = YES; 477 | GCC_C_LANGUAGE_STANDARD = gnu99; 478 | GCC_NO_COMMON_BLOCKS = YES; 479 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 480 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 481 | GCC_WARN_UNDECLARED_SELECTOR = YES; 482 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 483 | GCC_WARN_UNUSED_FUNCTION = YES; 484 | GCC_WARN_UNUSED_VARIABLE = YES; 485 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 486 | MTL_ENABLE_DEBUG_INFO = NO; 487 | SDKROOT = iphoneos; 488 | SWIFT_COMPILATION_MODE = wholemodule; 489 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 490 | TARGETED_DEVICE_FAMILY = "1,2"; 491 | VALIDATE_PRODUCT = YES; 492 | }; 493 | name = Release; 494 | }; 495 | 97C147061CF9000F007C117D /* Debug */ = { 496 | isa = XCBuildConfiguration; 497 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | CLANG_ENABLE_MODULES = YES; 501 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 502 | DEVELOPMENT_TEAM = 75Y2P2WSQQ; 503 | ENABLE_BITCODE = NO; 504 | FRAMEWORK_SEARCH_PATHS = ( 505 | "$(inherited)", 506 | "$(PROJECT_DIR)/Flutter", 507 | ); 508 | INFOPLIST_FILE = Runner/Info.plist; 509 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 510 | LD_RUNPATH_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "@executable_path/Frameworks", 513 | ); 514 | LIBRARY_SEARCH_PATHS = ( 515 | "$(inherited)", 516 | "$(PROJECT_DIR)/Flutter", 517 | ); 518 | PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.ble; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 521 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 522 | SWIFT_VERSION = 5.0; 523 | VERSIONING_SYSTEM = "apple-generic"; 524 | }; 525 | name = Debug; 526 | }; 527 | 97C147071CF9000F007C117D /* Release */ = { 528 | isa = XCBuildConfiguration; 529 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 530 | buildSettings = { 531 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 532 | CLANG_ENABLE_MODULES = YES; 533 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 534 | DEVELOPMENT_TEAM = 75Y2P2WSQQ; 535 | ENABLE_BITCODE = NO; 536 | FRAMEWORK_SEARCH_PATHS = ( 537 | "$(inherited)", 538 | "$(PROJECT_DIR)/Flutter", 539 | ); 540 | INFOPLIST_FILE = Runner/Info.plist; 541 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | ); 546 | LIBRARY_SEARCH_PATHS = ( 547 | "$(inherited)", 548 | "$(PROJECT_DIR)/Flutter", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.ble; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 553 | SWIFT_VERSION = 5.0; 554 | VERSIONING_SYSTEM = "apple-generic"; 555 | }; 556 | name = Release; 557 | }; 558 | /* End XCBuildConfiguration section */ 559 | 560 | /* Begin XCConfigurationList section */ 561 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 97C147031CF9000F007C117D /* Debug */, 565 | 97C147041CF9000F007C117D /* Release */, 566 | 249021D3217E4FDB00AE95B9 /* Profile */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 97C147061CF9000F007C117D /* Debug */, 575 | 97C147071CF9000F007C117D /* Release */, 576 | 249021D4217E4FDB00AE95B9 /* Profile */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | /* End XCConfigurationList section */ 582 | }; 583 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 584 | } 585 | --------------------------------------------------------------------------------