4 Commits

Author SHA1 Message Date
Jose Olarte III
0bc75372b5 fix(android): target alarm broadcast to app package so receiver is triggered
Set Intent.setPackage(context.packageName) when creating PendingIntents
for AlarmManager so the broadcast is delivered to DailyNotificationReceiver
on all OEMs. Alarms were firing but the receiver was not invoked when the
component was not explicitly package-targeted.

- NotifyReceiver: setPackage on schedule, cancel, and isAlarmScheduled intents
- ReactivationManager: alarmsExist() use DailyNotificationReceiver + setPackage
- DailyNotificationScheduler: setPackage on ExactAlarmManager path intent
2026-02-05 19:28:30 +08:00
Jose Olarte III
57c7ddb7eb docs(testing): EMULATOR_GUIDE prerequisites, API 35, Apple Silicon; build.sh Android-only sync
EMULATOR_GUIDE.md:
- Add "Checking and Installing Prerequisites" (how to check Node, npm, Java,
  ANDROID_HOME, adb, emulator, AVDs; install steps; reference to
  scripts/check-environment.js)
- Use API 35 and Pixel8_API35 throughout to match project compileSdk/targetSdk
- Document arm64-v8a for Apple Silicon and x86_64 for Intel; add
  troubleshooting for "x86_64 not supported on aarch64 host"
- Bump version to 1.1.0 and last-updated date

test-apps/daily-notification-test/scripts/build.sh:
- When building only Android (--android / --run-android), run
  cap:sync:android instead of cap:sync so iOS pod install is skipped and
  Android build/run succeeds without fixing the iOS Podfile
2026-02-05 18:13:09 +08:00
Jose Olarte III
a3afefeda9 docs(testing): EMULATOR_GUIDE prerequisites and API 35
- Add "Checking and Installing Prerequisites" section:
  - How to check Node, npm, Java, ANDROID_HOME, adb, emulator, AVDs
  - Reference scripts/check-environment.js for partial check
  - Install steps for Node, Java, Android SDK (cmdline-tools only),
    sdkmanager packages, and avdmanager AVD creation
- Align SDK and AVD with project: use API 35 (android-35, build-tools 35.0.0,
  Pixel8_API35) to match compileSdk/targetSdk in variables.gradle
- Bump guide version to 1.1.0 and last-updated date
2026-02-05 17:48:46 +08:00
Jose Olarte III
d0155f0b22 docs(building): update BUILDING.md with iOS prerequisites and clean-build script
Updates BUILDING.md to reflect recent changes in build-native.sh, especially
the Xcode Command Line Tools prerequisite check and the clean-build script.

Problem:
- BUILDING.md didn't mention Xcode Command Line Tools prerequisite
  (recently added to build-native.sh)
- clean-build.sh script exists but wasn't documented
- iOS build troubleshooting lacked Command Line Tools guidance

Changes:
- Add Xcode Command Line Tools to Prerequisites section
  - Document installation command (xcode-select --install)
  - Include verification steps (xcode-select -p, xcodebuild -version)
  - Note that build script automatically checks for these tools
  - Explain that sqlite3 is part of Command Line Tools

- Document clean-build.sh script in Build Scripts section
  - Basic usage: ./scripts/clean-build.sh
  - All options: --all, --clean-gradle-cache, --clean-derived-data,
    --reinstall-node
  - Explain when to use clean builds

- Enhance iOS Native Build Process section
  - Add prerequisite note about Command Line Tools
  - Include troubleshooting commands for pod install issues
  - Reference prerequisites section for details

- Add comprehensive troubleshooting sections
  - Clean Build section at start of Troubleshooting
    - Recommends clean-build as first step for many issues
    - 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

The documentation now accurately reflects:
- Xcode Command Line Tools as required iOS prerequisite
- clean-build.sh as available build tool
- Complete iOS troubleshooting workflow

Files modified:
- BUILDING.md
2026-01-16 15:38:41 +08:00
7 changed files with 291 additions and 213 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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 apps `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

View File

@@ -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