I’ve efficiently created an onboarding circulation that presents a bluetooth low power permission dialogue when the consumer faucets a proceed button in a specific display screen throughout onboarding. Nevertheless, it is solely working for Android. The app targets iOS 13 or newer. In iOS 13+, when the onboarding is launched the bluetooth permission dialogue seems mechanically. It does not look ahead to the consumer to faucet on the proceed button, within the second onboarding display screen, to activate it manually prefer it does in Android.
After launching the app for the primary time: I am requested if I wish to enable my app to seek out and hook up with gadgets on my native community, to which I agree. When the primary display screen of onboarding seems I am requested if I wish to enable bluetooth. I get the next log output:
LOG Supervisor initialized, checking Bluetooth state...
LOG Present Bluetooth state: Unknown
ERROR Error enabling Bluetooth: [BleError: Bluetooth state change failed]
I faucet on okay to permit bluetooth, then faucet proceed to get to the bluetooth permission display screen. Once I faucet on proceed, the alert dialogue seems from the handleEnableBluetooth methodology in BluetoothScreen (see code snippet 2 under). I get the next output:
LOG Requesting Bluetooth permission...
LOG Bluetooth permission: unavailable
LOG Location permission: unavailable
LOG Location All the time permission: unavailable
ERROR Bluetooth or Location permissions not granted
LOG Bluetooth permission: false
In my system settings bluetooth is enabled system large, and my app’s bluetooth toggle can be on.
My information.plist is:
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
TychoCare
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0.0
CFBundleSignature
????
CFBundleURLTypes
CFBundleURLSchemes
co.uk.tycho.provisioner
CFBundleURLSchemes
exp+provisioner
CFBundleVersion
14
ITSAppUsesNonExemptEncryption
LSMinimumSystemVersion
13.3
LSRequiresIPhoneOS
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSExceptionDomains
localhost
NSExceptionAllowsInsecureHTTPLoads
NSBluetoothAlwaysUsageDescription
$(PRODUCT_NAME) requires bluetooth entry to configure hubs and watches.
NSBluetoothPeripheralUsageDescription
$(PRODUCT_NAME) requires bluetooth entry to configure hubs and watches.
NSLocationAlwaysUsageDescription
$(PRODUCT_NAME) requires your location to seek out close by Bluetooth gadgets
NSLocationWhenInUseUsageDescription
$(PRODUCT_NAME) requires your location to seek out close by Bluetooth gadgets
NSLocationAlwaysAndWhenInUseUsageDescription
$(PRODUCT_NAME) requires your location
NSCameraUsageDescription
$(PRODUCT_NAME) wants entry to your digital camera to scan Tycho hub QR code to authorize you and take profile photos.
NSMicrophoneUsageDescription
$(PRODUCT_NAME) wants entry to your microphone.
UIBackgroundModes
bluetooth-peripheral
UILaunchStoryboardName
SplashScreen
UIRequiredDeviceCapabilities
armv7
UIRequiresFullScreen
UIStatusBarStyle
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIUserInterfaceStyle
Computerized
UIViewControllerBasedStatusBarAppearance
My onboarding bluetooth display screen with the proceed button:
import React from "react";
import { View, Textual content, Alert } from "react-native";
import { useBluetoothConnection } from "../../Context/BLEContext";
import Icon from "react-native-vector-icons/Ionicons";
import AppButton from "../../Parts/ButtonComponent";
import Pagination from "../../Parts/PaginationComponent";
import ButtonStyle from "../../Kinds/ButtonStyle";
import Colours from "../../Kinds/ColorsStyle";
import Kinds from "../../Kinds/GeneralStyle";
const BluetoothScreen = ({ navigation }) => {
const { requestPermissions } = useBluetoothConnection();
const handleEnableBluetooth = async () => {
console.log("Requesting Bluetooth permission...");
const granted = await requestPermissions();
console.log("Bluetooth permission: ", granted);
if (granted) {
navigation.navigate("Digital camera");
} else {
Alert.alert("Permission required", "Bluetooth permissions are required to proceed. Allow bluetooth in your system settings. Additionally, be certain location permissions are enabled for the app.");
}
};
return (
It's essential to allow bluetooth so you'll be able to configure Tycho hubs.
Enabling bluetooth ensures you'll be able to hook up with Tycho hubs to configure them.
);
};
export default BluetoothScreen;
My requestPermissions() function in my BLE context file:
import React, { createContext, useContext, useCallback, useState, useRef, useEffect } from "react";
import { Platform } from "react-native";
import { request, PERMISSIONS, RESULTS } from "react-native-permissions";
import { Buffer } from "buffer";
import { useActivityIndicator } from "./ActivityIndicatorContext.js";
import { useTheme } from "./ThemeContext.js";
import Colors from "../Styles/ColorsStyle.js";
import bleManagerSingleton from "../Singletons/BleManagerSingleton.js";
import {
UART_SERVICE_UUID,
UART_RX_CHARACTERISTIC_UUID,
UART_TX_CHARACTERISTIC_UUID,
EOT_MARKER
} from "../Constants/BLEConstants.js";
const BluetoothConnectionContext = createContext(null);
export const BluetoothConnectionProvider = ({ children }) => {
const [scanHubComplete, setScanHubComplete] = useState(false);
const [scanWatchComplete, setScanWatchComplete] = useState(false);
const [scanBleDeviceComplete, setScanBleDeviceComplete] = useState(false);
const [scanning, setScanning] = useState(false);
const [connectedDevice, setConnectedDevice] = useState(null);
const supervisor = bleManagerSingleton.getInstance();
const operationQueue = useRef([]);
const isOperationInProgress = useRef(false);
const { showLoader, hideLoader } = useActivityIndicator();
const { theme } = useTheme();
const processQueue = useCallback(async (loaderMessage) => {
if (operationQueue.present.size === 0 || isOperationInProgress.present) {
return;
}
isOperationInProgress.present = true;
const operation = operationQueue.present.shift();
attempt "Loading...");
await operation();
catch (error) {
console.error("Error processing BLE operation:", error);
} lastly {
isOperationInProgress.present = false;
hideLoader();
processQueue(loaderMessage); // Course of the subsequent operation within the queue
}
}, [showLoader, hideLoader, theme.text]);
const enqueueOperation = useCallback((operation, loaderMessage) => {
operationQueue.present.push(operation);
processQueue(loaderMessage);
}, [processQueue]);
const requestPermissions = useCallback(async () => {
if (Platform.OS === "android") {
attempt {
if (Platform.Model >= 31) {
const scanGranted = await request(PERMISSIONS.ANDROID.BLUETOOTH_SCAN);
const connectGranted = await request(PERMISSIONS.ANDROID.BLUETOOTH_CONNECT);
const locationGranted = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
if (
scanGranted !== RESULTS.GRANTED ||
connectGranted !== RESULTS.GRANTED ||
locationGranted !== RESULTS.GRANTED
) {
console.error("Bluetooth permissions not granted");
return false;
}
} else {
const locationGranted = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
if (locationGranted !== RESULTS.GRANTED) {
console.error("Location permission not granted");
return false;
}
}
return true; // Permissions granted
} catch (err) {
console.warn(err);
return false;
}
} else if (Platform.OS === "ios") {
attempt {
const bluetoothPermission = await request(PERMISSIONS.IOS.BLUETOOTH_PERIPHERAL);
const locationPermission = await request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
const locationAlwaysPermission = await request(PERMISSIONS.IOS.LOCATION_ALWAYS);
console.log("Bluetooth permission:", bluetoothPermission);
console.log("Location permission:", locationPermission);
console.log("Location All the time permission:", locationAlwaysPermission);
if (
bluetoothPermission !== RESULTS.GRANTED ||
locationPermission !== RESULTS.GRANTED ||
locationAlwaysPermission !== RESULTS.GRANTED
) {
console.error("Bluetooth or Location permissions not granted");
return false;
}
return true; // Permissions granted
} catch (err) {
console.warn(err);
return false;
}
}
// Default return true for platforms apart from Android and iOS
return true;
}, []);
const checkBluetoothState = useCallback(async () => {
return enqueueOperation(async () => {
const state = await supervisor.state();
console.log("Present Bluetooth state:", state);
if (state !== "PoweredOn") {
attempt {
await supervisor.allow();
} catch (error) {
console.error("Error enabling Bluetooth:", error);
}
}
}, "Checking Bluetooth state...");
}, [manager, enqueueOperation]);
useEffect(() => {
if (supervisor) {
console.log("Supervisor initialized, checking Bluetooth state...");
checkBluetoothState();
} else {
console.error("BleManager just isn't out there");
}
}, [manager]);
const startDeviceScan = useCallback((deviceType) => {
return new Promise((resolve, reject) => {
enqueueOperation(async () => {
if (Platform.OS === "android" && Platform.Model >= 31) {
const permissionsGranted = await requestPermissions();
if (!permissionsGranted) {
reject(new Error("Permissions not granted"));
return;
}
}
console.log("Beginning system scan...");
attempt {
setScanning(true);
let bestDevice = null;
supervisor.startDeviceScan(null, null, (error, scannedDevice) => {
if (error) {
console.error("Error throughout scan:", error);
if (deviceType === "Tycho-Hub") {
setScanHubComplete(true);
} else if (deviceType === "Tycho-Watch") {
setScanWatchComplete(true);
} else {
setScanBleDeviceComplete(true);
}
setScanning(false);
reject(error);
return;
}
if (scannedDevice.title && scannedDevice.title.consists of(deviceType)) {
// Verify if this system has the very best RSSI
if (!bestDevice || scannedDevice.rssi > bestDevice.rssi) {
bestDevice = scannedDevice;
}
}
});
// Cease scanning after a sure interval or situation
setTimeout(() => {
if (bestDevice) {
if (deviceType === "Tycho-Hub") {
setScanHubComplete(true);
} else if (deviceType === "Tycho-Watch") {
setScanWatchComplete(true);
} else {
setScanBleDeviceComplete(true);
}
console.log("Greatest system discovered:", bestDevice);
resolve(bestDevice);
} else {
reject(new Error("No system discovered"));
}
stopDeviceScan();
setScanning(false);
}, 7500); // Modify the timeout length as wanted
} catch (e) {
console.error("Exception throughout startDeviceScan:", e);
reject(e);
}
}, "Scanning...");
});
}, [stopDeviceScan, enqueueOperation, requestPermissions]);
const stopDeviceScan = useCallback(() => {
return enqueueOperation(async () => {
supervisor.stopDeviceScan();
}, "Stopping system scan...");
}, [enqueueOperation]);
const connectToDevice = useCallback(async (system) => {
return enqueueOperation(async () => {
attempt {
//await checkBluetoothState(); // Guarantee Bluetooth is powered on
console.log("Gadget in connectToDevice:", system);
const newlyConnectedDevice = await supervisor.connectToDevice(system.id);
console.log(`Related to system: ${newlyConnectedDevice.localName}`);
setConnectedDevice(newlyConnectedDevice);
} catch (error) {
console.error("Error connecting to system:", error);
throw new Error(error);
}
}, "Connecting to system...");
}, [enqueueOperation]);
const disconnectFromDevice = useCallback(async (system) => {
return enqueueOperation(async () => {
attempt {
await supervisor.cancelDeviceConnection(system.id);
} catch (error) "Unknown error", error.code);
}, "Disconnecting from system...");
}, [enqueueOperation]);
const characteristicsForService = useCallback(async (system) => {
if (!system) {
throw new Error("Gadget just isn't related to the app");
}
const service = await system.discoverAllServicesAndCharacteristics();
const traits = await service.characteristicsForService(UART_SERVICE_UUID);
return traits;
}, []);
const obtainUartServiceAndCharacteristics = useCallback(async (system) => {
const traits = await characteristicsForService(system);
const rxCharacteristic = traits.discover(c => c.uuid === UART_RX_CHARACTERISTIC_UUID);
const txCharacteristic = traits.discover(c => c.uuid === UART_TX_CHARACTERISTIC_UUID);
if (!rxCharacteristic) {
throw new Error("RX attribute not discovered");
}
if (!txCharacteristic) {
throw new Error("TX attribute not discovered");
}
return { rxCharacteristic, txCharacteristic };
}, [characteristicsForService]);
const receiveFromHub = useCallback((system) => {
console.log("Receiving from hub...");
return new Promise((resolve, reject) => {
enqueueOperation(() => {
console.log("Gadget inside receiveFromHub is: ", system);
console.log(`Gadget is ${system.isConnected() ? "related" : "not related"}`);
if (system && !system.isConnected()) {
reconnect(system); // Name reconnect straight with out enqueuing
}
console.log("Gadget in receiveFromHub: ", system);
let fullData = "";
let timeoutHandle;
attempt {
const subscription = supervisor.monitorCharacteristicForDevice(
system.id,
UART_SERVICE_UUID,
UART_TX_CHARACTERISTIC_UUID,
(error, attribute) => {
if (error) {
console.error("Notification error:", error);
cleanup();
reject(error);
return;
}
const knowledge = attribute.worth;
const decodedData = Buffer.from(knowledge, "base64").toString("ascii");
console.log("Acquired notification knowledge:", decodedData);
if (decodedData.consists of(EOT_MARKER)) {
console.log("Full knowledge obtained:", fullData);
cleanup();
resolve(fullData);
} else {
fullData += decodedData;
}
}
);
// Set a timeout to reject the promise if knowledge just isn't obtained in time
const TIMEOUT_DURATION = 5000; // 5 seconds
timeoutHandle = setTimeout(() => {
console.error("Timeout reached with out receiving full knowledge");
cleanup();
reject(new Error("Timeout reached"));
}, TIMEOUT_DURATION);
// Cleanup operate to take away subscription and clear timeout
const cleanup = () => {
if (subscription) {
subscription.take away();
}
clearTimeout(timeoutHandle);
};
} catch (error) {
console.error("Error subscribing to notifications:", error);
reject(error);
}
}, "Receiving from hub...");
});
}, [manager, enqueueOperation, reconnect]);
const sendToHub = useCallback(async (system, dataToSend) => {
console.log("Sending to hub: ", dataToSend);
return enqueueOperation(async () => {
console.log("Gadget inside sendToHub is: ", system);
console.log(`Gadget is ${system.isConnected() ? "related" : "not related"}`);
if (system && !system.isConnected()) {
reconnect(system);
}
console.log("Gadget in sendToHub: ", system);
attempt {
const { rxCharacteristic } = await obtainUartServiceAndCharacteristics(system);
await rxCharacteristic.writeWithoutResponse(dataToSend);
console.log("Information despatched to hub:", dataToSend);
} catch (error) {
console.error("Error in sendToHub:", error);
throw new Error(error);
}
}, "Sending to hub...");
}, [obtainUartServiceAndCharacteristics, enqueueOperation]);
const requestConnectionPriority = useCallback(async (precedence) => {
return enqueueOperation(async () => {
attempt {
const system = await supervisor.gadgets([device.id]);
if (system && await system.isConnected()) {
await system.requestConnectionPriority(precedence);
console.log(`Requested connection precedence: ${precedence} for system: ${system.id}`);
} else {
console.error("Gadget just isn't related");
}
} catch (error) "Unknown error", error.code);
}, "Requesting connection precedence...");
}, [manager, enqueueOperation]);
const reconnect = useCallback(async (system) => {
attempt {
await disconnectFromDevice(system);
await connectToDevice(system);
} catch (error) {
console.error("Reconnection failed:", error);
setTimeout(() => reconnect(system), 5000); // Retry after 5 seconds
}
}, [disconnectFromDevice, connectToDevice]);
useEffect(() => {
const subscription = supervisor.onDeviceDisconnected((error, system) => {
if (error) {
console.error("Gadget disconnected:", error);
reconnect(system);
}
});
return () => subscription.take away();
}, [manager, reconnect]);
return (
{youngsters}
);
};
export const useBluetoothConnection = () => {
const context = useContext(BluetoothConnectionContext);
if (!context) {
throw new Error("useBluetoothConnection have to be used inside a BluetoothConnectionProvider");
}
return context;
};