fix(android): configure native fetcher, use DailyNotificationFetchWorker, and cancel notifications on dismiss
Fix three critical issues in the Android notification system: 1. configureNativeFetcher() now actually calls nativeFetcher.configure() method - Previously only stored config in database without configuring fetcher instance - Added synchronous configure() call with proper error handling - Stores valid but empty config entry if configure() fails to prevent downstream errors - Adds FETCHER|CONFIGURE_START and FETCHER|CONFIGURE_COMPLETE instrumentation logs 2. Prefetch operations now use DailyNotificationFetchWorker instead of legacy FetchWorker - Replaced FetchWorker.scheduleDelayedFetch() with WorkManager scheduling - Uses correct input data format (scheduled_time, fetch_time, retry_count, immediate) - Enables native fetcher SPI to be used for prefetch operations - Handles both delayed and immediate prefetch scenarios 3. Notification dismiss now cancels notification from NotificationManager - Added notification cancellation before removing from storage - Uses notificationId.hashCode() to match display notification ID - Ensures notification disappears immediately when dismiss button is clicked - Adds DN|DISMISS_CANCEL_NOTIF instrumentation log Version bump: 1.0.8 → 1.0.11
This commit is contained in:
@@ -17,6 +17,10 @@ import androidx.core.app.ActivityCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.Data
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import com.timesafari.dailynotification.DailyNotificationFetchWorker
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
@@ -496,17 +500,41 @@ open class DailyNotificationPlugin : Plugin() {
|
|||||||
|
|
||||||
Log.i(TAG, "Configuring native fetcher: apiBaseUrl=$apiBaseUrl, activeDid=$activeDid")
|
Log.i(TAG, "Configuring native fetcher: apiBaseUrl=$apiBaseUrl, activeDid=$activeDid")
|
||||||
|
|
||||||
// Call the native fetcher's configure method
|
// Call the native fetcher's configure method FIRST
|
||||||
// Note: This assumes the native fetcher has a configure method
|
// This configures the fetcher instance with API credentials for background operations
|
||||||
// If the native fetcher interface doesn't have configure, we'll need to handle it differently
|
var configureSuccess = false
|
||||||
|
var configureError: Exception? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Store configuration in database for later use
|
Log.d(TAG, "FETCHER|CONFIGURE_START apiBaseUrl=$apiBaseUrl, activeDid=${activeDid.take(30)}...")
|
||||||
|
nativeFetcher.configure(apiBaseUrl, activeDid, jwtToken)
|
||||||
|
configureSuccess = true
|
||||||
|
Log.i(TAG, "FETCHER|CONFIGURE_COMPLETE success=true")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
configureError = e
|
||||||
|
Log.e(TAG, "FETCHER|CONFIGURE_COMPLETE success=false error=${e.message}", e)
|
||||||
|
// Continue to store empty config entry - don't fail the entire operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store configuration in database for persistence across app restarts
|
||||||
|
// If configure() failed, store a valid but empty entry that won't cause errors
|
||||||
val configId = "native_fetcher_config"
|
val configId = "native_fetcher_config"
|
||||||
val configValue = JSONObject().apply {
|
val configValue = if (configureSuccess) {
|
||||||
|
// Store actual configuration values
|
||||||
|
JSONObject().apply {
|
||||||
put("apiBaseUrl", apiBaseUrl)
|
put("apiBaseUrl", apiBaseUrl)
|
||||||
put("activeDid", activeDid)
|
put("activeDid", activeDid)
|
||||||
put("jwtToken", jwtToken)
|
put("jwtToken", jwtToken)
|
||||||
}.toString()
|
}.toString()
|
||||||
|
} else {
|
||||||
|
// Store valid but empty entry to prevent errors in code that reads this config
|
||||||
|
JSONObject().apply {
|
||||||
|
put("apiBaseUrl", "")
|
||||||
|
put("activeDid", "")
|
||||||
|
put("jwtToken", "")
|
||||||
|
put("configureError", configureError?.message ?: "Unknown error")
|
||||||
|
}.toString()
|
||||||
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
@@ -514,16 +542,18 @@ open class DailyNotificationPlugin : Plugin() {
|
|||||||
configId, null, "native_fetcher", "config", configValue, "json"
|
configId, null, "native_fetcher", "config", configValue, "json"
|
||||||
)
|
)
|
||||||
getDatabase().notificationConfigDao().insertConfig(config)
|
getDatabase().notificationConfigDao().insertConfig(config)
|
||||||
|
|
||||||
|
if (configureSuccess) {
|
||||||
call.resolve()
|
call.resolve()
|
||||||
|
} else {
|
||||||
|
// Configure failed but we stored a valid entry - reject with error details
|
||||||
|
call.reject("Native fetcher configure() failed: ${configureError?.message}")
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to store native fetcher config", e)
|
Log.e(TAG, "Failed to store native fetcher config", e)
|
||||||
call.reject("Failed to store configuration: ${e.message}")
|
call.reject("Failed to store configuration: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Native fetcher configuration failed", e)
|
|
||||||
call.reject("Native fetcher configuration failed: ${e.message}")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Configure native fetcher error", e)
|
Log.e(TAG, "Configure native fetcher error", e)
|
||||||
call.reject("Configuration error: ${e.message}")
|
call.reject("Configuration error: ${e.message}")
|
||||||
@@ -1257,15 +1287,46 @@ open class DailyNotificationPlugin : Plugin() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Always schedule prefetch 5 minutes before notification
|
// Always schedule prefetch 5 minutes before notification
|
||||||
// (URL is optional - generates mock content if not provided)
|
// (URL is optional - native fetcher will be used if registered)
|
||||||
val fetchTime = nextRunTime - (5 * 60 * 1000L) // 5 minutes before
|
val fetchTime = nextRunTime - (5 * 60 * 1000L) // 5 minutes before
|
||||||
FetchWorker.scheduleDelayedFetch(
|
val delayMs = fetchTime - System.currentTimeMillis()
|
||||||
context,
|
|
||||||
fetchTime,
|
if (delayMs > 0) {
|
||||||
nextRunTime,
|
// Schedule delayed prefetch
|
||||||
url // Can be null - FetchWorker will generate mock content
|
val inputData = Data.Builder()
|
||||||
)
|
.putLong("scheduled_time", nextRunTime)
|
||||||
Log.i(TAG, "Prefetch scheduled: fetchTime=$fetchTime, notificationTime=$nextRunTime, url=${url ?: "none (will generate mock)"}")
|
.putLong("fetch_time", fetchTime)
|
||||||
|
.putInt("retry_count", 0)
|
||||||
|
.putBoolean("immediate", false)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||||
|
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(inputData)
|
||||||
|
.addTag("prefetch")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueue(workRequest)
|
||||||
|
|
||||||
|
Log.i(TAG, "Prefetch scheduled: fetchTime=$fetchTime, notificationTime=$nextRunTime, delayMs=$delayMs, using native fetcher")
|
||||||
|
} else {
|
||||||
|
// Fetch time is in the past, schedule immediate fetch
|
||||||
|
val inputData = Data.Builder()
|
||||||
|
.putLong("scheduled_time", nextRunTime)
|
||||||
|
.putLong("fetch_time", System.currentTimeMillis())
|
||||||
|
.putInt("retry_count", 0)
|
||||||
|
.putBoolean("immediate", true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||||
|
.setInputData(inputData)
|
||||||
|
.addTag("prefetch")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueue(workRequest)
|
||||||
|
|
||||||
|
Log.i(TAG, "Immediate prefetch scheduled: notificationTime=$nextRunTime, using native fetcher")
|
||||||
|
}
|
||||||
|
|
||||||
// Store schedule in database
|
// Store schedule in database
|
||||||
val schedule = Schedule(
|
val schedule = Schedule(
|
||||||
|
|||||||
@@ -179,6 +179,16 @@ public class DailyNotificationWorker extends Worker {
|
|||||||
try {
|
try {
|
||||||
Log.d(TAG, "DN|DISMISS_START id=" + notificationId);
|
Log.d(TAG, "DN|DISMISS_START id=" + notificationId);
|
||||||
|
|
||||||
|
// Cancel the notification from NotificationManager FIRST
|
||||||
|
// This ensures the notification disappears immediately when dismissed
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (notificationManager != null) {
|
||||||
|
int systemNotificationId = notificationId.hashCode();
|
||||||
|
notificationManager.cancel(systemNotificationId);
|
||||||
|
Log.d(TAG, "DN|DISMISS_CANCEL_NOTIF systemId=" + systemNotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove from Room if present; also remove from legacy storage for compatibility
|
// Remove from Room if present; also remove from legacy storage for compatibility
|
||||||
try {
|
try {
|
||||||
DailyNotificationStorageRoom room = new DailyNotificationStorageRoom(getApplicationContext());
|
DailyNotificationStorageRoom room = new DailyNotificationStorageRoom(getApplicationContext());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@timesafari/daily-notification-plugin",
|
"name": "@timesafari/daily-notification-plugin",
|
||||||
"version": "1.0.8",
|
"version": "1.0.11",
|
||||||
"description": "TimeSafari Daily Notification Plugin - Enterprise-grade daily notification functionality with dual scheduling, callback support, TTL-at-fire logic, and comprehensive observability across Mobile (Capacitor) and Desktop (Electron) platforms",
|
"description": "TimeSafari Daily Notification Plugin - Enterprise-grade daily notification functionality with dual scheduling, callback support, TTL-at-fire logic, and comprehensive observability across Mobile (Capacitor) and Desktop (Electron) platforms",
|
||||||
"main": "dist/plugin.js",
|
"main": "dist/plugin.js",
|
||||||
"module": "dist/esm/index.js",
|
"module": "dist/esm/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user