|
| 1 | +# iOS Release Build Troubleshooting Guide |
| 2 | + |
| 3 | +**Date:** December 2024 |
| 4 | +**Last Updated:** December 2024 |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +This document captures critical issues discovered during iOS Release build and production configuration. These issues primarily affect release builds (AOT compilation), fresh app installations, and authentication workflows. This guide is a reference for future developers to prevent regressions. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## Issue 1: Fresh Install Crash — PatchMissingColumnsAsync |
| 13 | + |
| 14 | +### Symptom |
| 15 | +App starts but no data loads. Logs show repeated errors: `no such table: LearningResource` and similar table-not-found errors. |
| 16 | + |
| 17 | +### Root Cause |
| 18 | +The database initialization sequence is wrong. `PatchMissingColumnsAsync()` runs **before** `MigrateAsync()`. On fresh installs (empty database), it attempts to ALTER TABLE on tables that don't exist yet. The exception is caught silently (around line 203 in SyncService.cs), so `MigrateAsync()` never executes, and the database schema is never created. |
| 19 | + |
| 20 | +### Fix |
| 21 | +Added a SQLite `sqlite_master` table existence check before any ALTER TABLE statement. If a table doesn't exist, skip the patch operation — `MigrateAsync()` will create it with all required columns. |
| 22 | + |
| 23 | +**File:** `src/SentenceStudio.Shared/Services/SyncService.cs` → `PatchMissingColumnsAsync()` |
| 24 | + |
| 25 | +```csharp |
| 26 | +// Check if table exists in sqlite_master before attempting ALTER TABLE |
| 27 | +using var cmd = connection.CreateCommand(); |
| 28 | +cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name=?"; |
| 29 | +cmd.Parameters.AddWithValue("@table", tableName); |
| 30 | +var exists = cmd.ExecuteScalar() != null; |
| 31 | + |
| 32 | +if (!exists) continue; // Skip patch if table doesn't exist yet |
| 33 | +``` |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Issue 2: EF Core 10 Model Building on iOS Release (AOT) |
| 38 | + |
| 39 | +### Symptom |
| 40 | +Build succeeds but database queries fail in Release builds with error: `Model building is not supported when publishing with NativeAOT`. All data operations fail. |
| 41 | + |
| 42 | +### Root Cause |
| 43 | +iOS Release builds use Mono Full AOT (Ahead-of-Time compilation). In this mode, `RuntimeFeature.IsDynamicCodeSupported` is `false`. EF Core 10 refuses to dynamically build its data model at runtime. The model is **lazily built on first query**, not during `MigrateAsync()`, so Release builds fail when queries execute. |
| 44 | + |
| 45 | +### Fix (Temporary) |
| 46 | +Added `<UseInterpreter>true</UseInterpreter>` to the Release PropertyGroup in the iOS .csproj file. This enables the Mono interpreter alongside AOT, allowing dynamic code execution for EF Core model building. |
| 47 | + |
| 48 | +**File:** `src/SentenceStudio.iOS/SentenceStudio.iOS.csproj` |
| 49 | + |
| 50 | +```xml |
| 51 | +<PropertyGroup Condition="'$(Configuration)' == 'Release'"> |
| 52 | + <UseInterpreter>true</UseInterpreter> |
| 53 | +</PropertyGroup> |
| 54 | +``` |
| 55 | + |
| 56 | +### Fix (Proper - Recommended) |
| 57 | +Generate a compiled data model using: |
| 58 | +```bash |
| 59 | +dotnet ef dbcontext optimize --project src/SentenceStudio.Shared |
| 60 | +``` |
| 61 | +Then configure EF Core to use the compiled model. This eliminates runtime model building entirely. See [Microsoft.EntityFrameworkCore.Tasks](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics#using-compiled-models) for details. |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +## Issue 3: Logout Black Screen |
| 66 | + |
| 67 | +### Symptom |
| 68 | +Tapping Logout button navigates to a black screen or does nothing. App remains in authenticated state. |
| 69 | + |
| 70 | +### Root Cause |
| 71 | +`Logout()` method in NavMenu was synchronous but required async operations: |
| 72 | +- Never called `MauiAuthenticationStateProvider.LogOutAsync()` to clear auth state |
| 73 | +- Navigated to `/auth` (profile picker) instead of `/auth/login` (actual login page) |
| 74 | +- No async handling prevented proper state cleanup |
| 75 | + |
| 76 | +### Fix |
| 77 | +1. Made `Logout()` async |
| 78 | +2. Injected `AuthenticationStateProvider` |
| 79 | +3. Called `MauiAuthenticationStateProvider.LogOutAsync()` to clear auth state |
| 80 | +4. Changed navigation target from `/auth` to `/auth/login` |
| 81 | + |
| 82 | +**File:** `src/SentenceStudio.UI/Layout/NavMenu.razor` |
| 83 | + |
| 84 | +```csharp |
| 85 | +private async Task Logout() |
| 86 | +{ |
| 87 | + if (AuthStateProvider is MauiAuthenticationStateProvider provider) |
| 88 | + { |
| 89 | + await provider.LogOutAsync(); |
| 90 | + } |
| 91 | + Navigation.NavigateTo("/auth/login", forceLoad: true); |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## Issue 4: No Data After Login |
| 98 | + |
| 99 | +### Symptom |
| 100 | +Login succeeds and user is authenticated, but dashboard shows "No vocabulary data yet" instead of synced content. |
| 101 | + |
| 102 | +### Root Cause |
| 103 | +CoreSync initial sync fires at app startup (before login) and receives HTTP 401 Unauthorized. After successful login, no re-sync is triggered, so local data remains empty. |
| 104 | + |
| 105 | +### Fix |
| 106 | +Added post-login sync trigger in `Index.razor`. When the user is authenticated but local data is empty, manually call `SyncService.TriggerSyncAsync()` to pull data immediately after login. |
| 107 | + |
| 108 | +**File:** `src/SentenceStudio.UI/Pages/Index.razor` |
| 109 | + |
| 110 | +```csharp |
| 111 | +protected override async Task OnInitializedAsync() |
| 112 | +{ |
| 113 | + if (IsAuthenticated && !HasLocalData) |
| 114 | + { |
| 115 | + await SyncService.TriggerSyncAsync(); |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## Issue 5: Production Config Not Loading |
| 123 | + |
| 124 | +### Symptom |
| 125 | +Release builds connect to localhost instead of Azure. Production endpoints are ignored. |
| 126 | + |
| 127 | +### Root Cause |
| 128 | +`ConfigurationExtensions.cs` only loaded the base `appsettings.json` file. Environment-specific configuration files (`appsettings.Production.json`) were never loaded as embedded resources. |
| 129 | + |
| 130 | +### Fix |
| 131 | +Added environment detection and conditional loading: |
| 132 | +- Debug builds load `appsettings.Development.json` |
| 133 | +- Release builds load `appsettings.Production.json` |
| 134 | + |
| 135 | +Both are added as embedded resources in the project file and overlaid on the base configuration. |
| 136 | + |
| 137 | +**Files:** |
| 138 | +- `src/SentenceStudio.AppLib/Setup/ConfigurationExtensions.cs` — Updated to detect environment and load appropriate config |
| 139 | +- `src/SentenceStudio.AppLib/appsettings.Production.json` — Contains Azure endpoints and production settings |
| 140 | + |
| 141 | +```csharp |
| 142 | +var environmentSpecificFile = |
| 143 | +#if DEBUG |
| 144 | + "appsettings.Development.json"; |
| 145 | +#else |
| 146 | + "appsettings.Production.json"; |
| 147 | +#endif |
| 148 | + |
| 149 | +config.AddJsonFile($"SentenceStudio.AppLib.{environmentSpecificFile}", |
| 150 | + optional: false, reloadOnChange: false); |
| 151 | +``` |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Issue 6: EnableILStrip Error on iOS Release |
| 156 | + |
| 157 | +### Symptom |
| 158 | +Build fails with PE file or ILStrip error when building iOS Release on .NET 10. |
| 159 | + |
| 160 | +### Root Cause |
| 161 | +iOS Release builds attempt IL stripping by default in .NET 10. Some libraries or configurations conflict with this process. |
| 162 | + |
| 163 | +### Fix |
| 164 | +Disable IL stripping by adding `-p:EnableILStrip=false` to the build command. |
| 165 | + |
| 166 | +**Build Command:** |
| 167 | +```bash |
| 168 | +dotnet build -f net10.0-ios -c Release -p:RuntimeIdentifier=ios-arm64 -p:EnableILStrip=false |
| 169 | +``` |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Issue 7: Install on Physical Device |
| 174 | + |
| 175 | +### Symptom |
| 176 | +Deploying to physical iOS devices via `dotnet build -t:Run` is slow over WiFi. |
| 177 | + |
| 178 | +### Solution |
| 179 | +Use Xcode's device control CLI for faster WiFi deployment: |
| 180 | + |
| 181 | +```bash |
| 182 | +xcrun devicectl device install app --device {UDID} path/to/SentenceStudio.iOS.app/ |
| 183 | +``` |
| 184 | + |
| 185 | +Retrieve device UDID: |
| 186 | +```bash |
| 187 | +xcrun devicectl list devices |
| 188 | +``` |
| 189 | + |
| 190 | +This method is significantly faster than `dotnet build -t:Run` for iterative device testing. |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +## Prevention Checklist |
| 195 | + |
| 196 | +- [ ] **Database Init:** Verify `PatchMissingColumnsAsync()` checks `sqlite_master` before ALTER TABLE |
| 197 | +- [ ] **AOT Compatibility:** For EF Core, either use compiled models or enable `UseInterpreter` in Release builds |
| 198 | +- [ ] **Authentication:** After logout, always navigate to login page with `forceLoad: true` |
| 199 | +- [ ] **Post-Login Sync:** Trigger CoreSync after successful login if local data is empty |
| 200 | +- [ ] **Environment Config:** Ensure `appsettings.{Environment}.json` is loaded as embedded resource |
| 201 | +- [ ] **Build Flags:** Include `-p:EnableILStrip=false` in iOS Release builds |
| 202 | +- [ ] **Device Deployment:** Use `xcrun devicectl` for faster physical device iteration |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## References |
| 207 | + |
| 208 | +- [EF Core Compiled Models](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics#using-compiled-models) |
| 209 | +- [.NET MAUI iOS Deployment](https://learn.microsoft.com/en-us/dotnet/maui/ios/deployment/) |
| 210 | +- [Xcrun Device Control CLI](https://developer.apple.com/documentation/devicectl) |
0 commit comments