Compare commits
4 Commits
android-fi
...
android-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bc75372b5 | ||
|
|
57c7ddb7eb | ||
|
|
a3afefeda9 | ||
|
|
d0155f0b22 |
100
BUILDING.md
100
BUILDING.md
@@ -44,9 +44,11 @@ npx cap run android
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
### Required Software
|
### Required Software
|
||||||
- **Android Studio** (latest stable version)
|
- **Android Studio** (latest stable version) - for Android development
|
||||||
- **Java 11+** (for Kotlin compilation)
|
- **Java 11+** (for Kotlin compilation)
|
||||||
- **Android SDK** with API level 21+
|
- **Android SDK** with API level 21+
|
||||||
|
- **Xcode** (latest stable version) - for iOS development (macOS only)
|
||||||
|
- **Xcode Command Line Tools** - required for iOS builds (includes `xcodebuild`, `sqlite3`, etc.)
|
||||||
- **Node.js** 16+ (for TypeScript compilation)
|
- **Node.js** 16+ (for TypeScript compilation)
|
||||||
- **npm** or **yarn** (for dependency management)
|
- **npm** or **yarn** (for dependency management)
|
||||||
|
|
||||||
@@ -54,11 +56,35 @@ npx cap run android
|
|||||||
- **Gradle Wrapper** (included in project)
|
- **Gradle Wrapper** (included in project)
|
||||||
- **Kotlin** (configured in build.gradle)
|
- **Kotlin** (configured in build.gradle)
|
||||||
- **TypeScript** (for plugin interface)
|
- **TypeScript** (for plugin interface)
|
||||||
|
- **CocoaPods** - for iOS dependency management
|
||||||
|
|
||||||
|
### iOS-Specific Prerequisites
|
||||||
|
|
||||||
|
**Xcode Command Line Tools** are required for iOS builds. The build script will verify these are installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Xcode Command Line Tools (if not already installed)
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
```bash
|
||||||
|
# Check if Command Line Tools are configured
|
||||||
|
xcode-select -p
|
||||||
|
|
||||||
|
# Verify xcodebuild is available
|
||||||
|
xcodebuild -version
|
||||||
|
|
||||||
|
# Verify sqlite3 is available (part of Command Line Tools)
|
||||||
|
sqlite3 --version
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The build script automatically checks for Command Line Tools and will fail with clear error messages if they're missing.
|
||||||
|
|
||||||
### System Requirements
|
### System Requirements
|
||||||
- **RAM**: 4GB minimum, 8GB recommended
|
- **RAM**: 4GB minimum, 8GB recommended
|
||||||
- **Storage**: 2GB free space
|
- **Storage**: 2GB free space
|
||||||
- **OS**: Windows 10+, macOS 10.14+, or Linux
|
- **OS**: Windows 10+, macOS 10.14+, or Linux (iOS development requires macOS)
|
||||||
|
|
||||||
## Build Methods
|
## Build Methods
|
||||||
|
|
||||||
@@ -297,6 +323,8 @@ android/build/reports/tests/test/index.html
|
|||||||
|
|
||||||
### iOS Native Build Process
|
### iOS Native Build Process
|
||||||
|
|
||||||
|
**Prerequisites:** Ensure Xcode Command Line Tools are installed (see [Prerequisites](#prerequisites) section). The build script will verify this automatically.
|
||||||
|
|
||||||
#### 1. Navigate to iOS Directory
|
#### 1. Navigate to iOS Directory
|
||||||
```bash
|
```bash
|
||||||
cd ios
|
cd ios
|
||||||
@@ -307,6 +335,12 @@ cd ios
|
|||||||
pod install
|
pod install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** If you encounter issues with `pod install`, ensure Xcode Command Line Tools are properly configured:
|
||||||
|
```bash
|
||||||
|
xcode-select --install # Install if missing
|
||||||
|
xcode-select -p # Verify installation path
|
||||||
|
```
|
||||||
|
|
||||||
#### 3. Build Commands
|
#### 3. Build Commands
|
||||||
```bash
|
```bash
|
||||||
# Build using Xcode command line
|
# Build using Xcode command line
|
||||||
@@ -782,6 +816,13 @@ The project includes several automated build scripts in the `scripts/` directory
|
|||||||
./scripts/build-native.sh --platform ios
|
./scripts/build-native.sh --platform ios
|
||||||
./scripts/build-native.sh --verbose
|
./scripts/build-native.sh --verbose
|
||||||
|
|
||||||
|
# Clean build (removes all build artifacts and caches)
|
||||||
|
./scripts/clean-build.sh
|
||||||
|
./scripts/clean-build.sh --all # Also cleans caches and reinstalls dependencies
|
||||||
|
./scripts/clean-build.sh --clean-gradle-cache # Clean Gradle cache
|
||||||
|
./scripts/clean-build.sh --clean-derived-data # Clean Xcode DerivedData
|
||||||
|
./scripts/clean-build.sh --reinstall-node # Reinstall node_modules
|
||||||
|
|
||||||
# TimeSafari-specific builds
|
# TimeSafari-specific builds
|
||||||
node scripts/build-timesafari.js
|
node scripts/build-timesafari.js
|
||||||
|
|
||||||
@@ -948,6 +989,28 @@ adb logcat | grep DailyNotification
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Clean Build (First Step for Many Issues)
|
||||||
|
|
||||||
|
If you encounter persistent build issues, try a clean build first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean all build artifacts (recommended first step)
|
||||||
|
./scripts/clean-build.sh
|
||||||
|
|
||||||
|
# Clean everything including caches (for stubborn issues)
|
||||||
|
./scripts/clean-build.sh --all
|
||||||
|
|
||||||
|
# Then rebuild
|
||||||
|
./scripts/build-native.sh --platform all
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use clean-build:**
|
||||||
|
- Build errors that don't make sense
|
||||||
|
- Dependency conflicts
|
||||||
|
- Stale build artifacts
|
||||||
|
- After switching branches
|
||||||
|
- After updating dependencies
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
#### Gradle Sync Failures
|
#### Gradle Sync Failures
|
||||||
@@ -1019,6 +1082,39 @@ File → Project Structure → SDK Location
|
|||||||
# Solution: Check Kotlin version in build.gradle
|
# Solution: Check Kotlin version in build.gradle
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### iOS Build Issues
|
||||||
|
```bash
|
||||||
|
# Problem: "Xcode Command Line Tools not configured"
|
||||||
|
# Error: xcode-select -p fails or xcodebuild not found
|
||||||
|
# Solution: Install Command Line Tools
|
||||||
|
xcode-select --install
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
xcode-select -p
|
||||||
|
xcodebuild -version
|
||||||
|
sqlite3 --version
|
||||||
|
|
||||||
|
# Problem: "sqlite3 not found" or linker errors with SQLite
|
||||||
|
# Solution: Ensure Command Line Tools are properly installed
|
||||||
|
# The build script checks for this automatically, but if you see linker errors:
|
||||||
|
xcode-select --install
|
||||||
|
|
||||||
|
# Problem: pkgx SQLite conflicts with iOS builds
|
||||||
|
# Error: Linker errors about libsqlite3.dylib
|
||||||
|
# Solution: The build script automatically handles this by unsetting problematic
|
||||||
|
# environment variables. If issues persist:
|
||||||
|
unset PKGX_DIR DYLD_LIBRARY_PATH LD_LIBRARY_PATH
|
||||||
|
./scripts/build-native.sh --platform ios
|
||||||
|
|
||||||
|
# Problem: "pod install" fails
|
||||||
|
# Solution: Ensure Command Line Tools are installed
|
||||||
|
xcode-select --install
|
||||||
|
# Then reinstall CocoaPods dependencies
|
||||||
|
cd ios
|
||||||
|
pod deintegrate
|
||||||
|
pod install
|
||||||
|
```
|
||||||
|
|
||||||
#### Capacitor Integration Issues
|
#### Capacitor Integration Issues
|
||||||
```bash
|
```bash
|
||||||
# Problem: Plugin not found in Capacitor app
|
# Problem: Plugin not found in Capacitor app
|
||||||
|
|||||||
@@ -1,47 +1,46 @@
|
|||||||
fix(build): add SQLite conflict detection and Command Line Tools verification
|
docs(building): update BUILDING.md with iOS prerequisites and clean-build script
|
||||||
|
|
||||||
Prevents iOS build failures caused by pkgx SQLite linking conflicts and
|
Updates BUILDING.md to reflect recent changes in build-native.sh, especially
|
||||||
ensures Xcode Command Line Tools are properly installed.
|
the Xcode Command Line Tools prerequisite check and the clean-build script.
|
||||||
|
|
||||||
Problem:
|
Problem:
|
||||||
- pkgx installs SQLite built for macOS, causing linker errors when building
|
- BUILDING.md didn't mention Xcode Command Line Tools prerequisite
|
||||||
for iOS simulator: "linking in dylib built for 'macOS'"
|
(recently added to build-native.sh)
|
||||||
- Missing Command Line Tools cause build failures without clear error messages
|
- clean-build.sh script exists but wasn't documented
|
||||||
|
- iOS build troubleshooting lacked Command Line Tools guidance
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
- Add check_sqlite_conflicts() function
|
- Add Xcode Command Line Tools to Prerequisites section
|
||||||
- Detects pkgx SQLite installations in ~/.pkgx
|
- Document installation command (xcode-select --install)
|
||||||
- Warns about macOS dylibs that will cause iOS simulator build failures
|
- Include verification steps (xcode-select -p, xcodebuild -version)
|
||||||
- Checks for system SQLite from Command Line Tools
|
- Note that build script automatically checks for these tools
|
||||||
- Validates library paths (DYLD_LIBRARY_PATH, LD_LIBRARY_PATH)
|
- Explain that sqlite3 is part of Command Line Tools
|
||||||
|
|
||||||
- Add check_command_line_tools() function
|
- Document clean-build.sh script in Build Scripts section
|
||||||
- Verifies Xcode Command Line Tools are installed and configured
|
- Basic usage: ./scripts/clean-build.sh
|
||||||
- Checks for xcodebuild availability
|
- All options: --all, --clean-gradle-cache, --clean-derived-data,
|
||||||
- Verifies sqlite3 is available (part of Command Line Tools)
|
--reinstall-node
|
||||||
- Provides clear error messages with installation instructions
|
- Explain when to use clean builds
|
||||||
|
|
||||||
- Enhance pkgx detection in iOS build functions
|
- Enhance iOS Native Build Process section
|
||||||
- Specifically searches for pkgx SQLite dylibs
|
- Add prerequisite note about Command Line Tools
|
||||||
- Automatically removes pkgx paths from PATH environment variable
|
- Include troubleshooting commands for pod install issues
|
||||||
- Provides detailed warnings about detected conflicts
|
- Reference prerequisites section for details
|
||||||
- Cleans all problematic environment variables before building
|
|
||||||
|
|
||||||
- Integrate checks into environment validation
|
- Add comprehensive troubleshooting sections
|
||||||
- Runs automatically when building for iOS
|
- Clean Build section at start of Troubleshooting
|
||||||
- Provides early warnings before build starts
|
- Recommends clean-build as first step for many issues
|
||||||
- Fails fast with clear error messages if tools are missing
|
- Lists when to use clean builds
|
||||||
|
- iOS Build Issues section
|
||||||
|
- Command Line Tools configuration errors
|
||||||
|
- SQLite/linker issues and pkgx conflicts
|
||||||
|
- CocoaPods installation problems
|
||||||
|
- All with clear solutions and commands
|
||||||
|
|
||||||
This fixes the linker error:
|
The documentation now accurately reflects:
|
||||||
"ld: building for 'iOS-simulator', but linking in dylib
|
- Xcode Command Line Tools as required iOS prerequisite
|
||||||
(/Users/trent/.pkgx/sqlite.org/v3.44.2/lib/libsqlite3.0.dylib)
|
- clean-build.sh as available build tool
|
||||||
built for 'macOS'"
|
- Complete iOS troubleshooting workflow
|
||||||
|
|
||||||
The build script now:
|
|
||||||
- Detects pkgx SQLite conflicts before building
|
|
||||||
- Automatically fixes environment variables
|
|
||||||
- Verifies Command Line Tools are installed
|
|
||||||
- Provides clear guidance for manual fixes if needed
|
|
||||||
|
|
||||||
Files modified:
|
Files modified:
|
||||||
- scripts/build-native.sh
|
- BUILDING.md
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ package com.timesafari.dailynotification;
|
|||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -155,15 +154,10 @@ public class DailyNotificationScheduler {
|
|||||||
cancelNotification(duplicateId);
|
cancelNotification(duplicateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts
|
// Create intent for the notification; setPackage ensures AlarmManager delivery on all OEMs
|
||||||
// AlarmManager requires explicit component matching when delivering broadcasts
|
Intent intent = new Intent(context, DailyNotificationReceiver.class);
|
||||||
ComponentName receiverComponent = new ComponentName(
|
|
||||||
context.getPackageName(),
|
|
||||||
"com.timesafari.dailynotification.DailyNotificationReceiver"
|
|
||||||
);
|
|
||||||
Intent intent = new Intent(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
|
|
||||||
intent.setComponent(receiverComponent);
|
|
||||||
intent.setPackage(context.getPackageName());
|
intent.setPackage(context.getPackageName());
|
||||||
|
intent.setAction(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
|
||||||
intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
|
intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
|
||||||
|
|
||||||
// Check if this is a static reminder
|
// Check if this is a static reminder
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.app.NotificationChannel
|
|||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -147,15 +146,9 @@ class NotifyReceiver : BroadcastReceiver() {
|
|||||||
// This prevents duplicate alarms when multiple scheduling paths race
|
// This prevents duplicate alarms when multiple scheduling paths race
|
||||||
// Strategy: Check both by scheduleId (stable) and by trigger time (catches different scheduleIds for same time)
|
// Strategy: Check both by scheduleId (stable) and by trigger time (catches different scheduleIds for same time)
|
||||||
val requestCode = getRequestCode(stableScheduleId)
|
val requestCode = getRequestCode(stableScheduleId)
|
||||||
// CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts
|
val checkIntent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||||
// AlarmManager requires explicit component matching when delivering broadcasts
|
|
||||||
val receiverComponent = ComponentName(
|
|
||||||
context.packageName,
|
|
||||||
"com.timesafari.dailynotification.DailyNotificationReceiver"
|
|
||||||
)
|
|
||||||
val checkIntent = Intent("com.timesafari.daily.NOTIFICATION").apply {
|
|
||||||
setComponent(receiverComponent)
|
|
||||||
setPackage(context.packageName)
|
setPackage(context.packageName)
|
||||||
|
action = "com.timesafari.daily.NOTIFICATION"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check 1: Same scheduleId (stable requestCode) - most reliable
|
// Check 1: Same scheduleId (stable requestCode) - most reliable
|
||||||
@@ -277,21 +270,14 @@ class NotifyReceiver : BroadcastReceiver() {
|
|||||||
Log.w(TAG, "Failed to store notification content in database, continuing with alarm scheduling", e)
|
Log.w(TAG, "Failed to store notification content in database, continuing with alarm scheduling", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts
|
// FIX: Use DailyNotificationReceiver (registered in manifest) instead of NotifyReceiver
|
||||||
// AlarmManager requires explicit component matching when delivering broadcasts.
|
// FIX: Set action to match manifest registration; setPackage() ensures AlarmManager
|
||||||
// Using Intent(context, Class) constructor may not work reliably with AlarmManager
|
// delivery reaches this app on all OEMs (see daily-notification-plugin-android-receiver-issue)
|
||||||
// on all Android versions, especially when the app is in certain states.
|
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||||
// Solution: Create Intent with action, then explicitly set component and package.
|
|
||||||
val intent = Intent("com.timesafari.daily.NOTIFICATION").apply {
|
|
||||||
// Explicitly set component to ensure AlarmManager can match it to the receiver
|
|
||||||
setComponent(receiverComponent)
|
|
||||||
// Explicitly set package to ensure it matches the app's package (not plugin's)
|
|
||||||
setPackage(context.packageName)
|
setPackage(context.packageName)
|
||||||
// Must match manifest intent-filter action
|
action = "com.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action
|
||||||
// DailyNotificationReceiver expects this extra
|
putExtra("notification_id", notificationId) // DailyNotificationReceiver expects this extra
|
||||||
putExtra("notification_id", notificationId)
|
putExtra("schedule_id", stableScheduleId) // Add stable scheduleId for tracking
|
||||||
// Add stable scheduleId for tracking
|
|
||||||
putExtra("schedule_id", stableScheduleId)
|
|
||||||
// Also preserve original extras for backward compatibility if needed
|
// Also preserve original extras for backward compatibility if needed
|
||||||
putExtra("title", config.title)
|
putExtra("title", config.title)
|
||||||
putExtra("body", config.body)
|
putExtra("body", config.body)
|
||||||
@@ -299,8 +285,7 @@ class NotifyReceiver : BroadcastReceiver() {
|
|||||||
putExtra("vibration", config.vibration ?: true)
|
putExtra("vibration", config.vibration ?: true)
|
||||||
putExtra("priority", config.priority ?: "normal")
|
putExtra("priority", config.priority ?: "normal")
|
||||||
putExtra("is_static_reminder", isStaticReminder)
|
putExtra("is_static_reminder", isStaticReminder)
|
||||||
// Store trigger time for debugging
|
putExtra("trigger_time", triggerAtMillis) // Store trigger time for debugging
|
||||||
putExtra("trigger_time", triggerAtMillis)
|
|
||||||
if (reminderId != null) {
|
if (reminderId != null) {
|
||||||
putExtra("reminder_id", reminderId)
|
putExtra("reminder_id", reminderId)
|
||||||
}
|
}
|
||||||
@@ -425,14 +410,10 @@ class NotifyReceiver : BroadcastReceiver() {
|
|||||||
*/
|
*/
|
||||||
fun cancelNotification(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null) {
|
fun cancelNotification(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null) {
|
||||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
// CRITICAL FIX: Use same Intent format as scheduling (explicit component and package)
|
// FIX: Use DailyNotificationReceiver to match what was scheduled
|
||||||
val receiverComponent = ComponentName(
|
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||||
context.packageName,
|
|
||||||
"com.timesafari.dailynotification.DailyNotificationReceiver"
|
|
||||||
)
|
|
||||||
val intent = Intent("com.timesafari.daily.NOTIFICATION").apply {
|
|
||||||
setComponent(receiverComponent)
|
|
||||||
setPackage(context.packageName)
|
setPackage(context.packageName)
|
||||||
|
action = "com.timesafari.daily.NOTIFICATION"
|
||||||
}
|
}
|
||||||
val requestCode = when {
|
val requestCode = when {
|
||||||
scheduleId != null -> getRequestCode(scheduleId)
|
scheduleId != null -> getRequestCode(scheduleId)
|
||||||
@@ -461,14 +442,10 @@ class NotifyReceiver : BroadcastReceiver() {
|
|||||||
* @return true if alarm is scheduled, false otherwise
|
* @return true if alarm is scheduled, false otherwise
|
||||||
*/
|
*/
|
||||||
fun isAlarmScheduled(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null): Boolean {
|
fun isAlarmScheduled(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null): Boolean {
|
||||||
// CRITICAL FIX: Use same Intent format as scheduling (explicit component and package)
|
// FIX: Use DailyNotificationReceiver to match what was scheduled
|
||||||
val receiverComponent = ComponentName(
|
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||||
context.packageName,
|
|
||||||
"com.timesafari.dailynotification.DailyNotificationReceiver"
|
|
||||||
)
|
|
||||||
val intent = Intent("com.timesafari.daily.NOTIFICATION").apply {
|
|
||||||
setComponent(receiverComponent)
|
|
||||||
setPackage(context.packageName)
|
setPackage(context.packageName)
|
||||||
|
action = "com.timesafari.daily.NOTIFICATION"
|
||||||
}
|
}
|
||||||
val requestCode = when {
|
val requestCode = when {
|
||||||
scheduleId != null -> getRequestCode(scheduleId)
|
scheduleId != null -> getRequestCode(scheduleId)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.timesafari.dailynotification
|
package com.timesafari.dailynotification
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@@ -441,16 +440,10 @@ class ReactivationManager(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
private fun alarmsExist(): Boolean {
|
private fun alarmsExist(): Boolean {
|
||||||
return try {
|
return try {
|
||||||
// Check if any PendingIntent for our receiver exists
|
// Check if any PendingIntent for our receiver exists (must match NotifyReceiver schedule path)
|
||||||
// This is more reliable than nextAlarmClock
|
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||||
// CRITICAL FIX: Use DailyNotificationReceiver with explicit component/package
|
|
||||||
val receiverComponent = ComponentName(
|
|
||||||
context.packageName,
|
|
||||||
"com.timesafari.dailynotification.DailyNotificationReceiver"
|
|
||||||
)
|
|
||||||
val intent = Intent("com.timesafari.daily.NOTIFICATION").apply {
|
|
||||||
setComponent(receiverComponent)
|
|
||||||
setPackage(context.packageName)
|
setPackage(context.packageName)
|
||||||
|
action = "com.timesafari.daily.NOTIFICATION"
|
||||||
}
|
}
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Running Android App in Standalone Emulator (Without Android Studio)
|
# Running Android App in Standalone Emulator (Without Android Studio)
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
**Last Updated**: 2025-10-12 06:50:00 UTC
|
**Last Updated**: 2026-02-05
|
||||||
**Version**: 1.0.0
|
**Version**: 1.1.0
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -22,6 +22,81 @@ This guide demonstrates how to run the DailyNotification plugin test app in a st
|
|||||||
- **Storage**: 2GB free space for emulator
|
- **Storage**: 2GB free space for emulator
|
||||||
- **OS**: Linux, macOS, or Windows with WSL
|
- **OS**: Linux, macOS, or Windows with WSL
|
||||||
|
|
||||||
|
## Checking and Installing Prerequisites
|
||||||
|
|
||||||
|
### How to check
|
||||||
|
|
||||||
|
Run these in a terminal. If a command is missing or a check fails, use the install steps below.
|
||||||
|
|
||||||
|
| Requirement | How to check |
|
||||||
|
|------------------|--------------|
|
||||||
|
| **Node.js** | `node --version` (v14+ recommended; test app may require 20+) |
|
||||||
|
| **npm** | `npm --version` |
|
||||||
|
| **Java** | `java -version` (Java 11+; build scripts expect 11+) |
|
||||||
|
| **ANDROID_HOME** | `echo $ANDROID_HOME` (must be set to your Android SDK root) |
|
||||||
|
| **adb** | `adb version` (must be on PATH; usually `$ANDROID_HOME/platform-tools/adb`) |
|
||||||
|
| **emulator** | `emulator -version` (must be on PATH; usually `$ANDROID_HOME/emulator/emulator`) |
|
||||||
|
| **At least one AVD** | `emulator -list-avds` (must list at least one device name) |
|
||||||
|
|
||||||
|
**Project script:** From the repo root you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/check-environment.js
|
||||||
|
```
|
||||||
|
|
||||||
|
This checks Node, npm, Java, and `ANDROID_HOME`. It does **not** check `adb`, `emulator`, or AVDs—verify those manually as above.
|
||||||
|
|
||||||
|
### How to install
|
||||||
|
|
||||||
|
- **Node.js and npm**
|
||||||
|
- Install from [nodejs.org](https://nodejs.org/) (LTS), or on macOS: `brew install node`.
|
||||||
|
|
||||||
|
- **Java (JDK 11+)**
|
||||||
|
- macOS: `brew install openjdk@17` and follow the caveats to link (e.g. `sudo ln -sfn $(brew --prefix)/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk`).
|
||||||
|
- Or install [Eclipse Temurin](https://adoptium.net/) / [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) and ensure `java` and `javac` are on your PATH.
|
||||||
|
|
||||||
|
- **Android SDK (without Android Studio)**
|
||||||
|
1. Download the [Command-line tools only](https://developer.android.com/studio#command-tools) package for your OS.
|
||||||
|
2. Create an SDK directory, e.g. `mkdir -p ~/android-sdk` and extract the zip so that you have `~/android-sdk/cmdline-tools/latest/` (the `bin` folder with `sdkmanager` and `avdmanager` must be inside `cmdline-tools/latest/`).
|
||||||
|
3. Set environment variables (add to `~/.zshrc` or `~/.bashrc`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ANDROID_HOME=$HOME/android-sdk
|
||||||
|
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Install required SDK packages (accept licenses when prompted):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdkmanager "platform-tools"
|
||||||
|
sdkmanager "emulator"
|
||||||
|
sdkmanager "platforms;android-35"
|
||||||
|
sdkmanager "build-tools;35.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Install a system image that matches your host CPU:
|
||||||
|
- **Apple Silicon (M1/M2/M3, aarch64):** `sdkmanager "system-images;android-35;google_apis;arm64-v8a"`
|
||||||
|
- **Intel Mac / Windows / Linux (x86_64):** `sdkmanager "system-images;android-35;google_apis;x86_64"`
|
||||||
|
|
||||||
|
5. Create at least one AVD (use the same image type you installed):
|
||||||
|
|
||||||
|
**Apple Silicon:**
|
||||||
|
```bash
|
||||||
|
avdmanager create avd -n Pixel8_API35 -k "system-images;android-35;google_apis;arm64-v8a" -d "pixel_8"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Intel / x86_64:**
|
||||||
|
```bash
|
||||||
|
avdmanager create avd -n Pixel8_API35 -k "system-images;android-35;google_apis;x86_64" -d "pixel_8"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start the emulator with: `emulator -avd Pixel8_API35 -no-snapshot-load &` and use `adb wait-for-device` before building/installing the app.
|
||||||
|
|
||||||
|
- **Gradle**
|
||||||
|
The project uses the Gradle Wrapper (`gradlew`) inside the app’s `android` directory. No separate Gradle install is needed.
|
||||||
|
|
||||||
|
After installing, run the checks again to confirm `adb`, `emulator`, and `emulator -list-avds` work.
|
||||||
|
|
||||||
## Step-by-Step Process
|
## Step-by-Step Process
|
||||||
|
|
||||||
### 1. Check Available Emulators
|
### 1. Check Available Emulators
|
||||||
@@ -31,21 +106,21 @@ This guide demonstrates how to run the DailyNotification plugin test app in a st
|
|||||||
emulator -list-avds
|
emulator -list-avds
|
||||||
|
|
||||||
# Example output:
|
# Example output:
|
||||||
# Pixel8_API34
|
# Pixel8_API35
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Start the Emulator
|
### 2. Start the Emulator
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start emulator in background (recommended)
|
# Start emulator in background (recommended)
|
||||||
emulator -avd Pixel8_API34 -no-snapshot-load &
|
emulator -avd Pixel8_API35 -no-snapshot-load &
|
||||||
|
|
||||||
# Alternative: Start in foreground
|
# Alternative: Start in foreground
|
||||||
emulator -avd Pixel8_API34
|
emulator -avd Pixel8_API35
|
||||||
```
|
```
|
||||||
|
|
||||||
**Flags Explained:**
|
**Flags Explained:**
|
||||||
- `-avd Pixel8_API34` - Specifies the AVD to use
|
- `-avd Pixel8_API35` - Specifies the AVD to use
|
||||||
- `-no-snapshot-load` - Forces fresh boot (recommended for testing)
|
- `-no-snapshot-load` - Forces fresh boot (recommended for testing)
|
||||||
- `&` - Runs in background (optional)
|
- `&` - Runs in background (optional)
|
||||||
|
|
||||||
@@ -141,7 +216,7 @@ adb logcat -c && adb logcat
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Start emulator
|
# 1. Start emulator
|
||||||
emulator -avd Pixel8_API34 -no-snapshot-load &
|
emulator -avd Pixel8_API35 -no-snapshot-load &
|
||||||
|
|
||||||
# 2. Wait for emulator
|
# 2. Wait for emulator
|
||||||
adb wait-for-device
|
adb wait-for-device
|
||||||
@@ -211,7 +286,17 @@ ps aux | grep emulator
|
|||||||
pkill -f emulator
|
pkill -f emulator
|
||||||
|
|
||||||
# Start with verbose logging
|
# Start with verbose logging
|
||||||
emulator -avd Pixel8_API34 -verbose
|
emulator -avd Pixel8_API35 -verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
#### "x86_64 is not supported by the QEMU2 emulator on aarch64 host"
|
||||||
|
On Apple Silicon (M1/M2/M3), the emulator cannot run x86_64 system images. Use an ARM64 image and AVD instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdkmanager "system-images;android-35;google_apis;arm64-v8a"
|
||||||
|
avdmanager delete avd -n Pixel8_API35 # if you already created an x86_64 AVD
|
||||||
|
avdmanager create avd -n Pixel8_API35 -k "system-images;android-35;google_apis;arm64-v8a" -d "pixel_8"
|
||||||
|
emulator -avd Pixel8_API35 -no-snapshot-load
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ADB Connection Issues
|
#### ADB Connection Issues
|
||||||
@@ -256,13 +341,13 @@ cd android && ./gradlew clean
|
|||||||
#### Emulator Performance
|
#### Emulator Performance
|
||||||
```bash
|
```bash
|
||||||
# Start with hardware acceleration
|
# Start with hardware acceleration
|
||||||
emulator -avd Pixel8_API34 -accel on
|
emulator -avd Pixel8_API35 -accel on
|
||||||
|
|
||||||
# Start with specific RAM allocation
|
# Start with specific RAM allocation
|
||||||
emulator -avd Pixel8_API34 -memory 2048
|
emulator -avd Pixel8_API35 -memory 2048
|
||||||
|
|
||||||
# Start with GPU acceleration
|
# Start with GPU acceleration
|
||||||
emulator -avd Pixel8_API34 -gpu host
|
emulator -avd Pixel8_API35 -gpu host
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Build Performance
|
#### Build Performance
|
||||||
@@ -336,7 +421,7 @@ adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
|||||||
### Automated Testing
|
### Automated Testing
|
||||||
```bash
|
```bash
|
||||||
# CI/CD pipeline
|
# CI/CD pipeline
|
||||||
emulator -avd Pixel8_API34 -no-snapshot-load &
|
emulator -avd Pixel8_API35 -no-snapshot-load &
|
||||||
adb wait-for-device
|
adb wait-for-device
|
||||||
./scripts/build-native.sh --platform android
|
./scripts/build-native.sh --platform android
|
||||||
cd android && ./gradlew :app:assembleDebug
|
cd android && ./gradlew :app:assembleDebug
|
||||||
|
|||||||
@@ -108,8 +108,7 @@ check_requirements() {
|
|||||||
# Check Android requirements if building Android
|
# Check Android requirements if building Android
|
||||||
if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then
|
if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then
|
||||||
if ! command -v adb &> /dev/null; then
|
if ! command -v adb &> /dev/null; then
|
||||||
log_warn "Android SDK tools not found (adb not in PATH)."
|
log_warn "Android SDK not found (adb not in PATH). Android build will be skipped."
|
||||||
log_warn "APK can still be built, but install/launch requires adb."
|
|
||||||
else
|
else
|
||||||
log_info "✅ Android SDK: $(adb version | head -1)"
|
log_info "✅ Android SDK: $(adb version | head -1)"
|
||||||
fi
|
fi
|
||||||
@@ -221,11 +220,23 @@ if ! npm run build; then
|
|||||||
fi
|
fi
|
||||||
log_info "Web assets built successfully"
|
log_info "Web assets built successfully"
|
||||||
|
|
||||||
# Step 2: Sync Capacitor
|
# Step 2: Sync Capacitor (Android-only when building only Android to avoid iOS pod install failure)
|
||||||
log_step "Syncing Capacitor with native projects..."
|
log_step "Syncing Capacitor with native projects..."
|
||||||
if ! npm run cap:sync; then
|
if [ "$BUILD_ALL" = true ]; then
|
||||||
log_error "Capacitor sync failed"
|
if ! npm run cap:sync; then
|
||||||
exit 1
|
log_error "Capacitor sync failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ "$BUILD_ANDROID" = true ]; then
|
||||||
|
if ! npm run cap:sync:android; then
|
||||||
|
log_error "Capacitor sync (Android) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ "$BUILD_IOS" = true ]; then
|
||||||
|
if ! npm run cap:sync:ios; then
|
||||||
|
log_error "Capacitor sync (iOS) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
log_info "Capacitor sync completed"
|
log_info "Capacitor sync completed"
|
||||||
|
|
||||||
@@ -239,105 +250,28 @@ if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find Android SDK location
|
|
||||||
find_android_sdk() {
|
|
||||||
local android_dir=""
|
|
||||||
local local_props="$PROJECT_DIR/android/local.properties"
|
|
||||||
|
|
||||||
# Check environment variables first
|
|
||||||
if [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then
|
|
||||||
android_dir="$ANDROID_HOME"
|
|
||||||
log_info "Found Android SDK via ANDROID_HOME: $android_dir"
|
|
||||||
elif [ -n "$ANDROID_SDK_ROOT" ] && [ -d "$ANDROID_SDK_ROOT" ]; then
|
|
||||||
android_dir="$ANDROID_SDK_ROOT"
|
|
||||||
log_info "Found Android SDK via ANDROID_SDK_ROOT: $android_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check existing local.properties
|
|
||||||
if [ -z "$android_dir" ] && [ -f "$local_props" ]; then
|
|
||||||
# Temporarily disable exit on error for grep (may not find match)
|
|
||||||
set +e
|
|
||||||
sdk_line=$(grep "^sdk.dir=" "$local_props" 2>/dev/null)
|
|
||||||
set -e
|
|
||||||
if [ -n "$sdk_line" ]; then
|
|
||||||
android_dir=$(echo "$sdk_line" | cut -d'=' -f2 | sed 's|\\\\|/|g' | sed "s|^~|$HOME|")
|
|
||||||
if [ -n "$android_dir" ] && [ -d "$android_dir" ]; then
|
|
||||||
log_info "Found Android SDK in local.properties: $android_dir"
|
|
||||||
else
|
|
||||||
android_dir=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Try common locations
|
|
||||||
if [ -z "$android_dir" ]; then
|
|
||||||
# macOS default location
|
|
||||||
if [ -d "$HOME/Library/Android/sdk" ]; then
|
|
||||||
android_dir="$HOME/Library/Android/sdk"
|
|
||||||
log_info "Found Android SDK in default macOS location: $android_dir"
|
|
||||||
# Linux default location
|
|
||||||
elif [ -d "$HOME/Android/Sdk" ]; then
|
|
||||||
android_dir="$HOME/Android/Sdk"
|
|
||||||
log_info "Found Android SDK in default Linux location: $android_dir"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create/update local.properties if SDK found
|
|
||||||
if [ -n "$android_dir" ]; then
|
|
||||||
# Normalize path (convert to forward slashes, expand ~)
|
|
||||||
android_dir=$(echo "$android_dir" | sed 's|\\\\|/|g' | sed "s|^~|$HOME|")
|
|
||||||
|
|
||||||
# Create local.properties with SDK location
|
|
||||||
mkdir -p "$(dirname "$local_props")"
|
|
||||||
echo "## This file is automatically generated by build script" > "$local_props"
|
|
||||||
echo "## Location: $android_dir" >> "$local_props"
|
|
||||||
echo "sdk.dir=$android_dir" >> "$local_props"
|
|
||||||
log_info "✅ Configured Android SDK in local.properties"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log_error "Android SDK not found!"
|
|
||||||
log_error "Please set one of the following:"
|
|
||||||
log_error " 1. ANDROID_HOME environment variable"
|
|
||||||
log_error " 2. ANDROID_SDK_ROOT environment variable"
|
|
||||||
log_error " 3. Create android/local.properties with: sdk.dir=/path/to/android/sdk"
|
|
||||||
log_error ""
|
|
||||||
log_error "Common SDK locations:"
|
|
||||||
log_error " macOS: ~/Library/Android/sdk"
|
|
||||||
log_error " Linux: ~/Android/Sdk"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Android build
|
# Android build
|
||||||
if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then
|
if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then
|
||||||
log_step "Building Android app..."
|
log_step "Building Android app..."
|
||||||
|
|
||||||
# Ensure Android SDK is configured
|
# Check for Android SDK
|
||||||
if ! find_android_sdk; then
|
if ! command -v adb &> /dev/null; then
|
||||||
log_error "Cannot build Android app without SDK location"
|
log_warn "adb not found. Android SDK may not be installed."
|
||||||
exit 1
|
log_warn "Skipping Android build. Install Android SDK to build Android."
|
||||||
fi
|
else
|
||||||
|
cd "$PROJECT_DIR/android"
|
||||||
|
|
||||||
cd "$PROJECT_DIR/android"
|
# Build APK
|
||||||
|
if ./gradlew :app:assembleDebug; then
|
||||||
|
log_info "Android APK built successfully"
|
||||||
|
|
||||||
# Build APK (Gradle doesn't require adb for building)
|
APK_PATH="$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk"
|
||||||
if ./gradlew :app:assembleDebug; then
|
|
||||||
log_info "Android APK built successfully"
|
|
||||||
|
|
||||||
APK_PATH="$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk"
|
if [ -f "$APK_PATH" ]; then
|
||||||
|
log_info "APK location: $APK_PATH"
|
||||||
|
|
||||||
if [ -f "$APK_PATH" ]; then
|
# Run on emulator if requested
|
||||||
log_info "APK location: $APK_PATH"
|
if [ "$RUN_ALL" = true ] || [ "$RUN_ANDROID" = true ]; then
|
||||||
|
|
||||||
# Run on emulator if requested (requires adb)
|
|
||||||
if [ "$RUN_ALL" = true ] || [ "$RUN_ANDROID" = true ]; then
|
|
||||||
# Check for Android SDK tools (adb)
|
|
||||||
if ! command -v adb &> /dev/null; then
|
|
||||||
log_warn "adb not found in PATH. Cannot install/launch app."
|
|
||||||
log_warn "APK built successfully, but install/launch requires Android SDK."
|
|
||||||
log_info "To install manually: adb install -r $APK_PATH"
|
|
||||||
log_info "Or add Android SDK platform-tools to your PATH."
|
|
||||||
else
|
|
||||||
log_step "Installing and launching Android app..."
|
log_step "Installing and launching Android app..."
|
||||||
|
|
||||||
# Check for running emulator
|
# Check for running emulator
|
||||||
@@ -361,16 +295,16 @@ if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
log_error "APK not found at expected location: $APK_PATH"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "APK not found at expected location: $APK_PATH"
|
log_error "Android build failed"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_error "Android build failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# iOS build
|
# iOS build
|
||||||
|
|||||||
Reference in New Issue
Block a user