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.content.ContextCompat
|
||||
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.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
@@ -496,33 +500,59 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
Log.i(TAG, "Configuring native fetcher: apiBaseUrl=$apiBaseUrl, activeDid=$activeDid")
|
||||
|
||||
// Call the native fetcher's configure method
|
||||
// Note: This assumes the native fetcher has a configure method
|
||||
// If the native fetcher interface doesn't have configure, we'll need to handle it differently
|
||||
// Call the native fetcher's configure method FIRST
|
||||
// This configures the fetcher instance with API credentials for background operations
|
||||
var configureSuccess = false
|
||||
var configureError: Exception? = null
|
||||
|
||||
try {
|
||||
// Store configuration in database for later use
|
||||
val configId = "native_fetcher_config"
|
||||
val configValue = JSONObject().apply {
|
||||
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 configValue = if (configureSuccess) {
|
||||
// Store actual configuration values
|
||||
JSONObject().apply {
|
||||
put("apiBaseUrl", apiBaseUrl)
|
||||
put("activeDid", activeDid)
|
||||
put("jwtToken", jwtToken)
|
||||
}.toString()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val config = com.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
configId, null, "native_fetcher", "config", configValue, "json"
|
||||
)
|
||||
getDatabase().notificationConfigDao().insertConfig(config)
|
||||
} 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 {
|
||||
try {
|
||||
val config = com.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
configId, null, "native_fetcher", "config", configValue, "json"
|
||||
)
|
||||
getDatabase().notificationConfigDao().insertConfig(config)
|
||||
|
||||
if (configureSuccess) {
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to store native fetcher config", e)
|
||||
call.reject("Failed to store configuration: ${e.message}")
|
||||
} else {
|
||||
// Configure failed but we stored a valid entry - reject with error details
|
||||
call.reject("Native fetcher configure() failed: ${configureError?.message}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to store native fetcher config", e)
|
||||
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) {
|
||||
Log.e(TAG, "Configure native fetcher error", e)
|
||||
@@ -1257,15 +1287,46 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
)
|
||||
|
||||
// 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
|
||||
FetchWorker.scheduleDelayedFetch(
|
||||
context,
|
||||
fetchTime,
|
||||
nextRunTime,
|
||||
url // Can be null - FetchWorker will generate mock content
|
||||
)
|
||||
Log.i(TAG, "Prefetch scheduled: fetchTime=$fetchTime, notificationTime=$nextRunTime, url=${url ?: "none (will generate mock)"}")
|
||||
val delayMs = fetchTime - System.currentTimeMillis()
|
||||
|
||||
if (delayMs > 0) {
|
||||
// Schedule delayed prefetch
|
||||
val inputData = Data.Builder()
|
||||
.putLong("scheduled_time", nextRunTime)
|
||||
.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
|
||||
val schedule = Schedule(
|
||||
|
||||
@@ -179,6 +179,16 @@ public class DailyNotificationWorker extends Worker {
|
||||
try {
|
||||
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
|
||||
try {
|
||||
DailyNotificationStorageRoom room = new DailyNotificationStorageRoom(getApplicationContext());
|
||||
|
||||
Reference in New Issue
Block a user