From cde99b6537a9977e3de69de8ee76621f6f096be3 Mon Sep 17 00:00:00 2001 From: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:22:37 +0530 Subject: [PATCH 1/5] Fixed Race conditions, thread-safety issues --- 10.0/Apps/DeveloperBalance/AppShell.xaml.cs | 4 +- .../Data/CategoryRepository.cs | 34 +++++--- .../Apps/DeveloperBalance/Data/JsonContext.cs | 2 + .../Data/ProjectRepository.cs | 47 ++++++++--- .../DeveloperBalance/Data/SeedDataService.cs | 18 +---- .../DeveloperBalance/Data/TagRepository.cs | 80 +++++++++++++++---- .../{TaskRespository.cs => TaskRepository.cs} | 34 +++++--- .../DeveloperBalance/Models/ProjectsTags.cs | 8 -- .../PageModels/MainPageModel.cs | 4 +- 9 files changed, 156 insertions(+), 75 deletions(-) rename 10.0/Apps/DeveloperBalance/Data/{TaskRespository.cs => TaskRepository.cs} (91%) delete mode 100644 10.0/Apps/DeveloperBalance/Models/ProjectsTags.cs diff --git a/10.0/Apps/DeveloperBalance/AppShell.xaml.cs b/10.0/Apps/DeveloperBalance/AppShell.xaml.cs index 67235f79e..6ac942300 100644 --- a/10.0/Apps/DeveloperBalance/AppShell.xaml.cs +++ b/10.0/Apps/DeveloperBalance/AppShell.xaml.cs @@ -16,7 +16,7 @@ public AppShell() } public static async Task DisplaySnackbarAsync(string message) { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var snackbarOptions = new SnackbarOptions { @@ -41,7 +41,7 @@ public static async Task DisplayToastAsync(string message) var toast = Toast.Make(message, textSize: 18); - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await toast.Show(cts.Token); } diff --git a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs index 2460423cd..c1e3ae4fc 100644 --- a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class CategoryRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,27 +30,38 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + try + { + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Category ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Color TEXT NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error creating Category table"); + throw; + } + + _hasBeenInitialized = true; } - catch (Exception e) + finally { - _logger.LogError(e, "Error creating Category table"); - throw; + _initLock.Release(); } - - _hasBeenInitialized = true; } /// diff --git a/10.0/Apps/DeveloperBalance/Data/JsonContext.cs b/10.0/Apps/DeveloperBalance/Data/JsonContext.cs index 7b0d56cff..bb437dddd 100644 --- a/10.0/Apps/DeveloperBalance/Data/JsonContext.cs +++ b/10.0/Apps/DeveloperBalance/Data/JsonContext.cs @@ -1,6 +1,8 @@ using System.Text.Json.Serialization; using DeveloperBalance.Models; +namespace DeveloperBalance.Data; + [JsonSerializable(typeof(Project))] [JsonSerializable(typeof(ProjectTask))] [JsonSerializable(typeof(ProjectsJson))] diff --git a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs index bdb1aae9e..3a3442c16 100644 --- a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class ProjectRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; private readonly TaskRepository _taskRepository; private readonly TagRepository _tagRepository; @@ -35,13 +36,19 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + try + { + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Project ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, @@ -49,15 +56,20 @@ CREATE TABLE IF NOT EXISTS Project ( Icon TEXT NOT NULL, CategoryID INTEGER NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error creating Project table"); + throw; + } + + _hasBeenInitialized = true; } - catch (Exception e) + finally { - _logger.LogError(e, "Error creating Project table"); - throw; + _initLock.Release(); } - - _hasBeenInitialized = true; } /// @@ -87,10 +99,19 @@ public async Task> ListAsync() }); } + // Fetch all tasks and all tags in one query each, then assign in memory. + // This reduces 2N+1 queries to 3 regardless of project count. + var allTasks = await _taskRepository.ListAsync(); + var allTagsByProject = await _tagRepository.ListAllByProjectAsync(); + + var tasksByProject = allTasks + .GroupBy(t => t.ProjectID) + .ToDictionary(g => g.Key, g => g.ToList()); + foreach (var project in projects) { - project.Tags = await _tagRepository.ListAsync(project.ID); - project.Tasks = await _taskRepository.ListAsync(project.ID); + project.Tasks = tasksByProject.TryGetValue(project.ID, out var tasks) ? tasks : []; + project.Tags = allTagsByProject.TryGetValue(project.ID, out var tags) ? tags : []; } return projects; diff --git a/10.0/Apps/DeveloperBalance/Data/SeedDataService.cs b/10.0/Apps/DeveloperBalance/Data/SeedDataService.cs index dcaacb3ae..3239515b7 100644 --- a/10.0/Apps/DeveloperBalance/Data/SeedDataService.cs +++ b/10.0/Apps/DeveloperBalance/Data/SeedDataService.cs @@ -24,7 +24,7 @@ public SeedDataService(ProjectRepository projectRepository, TaskRepository taskR public async Task LoadSeedDataAsync() { - ClearTables(); + await ClearTables(); await using Stream templateStream = await FileSystem.OpenAppPackageFileAsync(_seedDataFilePath); @@ -83,19 +83,9 @@ public async Task LoadSeedDataAsync() } } - private async void ClearTables() + private async Task ClearTables() { - try - { - await Task.WhenAll( - _projectRepository.DropTableAsync(), - _taskRepository.DropTableAsync(), - _tagRepository.DropTableAsync(), - _categoryRepository.DropTableAsync()); - } - catch (Exception e) - { - Console.WriteLine(e); - } + await _projectRepository.DropTableAsync(); + await _categoryRepository.DropTableAsync(); } } \ No newline at end of file diff --git a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs index c97d2b3f4..a024c25f5 100644 --- a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class TagRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,35 +30,46 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + try + { + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Tag ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Color TEXT NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); - createTableCmd.CommandText = @" + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS ProjectsTags ( ProjectID INTEGER NOT NULL, TagID INTEGER NOT NULL, PRIMARY KEY(ProjectID, TagID) );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error creating tables"); + throw; + } + + _hasBeenInitialized = true; } - catch (Exception e) + finally { - _logger.LogError(e, "Error creating tables"); - throw; + _initLock.Release(); } - - _hasBeenInitialized = true; } /// @@ -88,6 +100,46 @@ public async Task> ListAsync() return tags; } + /// + /// Retrieves all tags grouped by their associated project ID in a single query. + /// + /// A dictionary mapping project ID to its list of objects. + public async Task>> ListAllByProjectAsync() + { + await Init(); + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var selectCmd = connection.CreateCommand(); + selectCmd.CommandText = @" + SELECT t.ID, t.Title, t.Color, pt.ProjectID + FROM Tag t + JOIN ProjectsTags pt ON t.ID = pt.TagID"; + + var result = new Dictionary>(); + + await using var reader = await selectCmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var tag = new Tag + { + ID = reader.GetInt32(0), + Title = reader.GetString(1), + Color = reader.GetString(2) + }; + var projectID = reader.GetInt32(3); + + if (!result.TryGetValue(projectID, out var list)) + { + list = []; + result[projectID] = list; + } + list.Add(tag); + } + + return result; + } + /// /// Retrieves a list of tags associated with a specific project. /// @@ -105,7 +157,7 @@ SELECT t.* FROM Tag t JOIN ProjectsTags pt ON t.ID = pt.TagID WHERE pt.ProjectID = @ProjectID"; - selectCmd.Parameters.AddWithValue("ProjectID", projectID); + selectCmd.Parameters.AddWithValue("@ProjectID", projectID); var tags = new List(); diff --git a/10.0/Apps/DeveloperBalance/Data/TaskRespository.cs b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs similarity index 91% rename from 10.0/Apps/DeveloperBalance/Data/TaskRespository.cs rename to 10.0/Apps/DeveloperBalance/Data/TaskRepository.cs index 472e2c088..459c27f8d 100644 --- a/10.0/Apps/DeveloperBalance/Data/TaskRespository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class TaskRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,28 +30,39 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + try + { + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Task ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, IsCompleted INTEGER NOT NULL, ProjectID INTEGER NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error creating Task table"); + throw; + } + + _hasBeenInitialized = true; } - catch (Exception e) + finally { - _logger.LogError(e, "Error creating Task table"); - throw; + _initLock.Release(); } - - _hasBeenInitialized = true; } /// diff --git a/10.0/Apps/DeveloperBalance/Models/ProjectsTags.cs b/10.0/Apps/DeveloperBalance/Models/ProjectsTags.cs deleted file mode 100644 index ac8ced3fd..000000000 --- a/10.0/Apps/DeveloperBalance/Models/ProjectsTags.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace DeveloperBalance.Models; - -public class ProjectsTags -{ - public int ID { get; set; } - public int ProjectID { get; set; } - public int TagID { get; set; } -} \ No newline at end of file diff --git a/10.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs b/10.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs index 462807802..e5addc820 100644 --- a/10.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs +++ b/10.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs @@ -152,8 +152,8 @@ private Task AddTask() => Shell.Current.GoToAsync($"task"); [RelayCommand] - private Task? NavigateToProject(Project project) - => project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}"); + private Task NavigateToProject(Project project) + => project is null ? Task.CompletedTask : Shell.Current.GoToAsync($"project?id={project.ID}"); [RelayCommand] private Task NavigateToTask(ProjectTask task) From af74ea846f692a0b1282baa446b12200720d5da5 Mon Sep 17 00:00:00 2001 From: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:15:09 +0530 Subject: [PATCH 2/5] Updated fix for SeedDataService.ClearTables() called Task.WhenAll on all four repositories --- .../Data/CategoryRepository.cs | 22 +++++++++++++------ .../Data/ProjectRepository.cs | 22 +++++++++++++------ .../DeveloperBalance/Data/TagRepository.cs | 15 +++++++++---- .../DeveloperBalance/Data/TaskRepository.cs | 22 +++++++++++++------ 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs index c1e3ae4fc..3ef30e419 100644 --- a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs @@ -183,14 +183,22 @@ public async Task DeleteItemAsync(Category item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Category"; - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Category"; + await dropTableCmd.ExecuteNonQueryAsync(); - await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file diff --git a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs index 3a3442c16..9dcfecc2f 100644 --- a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs @@ -218,16 +218,24 @@ public async Task DeleteItemAsync(Project item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var dropCmd = connection.CreateCommand(); + dropCmd.CommandText = "DROP TABLE IF EXISTS Project"; + await dropCmd.ExecuteNonQueryAsync(); - var dropCmd = connection.CreateCommand(); - dropCmd.CommandText = "DROP TABLE IF EXISTS Project"; - await dropCmd.ExecuteNonQueryAsync(); + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } await _taskRepository.DropTableAsync(); await _tagRepository.DropTableAsync(); - _hasBeenInitialized = false; } } \ No newline at end of file diff --git a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs index a024c25f5..b7196d7bf 100644 --- a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs @@ -340,9 +340,11 @@ public async Task DeleteItemAsync(Tag item, int projectID) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); var dropTableCmd = connection.CreateCommand(); dropTableCmd.CommandText = "DROP TABLE IF EXISTS Tag"; @@ -351,6 +353,11 @@ public async Task DropTableAsync() dropTableCmd.CommandText = "DROP TABLE IF EXISTS ProjectsTags"; await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file diff --git a/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs index 459c27f8d..6f32e6b14 100644 --- a/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs @@ -216,13 +216,21 @@ public async Task DeleteItemAsync(ProjectTask item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Task"; + await dropTableCmd.ExecuteNonQueryAsync(); - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Task"; - await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file From 78663b6cec88aff74520d9e5159512446ade7282 Mon Sep 17 00:00:00 2001 From: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:36:20 +0530 Subject: [PATCH 3/5] Updated fix in all four repository --- .../Data/CategoryRepository.cs | 19 +++++------ .../Data/ProjectRepository.cs | 19 +++++------ .../DeveloperBalance/Data/TagRepository.cs | 33 +++++++++---------- .../DeveloperBalance/Data/TaskRepository.cs | 19 +++++------ 4 files changed, 39 insertions(+), 51 deletions(-) diff --git a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs index 3ef30e419..6f6aa35b8 100644 --- a/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/CategoryRepository.cs @@ -39,25 +39,22 @@ private async Task Init() await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); - try - { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Category ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Color TEXT NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Error creating Category table"); - throw; - } + await createTableCmd.ExecuteNonQueryAsync(); _hasBeenInitialized = true; } + catch (Exception e) + { + _logger.LogError(e, "Error creating Category table"); + throw; + } finally { _initLock.Release(); diff --git a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs index 9dcfecc2f..e363fb976 100644 --- a/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/ProjectRepository.cs @@ -45,10 +45,8 @@ private async Task Init() await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); - try - { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Project ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, @@ -56,16 +54,15 @@ CREATE TABLE IF NOT EXISTS Project ( Icon TEXT NOT NULL, CategoryID INTEGER NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Error creating Project table"); - throw; - } + await createTableCmd.ExecuteNonQueryAsync(); _hasBeenInitialized = true; } + catch (Exception e) + { + _logger.LogError(e, "Error creating Project table"); + throw; + } finally { _initLock.Release(); diff --git a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs index b7196d7bf..4c673f8c0 100644 --- a/10.0/Apps/DeveloperBalance/Data/TagRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TagRepository.cs @@ -39,33 +39,30 @@ private async Task Init() await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); - try - { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Tag ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Color TEXT NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); - createTableCmd.CommandText = @" + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS ProjectsTags ( ProjectID INTEGER NOT NULL, TagID INTEGER NOT NULL, PRIMARY KEY(ProjectID, TagID) );"; - await createTableCmd.ExecuteNonQueryAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Error creating tables"); - throw; - } + await createTableCmd.ExecuteNonQueryAsync(); _hasBeenInitialized = true; } + catch (Exception e) + { + _logger.LogError(e, "Error creating tables"); + throw; + } finally { _initLock.Release(); @@ -346,12 +343,12 @@ public async Task DropTableAsync() await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Tag"; - await dropTableCmd.ExecuteNonQueryAsync(); + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Tag"; + await dropTableCmd.ExecuteNonQueryAsync(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS ProjectsTags"; - await dropTableCmd.ExecuteNonQueryAsync(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS ProjectsTags"; + await dropTableCmd.ExecuteNonQueryAsync(); _hasBeenInitialized = false; } diff --git a/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs index 6f32e6b14..11380c5e2 100644 --- a/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs +++ b/10.0/Apps/DeveloperBalance/Data/TaskRepository.cs @@ -39,26 +39,23 @@ private async Task Init() await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); - try - { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Task ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, IsCompleted INTEGER NOT NULL, ProjectID INTEGER NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Error creating Task table"); - throw; - } + await createTableCmd.ExecuteNonQueryAsync(); _hasBeenInitialized = true; } + catch (Exception e) + { + _logger.LogError(e, "Error creating Task table"); + throw; + } finally { _initLock.Release(); From 39fcc77a984b211f97fa3fa3fbab860b22458c93 Mon Sep 17 00:00:00 2001 From: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:38:35 +0530 Subject: [PATCH 4/5] Fix for .net 9 --- .../PageModels/ProjectListPageModel.cs | 4 +- 9.0/Apps/DeveloperBalance/AppShell.xaml.cs | 4 +- .../Data/CategoryRepository.cs | 41 +++++-- 9.0/Apps/DeveloperBalance/Data/JsonContext.cs | 2 + .../Data/ProjectRepository.cs | 54 ++++++--- .../DeveloperBalance/Data/SeedDataService.cs | 18 +-- .../DeveloperBalance/Data/TagRepository.cs | 105 ++++++++++++++---- .../{TaskRespository.cs => TaskRepository.cs} | 41 +++++-- .../DeveloperBalance/Models/ProjectsTags.cs | 8 -- .../PageModels/MainPageModel.cs | 4 +- .../PageModels/ProjectListPageModel.cs | 4 +- 11 files changed, 194 insertions(+), 91 deletions(-) rename 9.0/Apps/DeveloperBalance/Data/{TaskRespository.cs => TaskRepository.cs} (89%) delete mode 100644 9.0/Apps/DeveloperBalance/Models/ProjectsTags.cs diff --git a/10.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs b/10.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs index d5f0802ce..3434acd81 100644 --- a/10.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs +++ b/10.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs @@ -28,8 +28,8 @@ private async Task Appearing() } [RelayCommand] - Task? NavigateToProject(Project project) - => project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}"); + private Task NavigateToProject(Project project) + => project is null ? Task.CompletedTask : Shell.Current.GoToAsync($"project?id={project.ID}"); [RelayCommand] async Task AddProject() diff --git a/9.0/Apps/DeveloperBalance/AppShell.xaml.cs b/9.0/Apps/DeveloperBalance/AppShell.xaml.cs index 67235f79e..6ac942300 100644 --- a/9.0/Apps/DeveloperBalance/AppShell.xaml.cs +++ b/9.0/Apps/DeveloperBalance/AppShell.xaml.cs @@ -16,7 +16,7 @@ public AppShell() } public static async Task DisplaySnackbarAsync(string message) { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var snackbarOptions = new SnackbarOptions { @@ -41,7 +41,7 @@ public static async Task DisplayToastAsync(string message) var toast = Toast.Make(message, textSize: 18); - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await toast.Show(cts.Token); } diff --git a/9.0/Apps/DeveloperBalance/Data/CategoryRepository.cs b/9.0/Apps/DeveloperBalance/Data/CategoryRepository.cs index 2460423cd..6f6aa35b8 100644 --- a/9.0/Apps/DeveloperBalance/Data/CategoryRepository.cs +++ b/9.0/Apps/DeveloperBalance/Data/CategoryRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class CategoryRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,11 +30,15 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + var createTableCmd = connection.CreateCommand(); createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Category ( @@ -42,14 +47,18 @@ CREATE TABLE IF NOT EXISTS Category ( Color TEXT NOT NULL );"; await createTableCmd.ExecuteNonQueryAsync(); + + _hasBeenInitialized = true; } catch (Exception e) { _logger.LogError(e, "Error creating Category table"); throw; } - - _hasBeenInitialized = true; + finally + { + _initLock.Release(); + } } /// @@ -171,14 +180,22 @@ public async Task DeleteItemAsync(Category item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Category"; + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Category"; - await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + await dropTableCmd.ExecuteNonQueryAsync(); + + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/Data/JsonContext.cs b/9.0/Apps/DeveloperBalance/Data/JsonContext.cs index 7b0d56cff..bb437dddd 100644 --- a/9.0/Apps/DeveloperBalance/Data/JsonContext.cs +++ b/9.0/Apps/DeveloperBalance/Data/JsonContext.cs @@ -1,6 +1,8 @@ using System.Text.Json.Serialization; using DeveloperBalance.Models; +namespace DeveloperBalance.Data; + [JsonSerializable(typeof(Project))] [JsonSerializable(typeof(ProjectTask))] [JsonSerializable(typeof(ProjectsJson))] diff --git a/9.0/Apps/DeveloperBalance/Data/ProjectRepository.cs b/9.0/Apps/DeveloperBalance/Data/ProjectRepository.cs index bdb1aae9e..e363fb976 100644 --- a/9.0/Apps/DeveloperBalance/Data/ProjectRepository.cs +++ b/9.0/Apps/DeveloperBalance/Data/ProjectRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class ProjectRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; private readonly TaskRepository _taskRepository; private readonly TagRepository _tagRepository; @@ -35,11 +36,15 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + var createTableCmd = connection.CreateCommand(); createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Project ( @@ -50,14 +55,18 @@ CREATE TABLE IF NOT EXISTS Project ( CategoryID INTEGER NOT NULL );"; await createTableCmd.ExecuteNonQueryAsync(); + + _hasBeenInitialized = true; } catch (Exception e) { _logger.LogError(e, "Error creating Project table"); throw; } - - _hasBeenInitialized = true; + finally + { + _initLock.Release(); + } } /// @@ -87,10 +96,19 @@ public async Task> ListAsync() }); } + // Fetch all tasks and all tags in one query each, then assign in memory. + // This reduces 2N+1 queries to 3 regardless of project count. + var allTasks = await _taskRepository.ListAsync(); + var allTagsByProject = await _tagRepository.ListAllByProjectAsync(); + + var tasksByProject = allTasks + .GroupBy(t => t.ProjectID) + .ToDictionary(g => g.Key, g => g.ToList()); + foreach (var project in projects) { - project.Tags = await _tagRepository.ListAsync(project.ID); - project.Tasks = await _taskRepository.ListAsync(project.ID); + project.Tasks = tasksByProject.TryGetValue(project.ID, out var tasks) ? tasks : []; + project.Tags = allTagsByProject.TryGetValue(project.ID, out var tags) ? tags : []; } return projects; @@ -197,16 +215,24 @@ public async Task DeleteItemAsync(Project item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var dropCmd = connection.CreateCommand(); + dropCmd.CommandText = "DROP TABLE IF EXISTS Project"; + await dropCmd.ExecuteNonQueryAsync(); - var dropCmd = connection.CreateCommand(); - dropCmd.CommandText = "DROP TABLE IF EXISTS Project"; - await dropCmd.ExecuteNonQueryAsync(); + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } await _taskRepository.DropTableAsync(); await _tagRepository.DropTableAsync(); - _hasBeenInitialized = false; } } \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/Data/SeedDataService.cs b/9.0/Apps/DeveloperBalance/Data/SeedDataService.cs index dcaacb3ae..3239515b7 100644 --- a/9.0/Apps/DeveloperBalance/Data/SeedDataService.cs +++ b/9.0/Apps/DeveloperBalance/Data/SeedDataService.cs @@ -24,7 +24,7 @@ public SeedDataService(ProjectRepository projectRepository, TaskRepository taskR public async Task LoadSeedDataAsync() { - ClearTables(); + await ClearTables(); await using Stream templateStream = await FileSystem.OpenAppPackageFileAsync(_seedDataFilePath); @@ -83,19 +83,9 @@ public async Task LoadSeedDataAsync() } } - private async void ClearTables() + private async Task ClearTables() { - try - { - await Task.WhenAll( - _projectRepository.DropTableAsync(), - _taskRepository.DropTableAsync(), - _tagRepository.DropTableAsync(), - _categoryRepository.DropTableAsync()); - } - catch (Exception e) - { - Console.WriteLine(e); - } + await _projectRepository.DropTableAsync(); + await _categoryRepository.DropTableAsync(); } } \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/Data/TagRepository.cs b/9.0/Apps/DeveloperBalance/Data/TagRepository.cs index c97d2b3f4..2308eff79 100644 --- a/9.0/Apps/DeveloperBalance/Data/TagRepository.cs +++ b/9.0/Apps/DeveloperBalance/Data/TagRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class TagRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,35 +30,46 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { - var createTableCmd = connection.CreateCommand(); - createTableCmd.CommandText = @" + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + try + { + var createTableCmd = connection.CreateCommand(); + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Tag ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Color TEXT NOT NULL );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); - createTableCmd.CommandText = @" + createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS ProjectsTags ( ProjectID INTEGER NOT NULL, TagID INTEGER NOT NULL, PRIMARY KEY(ProjectID, TagID) );"; - await createTableCmd.ExecuteNonQueryAsync(); + await createTableCmd.ExecuteNonQueryAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error creating tables"); + throw; + } + + _hasBeenInitialized = true; } - catch (Exception e) + finally { - _logger.LogError(e, "Error creating tables"); - throw; + _initLock.Release(); } - - _hasBeenInitialized = true; } /// @@ -88,6 +100,46 @@ public async Task> ListAsync() return tags; } + /// + /// Retrieves all tags grouped by their associated project ID in a single query. + /// + /// A dictionary mapping project ID to its list of objects. + public async Task>> ListAllByProjectAsync() + { + await Init(); + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + var selectCmd = connection.CreateCommand(); + selectCmd.CommandText = @" + SELECT t.ID, t.Title, t.Color, pt.ProjectID + FROM Tag t + JOIN ProjectsTags pt ON t.ID = pt.TagID"; + + var result = new Dictionary>(); + + await using var reader = await selectCmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var tag = new Tag + { + ID = reader.GetInt32(0), + Title = reader.GetString(1), + Color = reader.GetString(2) + }; + var projectID = reader.GetInt32(3); + + if (!result.TryGetValue(projectID, out var list)) + { + list = []; + result[projectID] = list; + } + list.Add(tag); + } + + return result; + } + /// /// Retrieves a list of tags associated with a specific project. /// @@ -105,7 +157,7 @@ SELECT t.* FROM Tag t JOIN ProjectsTags pt ON t.ID = pt.TagID WHERE pt.ProjectID = @ProjectID"; - selectCmd.Parameters.AddWithValue("ProjectID", projectID); + selectCmd.Parameters.AddWithValue("@ProjectID", projectID); var tags = new List(); @@ -288,17 +340,24 @@ public async Task DeleteItemAsync(Tag item, int projectID) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Tag"; - await dropTableCmd.ExecuteNonQueryAsync(); + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Tag"; + await dropTableCmd.ExecuteNonQueryAsync(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS ProjectsTags"; - await dropTableCmd.ExecuteNonQueryAsync(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS ProjectsTags"; + await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/Data/TaskRespository.cs b/9.0/Apps/DeveloperBalance/Data/TaskRepository.cs similarity index 89% rename from 9.0/Apps/DeveloperBalance/Data/TaskRespository.cs rename to 9.0/Apps/DeveloperBalance/Data/TaskRepository.cs index 472e2c088..11380c5e2 100644 --- a/9.0/Apps/DeveloperBalance/Data/TaskRespository.cs +++ b/9.0/Apps/DeveloperBalance/Data/TaskRepository.cs @@ -10,6 +10,7 @@ namespace DeveloperBalance.Data; public class TaskRepository { private bool _hasBeenInitialized = false; + private readonly SemaphoreSlim _initLock = new(1, 1); private readonly ILogger _logger; /// @@ -29,11 +30,15 @@ private async Task Init() if (_hasBeenInitialized) return; - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); - + await _initLock.WaitAsync(); try { + if (_hasBeenInitialized) + return; + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + var createTableCmd = connection.CreateCommand(); createTableCmd.CommandText = @" CREATE TABLE IF NOT EXISTS Task ( @@ -43,14 +48,18 @@ CREATE TABLE IF NOT EXISTS Task ( ProjectID INTEGER NOT NULL );"; await createTableCmd.ExecuteNonQueryAsync(); + + _hasBeenInitialized = true; } catch (Exception e) { _logger.LogError(e, "Error creating Task table"); throw; } - - _hasBeenInitialized = true; + finally + { + _initLock.Release(); + } } /// @@ -204,13 +213,21 @@ public async Task DeleteItemAsync(ProjectTask item) /// public async Task DropTableAsync() { - await Init(); - await using var connection = new SqliteConnection(Constants.DatabasePath); - await connection.OpenAsync(); + await _initLock.WaitAsync(); + try + { + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); - var dropTableCmd = connection.CreateCommand(); - dropTableCmd.CommandText = "DROP TABLE IF EXISTS Task"; - await dropTableCmd.ExecuteNonQueryAsync(); - _hasBeenInitialized = false; + var dropTableCmd = connection.CreateCommand(); + dropTableCmd.CommandText = "DROP TABLE IF EXISTS Task"; + await dropTableCmd.ExecuteNonQueryAsync(); + + _hasBeenInitialized = false; + } + finally + { + _initLock.Release(); + } } } \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/Models/ProjectsTags.cs b/9.0/Apps/DeveloperBalance/Models/ProjectsTags.cs deleted file mode 100644 index ac8ced3fd..000000000 --- a/9.0/Apps/DeveloperBalance/Models/ProjectsTags.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace DeveloperBalance.Models; - -public class ProjectsTags -{ - public int ID { get; set; } - public int ProjectID { get; set; } - public int TagID { get; set; } -} \ No newline at end of file diff --git a/9.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs b/9.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs index 462807802..e5addc820 100644 --- a/9.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs +++ b/9.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs @@ -152,8 +152,8 @@ private Task AddTask() => Shell.Current.GoToAsync($"task"); [RelayCommand] - private Task? NavigateToProject(Project project) - => project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}"); + private Task NavigateToProject(Project project) + => project is null ? Task.CompletedTask : Shell.Current.GoToAsync($"project?id={project.ID}"); [RelayCommand] private Task NavigateToTask(ProjectTask task) diff --git a/9.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs b/9.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs index d5f0802ce..3434acd81 100644 --- a/9.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs +++ b/9.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs @@ -28,8 +28,8 @@ private async Task Appearing() } [RelayCommand] - Task? NavigateToProject(Project project) - => project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}"); + private Task NavigateToProject(Project project) + => project is null ? Task.CompletedTask : Shell.Current.GoToAsync($"project?id={project.ID}"); [RelayCommand] async Task AddProject() From 9bef013fe3b5999662d0a0e13cd57641e72fceb6 Mon Sep 17 00:00:00 2001 From: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:59:28 +0530 Subject: [PATCH 5/5] Fix for IllegalArgumentException in .net 9 for android platform --- .../Platforms/Android/MainActivity.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/9.0/Apps/DeveloperBalance/Platforms/Android/MainActivity.cs b/9.0/Apps/DeveloperBalance/Platforms/Android/MainActivity.cs index 59b281063..b79956c39 100644 --- a/9.0/Apps/DeveloperBalance/Platforms/Android/MainActivity.cs +++ b/9.0/Apps/DeveloperBalance/Platforms/Android/MainActivity.cs @@ -7,4 +7,24 @@ namespace DeveloperBalance; [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { + public override void OnBackPressed() + { + // Workaround for a .NET MAUI 9 bug where pressing Back from a root Shell page + // destroys the Activity. On relaunch, CollectionView adapters still reference + // the destroyed Activity context and attempt to load FontImageSource icons via + // Glide, causing: + // java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity + // + // Fix: when at the root of the navigation stack, move the app to the background + // instead of finishing the Activity, preventing Activity destruction. + // This is fixed in .NET 10: https://github.com/dotnet/maui/issues/29699 + var navStack = Shell.Current?.Navigation?.NavigationStack; + if (navStack is null || navStack.Count <= 1) + { + MoveTaskToBack(true); + return; + } + + base.OnBackPressed(); + } }