-0.5 C
New York
Wednesday, February 5, 2025

react native – I do not need bluetooth permission dialogue to look routinely in iOS 13+


I’ve efficiently created an onboarding movement that presents a bluetooth low vitality permission dialogue when the person faucets a proceed button in a selected display 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 routinely. It does not look ahead to the person to faucet on the proceed button, within the second onboarding display, 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 search out and connect with units on my native community, to which I agree. When the primary display 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. After 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 At all times permission: unavailable
ERROR  Bluetooth or Location permissions not granted
LOG  Bluetooth permission:  false

In my machine settings bluetooth is enabled system broad, and my app’s bluetooth toggle can be on.

My data.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 search out close by Bluetooth units
    NSLocationWhenInUseUsageDescription
    $(PRODUCT_NAME) requires your location to search out close by Bluetooth units
    NSLocationAlwaysAndWhenInUseUsageDescription
    $(PRODUCT_NAME) requires your location
    NSCameraUsageDescription
    $(PRODUCT_NAME) wants entry to your digicam to scan Tycho hub QR code to authorize you and take profile footage.
    NSMicrophoneUsageDescription
    $(PRODUCT_NAME) wants entry to your microphone.
    UIBackgroundModes
    
        bluetooth-peripheral
    
    UILaunchStoryboardName
    SplashScreen
    UIRequiredDeviceCapabilities
    
        armv7
    
    UIRequiresFullScreen
    
    UIStatusBarStyle
    
    UISupportedInterfaceOrientations
    
        UIInterfaceOrientationPortrait
        UIInterfaceOrientationPortraitUpsideDown
    
    UIUserInterfaceStyle
    Automated
    UIViewControllerBasedStatusBarAppearance
    


My onboarding bluetooth display 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 "../../Elements/ButtonComponent";
import Pagination from "../../Elements/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 machine settings. Additionally, ensure that location permissions are enabled for the app.");
        }
    };

    return (
        
            
                
                You could allow bluetooth so you may configure Tycho hubs.
                Enabling bluetooth ensures you may connect 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();

        strive  "Loading...");
            await operation();
         catch (error) {
            console.error("Error processing BLE operation:", error);
        } lastly {
            isOperationInProgress.present = false;
            hideLoader();
            processQueue(loaderMessage); // Course of the following 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") {
            strive {
                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") {
            strive {
                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 At all times 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") {
                strive {
                    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 accessible");
        }
    }, [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 machine scan...");
                strive {
                    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.contains(deviceType)) {
                            // Verify if this machine has the 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("Finest machine discovered:", bestDevice);
                            resolve(bestDevice);
                        } else {
                            reject(new Error("No machine discovered"));
                        }
                        stopDeviceScan();
                        setScanning(false);
                    }, 7500); // Regulate the timeout period 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 machine scan...");
    }, [enqueueOperation]);

    const connectToDevice = useCallback(async (machine) => {
        return enqueueOperation(async () => {
            strive {
                //await checkBluetoothState();  // Guarantee Bluetooth is powered on
                console.log("Gadget in connectToDevice:", machine);
                const newlyConnectedDevice = await supervisor.connectToDevice(machine.id);
                console.log(`Linked to machine: ${newlyConnectedDevice.localName}`);
                setConnectedDevice(newlyConnectedDevice);
            } catch (error) {
                console.error("Error connecting to machine:", error);
                throw new Error(error);
            }
        }, "Connecting to machine...");
    }, [enqueueOperation]);

    const disconnectFromDevice = useCallback(async (machine) => {
        return enqueueOperation(async () => {
            strive {
                await supervisor.cancelDeviceConnection(machine.id);
            } catch (error)  "Unknown error", error.code);
            
        }, "Disconnecting from machine...");
    }, [enqueueOperation]);

    const characteristicsForService = useCallback(async (machine) => {
        if (!machine) {
            throw new Error("Gadget just isn't related to the app");
        }
        const service = await machine.discoverAllServicesAndCharacteristics();
        const traits = await service.characteristicsForService(UART_SERVICE_UUID);
        return traits;
    }, []);

    const obtainUartServiceAndCharacteristics = useCallback(async (machine) => {
        const traits = await characteristicsForService(machine);
        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((machine) => {
        console.log("Receiving from hub...");

        return new Promise((resolve, reject) => {
            enqueueOperation(() => {
                console.log("Gadget inside receiveFromHub is: ", machine);
                console.log(`Gadget is ${machine.isConnected() ? "related" : "not related"}`);
                if (machine && !machine.isConnected()) {
                    reconnect(machine); // Name reconnect instantly with out enqueuing
                }
                console.log("Gadget in receiveFromHub: ", machine);

                let fullData = "";
                let timeoutHandle;

                strive {
                    const subscription = supervisor.monitorCharacteristicForDevice(
                        machine.id,
                        UART_SERVICE_UUID,
                        UART_TX_CHARACTERISTIC_UUID,
                        (error, attribute) => {
                            if (error) {
                                console.error("Notification error:", error);
                                cleanup();
                                reject(error);
                                return;
                            }

                            const information = attribute.worth;
                            const decodedData = Buffer.from(information, "base64").toString("ascii");
                            console.log("Acquired notification information:", decodedData);

                            if (decodedData.contains(EOT_MARKER)) {
                                console.log("Full information acquired:", fullData);
                                cleanup();
                                resolve(fullData);
                            } else {
                                fullData += decodedData;
                            }
                        }
                    );

                    // Set a timeout to reject the promise if information just isn't acquired in time
                    const TIMEOUT_DURATION = 5000; // 5 seconds
                    timeoutHandle = setTimeout(() => {
                        console.error("Timeout reached with out receiving full information");
                        cleanup();
                        reject(new Error("Timeout reached"));
                    }, TIMEOUT_DURATION);

                    // Cleanup perform 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 (machine, dataToSend) => {
        console.log("Sending to hub: ", dataToSend);
        return enqueueOperation(async () => {
            console.log("Gadget inside sendToHub is: ", machine);
            console.log(`Gadget is ${machine.isConnected() ? "related" : "not related"}`);
            if (machine && !machine.isConnected()) {
                reconnect(machine);
            }
            console.log("Gadget in sendToHub: ", machine);
            strive {
                const { rxCharacteristic } = await obtainUartServiceAndCharacteristics(machine);
                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 () => {
            strive {
                const machine = await supervisor.units([device.id]);
                if (machine && await machine.isConnected()) {
                    await machine.requestConnectionPriority(precedence);
                    console.log(`Requested connection precedence: ${precedence} for machine: ${machine.id}`);
                } else {
                    console.error("Gadget just isn't related");
                }
            } catch (error)  "Unknown error", error.code);
            
        }, "Requesting connection precedence...");
    }, [manager, enqueueOperation]);

    const reconnect = useCallback(async (machine) => {
        strive {
            await disconnectFromDevice(machine);
            await connectToDevice(machine);
        } catch (error) {
            console.error("Reconnection failed:", error);
            setTimeout(() => reconnect(machine), 5000); // Retry after 5 seconds
        }
    }, [disconnectFromDevice, connectToDevice]);

    useEffect(() => {
        const subscription = supervisor.onDeviceDisconnected((error, machine) => {
            if (error) {
                console.error("Gadget disconnected:", error);
                reconnect(machine);
            }
        });
        return () => subscription.take away();
    }, [manager, reconnect]);

    return (
        
            {kids}
        
    );
};

export const useBluetoothConnection = () => {
    const context = useContext(BluetoothConnectionContext);
    if (!context) {
        throw new Error("useBluetoothConnection have to be used inside a BluetoothConnectionProvider");
    }
    return context;
};

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles