12 KiB
Android Notification Implementation Comparison
Test App (Working) vs TimeSafari (Not Working)
This document identifies the critical differences between the test app where notifications work correctly and the TimeSafari app where notifications don't work at all. Use this as a checklist to fix TimeSafari.
Critical Issues (Must Fix)
1. Missing Custom Application Class
This is likely the primary cause of failure.
Test App (Working):
<!-- AndroidManifest.xml -->
<application
android:name=".TestApplication"
...>
// TestApplication.java
public class TestApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Context context = getApplicationContext();
NativeNotificationContentFetcher testFetcher =
new org.timesafari.dailynotification.test.TestNativeFetcher(context);
DailyNotificationPlugin.setNativeFetcher(testFetcher);
}
}
TimeSafari (Broken):
<!-- AndroidManifest.xml - NO android:name attribute -->
<application
android:allowBackup="true"
...>
- No custom Application class exists
- No native fetcher is registered
- Plugin cannot fetch notification content
Fix Required:
- Create
TimeSafariApplication.javainandroid/app/src/main/java/app/timesafari/ - Implement
NativeNotificationContentFetcherspecific to TimeSafari - Add
android:name=".TimeSafariApplication"to AndroidManifest.xml
2. Missing Capacitor Plugin Configuration
Test App (Working):
// capacitor.config.ts
plugins: {
DailyNotification: {
debugMode: true,
enableNotifications: true,
timesafariConfig: {
activeDid: "did:ethr:0x...",
endpoints: {
projectsLastUpdated: "http://..."
},
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: [...],
fetchInterval: '0 8 * * *'
},
credentialConfig: {
jwtSecret: '...',
tokenExpirationMinutes: 1
}
},
networkConfig: {
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000
},
contentFetch: {
enabled: true,
schedule: '0 00 * * *',
fetchLeadTimeMinutes: 5
}
}
}
TimeSafari (Broken):
// capacitor.config.ts - NO DailyNotification configuration at all
plugins: {
App: { ... },
SplashScreen: { ... },
CapSQLite: { ... }
// DailyNotification is MISSING
}
Fix Required:
Add DailyNotification configuration to capacitor.config.ts with appropriate values for TimeSafari.
3. Missing Permissions in AndroidManifest.xml
Test App has these permissions that TimeSafari is missing:
<!-- Add to TimeSafari's AndroidManifest.xml -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Current TimeSafari permissions (incomplete):
- ✅
INTERNET - ✅
POST_NOTIFICATIONS - ✅
SCHEDULE_EXACT_ALARM - ✅
RECEIVE_BOOT_COMPLETED - ✅
WAKE_LOCK - ❌
ACCESS_NETWORK_STATE- MISSING - ❌
FOREGROUND_SERVICE- MISSING - ❌
SYSTEM_ALERT_WINDOW- MISSING - ❌
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS- MISSING
4. Missing Gradle Dependencies
Test App (Working):
// android/app/build.gradle
dependencies {
// Capacitor annotation processor for automatic plugin discovery
annotationProcessor project(':capacitor-android')
// Required dependencies for the plugin
implementation 'androidx.work:work-runtime:2.9.0'
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
TimeSafari (Broken):
dependencies {
// Missing: annotationProcessor project(':capacitor-android')
implementation "androidx.work:work-runtime-ktx:2.9.0" // Using Kotlin version
// Missing: androidx.lifecycle:lifecycle-service
// Missing: com.google.code.gson:gson
}
Fix Required:
Add to TimeSafari's android/app/build.gradle:
annotationProcessor project(':capacitor-android')
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
implementation 'com.google.code.gson:gson:2.10.1'
Secondary Issues (Should Fix)
5. DailyNotificationReceiver Export Status
Test App (Working):
<receiver
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false"> <!-- Note: false -->
TimeSafari (Broken):
<receiver
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="true"> <!-- Note: true - potential security issue -->
The test app uses exported="false" because the plugin creates PendingIntents with explicit component targeting. Using exported="true" is unnecessary and a potential security concern.
6. Missing Network Security Config
Test App (Working):
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
TimeSafari (Broken):
<application>
<!-- No networkSecurityConfig -->
This may affect HTTP (non-HTTPS) requests during development.
7. Missing Java Compile Options
Test App (Working):
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
TimeSafari (Broken): No explicit compile options set.
Complete Fix Checklist
Step 1: Create Custom Application Class
Create file: android/app/src/main/java/app/timesafari/TimeSafariApplication.java
package app.timesafari;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import org.timesafari.dailynotification.DailyNotificationPlugin;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
public class TimeSafariApplication extends Application {
private static final String TAG = "TimeSafariApplication";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Initializing TimeSafari notifications");
// Register native fetcher with application context
Context context = getApplicationContext();
NativeNotificationContentFetcher fetcher =
new TimeSafariNativeFetcher(context);
DailyNotificationPlugin.setNativeFetcher(fetcher);
Log.i(TAG, "Native fetcher registered");
}
}
Step 2: Create Native Fetcher Implementation
Create file: android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java
package app.timesafari;
import android.content.Context;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
import org.timesafari.dailynotification.NotificationContent;
public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
private final Context context;
public TimeSafariNativeFetcher(Context context) {
this.context = context;
}
@Override
public NotificationContent fetchContent(String scheduleId) {
// TODO: Implement actual content fetching for TimeSafari
// This should query the TimeSafari API for notification content
return new NotificationContent(
"timesafari_" + System.currentTimeMillis(),
"TimeSafari Update",
"Check your starred projects for updates!",
System.currentTimeMillis(),
null,
System.currentTimeMillis()
);
}
}
Step 3: Update AndroidManifest.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".TimeSafariApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- ... existing content ... -->
<!-- Fix: Change exported to false -->
<receiver
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
<!-- ... rest of receivers ... -->
</application>
<!-- Existing permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- ADD these missing permissions -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
</manifest>
Step 4: Update build.gradle
Add to android/app/build.gradle:
android {
// ... existing config ...
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
// ... existing dependencies ...
// ADD these for notification plugin
annotationProcessor project(':capacitor-android')
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
Step 5: Update capacitor.config.ts
Add DailyNotification configuration:
plugins: {
// ... existing plugins ...
DailyNotification: {
debugMode: true,
enableNotifications: true,
timesafariConfig: {
activeDid: '', // Will be set dynamically from user's DID
endpoints: {
projectsLastUpdated: 'https://api.endorser.ch/api/v2/report/plansLastUpdatedBetween'
},
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: [],
fetchInterval: '0 8 * * *'
}
},
networkConfig: {
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000
},
contentFetch: {
enabled: true,
schedule: '0 8 * * *',
fetchLeadTimeMinutes: 5
}
}
}
Step 6: Rebuild
npx cap sync android
cd android && ./gradlew clean
cd .. && npx cap build android
Verification
After implementing fixes, verify:
-
Check logs for Application initialization:
adb logcat | grep -E "TimeSafariApplication|Native fetcher" -
Check alarm scheduling:
adb shell dumpsys alarm | grep -i timesafari -
Test receiver manually:
adb shell am broadcast -a org.timesafari.daily.NOTIFICATION \ --es id "test_notification" \ -n app.timesafari.app/org.timesafari.dailynotification.DailyNotificationReceiver -
Check notification permissions:
adb shell dumpsys package app.timesafari.app | grep -A 5 "granted=true"
Summary of Critical Differences
| Component | Test App (Working) | TimeSafari (Broken) |
|---|---|---|
| Custom Application class | ✅ TestApplication.java | ❌ None |
| Native fetcher registration | ✅ In Application.onCreate() | ❌ Not registered |
| DailyNotification config | ✅ Full config in capacitor.config.ts | ❌ Not configured |
| ACCESS_NETWORK_STATE | ✅ Present | ❌ Missing |
| FOREGROUND_SERVICE | ✅ Present | ❌ Missing |
| REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | ✅ Present | ❌ Missing |
| Gson dependency | ✅ Present | ❌ Missing |
| lifecycle-service dependency | ✅ Present | ❌ Missing |
| Capacitor annotation processor | ✅ Present | ❌ Missing |
The most critical missing piece is the custom Application class with native fetcher registration. Without this, the plugin has no way to fetch notification content when the alarm fires.