Skip to main content

Persist state

To tie everything together, let's persist the countdown state and update the notification to remind us that the thing is due.

Decide on the frequency you'd like to set and figure out how many milliseconds it is (then we can add that amount of milliseconds to Date.now() to get the next due date).

const oneDay = 24 * 60 * 60 * 1000;
const oneHour = 60 * 60 * 1000;
const oneMinute = 60 * 1000;
const oneSecond = 1 * 1000;
Update: app/counter/index.tsx
+++ b/app/counter/index.tsx
@@ -5,9 +5,17 @@ import * as Notifications from "expo-notifications";
import { useEffect, useState } from "react";
import { intervalToDuration, isBefore } from "date-fns";
import { TimeSegment } from "../../components/TimeSegment";
+import { getFromStorage, saveToStorage } from "../../utils/storage";

-// 10 seconds from now
-const timestamp = Date.now() + 10 * 1000;
+// 10 seconds in ms
+const frequency = 10 * 1000;
+
+const countdownStorageKey = "taskly-countdown";
+
+type PersistedCountdownState = {
+ currentNotificationId: string | undefined;
+ completedAtTimestamps: number[];
+};

type CountdownStatus = {
isOverdue: boolean;
@@ -15,13 +23,28 @@ type CountdownStatus = {
};

export default function CounterScreen() {
+ const [countdownState, setCountdownState] =
+ useState<PersistedCountdownState>();
const [status, setStatus] = useState<CountdownStatus>({
isOverdue: false,
distance: {},
});

+ useEffect(() => {
+ const init = async () => {
+ const value = await getFromStorage(countdownStorageKey);
+ setCountdownState(value);
+ };
+ init();
+ }, []);
+
+ const lastCompletedAt = countdownState?.completedAtTimestamps[0];
+
useEffect(() => {
const intervalId = setInterval(() => {
+ const timestamp = lastCompletedAt
+ ? lastCompletedAt + frequency
+ : Date.now();
const isOverdue = isBefore(timestamp, Date.now());

const distance = intervalToDuration(
@@ -36,17 +59,18 @@ export default function CounterScreen() {
return () => {
clearInterval(intervalId);
};
- }, []);
+ }, [lastCompletedAt]);

const scheduleNotification = async () => {
+ let pushNotificationId;
const result = await registerForPushNotificationsAsync();
if (result === "granted") {
- await Notifications.scheduleNotificationAsync({
+ pushNotificationId = await Notifications.scheduleNotificationAsync({
content: {
- title: "I'm a notification from your app! 📨",
+ title: "The thing is due!",
},
trigger: {
- seconds: 5,
+ seconds: frequency / 1000,
},
});
} else {
@@ -55,6 +79,23 @@ export default function CounterScreen() {
"Enable the notifications permission for Expo Go in settings",
);
}
+
+ if (countdownState?.currentNotificationId) {
+ await Notifications.cancelScheduledNotificationAsync(
+ countdownState.currentNotificationId,
+ );
+ }
+
+ const newCountdownState: PersistedCountdownState = {
+ currentNotificationId: pushNotificationId,
+ completedAtTimestamps: countdownState
+ ? [Date.now(), ...countdownState.completedAtTimestamps]
+ : [Date.now()],
+ };
+
+ setCountdownState(newCountdownState);
+
+ await saveToStorage(countdownStorageKey, newCountdownState);
};

return (

Checkpoint

AndroidiOS