├── .env.example ├── .gitignore ├── App.js ├── LICENSE ├── README.md ├── app.json ├── babel.config.js ├── jsconfig.json ├── metro.config.js ├── package-lock.json ├── package.json └── src ├── Services ├── errorHandler.js └── mqttService.js ├── config └── environment.js ├── constants └── Colors.js ├── context └── MqttContext.jsx ├── hooks ├── useAppStateReconnect.jsx └── useMqttConnection.jsx ├── navigator ├── Navigation.jsx └── StackNavigator.jsx └── screen └── Mttq.jsx /.env.example: -------------------------------------------------------------------------------- 1 | # APP 2 | EXPO_PUBLIC_TZ = "UTC" 3 | EXPO_PUBLIC_EMIT_CONSOLE_LOGS = true 4 | 5 | # MQTT 6 | EXPO_PUBLIC_MQTT_HOST = "localhost" 7 | EXPO_PUBLIC_MQTT_PORT = 15675 8 | EXPO_PUBLIC_MQTT_PORT_SSL = 15676 9 | EXPO_PUBLIC_MQTT_SSL = false 10 | EXPO_PUBLIC_MQTT_VERSION = 5 11 | EXPO_PUBLIC_MQTT_QOS = 0 12 | EXPO_PUBLIC_MQTT_USERNAME = "consumer" 13 | EXPO_PUBLIC_MQTT_PASSWORD = "123" 14 | EXPO_PUBLIC_MQTT_TOPICS = "topic/1,topic/2,topicGeneric/#" 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # dependencie files 7 | pnpm-lock.yaml 8 | 9 | # Expo 10 | .expo/ 11 | dist/ 12 | web-build/ 13 | android/ 14 | 15 | # Native 16 | *.orig.* 17 | *.jks 18 | *.p8 19 | *.p12 20 | *.key 21 | *.mobileprovision 22 | 23 | # Metro 24 | .metro-health-check* 25 | 26 | # debug 27 | npm-debug.* 28 | yarn-debug.* 29 | yarn-error.* 30 | 31 | # macOS 32 | .DS_Store 33 | *.pem 34 | 35 | # local env files 36 | .env 37 | .env*.local 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar' 2 | import { SafeAreaProvider } from 'react-native-safe-area-context' 3 | import { GestureHandlerRootView } from 'react-native-gesture-handler' 4 | import Navigation from './src/navigator/Navigation' 5 | import { MqttProvider } from 'src/context/MqttContext' 6 | 7 | 8 | export default function App() { 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTT.js implementation example for React Native 2 | 3 | This is a simple example of how to use [MQTT.js](https://github.com/mqttjs/MQTT.js) in a React Native project. This example also is using [Expo](https://expo.dev/) to make it easier to run the project. 4 | 5 | 6 | ## Table of Contents 7 | 8 | - [Getting Started](#getting-started) 9 | - [Environment Variables](#environment-variables) 10 | - [Example Mosquitto Broker](#example-mosquitto-broker) 11 | - [Using MQTT.js in React Native](#using-mqttjs-in-react-native) 12 | - [About important files](#about-important-files) 13 | - [Known Issues](#known-issues) 14 | - [Contributing](#contributing) 15 | 16 | 17 | 18 | 19 | ## Getting Started 20 | 21 | 1. Clone this repository 22 | 2. Install the dependencies with `npm install` 23 | 3. Copy the `.env.example` file to `.env` and configure the MQTT broker settings 24 | 4. Run the project with `npm start` 25 | 5. Open the project in your device using the [Expo Go APP](https://play.google.com/store/apps/details?id=host.exp.exponent) reading the QR code that will appear in the terminal. 26 | 6. Publish a message from your MQTT Broker 27 | 28 | 29 | 30 | 31 | ## Environment Variables 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 | 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 |
VariableTypeDescriptionExample
EXPO_PUBLIC_TZstringTimezone will be used to format the dateUTC | America/Santo_Domingo
EXPO_PUBLIC_EMIT_CONSOLE_LOGSbooleanShow in console all error logstrue | false
EXPO_PUBLIC_MQTT_HOSTstringMQTT Broker Hostlocalhost | domain.com
EXPO_PUBLIC_MQTT_PORTnumberMQTT Broker Port1883
EXPO_PUBLIC_MQTT_PORT_SSLnumberMQTT Broker SSL Port8883
EXPO_PUBLIC_MQTT_SSLbooleanEnable connection via SSLtrue | false
EXPO_PUBLIC_MQTT_VERSIONnumberMQTT Protocol Version 80 | 3 = 3.1 81 |
82 | 4 = 3.1.1 83 |
84 | 5 = 5 85 |
EXPO_PUBLIC_MQTT_QOSnumberMQTT Broker Quality of Service0 | 1 | 2
EXPO_PUBLIC_MQTT_USERNAMEstringMQTT Broker Usernameproducer | consumer
EXPO_PUBLIC_MQTT_PASSWORDstringMQTT Broker Password123 | 456
EXPO_PUBLIC_MQTT_TOPICSstringMQTT Broker Topics separated by comma ,topic/1 | topic/1,topic/2 | topic/#
112 | 113 | 114 | 115 | 116 | ### Example Mosquitto Broker 117 | 118 | [Mosquitto Test Server Configuration](https://test.mosquitto.org/) 119 | 120 | ``` 121 | # APP 122 | EXPO_PUBLIC_TZ = "UTC" 123 | EXPO_PUBLIC_EMIT_CONSOLE_LOGS = true 124 | 125 | # MQTT 126 | EXPO_PUBLIC_MQTT_HOST = "test.mosquitto.org" 127 | EXPO_PUBLIC_MQTT_PORT = 8080 128 | EXPO_PUBLIC_MQTT_PORT_SSL = 8081 129 | EXPO_PUBLIC_MQTT_SSL = true 130 | EXPO_PUBLIC_MQTT_VERSION = 4 131 | EXPO_PUBLIC_MQTT_QOS = 0 132 | EXPO_PUBLIC_MQTT_USERNAME = "" 133 | EXPO_PUBLIC_MQTT_PASSWORD = "" 134 | EXPO_PUBLIC_MQTT_TOPICS = "#" 135 | ``` 136 | 137 | 138 | 139 | 140 | ## Using MQTT.js in React Native 141 | 142 | 1. Configure the `metro.config.js` file to use the `mqtt` package as a default import. 143 | 144 | ```javascript 145 | const { getDefaultConfig } = require('expo/metro-config'); 146 | 147 | const config = getDefaultConfig(__dirname); 148 | 149 | config.resolver.unstable_enablePackageExports = true; 150 | 151 | module.exports = config; 152 | ``` 153 | 154 | Note: `unstable_enablePackageExports` will be removed in the future when it stabilizes, so be sure to check the [Expo documentation](https://reactnative.dev/blog/2023/06/21/package-exports-support) and follow the latest updates. 155 | 156 | 2. Import the `mqtt` package: 157 | ```javascript 158 | import mqtt from 'mqtt'; 159 | ``` 160 | 161 | Alternatively, if you don't want to use default import and metro config file, you can use the following import statement: 162 | 163 | ```javascript 164 | import mqtt from 'mqtt/dist/mqtt.esm.js'; 165 | ``` 166 | 167 | 168 | 169 | 170 | ## About important files 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 |
FileDescription
/App.jsMain entry point of the application
/src/screen/Mttq.jsxThe screen that will show the MQTT logs
185 | 186 | 187 | 188 | 189 | ## Known Issues 190 | These are some known issues that you _may_ encounter when using this example. 191 | 192 | - **Keepalive timeout** 193 | - The app crashes because it gets overwhelmed with the thousands of messages coming from broker, e.g. using mosquitto broker with `#` topic. 194 | 195 | If you find any solution to these issues, feel free to open a PR, look at the [Contributing](#contributing) section for more information. 196 | 197 | ## Motivation 198 | 199 | I was looking for a simple example of how to use MQTT.js in a React Native project, but I couldn't find any. So I decided to create this example to help others that are looking for the same thing. In the process I found a issue with the `mqtt` package that I had to fix, the PR is in [(#1840)](https://github.com/mqttjs/MQTT.js/pull/1840) if you want to check it out. 200 | 201 | 202 | 203 | 204 | ## Contributing 205 | 206 | Feel free to make a PR if you find any issue or if you want to improve this example. Also, if you have any other issue with the `mqtt` package, feel free to open an issue in the [MQTT.js](https://github.com/mqttjs/MQTT.js) repository. 207 | 208 | 209 | ## Contributors 210 | 211 | - [Juan Carlos](https://github.com/JuanCarlos008): author of this example 212 | - [Máximo Liberata](https://github.com/MaximoLiberata): author of the PR that fixed the issue with the `mqtt` package -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "mqtt-example", 4 | "slug": "mqtt-example", 5 | "platforms": [ 6 | "ios", 7 | "android", 8 | "web" 9 | ], 10 | "version": "2.0.0", 11 | "newArchEnabled": true, 12 | "orientation": "portrait", 13 | "splash": { 14 | "resizeMode": "contain" 15 | }, 16 | "updates": { 17 | "fallbackToCacheTimeout": 0 18 | }, 19 | "assetBundlePatterns": [ 20 | "**/*" 21 | ], 22 | "ios": { 23 | "supportsTablet": false 24 | }, 25 | "runtimeVersion": { 26 | "policy": "appVersion" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | 'react-native-reanimated/plugin', 7 | [ 8 | 'module-resolver', 9 | { 10 | root: ['./src'], 11 | extensions: [ 12 | '.ios.js', 13 | '.android.js', 14 | '.js', 15 | '.jsx', 16 | '.ts', 17 | '.tsx', 18 | '.json', 19 | ], 20 | alias: { 21 | src: './src', 22 | }, 23 | }, 24 | ], 25 | ], 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": ["./src/*"], 6 | "underscore": ["lodash"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require('expo/metro-config'); 2 | 3 | const config = getDefaultConfig(__dirname); 4 | 5 | config.resolver.unstable_enablePackageExports = true; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-app-example", 3 | "version": "2.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web", 10 | "tunel": "npx expo start -c --tunnel", 11 | "android-build": "eas build -p android --profile preview", 12 | "ios-build": "eas build -p ios --profile preview" 13 | }, 14 | "engines": { 15 | "node": ">=20.18.1" 16 | }, 17 | "dependencies": { 18 | "@expo/metro-runtime": "~4.0.1", 19 | "@react-navigation/native": "7.1.6", 20 | "@react-navigation/native-stack": "7.3.10", 21 | "expo": "~52.0.46", 22 | "expo-crypto": "~14.0.2", 23 | "expo-status-bar": "~2.0.1", 24 | "expo-updates": "~0.27.4", 25 | "mqtt": "5.12.0", 26 | "react": "18.3.1", 27 | "react-dom": "18.3.1", 28 | "react-native": "0.76.9", 29 | "react-native-gesture-handler": "~2.20.2", 30 | "react-native-reanimated": "~3.16.1", 31 | "react-native-web": "~0.19.13" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "7.26.10", 35 | "babel-plugin-module-resolver": "5.0.2" 36 | }, 37 | "private": true 38 | } 39 | -------------------------------------------------------------------------------- /src/Services/errorHandler.js: -------------------------------------------------------------------------------- 1 | import { envConfig } from 'src/config/environment'; 2 | 3 | 4 | /** 5 | * @typedef {"MqttTopic" | "MqttGeneral"} ErrorType 6 | */ 7 | 8 | /** 9 | * @param {Error} error 10 | * @returns {string} 11 | */ 12 | const processError = (error) => { 13 | return `Name: ${error?.name} || Message: ${error?.message} || Code: ${error?.code}`; 14 | } 15 | 16 | /** 17 | * @param {ErrorType} type 18 | * @param {Error} error 19 | */ 20 | const logError = (type, error) => { 21 | const errorMsg = processError(error); 22 | console.log(`${'[Error:' + type + ']'} || ${errorMsg}`); 23 | } 24 | 25 | /** 26 | * @param {(value: import('../hooks/useMqttConnection').MqttError) => void} callback 27 | * @param {ErrorType} type 28 | * @param {Error} error 29 | */ 30 | const emitStateError = (callback, type, error) => { 31 | 32 | const errorMsg = processError(error).replace(/ \|\|{0,2} /g, '\n'); 33 | 34 | callback({ 35 | type: '[Error:' + type + ']', 36 | msg: errorMsg 37 | }); 38 | 39 | if (envConfig.EMIT_CONSOLE_LOGS) { 40 | logError(type, error); 41 | } 42 | 43 | } 44 | 45 | 46 | export { 47 | logError, 48 | emitStateError 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/Services/mqttService.js: -------------------------------------------------------------------------------- 1 | import mqtt from 'mqtt' 2 | import { envConfig } from 'src/config/environment' 3 | import { emitStateError } from './errorHandler' 4 | 5 | 6 | /** 7 | * @typedef {{ 8 | * ssl: boolean, 9 | * setMqttStatus: (status: import('../hooks/useMqttConnection').MqttStatus) => void, 10 | * setMqttError: (error: string) => void, 11 | * uniqueId: string, 12 | * onMessage: (topic: string, message: any) => void, 13 | * }} CreateMqttClientOptions 14 | */ 15 | 16 | /** 17 | * @param {CreateMqttClientOptions} options 18 | * 19 | * @returns {mqtt.MqttClient} 20 | */ 21 | function createMqttClient({ 22 | setMqttStatus, 23 | setMqttError, 24 | uniqueId, 25 | onMessage, 26 | }) { 27 | const ssl = envConfig.MQTT_SSL 28 | const host = envConfig.MQTT_HOST 29 | const path = '/ws' 30 | const protocolVersion = envConfig.MQTT_VERSION 31 | let port = envConfig.MQTT_PORT 32 | let protocol = 'ws' 33 | 34 | if (ssl) { 35 | port = envConfig.MQTT_PORT_SSL 36 | protocol = 'wss' 37 | } 38 | 39 | const client = mqtt.connect({ 40 | protocol, 41 | host, 42 | port, 43 | path, 44 | protocolVersion, 45 | clientId: uniqueId, 46 | username: envConfig.MQTT_USERNAME, 47 | password: envConfig.MQTT_PASSWORD, 48 | reconnectPeriod: 5000, 49 | queueQoSZero: true, 50 | resubscribe: true, 51 | clean: true, 52 | keepalive: 30, 53 | properties: protocolVersion === 5 ? { 54 | sessionExpiryInterval: 600 55 | } : undefined, 56 | forceNativeWebSocket: true 57 | }) 58 | .on('connect', () => { 59 | setMqttStatus('Connected') 60 | }) 61 | .on('error', (error) => { 62 | setMqttStatus('Error') 63 | emitStateError(setMqttError, 'MqttGeneral', error) 64 | }) 65 | .on('disconnect', (packet) => { 66 | setMqttStatus('Disconnected') 67 | }) 68 | .on('offline', () => { 69 | setMqttStatus('Offline') 70 | }) 71 | .on('reconnect', () => { 72 | setMqttStatus('Reconnecting') 73 | }) 74 | .on('close', () => { 75 | setMqttStatus('Disconnected') 76 | }) 77 | .on('message', (topic, message, packet) => { 78 | onMessage(topic, message) 79 | }) 80 | 81 | 82 | return client 83 | 84 | } 85 | 86 | 87 | export { 88 | createMqttClient 89 | } 90 | -------------------------------------------------------------------------------- /src/config/environment.js: -------------------------------------------------------------------------------- 1 | 2 | const envConfig = { 3 | TZ: process.env.EXPO_PUBLIC_TZ ?? 'UTC', 4 | MQTT_HOST: process.env.EXPO_PUBLIC_MQTT_HOST ?? 'localhost', 5 | MQTT_SSL: process.env.EXPO_PUBLIC_MQTT_SSL === 'true', 6 | MQTT_PORT: typeof process.env.EXPO_PUBLIC_MQTT_PORT === 'string' ? parseInt(process.env.EXPO_PUBLIC_MQTT_PORT) : 0, 7 | MQTT_PORT_SSL: typeof process.env.EXPO_PUBLIC_MQTT_PORT_SSL === 'string' ? parseInt(process.env.EXPO_PUBLIC_MQTT_PORT_SSL) : 0, 8 | MQTT_VERSION: typeof process.env.EXPO_PUBLIC_MQTT_VERSION === 'string' ? parseInt(process.env.EXPO_PUBLIC_MQTT_VERSION) : 0, 9 | MQTT_QOS: typeof process.env.EXPO_PUBLIC_MQTT_QOS === 'string' ? parseInt(process.env.EXPO_PUBLIC_MQTT_QOS) : 0, 10 | MQTT_USERNAME: (process.env.EXPO_PUBLIC_MQTT_USERNAME ?? '').length === 0 ? undefined : process.env.EXPO_PUBLIC_MQTT_USERNAME, 11 | MQTT_PASSWORD: (process.env.EXPO_PUBLIC_MQTT_PASSWORD ?? '').length === 0 ? undefined : process.env.EXPO_PUBLIC_MQTT_PASSWORD, 12 | MQTT_TOPICS: typeof process.env.EXPO_PUBLIC_MQTT_TOPICS === 'string' ? process.env.EXPO_PUBLIC_MQTT_TOPICS.split(',') : [], 13 | EMIT_CONSOLE_LOGS: process.env.EXPO_PUBLIC_EMIT_CONSOLE_LOGS === 'true', 14 | } 15 | 16 | 17 | export { 18 | envConfig 19 | } -------------------------------------------------------------------------------- /src/constants/Colors.js: -------------------------------------------------------------------------------- 1 | export const COLORS = { 2 | bg: '#000926', 3 | primary: '#001043', 4 | secondary: '#FEFEFE', 5 | text: '#FFFFFF', 6 | } 7 | -------------------------------------------------------------------------------- /src/context/MqttContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from 'react' 2 | import useAppStateBackground from 'src/hooks/useAppStateReconnect' 3 | import useMqttConnection from 'src/hooks/useMqttConnection' 4 | import { emitStateError } from 'src/Services/errorHandler' 5 | 6 | 7 | /** 8 | * @typedef {(topics: string[], options?: { qos: 0 | 1 | 2 }) => void} SubscribeToTopic 9 | */ 10 | 11 | const MqttContext = createContext(null) 12 | 13 | export const MqttProvider = ({ children }) => { 14 | 15 | const [doMqttConnection, setDoMqttConnection] = useState(true) 16 | const { mqttClient, mqttData, mqttStatus, mqttError, setMqttError, setMqttStatus } = useMqttConnection(doMqttConnection) 17 | 18 | useAppStateBackground(mqttClient) 19 | 20 | /** 21 | * @type {SubscribeToTopic} 22 | */ 23 | const subscribeToTopic = (topics, { qos = 1 } = {}) => { 24 | 25 | if (!mqttClient) return 26 | 27 | for (const topic of topics) { 28 | 29 | mqttClient.subscribe(topic, { qos }, (error, granted) => { 30 | if (error) { 31 | setMqttStatus('Error'); 32 | emitStateError(setMqttError, 'MqttTopic', error); 33 | } 34 | }); 35 | 36 | } 37 | 38 | } 39 | 40 | 41 | return ( 42 | 52 | {children} 53 | 54 | ) 55 | 56 | } 57 | 58 | 59 | /** 60 | * @type {{() => { 61 | * mqttClient: import('src/hooks/useMqttConnection').MqttClient, 62 | * mqttData: import('src/hooks/useMqttConnection').MqttData, 63 | * mqttStatus: import('src/hooks/useMqttConnection').MqttStatus, 64 | * mqttError: import('src/hooks/useMqttConnection').MqttError, 65 | * subscribeToTopic: SubscribeToTopic, 66 | * }}} 67 | */ 68 | export const useMqtt = () => useContext(MqttContext) 69 | -------------------------------------------------------------------------------- /src/hooks/useAppStateReconnect.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import { AppState } from 'react-native' 3 | 4 | function useAppStateBackground(mqttClient) { 5 | 6 | const appState = useRef(AppState.currentState) 7 | 8 | useEffect(() => { 9 | 10 | const subscription = AppState.addEventListener('change', (nextAppState) => { 11 | 12 | if ( 13 | appState.current.match(/inactive|background/) && 14 | nextAppState === 'active' && 15 | mqttClient 16 | ) { 17 | mqttClient.reconnect() 18 | } else if (mqttClient) { 19 | mqttClient.end() 20 | } 21 | 22 | appState.current = nextAppState 23 | 24 | }) 25 | 26 | return () => { 27 | subscription.remove() 28 | } 29 | 30 | }, [mqttClient]) 31 | 32 | 33 | return appState.current 34 | 35 | } 36 | 37 | 38 | export default useAppStateBackground 39 | -------------------------------------------------------------------------------- /src/hooks/useMqttConnection.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { createMqttClient } from 'src/Services/mqttService' 3 | 4 | 5 | /** 6 | * @typedef {'Connected' | 'Disconnected' | 'Offline' | 'Reconnecting' | 'Error'} MqttStatus 7 | */ 8 | 9 | /** 10 | * @typedef {{ type: string, msg: string }} MqttError 11 | */ 12 | 13 | /** 14 | * @typedef {{ message: any, topic: string }} MqttData 15 | */ 16 | 17 | /** 18 | * @typedef {import('mqtt').MqttClient} MqttClient 19 | */ 20 | 21 | function useMqttConnection(doMqttConnection) { 22 | 23 | /** 24 | * @type [MqttStatus, React.Dispatch] 25 | */ 26 | const [mqttStatus, setMqttStatus] = useState('Disconnected') 27 | 28 | /** 29 | * @type [MqttError, React.Dispatch] 30 | */ 31 | const [mqttError, setMqttError] = useState({}) 32 | 33 | /** 34 | * @type [MqttData, React.Dispatch] 35 | */ 36 | const [mqttData, setMqttData] = useState({}) 37 | 38 | /** 39 | * @type [MqttClient, React.Dispatch] 40 | */ 41 | const [mqttClient, setMqttClient] = useState(null) 42 | 43 | useEffect(() => { 44 | 45 | if (!doMqttConnection) return 46 | 47 | const client = createMqttClient({ 48 | setMqttStatus, 49 | setMqttError, 50 | uniqueId: 'react-native-0000', 51 | onMessage: (topic, message) => { 52 | setMqttData(() => ({ 53 | message, 54 | topic 55 | })) 56 | }, 57 | }) 58 | 59 | setMqttClient(client) 60 | 61 | 62 | return () => { 63 | if (client) { 64 | client.end() 65 | } 66 | } 67 | 68 | }, [doMqttConnection]) 69 | 70 | 71 | return { 72 | mqttClient, 73 | mqttData, 74 | mqttStatus, 75 | mqttError, 76 | setMqttStatus, 77 | setMqttError 78 | } 79 | 80 | } 81 | 82 | export default useMqttConnection 83 | -------------------------------------------------------------------------------- /src/navigator/Navigation.jsx: -------------------------------------------------------------------------------- 1 | import { NavigationContainer } from '@react-navigation/native' 2 | import React from 'react' 3 | import Stacks from './StackNavigator' 4 | 5 | 6 | const Navigation = () => { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | 15 | export default Navigation 16 | -------------------------------------------------------------------------------- /src/navigator/StackNavigator.jsx: -------------------------------------------------------------------------------- 1 | import { createNativeStackNavigator } from '@react-navigation/native-stack' 2 | import MttqScreen from 'src/screen/Mttq' 3 | import { COLORS } from 'src/constants/Colors' 4 | 5 | 6 | const Stack = createNativeStackNavigator() 7 | 8 | const stackOptions = { 9 | // headerShown: false, 10 | headerStyle: { 11 | backgroundColor: COLORS.bg, 12 | }, 13 | headerTransparent: true, 14 | headerTintColor: COLORS.text, 15 | headerTitleStyle: { 16 | fontWeight: 'bold', 17 | }, 18 | headerTitleAlign: 'center', 19 | } 20 | 21 | const Stacks = () => { 22 | return ( 23 | 24 | 29 | 30 | ) 31 | } 32 | 33 | 34 | export default Stacks 35 | -------------------------------------------------------------------------------- /src/screen/Mttq.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Button, StyleSheet, Text, View } from 'react-native' 3 | import { envConfig } from 'src/config/environment' 4 | import { useMqtt } from 'src/context/MqttContext' 5 | 6 | 7 | let dateLastTopicRecived = 'N/A'; 8 | 9 | const MttqScreen = () => { 10 | 11 | const { mqttClient, mqttData, mqttStatus, mqttError, subscribeToTopic } = useMqtt() 12 | 13 | useEffect(() => { 14 | subscribeToTopic(envConfig.MQTT_TOPICS, { qos: envConfig.MQTT_QOS }) 15 | }, []) 16 | 17 | /** 18 | * @param {string | null | undefined} topic 19 | */ 20 | const lastTopicRecived = (topic) => { 21 | if (topic) { 22 | dateLastTopicRecived = (new Date()).toLocaleString('af-ZA', { timeZone: envConfig.TZ }) 23 | } 24 | 25 | return dateLastTopicRecived; 26 | } 27 | 28 | return ( 29 | 37 | [Connection Status] 38 | {mqttStatus} 39 | 40 | [Topic] 41 | {mqttData?.topic ?? 'N/A'} 42 | 43 | [Date Last Topic Received] 44 | {lastTopicRecived(mqttData?.topic)} 45 | 46 | 47 | {mqttError?.type ?? '[Error:N/A]'} 48 | 49 | 50 | {mqttError?.msg ?? ''} 51 | 52 | 53 | 59 |