Skip to content

Commit

Permalink
working on dexcom sync in background fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorpfiz committed Sep 13, 2024
1 parent b94b05f commit d239949
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 4 deletions.
3 changes: 3 additions & 0 deletions apps/expo/src/app/(app)/(tabs)/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import DexcomCGMData from "~/components/dexcom/dexcom-data";
import DexcomDevicesList from "~/components/dexcom/dexcom-devices";
import { DexcomLogin } from "~/components/dexcom/dexcom-login";
import { DexcomBackgroundSync } from "~/components/dexcom/dexcom-sync";
import { ChangeRangeSetting } from "~/components/glucose/change-range-setting";
import { CalculateRecap } from "~/components/glucose/create-recap";
import { ThemeToggle } from "~/components/theme-toggle";
Expand Down Expand Up @@ -51,6 +52,8 @@ export default function AccountScreen() {

<CalculateRecap />

<DexcomBackgroundSync />

<DexcomDevicesList />
</ScrollView>
);
Expand Down
79 changes: 79 additions & 0 deletions apps/expo/src/components/dexcom/dexcom-sync.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { View } from "react-native";
import * as BackgroundFetch from "expo-background-fetch";
import * as Notifications from "expo-notifications";
import * as TaskManager from "expo-task-manager";

import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
import { useDexcomSync } from "~/hooks/use-dexcom-sync";
import { BACKGROUND_FETCH_DEXCOM_SYNC } from "~/lib/constants";
import { useGlucoseStore } from "~/stores/glucose-store";

const BACKGROUND_FETCH_TASK = "background-fetch";

// 1. Define the task by providing a name and the function that should be executed
// Note: This needs to be called in the global scope (e.g outside of your React components)
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const { lastSyncedTime } = useGlucoseStore();

try {
const result = await performSync(lastSyncedTime);

if (result.newData) {
await Notifications.scheduleNotificationAsync({
content: {
title: "New CGM Data Available",
body: "Tap to view your latest glucose readings.",
},
trigger: null,
});
return BackgroundFetch.BackgroundFetchResult.NewData;
} else {
return BackgroundFetch.BackgroundFetchResult.NoData;
}
} catch (error) {
console.error("Background sync failed:", error);
return BackgroundFetch.BackgroundFetchResult.Failed;
}
});

export function DexcomBackgroundSync() {
const { status, syncNow, isPending, isRegistered, toggleFetchTask } =
useDexcomSync();

return (
<View className="flex-1 items-center justify-center">
<View className="mb-5">
<Text className="text-base">
Background fetch status:{" "}
<Text className="font-bold">
{status !== null
? BackgroundFetch.BackgroundFetchStatus[status]
: "Unknown"}
</Text>
</Text>
<Text className="mt-2 text-base">
Background fetch task name:{" "}
<Text className="font-bold">
{isRegistered
? BACKGROUND_FETCH_DEXCOM_SYNC
: "Not registered yet!"}
</Text>
</Text>
</View>
<View className="w-4/5">
<Button onPress={toggleFetchTask}>
<Text>
{isRegistered
? "Unregister BackgroundFetch task"
: "Register BackgroundFetch task"}
</Text>
</Button>
<View className="h-4" />
<Button onPress={syncNow} disabled={isPending}>
<Text>{isPending ? "Syncing..." : "Sync Now"}</Text>
</Button>
</View>
</View>
);
}
3 changes: 0 additions & 3 deletions apps/expo/src/components/home/blood-sugar-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ export default function BloodSugarWidget() {

const bloodSugarColors = getBloodSugarColors(bloodSugar, isDark, rangeView);

console.log("Raw system time:", latestEgv?.egv?.systemTime);
console.log("Parsed system time:", localTime.toISO());

return (
<View className="flex-col items-center justify-between pb-4">
{/* Blood Sugar */}
Expand Down
110 changes: 110 additions & 0 deletions apps/expo/src/hooks/use-dexcom-sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useCallback, useEffect, useState } from "react";
import * as BackgroundFetch from "expo-background-fetch";
import * as TaskManager from "expo-task-manager";
import { DateTime } from "luxon";

import { BACKGROUND_FETCH_DEXCOM_SYNC } from "~/lib/constants";
import { useGlucoseStore } from "~/stores/glucose-store";
import { api } from "~/utils/api";

// 2. Register the task at some point in your app by providing the same name,
// and some configuration options for how the background fetch should behave
// Note: This does NOT need to be in the global scope and CAN be used in your React components!
export async function registerBackgroundFetchAsync() {
try {
await BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_DEXCOM_SYNC, {
minimumInterval: 15 * 60, // 15 minutes
stopOnTerminate: false, // android only
startOnBoot: true, // android only
});
await BackgroundFetch.setMinimumIntervalAsync(15 * 60); // 15 minutes, iOS minimum
console.log("Background fetch registered");
} catch (err) {
console.log("Background fetch failed to register", err);
}
}

// 3. (Optional) Unregister tasks by specifying the task name
// This will cancel any future background fetch calls that match the given name
// Note: This does NOT need to be in the global scope and CAN be used in your React components!
export async function unregisterBackgroundFetchAsync() {
return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_DEXCOM_SYNC);
}

export function useDexcomSync() {
const { lastSyncedTime, setLastSyncedTime } = useGlucoseStore();
const [status, setStatus] =
useState<BackgroundFetch.BackgroundFetchStatus | null>(null);
const [isRegistered, setIsRegistered] = useState(false);

const fetchDataRangeQuery = api.dexcom.fetchDataRange.useQuery({
lastSyncTime: lastSyncedTime ?? undefined,
});
const fetchAndStoreEGVsMutation = api.dexcom.fetchAndStoreEGVs.useMutation();

useEffect(() => {
void checkStatusAsync();
}, []);

const checkStatusAsync = async () => {
const fetchStatus = await BackgroundFetch.getStatusAsync();
const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_FETCH_DEXCOM_SYNC,
);
setStatus(fetchStatus);
setIsRegistered(isTaskRegistered);
};

const toggleFetchTask = async () => {
if (isRegistered) {
await unregisterBackgroundFetchAsync();
} else {
await registerBackgroundFetchAsync();
}
await checkStatusAsync();
};

const syncNow = useCallback(async () => {
try {
if (fetchDataRangeQuery.data?.egvs) {
const startDate = DateTime.fromISO(
fetchDataRangeQuery.data.egvs.start.systemTime,
{ zone: "utc" },
);
const endDate = DateTime.fromISO(
fetchDataRangeQuery.data.egvs.end.systemTime,
{ zone: "utc" },
);

const result = await fetchAndStoreEGVsMutation.mutateAsync({
startDate: startDate.toISO() ?? "",
endDate: endDate.toISO() ?? "",
});

if (result.recordsInserted > 0 && result.latestEGVTimestamp) {
setLastSyncedTime(
DateTime.fromISO(result.latestEGVTimestamp, { zone: "utc" }),
);
console.log("New data synced");
} else {
console.log("No new data available");
}
}
} catch (error) {
console.error("Sync failed", error);
}
}, [
fetchDataRangeQuery.data?.egvs,
fetchAndStoreEGVsMutation,
setLastSyncedTime,
]);

return {
syncNow,
status,
isRegistered,
toggleFetchTask,
isPending:
fetchDataRangeQuery.isPending || fetchAndStoreEGVsMutation.isPending,
};
}
2 changes: 2 additions & 0 deletions apps/expo/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ export const INTRO_CONTENT = [
fontColor: colors.pink[500],
},
];

export const BACKGROUND_FETCH_DEXCOM_SYNC = "background-fetch";
1 change: 0 additions & 1 deletion packages/api/src/router/dexcom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { TRPCRouterRecord } from "@trpc/server";
import { TRPCError } from "@trpc/server";
import { parseISO } from "date-fns";
import { DateTime } from "luxon";
import { z } from "zod";

Expand Down

0 comments on commit d239949

Please sign in to comment.