iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
佛心分享-SideProject30

30 天開發一款真正能每天使用的散步 App系列 第 16

根據散步習慣,自動決定提醒時間

  • 分享至 

  • xImage
  •  

完成散步功能後,我希望可以在適當的時間提醒使用者出去走走,而不是每次都要自己打開 App。

一開始我想得很簡單,不就是每天晚上七點跳一則通知嗎?但仔細想想,每個人的生活習慣都不一樣。

有人習慣早上散步,有人下班後才出門,也有人只有假日有空。

如果所有人都在同一個時間收到通知,那通知就沒有意義了。

因此今天要來實作提醒功能,不過會分成兩種:

  1. 由使用者自行設定提醒時間
  2. 由 App 分析最近的散步紀錄,自動找出使用者最常散步的時間

固定時間的實作很單純,只要依照使用者設定排程即可。

今天主要想分享的是第二種,也就是 App 如何從散步紀錄找出真正適合提醒的時間。

為什麼使用 Local Notification?

App 的通知大致可以分成兩種:

  1. Push Notification:由 Server 主動推送通知。
    後端
     │
     ▼
    FCM / APNs
     │
     ▼
    使用者裝置
    
    這種方式適合聊天訊息、好友邀請、活動公告等需要由後端主動通知的情境。
    不過它需要管理裝置 Token,也要處理 Token 更新、失效,以及後端排程。
  2. Local Notification:由 App 直接在裝置上建立通知排程。

但現在需要的只是取得最近的散步紀錄,分析出使用者的散步習慣,再把通知交給 Android 或 iOS 排程即可,不需要每天都由後端在固定時間推播。

整個流程如下:

  後端提供散步紀錄
        │
        ▼
  App 分析散步習慣
        │
        ▼
   決定提醒時間
        │
        ▼
Local Notification
        │
        ▼
Android / iOS 排程
        │
        ▼
 時間到自動顯示通知

因此這裡選擇使用 Local Notification。

使用 react-native-notify-kit 建立通知

React Native 本身沒有提供完整的跨平台通知排程功能,因此我使用的是 react-native-notify-kit,它封裝了 Android 與 iOS 的通知 API,App 只需要提供通知內容以及通知時間即可。

首先請求通知權限:

import notifee from 'react-native-notify-kit';

await notifee.requestPermission();

Android 還需要建立 Notification Channel:

import notifee, { AndroidImportance } from 'react-native-notify-kit';

await notifee.createChannel({
  id: 'walk-reminder',
  name: '散步提醒',
  importance: AndroidImportance.HIGH,
});

建立通知時,只需要提供通知內容與觸發時間:

import notifee, {
  TriggerType,
  TimestampTrigger,
} from 'react-native-notify-kit';

const trigger: TimestampTrigger = {
  type: TriggerType.TIMESTAMP,
  timestamp: reminderTime.getTime(),
};

await notifee.createTriggerNotification(
  {
    id: notificationId,
    title: '該出去散步囉!',
    body: '差不多到平常散步的時間了,出去走走吧!',
    android: {
      channelId: 'walk-reminder',
    },
  },
  trigger,
);

reminderTime 就是最後決定好的提醒時間。

建立完成後,App 不需要一直在背景等待,Android 或 iOS 會在指定時間自動顯示通知。

真正困難的地方,其實不是通知本身,而是 reminderTime 到底該是多少。

怎麼決定提醒時間?

如果提醒採用固定時間,例如每天晚上七點,那當然沒有問題。

但如果希望 App 自己判斷,就必須先回答一個問題「使用者平常到底是幾點散步?」

例如最近十四天的紀錄:

18:10
18:20
18:30
18:25
18:15

人一眼就能看出這個使用者平時都是 18:00 左右散步,但是程式不會知道,因此需要一個 Habit Analyzer,把散步紀錄一步一步轉成 Habit,再決定提醒時間。

第一步:先拆成平日與假日

生活習慣通常不會完全一樣,例如:

平日

18:20
18:30
18:40

假日

08:00
08:10
07:50

如果全部混在一起分析,可能會得到一個使用者實際上不會散步的時間。

因此會先將資料拆成平日與假日,再分別進行分析。

最後可能得到:

平日 Habit

18:30

假日 Habit

08:00

到了平日就使用平日 Habit,到了星期六、星期日則使用假日 Habit,兩者互不影響。

第二步:把相近時間分成同一群

接著要將時間相近的散步紀錄放進同一個群組。

例如目前將相差不超過 60 分鐘的紀錄視為同一群:

18:10
18:25
18:40

這三筆紀錄都會進入傍晚的時間群組。

但下面這筆與傍晚相差太遠,因此會屬於另一個群組:

08:00

除了普通的時間差,還需要考慮跨過0點的情況:

23:50
00:10

雖然日期不同,但實際只差二十分鐘。

如果直接把分鐘數相減,就會得到接近二十四小時的差距,因此要使用循環時間差來判斷,避免把原本屬於同一個習慣的資料拆開。

第三步:確認是否真的形成習慣

只有把時間分群還不夠,還需要確認這個時間群組是否經常出現在不同日期。

例如同一天散步三次:

星期一

18:10
18:20
18:40

如果直接拿來統計,18 點這個群組就會增加三筆資料。

但真正想知道的是「有多少天是在這個時間附近散步」,因此在同一個時間群組內,統計的是不同日期數,而不是散步次數。

上面的例子最後只會算成:

18 點群組

星期一

而不是:

18 點群組

星期一
星期一
星期一

不過,同一天仍然可能出現不同的散步習慣,例如:

星期一

08:00
14:00
20:00

這三筆紀錄相隔很遠,因此會分別進入不同的時間群組:

08 點群組
星期一

14 點群組
星期一

20 點群組
星期一

也就是說,同一個日期可以出現在不同的 Habit 群組中,但在同一個群組內,同一天最多只計算一次。

目前的門檻設定為:

  • 平日至少 3 個不同日期
  • 假日至少 2 個不同日期

假日門檻較低,是因為最近十四天內,本來就只有四天左右是星期六或星期日。如果假日也要求三到四個不同日期,需要更長的時間才可能形成 Habit。

第四步:判斷時間是否足夠集中

即使不同日期數量足夠,也不代表使用者真的有固定習慣。

例如:

18:10
18:20
18:30
18:35

這組資料非常集中,可以合理認為使用者通常會在 18:30 左右開始散步。

但下面這組資料就沒有這麼穩定:

17:00
18:00
19:00

雖然都發生在傍晚,但最早與最晚相差兩個小時。

如果仍然直接選擇 18:00 建立提醒,通知出現時,使用者不一定真的有散步的打算。

因此還要計算群組內時間的分散程度。

目前設定最大平均偏差為 35 分鐘。如果群組內的平均時間偏差超過這個範圍,就代表這個 Habit 還不夠穩定。

第五步:計算 confidence

通過前面的初步條件後,最後再將不同日期數、樣本數以及時間集中程度整合成一個 confidence。

confidence 代表系統對這個 Habit 的信心程度,其計算主要結合了三個維度的權重:

  • 不同日期涵蓋率(40%):是否在多個不同日期持續出現
  • 總樣本數(30%):累積的散步紀錄是否足夠豐富
  • 時間集中度(30%):根據前一步計算出的時間平均偏差

例如:

18:10
18:20
18:25
18:30

分析後可能得到:

平日 Habit
18:30
confidence = 0.91

綜合計算出 0 到 1 之間的分數後,目前只有當 confidence >= 0.65 時才會將這個時間群組視為有效 Habit,並繼續建立提醒。

如果 confidence 沒有達到門檻 (>=0.65),就代表散步時間還不夠穩定,因此不會建立自動提醒,而是等累積更多散步紀錄後再重新分析。

最後怎麼決定提醒時間?

經過前面的分析後,找出的是使用者通常開始散步的時間,而不是通知實際出現的時間。

例如分析結果為:

平日 Habit
18:30

假日 Habit
08:00

需要根據今天是平日還是假日,選擇對應的時間發送通知。

為了讓使用者有時間準備,通知會比 Habit 提前十分鐘。

例如今天是星期三,那就是 18:20 發送通知,而如果是星期六,就 7:50 發送通知。

最終流程為:

 最近 14 天散步紀錄
        │
        ▼
   分成平日與假日
        │
        ▼
   將相近時間分群
        │
        ▼
確認是否形成穩定習慣
        │
        ▼
 計算 confidence
        │
        ▼
 confidence 是否達標?
      │         │
      │否       |是
      ▼         ▼
 不建立提醒   產生 Habit
                │
                ▼
           提前 10 分鐘
                │
                ▼
     建立 Local Notification

什麼時候重新分析?

提醒時間不是計算一次後就永遠不變。

使用者原本可能習慣六點半散步,之後因為工作時間改變,逐漸變成七點才出門。

因此目前會在兩個時機重新分析:

App 啟動並恢復登入後

 恢復 Session
      │
      ▼
初始化通知與權限
      │
      ▼
取得最近 14 天散步紀錄
      │
      ▼
重新分析 Habit
      │
      ▼
  更新通知排程

這樣即使使用者很久沒有開啟 App,或是在其他裝置完成散步,也能重新同步最新的提醒時間。

完成一筆散步之後

   完成散步
      │
      ▼
  資料同步完成
      │
      ▼
重新取得最近 14 天散步紀錄
      │
      ▼
重新分析 Habit
      │
      ▼
   更新提醒

新增一筆散步後,原本不足的樣本可能形成新的 Habit,也可能讓原本六點半散步的 Habit 逐漸變成七點。

目前的作法只會保留平日與週末 confidence 最高的 Habit,因此一天最多只有一次提醒。

在這個前提下,如果使用者今天已經完成散步,就可以取消今天尚未觸發的提醒,再重新排到下一個符合 Habit 的日期,避免剛散步完又收到提醒。

衍生問題

如果未來要改成一天支持多個時段提醒,那就會遇到下面兩個問題:

一天需要提醒兩次怎麼辦?

目前只保留平日與假日中 confidence 最高的 Habit,實作和排程都比較單純,但使用者也可能每天固定散步不只一次。

例如最近的散步紀錄是:

平日
├── Habit #1
│   08:00
├── Habit #2
│   14:00
└── Habit #3
    20:00

如果三個群組都有足夠的不同日期、時間也足夠集中,而且 confidence 都通過門檻,就可以保留三個 Habit,而不是只留下分數最高的那一個。

此時 Scheduler 管理的就不再是「今天的一個提醒」,而是每個 Habit 各自對應的提醒。

為了避免產生過多通知可以加上限制,例如:

  • 每天最多保留兩到三個 Habit
  • 不同 Habit 之間至少相隔一段時間

早上走完晚上提醒被取消

支援多個 Habit 後,就不能再用「今天是否已經散步」決定要不要取消所有提醒。

例如今天有兩個 Habit:

08:00 Habit
18:30 Habit

早上 07:50 發送提醒,使用者在 08:10 開始散步並完成,只代表早上的 Habit 已經完成:

08:00 Habit   已完成
18:30 Habit   尚未完成

因此只能取消或標記 08:00 Habit 對應的提醒,晚上的提醒仍然需要保留。

怎麼判斷完成的是哪一個 Habit?

要依照 Habit 個別管理,還需要判斷每次完成的散步對應哪一個 Habit。

最直接的作法是使用散步的開始時間,尋找當天時間最接近的 Habit,並限制可以接受的時間範圍。

例如:

08:00 Habit

允許配對範圍
07:00~09:00

如果使用者在 08:20 開始散步,就可以視為完成 08:00 Habit。

但如果使用者在 11:00 才開始散步,與 08:00 及 14:00 Habit 都相差太遠,這次就只記錄為一般散步,不會將任何 Habit 標記為完成。

如果同時有多個 Habit 落在允許範圍內,則選擇時間差最小的那一個,避免同一筆散步同時完成多個 Habit。

因此支援多次提醒後,完成散步時不再是取消「今天所有通知」,而是:

完成散步
    │
    ▼
尋找時間最接近的 Habit
    │
    ▼
是否落在允許範圍內?
   │            │
   │否          │是
   ▼            ▼
一般散步      標記該 Habit 完成
                 │
                 ▼
        保留其他 Habit 的提醒

上一篇
外面在下雨?結合天氣給出散步建議
下一篇
累積散步紀錄,完成散步統計頁
系列文
30 天開發一款真正能每天使用的散步 App18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言