react-native-geofence-manager 0.1.0-beta.14 → 0.1.0-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,173 +1,161 @@
1
- # react-native-geofence-manager
2
-
3
- > **⚠️ Status: POC (proof of concept)**
4
- > Esta biblioteca é um **protótipo experimental**. A API, o comportamento e a estabilidade podem mudar sem aviso prévio. **Não use em produção** sem avaliar riscos e fazer testes próprios.
5
-
6
- > **📱 Plataforma: apenas Android**
7
- > A implementação nativa existe **somente para Android** (Google Play Services / Geofencing API). No **iOS** não há suporte funcional — não espere geofences nesta lib em dispositivos Apple.
8
-
9
- ---
10
-
11
- ## O que faz
12
-
13
- Permite registrar **geofences circulares** e receber eventos de **entrada**, **saída** e **permanência (dwell)** no JavaScript, inclusive quando o app está em **segundo plano** ou **fechado**, desde que a headless task esteja registrada no `index` do app.
14
-
15
- ---
16
-
17
- ## Instalação
18
-
19
- ```sh
20
- npm install react-native-geofence-manager
21
- # ou
22
- yarn add react-native-geofence-manager
23
- ```
24
-
25
- Siga o fluxo normal de autolinking do React Native. **Use apenas em projetos Android** para obter o comportamento descrito abaixo.
26
-
27
- ---
28
-
29
- ## Uso
30
-
31
- ### 1. Registrar a headless task (obrigatório para eventos com app em background/fechado)
32
-
33
- No arquivo de entrada do app (`index.js` / `index.tsx`), **antes** de `AppRegistry.registerComponent`:
34
-
35
- ```tsx
36
- import { AppRegistry } from 'react-native';
37
- import {
38
- registerGeofenceHeadlessTask,
39
- type GeofenceEvent,
40
- } from 'react-native-geofence-manager';
41
- import App from './App';
42
- import { name as appName } from './app.json';
43
-
44
- registerGeofenceHeadlessTask((event: GeofenceEvent) => {
45
- // Ex.: notificação local, log, envio para API
46
- console.log('Geofence (headless):', event.geofenceId, event.event);
47
- });
48
-
49
- AppRegistry.registerComponent(appName, () => App);
50
- ```
51
-
52
- ### 2. Adicionar geofences
53
-
54
- ```tsx
55
- import { addGeofences } from 'react-native-geofence-manager';
56
-
57
- const ok = await addGeofences([
58
- {
59
- id: 'loja-1',
60
- name: 'Loja centro',
61
- latitude: -23.5505,
62
- longitude: -46.6333,
63
- radius: 150, // metros (opcional; padrão nativo ~100 m se omitido)
64
- },
65
- ]);
66
- // ok === true se o registro foi aceito pelo sistema
67
- ```
68
-
69
- ### 3. Listener com o app em primeiro plano
70
-
71
- ```tsx
72
- import { addGeofenceEventListener } from 'react-native-geofence-manager';
73
-
74
- const unsubscribe = addGeofenceEventListener((event) => {
75
- console.log(event.geofenceId, event.event); // 'enter' | 'exit' | 'dwell'
76
- });
77
-
78
- // ao desmontar:
79
- unsubscribe();
80
- ```
81
-
82
- ### 4. Remover todas as geofences
83
-
84
- ```tsx
85
- import { removeAllGeofences } from 'react-native-geofence-manager';
86
-
87
- await removeAllGeofences();
88
- ```
89
-
90
- ---
91
-
92
- ## Modelo de geofence (`GeofenceModel`)
93
-
94
- | Campo | Tipo | Obrigatório | Descrição |
95
- |---------------|----------|-------------|------------------------------------|
96
- | `id` | `string` | Sim | ID único da geofence |
97
- | `latitude` | `number` | Sim | Latitude do centro |
98
- | `longitude` | `number` | Sim | Longitude do centro |
99
- | `name` | `string` | Não | Nome exibido nos eventos |
100
- | `radius` | `number` | Não | Raio em metros |
101
-
102
- ---
103
-
104
- ## Evento (`GeofenceEvent`)
105
-
106
- - `geofenceId` — ID da geofence
107
- - `event` `'enter' | 'exit' | 'dwell'`
108
- - `timestamp`, `name`, `latitude`, `longitude`, `radius` — quando disponíveis
109
-
110
- ---
111
-
112
- ## Permissões (Android)
113
-
114
- Para geofences confiáveis é necessário **localização** (fina ou aproximada) e, em geral, **localização em segundo plano** (Android 10+) se quiser eventos com o app fechado. Notificações (API 33+) dependem do seu fluxo. Consulte a documentação oficial do Android e o app de exemplo.
115
-
116
- ---
117
-
118
- ## Depuração (logs)
119
-
120
- A lib emite logs em cada etapa para localizar falhas (SDK nativo + JS).
121
-
122
- **Android (Logcat)** filtre pela tag:
123
-
124
- ```text
125
- adb logcat -s GeofenceManager
126
- ```
127
-
128
- Você verá, entre outros: inicialização do módulo, `addGeofences` (sucesso/falha do Play Services), `BroadcastReceiver` (transição, foreground vs headless), `HeadlessTaskService` e erros de reflexão.
129
-
130
- **JavaScript** — mensagens com prefixo `[GeofenceManager]` no console (Metro) e também no Logcat como `ReactNativeJS` ao rodar no dispositivo.
131
-
132
- ---
133
-
134
- ## App de exemplo
135
-
136
- Na raiz do repositório:
137
-
138
- ```sh
139
- yarn example start
140
- yarn example android
141
- ```
142
-
143
- O diretório `example/` demonstra permissões, registro de geofences, listener e notificações locais (Notifee).
144
-
145
- ---
146
-
147
- ## API exportada (resumo)
148
-
149
- | Export | Descrição |
150
- |--------------------------------|------------------------------------------------|
151
- | `addGeofences` | Registra uma lista de geofences |
152
- | `removeAllGeofences` | Remove todas as geofences registradas |
153
- | `addGeofenceEventListener` | Inscreve callback para eventos (foreground) |
154
- | `onGeofenceEvent` | Alias de `addGeofenceEventListener` |
155
- | `registerGeofenceHeadlessTask` | Registra handler para background/app fechado |
156
- | `GEOFENCE_HEADLESS_TASK_NAME` | Nome interno da headless task |
157
- | Tipos `GeofenceModel`, `GeofenceEvent`, etc. | TypeScript |
158
-
159
- ---
160
-
161
- ## Contributing
162
-
163
- - [Development workflow](CONTRIBUTING.md#development-workflow)
164
- - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
165
- - [Code of conduct](CODE_OF_CONDUCT.md)
166
-
167
- ## License
168
-
169
- MIT
170
-
171
- ---
172
-
173
- Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
1
+ # react-native-geofence-manager
2
+
3
+ > **⚠️ Status: POC (proof of concept)**
4
+ > Esta biblioteca é um **protótipo experimental**. A API, o comportamento e a estabilidade podem mudar sem aviso prévio. **Não use em produção** sem avaliar riscos e fazer testes próprios.
5
+
6
+ > **📱 Plataforma: apenas Android**
7
+ > A implementação nativa existe **somente para Android** (Google Play Services / Geofencing API). No **iOS** não há suporte funcional — não espere geofences nesta lib em dispositivos Apple.
8
+
9
+ ---
10
+
11
+ ## O que faz
12
+
13
+ Permite registrar **geofences circulares** e receber eventos de **entrada**, **saída** e **permanência (dwell)** no JavaScript, inclusive quando o app está em **segundo plano** ou **fechado**, desde que a headless task esteja registrada no `index` do app.
14
+
15
+ ---
16
+
17
+ ## Instalação
18
+
19
+ ```sh
20
+ npm install react-native-geofence-manager
21
+ # ou
22
+ yarn add react-native-geofence-manager
23
+ ```
24
+
25
+ Siga o fluxo normal de autolinking do React Native. **Use apenas em projetos Android** para obter o comportamento descrito abaixo.
26
+
27
+ ---
28
+
29
+ ## Uso
30
+
31
+ ### 1. Registrar a headless task (obrigatório para eventos com app em background/fechado)
32
+
33
+ No arquivo de entrada do app (`index.js` / `index.tsx`), **antes** de `AppRegistry.registerComponent`:
34
+
35
+ ```tsx
36
+ import { AppRegistry } from 'react-native';
37
+ import {
38
+ registerGeofenceHeadlessTask,
39
+ type GeofenceEvent,
40
+ } from 'react-native-geofence-manager';
41
+ import App from './App';
42
+ import { name as appName } from './app.json';
43
+
44
+ registerGeofenceHeadlessTask((event: GeofenceEvent) => {
45
+ // Ex.: notificação local, log, envio para API
46
+ console.log('Geofence (headless):', event.geofenceId, event.event);
47
+ });
48
+
49
+ AppRegistry.registerComponent(appName, () => App);
50
+ ```
51
+
52
+ ### 2. Adicionar geofences
53
+
54
+ ```tsx
55
+ import { addGeofences } from 'react-native-geofence-manager';
56
+
57
+ const ok = await addGeofences([
58
+ {
59
+ id: 'loja-1',
60
+ name: 'Loja centro',
61
+ latitude: -23.5505,
62
+ longitude: -46.6333,
63
+ radius: 150,
64
+ // opcional: string livre (ex. JSON) — volta em GeofenceEvent.data
65
+ data: JSON.stringify({ lojaId: '123', promo: true }),
66
+ },
67
+ ]);
68
+ // ok === true se o registro foi aceito pelo sistema
69
+ ```
70
+
71
+ ### 3. Listener com o app em primeiro plano
72
+
73
+ ```tsx
74
+ import { addGeofenceEventListener } from 'react-native-geofence-manager';
75
+
76
+ const unsubscribe = addGeofenceEventListener((event) => {
77
+ console.log(event.geofenceId, event.event); // 'enter' | 'exit' | 'dwell'
78
+ });
79
+
80
+ // ao desmontar:
81
+ unsubscribe();
82
+ ```
83
+
84
+ ### 4. Remover todas as geofences
85
+
86
+ ```tsx
87
+ import { removeAllGeofences } from 'react-native-geofence-manager';
88
+
89
+ await removeAllGeofences();
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Modelo de geofence (`GeofenceModel`)
95
+
96
+ | Campo | Tipo | Obrigatório | Descrição |
97
+ |---------------|----------|-------------|------------------------------------|
98
+ | `id` | `string` | Sim | ID único da geofence |
99
+ | `latitude` | `number` | Sim | Latitude do centro |
100
+ | `longitude` | `number` | Sim | Longitude do centro |
101
+ | `name` | `string` | Não | Nome exibido nos eventos |
102
+ | `radius` | `number` | Não | Raio em metros |
103
+ | `data` | `string` | Não | Payload opaco; repassado no evento para você dar `JSON.parse` etc. |
104
+
105
+ ---
106
+
107
+ ## Evento (`GeofenceEvent`)
108
+
109
+ - `geofenceId` — ID da geofence
110
+ - `event` — `'enter' | 'exit' | 'dwell'`
111
+ - `timestamp`, `name`, `latitude`, `longitude`, `radius` — quando disponíveis
112
+ - `data` — mesma string definida em `GeofenceModel.data` (vazio se não houver)
113
+
114
+ ---
115
+
116
+ ## Permissões (Android)
117
+
118
+ Para geofences confiáveis é necessário **localização** (fina ou aproximada) e, em geral, **localização em segundo plano** (Android 10+) se quiser eventos com o app fechado. Notificações (API 33+) dependem do seu fluxo. Consulte a documentação oficial do Android e o app de exemplo.
119
+
120
+ ---
121
+
122
+ ## App de exemplo
123
+
124
+ Na raiz do repositório:
125
+
126
+ ```sh
127
+ yarn example start
128
+ yarn example android
129
+ ```
130
+
131
+ O diretório `example/` demonstra permissões, registro de geofences, listener e notificações locais (Notifee).
132
+
133
+ ---
134
+
135
+ ## API exportada (resumo)
136
+
137
+ | Export | Descrição |
138
+ |--------------------------------|------------------------------------------------|
139
+ | `addGeofences` | Registra uma lista de geofences |
140
+ | `removeAllGeofences` | Remove todas as geofences registradas |
141
+ | `addGeofenceEventListener` | Inscreve callback para eventos (foreground) |
142
+ | `onGeofenceEvent` | Alias de `addGeofenceEventListener` |
143
+ | `registerGeofenceHeadlessTask` | Registra handler para background/app fechado |
144
+ | `GEOFENCE_HEADLESS_TASK_NAME` | Nome interno da headless task |
145
+ | Tipos `GeofenceModel`, `GeofenceEvent`, etc. | TypeScript |
146
+
147
+ ---
148
+
149
+ ## Contributing
150
+
151
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
152
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
153
+ - [Code of conduct](CODE_OF_CONDUCT.md)
154
+
155
+ ## License
156
+
157
+ MIT
158
+
159
+ ---
160
+
161
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -1,133 +1,91 @@
1
- package com.geofencemanager
2
-
3
- import android.content.BroadcastReceiver
4
- import android.content.Context
5
- import android.content.Intent
6
- import android.os.Build
7
- import android.util.Log
8
- import android.os.Handler
9
- import android.os.Looper
10
- import com.facebook.react.HeadlessJsTaskService
11
- import com.facebook.react.bridge.Arguments
12
- import com.facebook.react.bridge.ReactApplicationContext
13
- import com.facebook.react.bridge.WritableMap
14
- import com.facebook.react.modules.core.DeviceEventManagerModule
15
- import com.google.android.gms.location.Geofence
16
- import com.google.android.gms.location.GeofencingEvent
17
-
18
- class GeofenceBroadcastReceiver : BroadcastReceiver() {
19
-
20
- companion object {
21
- private const val TAG = "GeofenceManager"
22
- }
23
-
24
- override fun onReceive(context: Context?, intent: Intent?) {
25
- Log.i(TAG, "BroadcastReceiver.onReceive: invoked action=${intent?.action}")
26
- if (context == null || intent == null) {
27
- Log.w(TAG, "BroadcastReceiver.onReceive: abort null context or intent")
28
- return
29
- }
30
- if (intent.action != GeofenceManagerModule.GEOFENCE_ACTION) {
31
- Log.w(TAG, "BroadcastReceiver.onReceive: wrong action, expected ${GeofenceManagerModule.GEOFENCE_ACTION}")
32
- return
33
- }
34
-
35
- val geofencingEvent = GeofencingEvent.fromIntent(intent)
36
- if (geofencingEvent == null) {
37
- Log.e(TAG, "BroadcastReceiver.onReceive: GeofencingEvent.fromIntent returned null")
38
- return
39
- }
40
- if (geofencingEvent.hasError()) {
41
- Log.e(TAG, "BroadcastReceiver.onReceive: Geofencing error code=${geofencingEvent.errorCode}")
42
- return
43
- }
44
-
45
- if (GeofenceManagerModule.isWithinGracePeriod(context)) {
46
- Log.i(TAG, "BroadcastReceiver.onReceive: skipped — within grace period after addGeofences")
47
- return
48
- }
49
-
50
- val transition = geofencingEvent.geofenceTransition
51
- val eventName = when (transition) {
52
- Geofence.GEOFENCE_TRANSITION_ENTER -> "enter"
53
- Geofence.GEOFENCE_TRANSITION_EXIT -> "exit"
54
- Geofence.GEOFENCE_TRANSITION_DWELL -> "dwell"
55
- else -> {
56
- Log.w(TAG, "BroadcastReceiver.onReceive: unknown transition=$transition")
57
- return
58
- }
59
- }
60
- Log.i(TAG, "BroadcastReceiver.onReceive: transition=$eventName")
61
-
62
- val triggeringGeofences = geofencingEvent.triggeringGeofences
63
- if (triggeringGeofences == null || triggeringGeofences.isEmpty()) {
64
- Log.w(TAG, "BroadcastReceiver.onReceive: no triggering geofences")
65
- return
66
- }
67
-
68
- val reactContext = GeofenceManagerModule.reactContextRef
69
- val timestamp = System.currentTimeMillis().toDouble()
70
- val isForeground = reactContext != null && reactContext.hasActiveReactInstance()
71
- Log.i(
72
- TAG,
73
- "BroadcastReceiver.onReceive: ids=${triggeringGeofences.map { it.requestId }} isForeground=$isForeground hasReactContextRef=${reactContext != null}"
74
- )
75
-
76
- if (isForeground) {
77
- val ctx = reactContext!!
78
- val geofenceIds = triggeringGeofences.map { it.requestId }
79
- val eventNameForRunnable = eventName
80
- Handler(Looper.getMainLooper()).postDelayed({
81
- if (!ctx.hasActiveReactInstance()) {
82
- Log.w(TAG, "BroadcastReceiver: foreground path — React instance gone after delay, skipping emit")
83
- return@postDelayed
84
- }
85
- try {
86
- val emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
87
- for (geofenceId in geofenceIds) {
88
- val details = GeofenceManagerModule.getGeofenceDetails(context, geofenceId)
89
- val params = Arguments.createMap().apply {
90
- putString("geofenceId", geofenceId)
91
- putString("event", eventNameForRunnable)
92
- putDouble("timestamp", timestamp)
93
- putString("name", details?.name ?: "")
94
- details?.let {
95
- putDouble("latitude", it.latitude)
96
- putDouble("longitude", it.longitude)
97
- putDouble("radius", it.radius.toDouble())
98
- }
99
- }
100
- Log.i(TAG, "BroadcastReceiver: emitting onGeofenceTransition id=$geofenceId event=$eventNameForRunnable")
101
- emitter.emit("onGeofenceTransition", params)
102
- }
103
- } catch (e: Exception) {
104
- Log.e(TAG, "BroadcastReceiver: emit to JS failed", e)
105
- }
106
- }, 50)
107
- return
108
- }
109
-
110
- Log.i(TAG, "BroadcastReceiver: background/headless path — starting GeofenceHeadlessTaskService")
111
- val appContext = context.applicationContext
112
- HeadlessJsTaskService.acquireWakeLockNow(appContext)
113
- for (geofence in triggeringGeofences) {
114
- val serviceIntent = Intent(appContext, GeofenceHeadlessTaskService::class.java).apply {
115
- putExtra(GeofenceHeadlessTaskService.EXTRA_GEOFENCE_ID, geofence.requestId)
116
- putExtra(GeofenceHeadlessTaskService.EXTRA_EVENT, eventName)
117
- putExtra(GeofenceHeadlessTaskService.EXTRA_TIMESTAMP, timestamp)
118
- }
119
- try {
120
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
121
- appContext.startForegroundService(serviceIntent)
122
- Log.i(TAG, "BroadcastReceiver: startForegroundService for id=${geofence.requestId}")
123
- } else {
124
- appContext.startService(serviceIntent)
125
- Log.i(TAG, "BroadcastReceiver: startService for id=${geofence.requestId}")
126
- }
127
- } catch (e: Exception) {
128
- Log.e(TAG, "BroadcastReceiver: failed to start headless service", e)
129
- }
130
- }
131
- }
132
-
133
- }
1
+ package com.geofencemanager
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.os.Build
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import com.facebook.react.HeadlessJsTaskService
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.modules.core.DeviceEventManagerModule
12
+ import com.google.android.gms.location.Geofence
13
+ import com.google.android.gms.location.GeofencingEvent
14
+
15
+ class GeofenceBroadcastReceiver : BroadcastReceiver() {
16
+
17
+ override fun onReceive(context: Context?, intent: Intent?) {
18
+ if (context == null || intent == null) return
19
+ if (intent.action != GeofenceManagerModule.GEOFENCE_ACTION) return
20
+
21
+ val geofencingEvent = GeofencingEvent.fromIntent(intent) ?: return
22
+ if (geofencingEvent.hasError()) return
23
+
24
+ if (GeofenceManagerModule.isWithinGracePeriod(context)) return
25
+
26
+ val transition = geofencingEvent.geofenceTransition
27
+ val eventName = when (transition) {
28
+ Geofence.GEOFENCE_TRANSITION_ENTER -> "enter"
29
+ Geofence.GEOFENCE_TRANSITION_EXIT -> "exit"
30
+ Geofence.GEOFENCE_TRANSITION_DWELL -> "dwell"
31
+ else -> return
32
+ }
33
+
34
+ val triggeringGeofences = geofencingEvent.triggeringGeofences ?: return
35
+ if (triggeringGeofences.isEmpty()) return
36
+
37
+ val reactContext = GeofenceManagerModule.reactContextRef
38
+ val timestamp = System.currentTimeMillis().toDouble()
39
+ val isForeground = reactContext != null && reactContext.hasActiveReactInstance()
40
+
41
+ if (isForeground) {
42
+ val ctx = reactContext!!
43
+ val geofenceIds = triggeringGeofences.map { it.requestId }
44
+ val eventNameForRunnable = eventName
45
+ Handler(Looper.getMainLooper()).postDelayed({
46
+ if (!ctx.hasActiveReactInstance()) return@postDelayed
47
+ try {
48
+ val emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
49
+ for (geofenceId in geofenceIds) {
50
+ val details = GeofenceManagerModule.getGeofenceDetails(context, geofenceId)
51
+ val params = GeofenceManagerModule.buildEventParams(
52
+ geofenceId,
53
+ eventNameForRunnable,
54
+ timestamp,
55
+ details
56
+ ) ?: continue
57
+ emitter.emit("onGeofenceTransition", params)
58
+ }
59
+ } catch (_: Exception) { }
60
+ }, 50)
61
+ return
62
+ }
63
+
64
+ val appContext = context.applicationContext
65
+ HeadlessJsTaskService.acquireWakeLockNow(appContext)
66
+ for (geofence in triggeringGeofences) {
67
+ val details = GeofenceManagerModule.getGeofenceDetails(context, geofence.requestId)
68
+ if (GeofenceManagerModule.buildEventParams(
69
+ geofence.requestId,
70
+ eventName,
71
+ timestamp,
72
+ details
73
+ ) == null
74
+ ) {
75
+ continue
76
+ }
77
+ val serviceIntent = Intent(appContext, GeofenceHeadlessTaskService::class.java).apply {
78
+ putExtra(GeofenceHeadlessTaskService.EXTRA_GEOFENCE_ID, geofence.requestId)
79
+ putExtra(GeofenceHeadlessTaskService.EXTRA_EVENT, eventName)
80
+ putExtra(GeofenceHeadlessTaskService.EXTRA_TIMESTAMP, timestamp)
81
+ }
82
+ try {
83
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
84
+ appContext.startForegroundService(serviceIntent)
85
+ } else {
86
+ appContext.startService(serviceIntent)
87
+ }
88
+ } catch (_: Exception) { }
89
+ }
90
+ }
91
+ }