Browse Source
Add configureNativeFetcher() plugin method to enable TypeScript configuration of native fetchers with API credentials. This provides a cross-platform mechanism for passing configuration from JavaScript to native code without relying on platform-specific storage. - Add configure() method to NativeNotificationContentFetcher interface (optional, defaults to no-op for fetchers that don't need config) - Add configureNativeFetcher plugin method in DailyNotificationPlugin - Add TypeScript definitions and comprehensive JSDoc - Create NATIVE_FETCHER_CONFIGURATION.md documentation - Update TestNativeFetcher to use real API endpoint (10.0.2.2:3000) - Update DemoNativeFetcher Javadoc explaining configure() is optional - Add configureNativeFetcher() call to demo app's configurePlugin() Enables host apps to configure native fetchers from TypeScript, keeping the interface consistent across Android, iOS, and web platforms.master
5 changed files with 954 additions and 30 deletions
@ -0,0 +1,576 @@ |
|||||
|
# Native Fetcher Configuration Guide |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Date**: 2025-01-28 |
||||
|
**Status**: 🎯 **REFERENCE** - Complete guide for configuring native fetchers |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
The `configureNativeFetcher()` method provides a **cross-platform** mechanism for passing API credentials from TypeScript/JavaScript code to native fetcher implementations. This guide explains how to use this feature, why it exists, and provides complete examples. |
||||
|
|
||||
|
## Table of Contents |
||||
|
|
||||
|
- [Why `configureNativeFetcher()`?](#why-configurenativefetcher) |
||||
|
- [Architecture](#architecture) |
||||
|
- [TypeScript Interface](#typescript-interface) |
||||
|
- [Native Implementation](#native-implementation) |
||||
|
- [Complete Example](#complete-example) |
||||
|
- [Error Handling](#error-handling) |
||||
|
- [Thread Safety](#thread-safety) |
||||
|
- [Best Practices](#best-practices) |
||||
|
- [Troubleshooting](#troubleshooting) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Why `configureNativeFetcher()`? |
||||
|
|
||||
|
### The Problem |
||||
|
|
||||
|
Native fetchers (implementing `NativeNotificationContentFetcher`) are called from background workers (WorkManager on Android, BGTaskScheduler on iOS). These background workers cannot access: |
||||
|
- JavaScript/TypeScript variables |
||||
|
- Capacitor bridge (unreliable in background) |
||||
|
- React/Vue component state |
||||
|
- WebView storage |
||||
|
|
||||
|
### The Solution |
||||
|
|
||||
|
`configureNativeFetcher()` provides a **direct injection** mechanism: |
||||
|
- ✅ **Cross-platform**: Same TypeScript interface on Android, iOS, and web |
||||
|
- ✅ **Simple**: Only 3 required parameters (apiBaseUrl, activeDid, jwtSecret) |
||||
|
- ✅ **Type-safe**: TypeScript types ensure correct usage |
||||
|
- ✅ **No storage dependency**: Doesn't require SharedPreferences, UserDefaults, etc. |
||||
|
|
||||
|
### Alternatives (and why we don't use them) |
||||
|
|
||||
|
| Approach | Why Not | |
||||
|
|----------|---------| |
||||
|
| Hardcode in native code | ❌ Can't switch environments, credentials in code | |
||||
|
| SharedPreferences/UserDefaults | ❌ Platform-specific, complex sync | |
||||
|
| JS bridge at fetch time | ❌ Unreliable in background workers | |
||||
|
| Capacitor preferences plugin | ❌ Overhead, async issues in background | |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Architecture |
||||
|
|
||||
|
``` |
||||
|
┌─────────────────────────────────────────────────────────┐ |
||||
|
│ TypeScript/JavaScript (Host App) │ |
||||
|
│ │ |
||||
|
│ await DailyNotification.configureNativeFetcher({ │ |
||||
|
│ apiBaseUrl: 'http://10.0.2.2:3000', │ |
||||
|
│ activeDid: 'did:ethr:0x...', │ |
||||
|
│ jwtSecret: 'secret' │ |
||||
|
│ }); │ |
||||
|
└─────────────────────┬───────────────────────────────────┘ |
||||
|
│ Capacitor Bridge |
||||
|
▼ |
||||
|
┌─────────────────────────────────────────────────────────┐ |
||||
|
│ DailyNotificationPlugin (Android/iOS) │ |
||||
|
│ │ |
||||
|
│ @PluginMethod │ |
||||
|
│ configureNativeFetcher(PluginCall call) { │ |
||||
|
│ fetcher.configure(apiBaseUrl, activeDid, jwtSecret); │ |
||||
|
│ } │ |
||||
|
└───────────────────────┬─────────────────────────────────────────┘ |
||||
|
│ Direct method call |
||||
|
▼ |
||||
|
┌─────────────────────────────────────────────────────────┐ |
||||
|
│ NativeNotificationContentFetcher (Host App Native Code) │ |
||||
|
│ │ |
||||
|
│ @Override │ |
||||
|
│ void configure(String apiBaseUrl, │ |
||||
|
│ String activeDid, │ |
||||
|
│ String jwtSecret) { │ |
||||
|
│ this.apiBaseUrl = apiBaseUrl; │ |
||||
|
│ this.activeDid = activeDid; │ |
||||
|
│ this.jwtSecret = jwtSecret; │ |
||||
|
│ } │ |
||||
|
└─────────────────────────────────────────────────────────┘ |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## TypeScript Interface |
||||
|
|
||||
|
### Method Signature |
||||
|
|
||||
|
```typescript |
||||
|
interface DailyNotificationPlugin { |
||||
|
configureNativeFetcher(options: { |
||||
|
apiBaseUrl: string; // Base URL for API server |
||||
|
activeDid: string; // Active DID for authentication |
||||
|
jwtSecret: string; // JWT secret for signing tokens |
||||
|
}): Promise謹<void>; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Parameters |
||||
|
|
||||
|
| Parameter | Type | Required | Description | Example | |
||||
|
|-----------|------|----------|-------------|---------| |
||||
|
| `apiBaseUrl` | `string` | ✅ Yes | Base URL for API server. For Android emulator, use `10.0.2.2` to access host machine's `localhost`. | `"http://10.0.2.2:3000"` (Android emulator)<br>`"http://localhost:3000"` (iOS simulator)<br>`"https://api.timesafari.com"` (production) | |
||||
|
| `activeDid` | `string` | ✅ Yes | Active DID (Decentralized Identifier) for authentication. Used as JWT issuer/subject. | `"did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"` | |
||||
|
| `jwtSecret` | `string` | ✅ Yes | JWT secret key for signing authentication tokens. **Keep secure!** | `"test-jwt-secret-for-development"` | |
||||
|
|
||||
|
### Return Value |
||||
|
|
||||
|
- **Success**: `Promise<void>` resolves |
||||
|
- **Failure**: `Promise<void>` rejects with error message |
||||
|
|
||||
|
### Example Usage |
||||
|
|
||||
|
```typescript |
||||
|
import { DailyNotification } from '@capacitor-community/daily-notification'; |
||||
|
import { TEST_USER_ZERO_CONFIG } from './config/test-user-zero'; |
||||
|
|
||||
|
async function initializeNotificationPlugin() { |
||||
|
try { |
||||
|
// Configure native fetcher with User Zero credentials |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: TEST_USER_ZERO_CONFIG.getApiServerUrl(), |
||||
|
activeDid: TEST_USER_ZERO_CONFIG.identity.did, |
||||
|
jwtSecret: 'test-jwt-secret-for-user-zero-development-only' |
||||
|
}); |
||||
|
|
||||
|
console.log('✅ Native fetcher configured successfully'); |
||||
|
} catch (error) { |
||||
|
console.error('❌ Failed to configure native fetcher:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Native Implementation |
||||
|
|
||||
|
### Java/Kotlin Interface |
||||
|
|
||||
|
The `NativeNotificationContentFetcher` interface includes an optional `configure()` method: |
||||
|
|
||||
|
```java |
||||
|
public interface NativeNotificationContentFetcher { |
||||
|
CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context); |
||||
|
|
||||
|
/** |
||||
|
* Optional: Configure the native fetcher with API credentials |
||||
|
*/ |
||||
|
default void configure(String apiBaseUrl, String activeDid, String jwtSecret) { |
||||
|
// Default no-op - fetchers that need config can override |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Implementation Pattern |
||||
|
|
||||
|
```java |
||||
|
public class MyNativeFetcher implements NativeNotificationContentFetcher { |
||||
|
private static final String TAG = "MyNativeFetcher"; |
||||
|
|
||||
|
// Use volatile for thread-safe access from background workers |
||||
|
private volatile String apiBaseUrl; |
||||
|
private volatile String activeDid; |
||||
|
private volatile String jwtSecret; |
||||
|
|
||||
|
@Override |
||||
|
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) { |
||||
|
this.apiBaseUrl = apiBaseUrl; |
||||
|
this.activeDid = activeDid; |
||||
|
this.jwtSecret = jwtSecret; |
||||
|
|
||||
|
Log.i(TAG, "Fetcher configured - API: " + apiBaseUrl + |
||||
|
", DID: " + activeDid.substring(0, Math.min(30, activeDid.length())) + "..."); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) { |
||||
|
return CompletableFuture.supplyAsync(() -> { |
||||
|
// Check if configured |
||||
|
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) { |
||||
|
Log.e(TAG, "Not configured! Call configureNativeFetcher() from TypeScript first."); |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
// Use configured values to make API calls |
||||
|
// ... |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### When to Override `configure()` |
||||
|
|
||||
|
**Override if:** |
||||
|
- ✅ Your fetcher needs API credentials from TypeScript |
||||
|
- ✅ Configuration should come from app config (not hardcoded) |
||||
|
- ✅ You want to avoid platform-specific storage |
||||
|
|
||||
|
**Use default no-op if:** |
||||
|
- ✅ Credentials come from platform storage (SharedPreferences, Keychain) |
||||
|
- ✅ You have hardcoded test credentials |
||||
|
- ✅ Configuration is handled internally |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Complete Example |
||||
|
|
||||
|
### 1. Register Native Fetcher (Android) |
||||
|
|
||||
|
**File**: `android/app/src/main/java/com/example/app/MyApplication.java` |
||||
|
|
||||
|
```java |
||||
|
package com.example.app; |
||||
|
|
||||
|
import android.app.Application; |
||||
|
import com.timesafari.dailynotification.DailyNotificationPlugin; |
||||
|
import com.timesafari.dailynotification.NativeNotificationContentFetcher; |
||||
|
|
||||
|
public class MyApplication extends Application { |
||||
|
@Override |
||||
|
public void onCreate() { |
||||
|
super.onCreate(); |
||||
|
|
||||
|
// Register native fetcher |
||||
|
NativeNotificationContentFetcher fetcher = new MyNativeFetcher(); |
||||
|
DailyNotificationPlugin.setNativeFetcher(fetcher); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**AndroidManifest.xml**: |
||||
|
```xml |
||||
|
<application android:name=".MyApplication"> |
||||
|
<!-- ... --> |
||||
|
</application> |
||||
|
``` |
||||
|
|
||||
|
### 2. Configure from TypeScript |
||||
|
|
||||
|
**File**: `src/services/NotificationService.ts` |
||||
|
|
||||
|
```typescript |
||||
|
import { DailyNotification } from '@capacitor-community/daily-notification'; |
||||
|
import { getApiServerUrl, getActiveDid, getJwtSecret } from './config'; |
||||
|
|
||||
|
export async function setupDailyNotifications() { |
||||
|
// Step 1: Configure native fetcher |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: getApiServerUrl(), |
||||
|
activeDid: getActiveDid(), |
||||
|
jwtSecret: getJwtSecret() |
||||
|
}); |
||||
|
|
||||
|
// Step 2: Configure plugin policy |
||||
|
await DailyNotification.setPolicy({ |
||||
|
prefetchWindowMs: 5 * 60 * 1000, // 5 minutes |
||||
|
retryBackoff: { |
||||
|
minMs: 2000, |
||||
|
maxMs: 600000, |
||||
|
factor: 2, |
||||
|
jitterPct: 20 |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Step 3: Schedule notifications |
||||
|
// ... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. Native Fetcher Implementation |
||||
|
|
||||
|
**File**: `android/app/src/main/java/com/example/app/MyNativeFetcher.java` |
||||
|
|
||||
|
```java |
||||
|
package com.example.app; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
import com.timesafari.dailynotification.*; |
||||
|
import java.util.*; |
||||
|
|
||||
|
public class MyNativeFetcher implements NativeNotificationContentFetcher { |
||||
|
private static final String TAG = "MyNativeFetcher"; |
||||
|
private static final String ENDORSER_ENDPOINT = "/api/v2/report/plansLastUpdatedBetween"; |
||||
|
|
||||
|
// Thread-safe configuration storage |
||||
|
private volatile String apiBaseUrl; |
||||
|
private volatile String activeDid; |
||||
|
private volatile String jwtSecret; |
||||
|
|
||||
|
@Override |
||||
|
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) { |
||||
|
this.apiBaseUrl = apiBaseUrl; |
||||
|
this.activeDid = activeDid; |
||||
|
this.jwtSecret = jwtSecret; |
||||
|
Log.i(TAG, "Configured with API: " + apiBaseUrl); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) { |
||||
|
return CompletableFuture.supplyAsync(() -> { |
||||
|
try { |
||||
|
// Validate configuration |
||||
|
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) { |
||||
|
Log.e(TAG, "Not configured! Call configureNativeFetcher() first."); |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
// Generate JWT token |
||||
|
String jwt = generateJWT(activeDid, jwtSecret); |
||||
|
|
||||
|
// Make API call |
||||
|
String url = apiBaseUrl + reconstituir ENDORSER_ENDPOINT; |
||||
|
// ... HTTP request logic ... |
||||
|
|
||||
|
// Convert response to NotificationContent |
||||
|
List<NotificationContent> contents = parseResponse(response); |
||||
|
return contents; |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Fetch failed", e); |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private String generateJWT(String did, String secret) { |
||||
|
// JWT generation logic |
||||
|
// ... |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Error Handling |
||||
|
|
||||
|
### TypeScript Errors |
||||
|
|
||||
|
```typescript |
||||
|
try { |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: 'http://10.0.2.2:3000', |
||||
|
activeDid: 'did:ethr:0x...', |
||||
|
jwtSecret: 'secret' |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
// Possible errors: |
||||
|
// - "Missing required parameters: apiBaseUrl, activeDid, and jwtSecret are required" |
||||
|
// - "No native fetcher registered. Register one in Application.onCreate() before configuring." |
||||
|
// - "Failed to configure native fetcher: <native exception message>" |
||||
|
console.error('Configuration failed:', error); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Native Fetcher Validation |
||||
|
|
||||
|
```java |
||||
|
@Override |
||||
|
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) { |
||||
|
return CompletableFuture.supplyAsync(() -> { |
||||
|
// Always check configuration before use |
||||
|
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) { |
||||
|
Log.e(TAG, "Not configured! Returning empty list."); |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
// Proceed with fetch... |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Thread Safety |
||||
|
|
||||
|
### Background Worker Context |
||||
|
|
||||
|
Native fetchers are called from **background worker threads** (WorkManager, BGTaskScheduler). Configuration may be set from the **main thread** (TypeScript call). |
||||
|
|
||||
|
### Volatile Fields |
||||
|
|
||||
|
Use `volatile` for configuration fields to ensure: |
||||
|
- ✅ Changes are immediately visible to all threads |
||||
|
- ✅ No caching of stale values |
||||
|
- ✅ Thread-safe access without synchronization |
||||
|
|
||||
|
```java |
||||
|
// ✅ Correct: Volatile fields |
||||
|
private volatile String apiBaseUrl; |
||||
|
private volatile String activeDid; |
||||
|
private volatile String jwtSecret; |
||||
|
|
||||
|
// ❌ Incorrect: Non-volatile fields (may see stale values) |
||||
|
private String apiBaseUrl; |
||||
|
private String activeDid; |
||||
|
private String jwtSecret; |
||||
|
``` |
||||
|
|
||||
|
### Reconfiguration |
||||
|
|
||||
|
Configuration can be updated at any time: |
||||
|
|
||||
|
```typescript |
||||
|
// Update configuration when user switches accounts |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: newApiUrl, |
||||
|
activeDid: newActiveDid, |
||||
|
jwtSecret: newJwtSecret |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
Subsequent `fetchContent()` calls will use the new configuration immediately. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Best Practices |
||||
|
|
||||
|
### 1. Configure Early |
||||
|
|
||||
|
Call `configureNativeFetcher()` as soon as credentials are available: |
||||
|
|
||||
|
```typescript |
||||
|
// ✅ Good: Configure immediately after app startup |
||||
|
await appInit(); |
||||
|
await DailyNotification.configureNativeFetcher(config); |
||||
|
|
||||
|
// ❌ Bad: Wait until first fetch |
||||
|
// (background worker might trigger before configuration) |
||||
|
``` |
||||
|
|
||||
|
### 2. Validate in Native Code |
||||
|
|
||||
|
Always validate configuration before use: |
||||
|
|
||||
|
```java |
||||
|
@Override |
||||
|
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) { |
||||
|
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) { |
||||
|
return CompletableFuture.completedFuture(Collections.emptyList()); |
||||
|
} |
||||
|
// ... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. Log Configuration (Sanitized) |
||||
|
|
||||
|
Log configuration for debugging, but sanitize sensitive data: |
||||
|
|
||||
|
```java |
||||
|
@Override |
||||
|
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) { |
||||
|
this.apiBaseUrl = apiBaseUrl; |
||||
|
this.activeDid = activeDid; |
||||
|
this.jwtSecret = jwtSecret; |
||||
|
|
||||
|
Log.i(TAG, "Configured - API: " + apiBaseUrl + |
||||
|
", DID: " + activeDid.substring(0, Math.min(30, activeDid.length())) + "..."); |
||||
|
// Don't log jwtSecret! |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 4. Handle Reconfiguration |
||||
|
|
||||
|
If configuration changes (e.g., user switches accounts), update gracefully: |
||||
|
|
||||
|
```java |
||||
|
@Override |
||||
|
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) { |
||||
|
// Clear any cached state if DID changes |
||||
|
if (this.activeDid != null && !this.activeDid.equals(activeDid)) { |
||||
|
Log.i(TAG, "ActiveDid changed - clearing cache"); |
||||
|
clearCache(); |
||||
|
} |
||||
|
|
||||
|
this.apiBaseUrl = apiBaseUrl; |
||||
|
this.activeDid = activeDid; |
||||
|
this.jwtSecret = jwtSecret; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 5. Secure JWT Secrets |
||||
|
|
||||
|
In production, consider: |
||||
|
- Using secure storage (Android Keystore, iOS Keychain) |
||||
|
- Deriving secret from device-specific keys |
||||
|
- Using short-lived secrets with rotation |
||||
|
|
||||
|
```typescript |
||||
|
// Example: Get secret from secure storage |
||||
|
const jwtSecret = await SecureStorage.get({ key: 'jwt_secret' }); |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: apiUrl, |
||||
|
activeDid: activeDid, |
||||
|
jwtSecret: jwtSecret.value |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
### Error: "No native fetcher registered" |
||||
|
|
||||
|
**Cause**: Native fetcher not registered in `Application.onCreate()` |
||||
|
|
||||
|
**Solution**: |
||||
|
```java |
||||
|
// In MyApplication.java |
||||
|
@Override |
||||
|
public void onCreate() { |
||||
|
super.onCreate(); |
||||
|
DailyNotificationPlugin.setNativeFetcher(new MyNativeFetcher()); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Error: "Missing required parameters" |
||||
|
|
||||
|
**Cause**: One or more parameters not provided |
||||
|
|
||||
|
**Solution**: Ensure all three parameters are provided: |
||||
|
```typescript |
||||
|
await DailyNotification.configureNativeFetcher({ |
||||
|
apiBaseUrl: 'http://10.0.2.2:3000', // ✅ Required |
||||
|
activeDid: 'did:ethr:0x...', // ✅ Required |
||||
|
jwtSecret: 'secret' // ✅ Required |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### Fetcher Not Using Configuration |
||||
|
|
||||
|
**Cause**: Native fetcher not checking configuration before use |
||||
|
|
||||
|
**Solution**: Validate in `fetchContent()`: |
||||
|
```java |
||||
|
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) { |
||||
|
Log.e(TAG, "Not configured!"); |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Configuration Lost After App Restart |
||||
|
|
||||
|
**Cause**: Configuration is in-memory only (by design) |
||||
|
|
||||
|
**Solution**: Reconfigure after app startup: |
||||
|
```typescript |
||||
|
// In app initialization |
||||
|
platform.whenReady(() => { |
||||
|
setupDailyNotifications(); // Calls configureNativeFetcher() |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Related Documentation |
||||
|
|
||||
|
- **[Integration Point Refactor Guide](./INTEGRATION_REFACTOR_QUICK_START.md)** - Overall architecture |
||||
|
- **[API Reference](./API.md)** - Complete plugin API |
||||
|
- **[Native Fetcher Examples](../examples/native-fetcher-android.kt)** - Kotlin implementation examples |
||||
|
- **[Test Implementation](../test-apps/daily-notification-test/android/app/src/main/java/com/timesafari/dailynotification/test/TestNativeFetcher.java)** - Complete reference implementation |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Last Updated**: 2025-01-28 |
||||
|
**Version**: 1.0.0 |
||||
|
|
||||
Loading…
Reference in new issue