Skip to content

Add Daily Puzzle Notification#218

Open
amorris13 wants to merge 1 commit into
masterfrom
feature/daily-puzzle-notification-2702830574255099670
Open

Add Daily Puzzle Notification#218
amorris13 wants to merge 1 commit into
masterfrom
feature/daily-puzzle-notification-2702830574255099670

Conversation

@amorris13

@amorris13 amorris13 commented Mar 29, 2026

Copy link
Copy Markdown
Owner

This change adds a daily reminder notification for the daily puzzle. Users can opt-in and configure the reminder time (default 8 PM) from either the Daily Statistics page or the main Settings page. The notification is only shown if the user hasn't completed the puzzle for the current day. The implementation includes reboot persistence and handles modern Android notification permissions.

Fixes #217


PR created automatically by Jules for task 2702830574255099670 started by @amorris13

Summary

This pull request implements an opt-in daily reminder notification for the daily puzzle. Users can enable the feature and configure the reminder time (default 8 PM) from the Daily Statistics page or Settings page. Notifications are only shown if the user hasn't completed that day's puzzle.

CC: @jules

Implementation Details

Core Notification System

  • DailyNotificationReceiver: New BroadcastReceiver that triggers notification display when the daily puzzle is incomplete. Posts a notification via NotificationManagerCompat with a link to DailyGameActivity, handling SecurityException for missing notification permissions.
  • NotificationUtils: Utility class managing notification scheduling via AlarmManager. Reads user preferences (enabled flag, hour, minute) and schedules inexact daily repeating alarms. Defaults to 8 PM (hour 20). Handles cancellation of existing alarms.
  • OnBootReceiver: Ensures notifications are rescheduled on device reboot by listening to BOOT_COMPLETED broadcasts.

User Interface

  • DailyStatisticsFragment: Adds a new settings section with:

    • Toggle switch for enabling/disabling daily notifications
    • Time picker for configuring reminder time
    • Updates preferences and reschedules notifications on changes
    • Runtime permission handling for POST_NOTIFICATIONS on Android 13+
  • SettingsFragment: Integrates notification settings into the main Settings page with:

    • SwitchPreferenceCompat for enabling/disabling notifications
    • Dependent time preference that displays configured time
    • Runtime permission request launcher for Android 13+

Manifest & Permissions

  • Declares POST_NOTIFICATIONS and RECEIVE_BOOT_COMPLETED permissions
  • Registers DailyNotificationReceiver (not exported) and OnBootReceiver (exported) for BOOT_COMPLETED
  • Creates notification channel for Android 26+

App Integration

  • MainActivity schedules daily notifications on app launch via NotificationUtils
  • Preferences stored in SharedPreferences (enabled flag, hour, minute)
  • Lifecycle-safe preference updates with race condition handling

Key Features

  • Opt-in notification with configurable time in user's local timezone
  • Reboot persistence via OnBootReceiver
  • Modern Android 13+ notification permission handling
  • Notification only shown if daily puzzle is incomplete
  • User-friendly time picker UI on both Daily Statistics and Settings pages

String Resources

Added 9 new string resources for UI labels, summaries, notification title/content, and channel metadata.

- Implement DailyNotificationReceiver to show reminders if today's puzzle is incomplete.
- Add NotificationUtils for scheduling alarms via AlarmManager.
- Add notification settings (toggle and time picker) to both the Settings page and Daily Statistics page.
- Ensure reboot persistence with OnBootReceiver and RECEIVE_BOOT_COMPLETED permission.
- Handle POST_NOTIFICATIONS permission for Android 13+.
- Schedule notifications on app launch in MainActivity.
- Fix race conditions and ensure lifecycle safety in SettingsFragment.

Co-authored-by: amorris13 <4523811+amorris13@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The changes implement a daily puzzle notification system allowing users to opt-in and configure notification time. The system uses AlarmManager for scheduling repeating notifications, broadcasts receivers for posting notifications and rescheduling on device boot, and preference/UI controls in both the daily statistics page and settings. Notifications only trigger if the daily puzzle remains incomplete.

Changes

Cohort / File(s) Summary
Manifest & Boot Integration
app/src/main/AndroidManifest.xml
Added POST_NOTIFICATIONS and RECEIVE_BOOT_COMPLETED permissions; registered DailyNotificationReceiver (private) and OnBootReceiver (public with boot-completed intent-filter).
Notification Core
app/src/main/java/com/antsapps/triples/NotificationUtils.java, app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java, app/src/main/java/com/antsapps/triples/OnBootReceiver.java
New utility for scheduling/canceling daily repeating alarms with stored hour/minute preferences; receiver posts notifications only if daily puzzle incomplete; boot receiver reschedules alarms on device restart.
Activity & Fragment UI
app/src/main/java/com/antsapps/triples/MainActivity.java, app/src/main/java/com/antsapps/triples/DailyStatisticsFragment.java, app/src/main/java/com/antsapps/triples/SettingsFragment.java
Main activity schedules daily notifications on launch; daily statistics fragment adds toggle switch and time picker for notification configuration with Android 13+ permission handling; settings fragment adds preference wiring and permission/time logic.
UI Resources
app/src/main/res/layout/daily_stats_fragment.xml, app/src/main/res/values/strings.xml, app/src/main/res/xml/preferences.xml
New settings section in daily stats layout with switch and time container; new string resources for notification titles/summaries; new preference category with switchable enable toggle and dependent time preference.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Settings as DailyStatsFragment
    participant Utils as NotificationUtils
    participant AM as AlarmManager
    participant SharedPrefs as SharedPreferences

    User->>Settings: Toggle notification switch
    activate Settings
    Settings->>SharedPrefs: Save enabled=true + hour/minute
    activate SharedPrefs
    SharedPrefs-->>Settings: 
    deactivate SharedPrefs
    Settings->>Utils: scheduleDailyNotification(context)
    activate Utils
    Utils->>SharedPrefs: Read enabled, hour, minute
    activate SharedPrefs
    SharedPrefs-->>Utils: preferences
    deactivate SharedPrefs
    Utils->>AM: setInexactRepeating(RTC_WAKEUP, ..., intent)
    activate AM
    AM-->>Utils: 
    deactivate AM
    Utils-->>Settings: 
    deactivate Utils
    Settings-->>User: Alarm scheduled
    deactivate Settings
Loading
sequenceDiagram
    participant AM as AlarmManager
    participant Receiver as DailyNotificationReceiver
    participant App as Application
    participant Game as DailyGame
    participant NM as NotificationManager
    participant User as User

    AM->>Receiver: onReceive(intent)
    activate Receiver
    Receiver->>App: Retrieve singleton
    activate App
    App-->>Receiver: Application instance
    deactivate App
    Receiver->>Game: Get today's game
    activate Game
    Game-->>Receiver: Game data
    deactivate Game
    alt Game incomplete
        Receiver->>NM: createNotificationChannel (API 26+)
        Receiver->>NM: notify(NOTIFICATION_ID, notification)
        activate NM
        NM-->>User: Display notification
        deactivate NM
    else Game completed
        Receiver->>Receiver: Skip notification
    end
    deactivate Receiver
Loading
sequenceDiagram
    participant System as System
    participant BootReceiver as OnBootReceiver
    participant Utils as NotificationUtils
    participant AM as AlarmManager

    System->>BootReceiver: ACTION_BOOT_COMPLETED
    activate BootReceiver
    BootReceiver->>Utils: scheduleDailyNotification(context)
    activate Utils
    Utils->>AM: setInexactRepeating(...)
    activate AM
    AM-->>Utils: Alarm scheduled
    deactivate AM
    Utils-->>BootReceiver: 
    deactivate Utils
    BootReceiver-->>System: 
    deactivate BootReceiver
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A daily hop toward puzzle delight,
Notifications now chime at just the right time,
Users choose when to play (or decline),
Boot up and reschedule—no puzzle left behind, 🔔✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change—adding daily puzzle notification functionality.
Linked Issues check ✅ Passed The PR fully implements all requirements from issue #217: opt-in notifications, configuration from Daily Statistics and Settings pages, local timezone support with 8 PM default, completion check, and Android 13+ permission handling.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing daily puzzle notifications as specified in issue #217; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/daily-puzzle-notification-2702830574255099670

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
app/src/main/res/xml/preferences.xml (1)

49-63: Inconsistent preference key format.

Other preferences in this file use string resources for keys (e.g., @string/pref_screen_lock), but the new notification preferences use hardcoded strings. This inconsistency could complicate future maintenance and i18n.

Additionally, the category title "Daily Puzzle" should use a string resource.

♻️ Suggested consistency improvement

Add to strings.xml:

<string name="pref_daily_notification_enabled">pref_daily_notification_enabled</string>
<string name="pref_daily_notification_time">pref_daily_notification_time</string>
<string name="daily_puzzle_category_title">Daily Puzzle</string>

Then update preferences.xml:

   <androidx.preference.PreferenceCategory
-      android:title="Daily Puzzle">
+      android:title="@string/daily_puzzle_category_title">

     <androidx.preference.SwitchPreferenceCompat
         android:defaultValue="false"
-        android:key="pref_daily_notification_enabled"
+        android:key="@string/pref_daily_notification_enabled"
         android:summary="@string/pref_daily_notification_enabled_summary"
         android:title="@string/pref_daily_notification_enabled_title" />

     <androidx.preference.Preference
-        android:dependency="pref_daily_notification_enabled"
-        android:key="pref_daily_notification_time"
+        android:dependency="@string/pref_daily_notification_enabled"
+        android:key="@string/pref_daily_notification_time"
         android:title="@string/pref_daily_notification_time_title" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/xml/preferences.xml` around lines 49 - 63, Replace the
hardcoded preference keys and category title with string resources: add string
entries for "pref_daily_notification_enabled", "pref_daily_notification_time",
and "daily_puzzle_category_title" in strings.xml, then update the
PreferenceCategory title to use `@string/daily_puzzle_category_title` and change
the SwitchPreferenceCompat key and the Preference key to use
`@string/pref_daily_notification_enabled` and `@string/pref_daily_notification_time`
respectively; modify the Preference elements referenced (PreferenceCategory,
SwitchPreferenceCompat, Preference) to use these `@string/`... keys to match the
existing preference key format.
app/src/main/res/layout/daily_stats_fragment.xml (1)

254-279: Consider adding contentDescription for accessibility.

The notification_time_container is clickable and focusable but lacks a contentDescription. Screen reader users may not understand the purpose of this clickable row.

♿ Suggested accessibility improvement
             <LinearLayout
                 android:id="@+id/notification_time_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="horizontal"
                 android:gravity="center_vertical"
                 android:layout_marginTop="8dp"
                 android:clickable="true"
                 android:focusable="true"
-                android:background="?attr/selectableItemBackground">
+                android:background="?attr/selectableItemBackground"
+                android:contentDescription="@string/pref_daily_notification_time_title">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/layout/daily_stats_fragment.xml` around lines 254 - 279, The
clickable/focusable LinearLayout notification_time_container lacks an
accessibility label; add a contentDescription (or use android:labelFor pointing
to notification_time_tv) so screen readers announce its purpose—update the
LinearLayout with android:contentDescription="@string/..." (or set it
programmatically in the fragment/activity using
findViewById(R.id.notification_time_container).setContentDescription(...)) and
ensure the string resource describes the row (e.g., "Daily notification time,
opens time picker").
app/src/main/java/com/antsapps/triples/NotificationUtils.java (2)

34-34: Add null check for AlarmManager service.

While extremely rare on real devices, getSystemService() can return null. A defensive null check prevents potential NPE crashes.

Proposed fix
   AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+  if (alarmManager == null) {
+    return;
+  }
   Intent intent = new Intent(context, DailyNotificationReceiver.class);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/NotificationUtils.java` at line 34,
Add a defensive null check after calling context.getSystemService for the
AlarmManager in NotificationUtils: verify that AlarmManager alarmManager is not
null before using it; if it is null, log or report the condition (e.g., via
Log.w or a logger) and return/skip scheduling rather than proceeding, so any
subsequent calls that use alarmManager (in NotificationUtils methods) cannot
throw an NPE.

56-63: Same null check recommendation for AlarmManager.

For consistency and defensive coding, apply the same null check here as suggested for the scheduling method.

Proposed fix
 public static void cancelDailyNotification(Context context) {
   AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+  if (alarmManager == null) {
+    return;
+  }
   Intent intent = new Intent(context, DailyNotificationReceiver.class);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/NotificationUtils.java` around lines
56 - 63, The cancelDailyNotification method currently assumes AlarmManager is
non-null; add the same defensive null check used in the scheduling method:
retrieve AlarmManager via (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE), and only call
alarmManager.cancel(pendingIntent) if alarmManager != null (leave creating the
Intent, PendingIntent (PendingIntent.getBroadcast with
FLAG_UPDATE_CURRENT|FLAG_IMMUTABLE) unchanged); this prevents a possible NPE
when AlarmManager is unavailable while keeping the rest of
cancelDailyNotification and the DailyNotificationReceiver/pendingIntent logic
intact.
app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java (1)

53-57: Empty catch block silently swallows notification failures.

Consider logging the SecurityException for diagnostic purposes, especially useful during development or when troubleshooting user-reported issues.

Proposed fix
     try {
       notificationManager.notify(NOTIFICATION_ID, builder.build());
     } catch (SecurityException e) {
-      // Handle missing permission
+      // Missing POST_NOTIFICATIONS permission; user has denied or not yet granted
+      android.util.Log.w("DailyNotificationReceiver", "Notification permission not granted", e);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java` around
lines 53 - 57, The catch block around
notificationManager.notify(NOTIFICATION_ID, builder.build()) in
DailyNotificationReceiver currently swallows SecurityException; change it to log
the exception (e.g., Log.e or your app's logger) including a clear message and
the caught exception object so failures are visible during
development/troubleshooting; keep the catch handling minimal (log the error and
optionally record analytics) and do not rethrow to preserve behavior for missing
permission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java`:
- Around line 34-37: The BroadcastReceiver calls
Application.getDailyGameForDate() which can trigger synchronous DB writes (via
DailyGame.createFromDay() -> addDailyGame() -> DBAdapter.addDailyGame()),
risking ANR; modify DailyNotificationReceiver.onReceive() to call
getDailyGameByGameDay() (which returns null if absent) and pass that DailyGame
instance into showNotification(), and update showNotification(DailyGame) to
handle a null game gracefully (e.g., show a fallback notification or schedule
background work to create the game), so no synchronous DB writes occur inside
the receiver.

In `@app/src/main/java/com/antsapps/triples/DailyStatisticsFragment.java`:
- Around line 99-109: The permission callback may call getContext() after the
fragment is detached causing an NPE; update the callback in
DailyStatisticsFragment to guard against a detached fragment by checking
isAdded() (and/or getContext() != null) before calling
PreferenceManager.getDefaultSharedPreferences(getContext()) and
NotificationUtils.scheduleDailyNotification(getContext(), true), and only
perform those actions when the fragment is still attached; if the fragment is
detached or context is null ensure you still update mNotificationSwitch safely
(e.g., setChecked(false) or skip UI work) to avoid touching views when not
attached.
- Around line 265-281: The TimePickerDialog callback currently calls
getContext(), prefs, updateNotificationTimeTv(), and
NotificationUtils.scheduleDailyNotification(...) without guarding against the
Fragment being detached; capture the fragment state and context defensively
inside the lambda: inside the callback first obtain Context ctx = getContext()
and/or check isAdded(), return early if ctx == null or !isAdded(), then use ctx
when editing prefs (or re-resolve SharedPreferences from ctx if prefs can be
null) and when calling NotificationUtils.scheduleDailyNotification(ctx); this
prevents a NullPointerException if the fragment is detached while the dialog is
shown.

In `@app/src/main/java/com/antsapps/triples/SettingsFragment.java`:
- Around line 125-148: timePref.setOnPreferenceClickListener's TimePickerDialog
callback calls getContext() directly which can be null during configuration
changes; capture a non-null Context/Fragment reference before creating the
dialog (e.g., Context ctx = getContext() or Activity act = getActivity()) and
use that captured reference inside the onTimeSet lambda for prefs edits,
updateTimePrefSummary and NotificationUtils.scheduleDailyNotification to avoid
NPEs, and bail out if the captured reference is null before showing the dialog.

---

Nitpick comments:
In `@app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java`:
- Around line 53-57: The catch block around
notificationManager.notify(NOTIFICATION_ID, builder.build()) in
DailyNotificationReceiver currently swallows SecurityException; change it to log
the exception (e.g., Log.e or your app's logger) including a clear message and
the caught exception object so failures are visible during
development/troubleshooting; keep the catch handling minimal (log the error and
optionally record analytics) and do not rethrow to preserve behavior for missing
permission.

In `@app/src/main/java/com/antsapps/triples/NotificationUtils.java`:
- Line 34: Add a defensive null check after calling context.getSystemService for
the AlarmManager in NotificationUtils: verify that AlarmManager alarmManager is
not null before using it; if it is null, log or report the condition (e.g., via
Log.w or a logger) and return/skip scheduling rather than proceeding, so any
subsequent calls that use alarmManager (in NotificationUtils methods) cannot
throw an NPE.
- Around line 56-63: The cancelDailyNotification method currently assumes
AlarmManager is non-null; add the same defensive null check used in the
scheduling method: retrieve AlarmManager via (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE), and only call
alarmManager.cancel(pendingIntent) if alarmManager != null (leave creating the
Intent, PendingIntent (PendingIntent.getBroadcast with
FLAG_UPDATE_CURRENT|FLAG_IMMUTABLE) unchanged); this prevents a possible NPE
when AlarmManager is unavailable while keeping the rest of
cancelDailyNotification and the DailyNotificationReceiver/pendingIntent logic
intact.

In `@app/src/main/res/layout/daily_stats_fragment.xml`:
- Around line 254-279: The clickable/focusable LinearLayout
notification_time_container lacks an accessibility label; add a
contentDescription (or use android:labelFor pointing to notification_time_tv) so
screen readers announce its purpose—update the LinearLayout with
android:contentDescription="@string/..." (or set it programmatically in the
fragment/activity using
findViewById(R.id.notification_time_container).setContentDescription(...)) and
ensure the string resource describes the row (e.g., "Daily notification time,
opens time picker").

In `@app/src/main/res/xml/preferences.xml`:
- Around line 49-63: Replace the hardcoded preference keys and category title
with string resources: add string entries for "pref_daily_notification_enabled",
"pref_daily_notification_time", and "daily_puzzle_category_title" in
strings.xml, then update the PreferenceCategory title to use
`@string/daily_puzzle_category_title` and change the SwitchPreferenceCompat key
and the Preference key to use `@string/pref_daily_notification_enabled` and
`@string/pref_daily_notification_time` respectively; modify the Preference
elements referenced (PreferenceCategory, SwitchPreferenceCompat, Preference) to
use these `@string/`... keys to match the existing preference key format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f0f9d4a-1358-4170-b3c5-3964102bc40d

📥 Commits

Reviewing files that changed from the base of the PR and between ed77e1e and 4f763fb.

⛔ Files ignored due to path filters (4)
  • app/src/test/screenshots/settings_dark.png is excluded by !**/*.png
  • app/src/test/screenshots/settings_light.png is excluded by !**/*.png
  • app/src/test/screenshots/statistics_daily_dark.png is excluded by !**/*.png
  • app/src/test/screenshots/statistics_daily_light.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java
  • app/src/main/java/com/antsapps/triples/DailyStatisticsFragment.java
  • app/src/main/java/com/antsapps/triples/MainActivity.java
  • app/src/main/java/com/antsapps/triples/NotificationUtils.java
  • app/src/main/java/com/antsapps/triples/OnBootReceiver.java
  • app/src/main/java/com/antsapps/triples/SettingsFragment.java
  • app/src/main/res/layout/daily_stats_fragment.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/preferences.xml

Comment on lines +34 to +37
Intent intent = new Intent(context, DailyGameActivity.class);
DailyGame todayGame =
Application.getInstance(context).getDailyGameForDate(DailyGame.Day.forToday());
intent.putExtra(Game.ID_TAG, todayGame.getId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential ANR: getDailyGameForDate() may perform synchronous database writes in a BroadcastReceiver.

Per the context snippet for Application.getDailyGameForDate(), if the game doesn't exist in memory, it calls DailyGame.createFromDay() and addDailyGame() which triggers synchronous database writes via DBAdapter.addDailyGame(). BroadcastReceivers have a ~10 second execution limit, and disk I/O could cause ANR on slower devices.

Since onReceive() already calls getDailyGameByGameDay() which returns null if the game doesn't exist, consider passing the game instance to showNotification() or using a different approach when the game is null.

Proposed fix: Pass the game from onReceive() and handle null case
 `@Override`
 public void onReceive(Context context, Intent intent) {
   Application application = Application.getInstance(context);
   DailyGame todayGame = application.getDailyGameByGameDay(DailyGame.Day.forToday());

   if (todayGame == null || todayGame.getGameState() != Game.GameState.COMPLETED) {
-    showNotification(context);
+    showNotification(context, todayGame);
   }
 }

-private void showNotification(Context context) {
+private void showNotification(Context context, DailyGame todayGame) {
   createNotificationChannel(context);

   Intent intent = new Intent(context, DailyGameActivity.class);
-  DailyGame todayGame =
-      Application.getInstance(context).getDailyGameForDate(DailyGame.Day.forToday());
-  intent.putExtra(Game.ID_TAG, todayGame.getId());
+  // Only include game ID if available; DailyGameActivity handles missing ID
+  if (todayGame != null) {
+    intent.putExtra(Game.ID_TAG, todayGame.getId());
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/DailyNotificationReceiver.java` around
lines 34 - 37, The BroadcastReceiver calls Application.getDailyGameForDate()
which can trigger synchronous DB writes (via DailyGame.createFromDay() ->
addDailyGame() -> DBAdapter.addDailyGame()), risking ANR; modify
DailyNotificationReceiver.onReceive() to call getDailyGameByGameDay() (which
returns null if absent) and pass that DailyGame instance into
showNotification(), and update showNotification(DailyGame) to handle a null game
gracefully (e.g., show a fallback notification or schedule background work to
create the game), so no synchronous DB writes occur inside the receiver.

Comment on lines +99 to +109
isGranted -> {
if (isGranted) {
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit()
.putBoolean(NotificationUtils.PREF_DAILY_NOTIFICATION_ENABLED, true)
.apply();
NotificationUtils.scheduleDailyNotification(getContext(), true);
} else {
mNotificationSwitch.setChecked(false);
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NullPointerException if fragment is detached during permission callback.

The permission result callback uses getContext() without null checks. If the fragment is detached before the callback executes, this will cause a crash.

🛡️ Suggested defensive fix
           mRequestPermissionLauncher =
               registerForActivityResult(
                   new ActivityResultContracts.RequestPermission(),
                   isGranted -> {
+                    Context context = getContext();
+                    if (context == null) return;
                     if (isGranted) {
-                      PreferenceManager.getDefaultSharedPreferences(getContext())
+                      PreferenceManager.getDefaultSharedPreferences(context)
                           .edit()
                           .putBoolean(NotificationUtils.PREF_DAILY_NOTIFICATION_ENABLED, true)
                           .apply();
-                      NotificationUtils.scheduleDailyNotification(getContext(), true);
+                      NotificationUtils.scheduleDailyNotification(context, true);
                     } else {
                       mNotificationSwitch.setChecked(false);
                     }
                   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/DailyStatisticsFragment.java` around
lines 99 - 109, The permission callback may call getContext() after the fragment
is detached causing an NPE; update the callback in DailyStatisticsFragment to
guard against a detached fragment by checking isAdded() (and/or getContext() !=
null) before calling PreferenceManager.getDefaultSharedPreferences(getContext())
and NotificationUtils.scheduleDailyNotification(getContext(), true), and only
perform those actions when the fragment is still attached; if the fragment is
detached or context is null ensure you still update mNotificationSwitch safely
(e.g., setChecked(false) or skip UI work) to avoid touching views when not
attached.

Comment on lines +265 to +281
TimePickerDialog timePickerDialog =
new TimePickerDialog(
getContext(),
(view, hourOfDay, minuteOfHour) -> {
prefs
.edit()
.putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, hourOfDay)
.putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, minuteOfHour)
.apply();
updateNotificationTimeTv();
NotificationUtils.scheduleDailyNotification(getContext());
},
hour,
minute,
android.text.format.DateFormat.is24HourFormat(getContext()));
timePickerDialog.show();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NullPointerException in TimePickerDialog callback.

The TimePickerDialog callback uses getContext() without null checks. If the fragment is detached while the dialog is showing (e.g., configuration change), the callback will crash when accessing preferences or scheduling notifications.

🛡️ Suggested defensive fix
           TimePickerDialog timePickerDialog =
               new TimePickerDialog(
                   getContext(),
                   (view, hourOfDay, minuteOfHour) -> {
+                    Context context = getContext();
+                    if (context == null) return;
+                    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
                     prefs
                         .edit()
                         .putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, hourOfDay)
                         .putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, minuteOfHour)
                         .apply();
                     updateNotificationTimeTv();
-                    NotificationUtils.scheduleDailyNotification(getContext());
+                    NotificationUtils.scheduleDailyNotification(context);
                   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/DailyStatisticsFragment.java` around
lines 265 - 281, The TimePickerDialog callback currently calls getContext(),
prefs, updateNotificationTimeTv(), and
NotificationUtils.scheduleDailyNotification(...) without guarding against the
Fragment being detached; capture the fragment state and context defensively
inside the lambda: inside the callback first obtain Context ctx = getContext()
and/or check isAdded(), return early if ctx == null or !isAdded(), then use ctx
when editing prefs (or re-resolve SharedPreferences from ctx if prefs can be
null) and when calling NotificationUtils.scheduleDailyNotification(ctx); this
prevents a NullPointerException if the fragment is detached while the dialog is
shown.

Comment on lines +125 to +148
timePref.setOnPreferenceClickListener(
preference -> {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
int hour = prefs.getInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, 20);
int minute = prefs.getInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, 0);

TimePickerDialog timePickerDialog =
new TimePickerDialog(
getContext(),
(view, hourOfDay, minuteOfHour) -> {
prefs
.edit()
.putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, hourOfDay)
.putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, minuteOfHour)
.apply();
updateTimePrefSummary(timePref);
NotificationUtils.scheduleDailyNotification(getContext());
},
hour,
minute,
android.text.format.DateFormat.is24HourFormat(getContext()));
timePickerDialog.show();
return true;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NullPointerException in TimePickerDialog callback.

Similar to DailyStatisticsFragment, the time picker callback uses getContext() without null checks. Configuration changes while the dialog is showing could cause a crash.

🛡️ Suggested defensive fix
     timePref.setOnPreferenceClickListener(
         preference -> {
-          SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+          Context context = getContext();
+          if (context == null) return true;
+          SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
           int hour = prefs.getInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, 20);
           int minute = prefs.getInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, 0);

           TimePickerDialog timePickerDialog =
               new TimePickerDialog(
-                  getContext(),
+                  context,
                   (view, hourOfDay, minuteOfHour) -> {
+                    Context ctx = getContext();
+                    if (ctx == null) return;
                     prefs
                         .edit()
                         .putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_HOUR, hourOfDay)
                         .putInt(NotificationUtils.PREF_DAILY_NOTIFICATION_MINUTE, minuteOfHour)
                         .apply();
                     updateTimePrefSummary(timePref);
-                    NotificationUtils.scheduleDailyNotification(getContext());
+                    NotificationUtils.scheduleDailyNotification(ctx);
                   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/antsapps/triples/SettingsFragment.java` around lines
125 - 148, timePref.setOnPreferenceClickListener's TimePickerDialog callback
calls getContext() directly which can be null during configuration changes;
capture a non-null Context/Fragment reference before creating the dialog (e.g.,
Context ctx = getContext() or Activity act = getActivity()) and use that
captured reference inside the onTimeSet lambda for prefs edits,
updateTimePrefSummary and NotificationUtils.scheduleDailyNotification to avoid
NPEs, and bail out if the captured reference is null before showing the dialog.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a notification for the daily puzzle

1 participant