-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathuseStartBackgroundLocationUpdates.ts
More file actions
133 lines (122 loc) · 4.49 KB
/
useStartBackgroundLocationUpdates.ts
File metadata and controls
133 lines (122 loc) · 4.49 KB
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
import * as Location from 'expo-location';
import { useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { store } from '~/store';
import {
backgroundLocationTrackingAtom,
setLocation,
} from '~/store/atoms/location';
import navigationState from '~/store/atoms/navigation';
import {
LOCATION_START_MAX_RETRIES,
LOCATION_START_RETRY_BASE_DELAY_MS,
LOCATION_TASK_NAME,
LOCATION_TASK_OPTIONS,
} from '../constants';
import { translate } from '../translation';
import { useLocationPermissionsGranted } from './useLocationPermissionsGranted';
const wait = (ms: number) =>
new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});
export const useStartBackgroundLocationUpdates = () => {
const bgPermGranted = useLocationPermissionsGranted();
const { autoModeEnabled } = useAtomValue(navigationState);
useEffect(() => {
if (autoModeEnabled || !bgPermGranted) {
return;
}
let cancelled = false;
(async () => {
for (let attempt = 0; attempt <= LOCATION_START_MAX_RETRIES; attempt++) {
if (cancelled) {
return;
}
try {
// Android/iOS共通でexpo-locationのフォアグラウンドサービスを使用
// Androidではフォアグラウンドサービスにより、バックグラウンドでの位置情報更新の
// スロットリングを回避し、アプリプロセスの生存を維持する(Android 8以降の制約)
// ただしexpo-task-managerのJS配信はJobScheduler経由のため、
// Android 16のクォータ制限の影響を受ける点に注意
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
...LOCATION_TASK_OPTIONS,
// NOTE: マップマッチが勝手に行われると電車での経路と大きく異なることがあるはずなので
// OtherNavigationは必須
activityType: Location.ActivityType.OtherNavigation,
showsBackgroundLocationIndicator: true,
foregroundService: {
notificationTitle: translate('bgAlertTitle'),
notificationBody: translate('bgAlertContent'),
killServiceOnDestroy: false,
},
});
// クリーンアップがstartの完了前に実行された場合、
// stopが先に走り開始済みの更新が残るため、ここで再度停止する
if (cancelled) {
try {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
} catch (stopError) {
console.warn(
'バックグラウンド位置情報の更新停止に失敗しました:',
stopError
);
}
} else {
store.set(backgroundLocationTrackingAtom, true);
}
return;
} catch (error) {
if (attempt < LOCATION_START_MAX_RETRIES) {
const delay = LOCATION_START_RETRY_BASE_DELAY_MS * 2 ** attempt;
console.warn(
`バックグラウンド位置情報の更新開始に失敗しました(リトライ ${attempt + 1}/${LOCATION_START_MAX_RETRIES}):`,
error
);
await wait(delay);
} else {
console.warn(
'バックグラウンド位置情報の更新開始に失敗しました(リトライ上限到達):',
error
);
}
}
}
})();
return () => {
cancelled = true;
store.set(backgroundLocationTrackingAtom, false);
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME).catch((error) => {
console.warn(
'バックグラウンド位置情報の更新停止に失敗しました:',
error
);
});
};
}, [autoModeEnabled, bgPermGranted]);
useEffect(() => {
let watchPositionSub: Location.LocationSubscription | null = null;
let cancelled = false;
if (autoModeEnabled || bgPermGranted) {
return;
}
(async () => {
try {
const sub = await Location.watchPositionAsync(
LOCATION_TASK_OPTIONS,
setLocation
);
if (cancelled) {
sub.remove();
} else {
watchPositionSub = sub;
}
} catch (error) {
console.warn('位置情報の監視開始に失敗しました:', error);
}
})();
return () => {
cancelled = true;
watchPositionSub?.remove();
};
}, [autoModeEnabled, bgPermGranted]);
};