I'm working on a Xamarin.Forms app and for some reason there are singletons that are created multiple times. This does not happen every time however and it seems to be at random. My dependency injection setup happens in the App.xaml.cs
using System;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Extensions.DependencyInjection;
using Peripass.Mobile.Framework;
using Peripass.Mobile.Framework.DependencyInterfaces;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.FileSystem;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Queues;
using Peripass.YardAssetManagementApp.Data.RemoteDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.Data.Syncing.QueueItemSyncActions;
using Peripass.YardAssetManagementApp.Device;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.MVVM.Pages;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.Services;
using Peripass.YardAssetManagementApp.StepsWizard;
using Peripass.YardAssetManagementApp.UI.Views;
using Peripass.YardAssetManagementApp.UserAuthentication;
using Peripass.YardAssetManagementApp.ViewModels;
using Xamarin.Forms;
using Application = Xamarin.Forms.Application;
namespace Peripass.YardAssetManagementApp {
public partial class App : Application, IApp {
private string _startedByAndroidNotificationMessage;
public App() {
ConfigureServices();
InitializeComponent();
AppCenter.Start($"android={Configuration.Configuration.ApplicationInsightsKeyAndroid};", typeof(Analytics), typeof(Crashes));
MainPage = ServiceProvider.GetRequiredService<MainPage>();
}
public static IServiceCollection Services { get; } = new ServiceCollection();
public IServiceProvider ServiceProvider { get; set; }
private void ConfigureServices() {
Services.AddSingleton<ILifecycleHooksService, LifecycleHooksService>();
Services.AddTransient<INetworkInformationProvider, NetworkInformationProvider>();
RegisterLocalDataServices();
RegisterRemoteDataServices();
RegisterSyncLogic();
RegisterServices();
RegisterViews();
Services.AddSingleton<IUserManager, UserManager>();
Services.AddSingleton<HttpClientProvider>();
Services.AddSingleton<ILogger, Logger>();
Services.AddSingleton<INavigationService, NavigationService>();
Services.AddSingleton<StepsWizardManager>();
Services.AddSingleton<MainPage>();
Services.AddSingleton(DependencyService.Get<ILocalFileSystem>());
Services.AddSingleton<IRestServiceHelper, RestServiceHelper>();
Services.AddSingleton<IPushNotificationsService, PushNotificationsService>();
ServiceProvider = Services.BuildServiceProvider();
}
public void RegisterServices() {
Services.AddTransient<ITaskService, TaskService>();
Services.AddTransient<ILocationService, LocationService>();
Services.AddTransient<ILocalizationService, LocalizationService>();
Services.AddTransient<IDiagnosticsService, DiagnosticsService>();
}
public void RegisterLocalDataServices() {
Services.AddSingleton<ILocalTaskDataService, LocalTaskDataService>();
Services.AddSingleton<ILocalLocationDataService, LocalLocationDataService>();
Services.AddSingleton<ILocalUserDataService, LocalUserDataService>();
Services.AddSingleton<ILocalPushNotificationDataService, LocalPushNotificationDataService>();
Services.AddSingleton<ILocalPictureDataService, LocalPictureDataService>();
Services.AddSingleton<ISafeFileSystem, SafeFileSystem>();
Services.AddSingleton<ILocalLocalizationDataService, LocalLocalizationDataService>();
}
public void RegisterRemoteDataServices() {
Services.AddSingleton<IRemoteTaskDataService, RemoteTaskDataService>();
Services.AddSingleton<IRemoteLocationDataService, RemoteLocationDataService>();
Services.AddSingleton<IRemoteUserDataService, RemoteUserDataService>();
Services.AddSingleton<IRemoteFileDataService, RemoteFileDataService>();
Services.AddSingleton<IRemoteLocalizationDataService, RemoteLocalizationDataService>();
}
public void RegisterSyncLogic() {
Services.AddSingleton<IToMobileTasksSyncer, ToMobileTasksSyncer>();
Services.AddSingleton<IQueue, IncomingHighPriorityQueue>();
Services.AddSingleton<IQueue, IncomingLowPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingHighPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncHighPriorityQueue>();
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
Services.AddSingleton<IQueueItemSyncAction, FetchTaskDetailSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingStepValueUpdateSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingTaskStatusChangedSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, SyncPictureAction>();
Services.AddSingleton<IFileIntegrityHelper, FileIntegrityHelper>();
}
public void RegisterViews() {
Services.AddTransient<LoginViewContent>();
Services.AddSingleton<LoginViewModel>();
Services.AddTransient<UserInfoViewContent>();
Services.AddSingleton<UserInfoViewModel>();
Services.AddTransient<MainTaskListViewContent>();
Services.AddSingleton<MainTaskListViewModel>();
Services.AddTransient<TaskDetailViewContent>();
Services.AddSingleton<TaskDetailViewModel>();
Services.AddTransient<IntegerFieldTaskStepViewContent>();
Services.AddSingleton<IntegerFieldTaskStepViewModel>();
Services.AddTransient<TextAreaTaskStepViewContent>();
Services.AddSingleton<TextAreaTaskStepViewModel>();
Services.AddTransient<DropDownTaskStepViewContent>();
Services.AddSingleton<DropDownTaskStepViewModel>();
Services.AddTransient<MoveToLocationSummaryViewContent>();
Services.AddSingleton<MoveToLocationSummaryViewModel>();
Services.AddTransient<TakePictureStepViewContent>();
Services.AddSingleton<TakePictureStepViewModel>();
Services.AddTransient<MoveToLocationStepViewContent>();
Services.AddSingleton<MoveToLocationStepViewModel>();
Services.AddTransient<TaskCompletedViewContent>();
Services.AddSingleton<TaskCompletedViewModel>();
Services.AddTransient<LoggedOutViewContent>();
Services.AddSingleton<LoggedOutViewModel>();
Services.AddTransient<TextFieldTaskStepViewContent>();
Services.AddSingleton<TextFieldTaskStepViewModel>();
Services.AddTransient<DecimalFieldTaskStepViewContent>();
Services.AddSingleton<DecimalFieldTaskStepViewModel>();
Services.AddTransient<GenericExceptionPageViewContent>();
Services.AddSingleton<GenericExceptionPageViewModel>();
Services.AddTransient<CleanupBeforeLogoutPageViewContent>();
Services.AddSingleton<CleanupBeforeLogoutPageViewModel>();
Services.AddTransient<SelectLanguageViewContent>();
Services.AddSingleton<SelectLanguageViewModel>();
}
protected override async void OnStart() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnStart();
}
protected override void OnSleep() { }
protected override async void OnResume() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnResume();
}
public void StartedByTapOnAndroidNotification(string message) {
_startedByAndroidNotificationMessage = message;
}
}
}
The singletons that are created multiple times are these ones. It could be that other singletons are also created muiltiple times but these I'm sure of.
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
I see that the OnStart method is sometimes called multiple times creating multiple instances of these singletons when resolving the dependencies of the ILifecycleHooksService. This is the code in that LifeCycleHooksService:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.ViewModels;
namespace Peripass.YardAssetManagementApp {
public interface ILifecycleHooksService {
Task OnStart();
Task OnResume();
}
public class LifecycleHooksService : ILifecycleHooksService {
private readonly INavigationService _navigationService;
private readonly ILocalUserDataService _localUserDataService;
private readonly ILocalTaskDataService _localTaskDataService;
private readonly ILocalLocationDataService _localLocationDataService;
private readonly ILocalPushNotificationDataService _localPushNotificationDataService;
private readonly ILocalPictureDataService _localPictureDataService;
private readonly IToMobileTasksSyncer _toMobileTasksSyncer;
private readonly IEnumerable<IQueueProcessor> _queueProcessors;
private readonly IPushNotificationsService _pushNotificationsService;
private readonly ILocalLocalizationDataService _localLocalizationDataService;
public LifecycleHooksService(INavigationService navigationService,
ILocalUserDataService localUserDataService,
IToMobileTasksSyncer toMobileTasksSyncer,
IEnumerable<IQueueProcessor> queueProcessors,
ILocalTaskDataService localTaskDataService,
ILocalLocationDataService localLocationDataService,
ILocalPushNotificationDataService localPushNotificationDataService,
IPushNotificationsService pushNotificationsService,
ILocalPictureDataService localPictureDataService, ILocalLocalizationDataService localLocalizationDataService) {
_navigationService = navigationService;
_localUserDataService = localUserDataService;
_toMobileTasksSyncer = toMobileTasksSyncer;
_queueProcessors = queueProcessors;
_localTaskDataService = localTaskDataService;
_localLocationDataService = localLocationDataService;
_localPushNotificationDataService = localPushNotificationDataService;
_pushNotificationsService = pushNotificationsService;
_localPictureDataService = localPictureDataService;
_localLocalizationDataService = localLocalizationDataService;
}
public async Task OnStart() {
await ReloadData();
await CreateDeviceIdOnFirstStartAsync();
var currentUser = _localUserDataService.GetCurrentUser();
if (currentUser.IsLoggedIn) {
Configuration.Configuration.SetEnvironment(currentUser.Environment);
await _navigationService.NavigateToViewModelAsync<MainTaskListViewModel>(true);
try {
await _toMobileTasksSyncer.SyncAllMetaData();
}
catch {
// ignored
}
}
else {
await _navigationService.NavigateToViewModelAsync<LoginViewModel>();
}
foreach (var queueProcessor in _queueProcessors) {
_ = Task.Run(async () => await queueProcessor.StartPeriodicProcessing());
}
}
public async Task OnResume() {
try {
await _toMobileTasksSyncer.SyncAllTasks(true);
}
catch {
// ignored
}
}
public async Task CreateDeviceIdOnFirstStartAsync() {
var currentPushNotificationInfo = await _localPushNotificationDataService.GetCurrentPushNotificationInfo();
if (string.IsNullOrEmpty(currentPushNotificationInfo.DeviceIdTag)) {
await _localPushNotificationDataService.SetDeviceId(Guid.NewGuid().ToString());
}
}
private async Task ReloadData() {
await _localLocalizationDataService.ReloadData();
await _localUserDataService.ReloadData();
await _localTaskDataService.ReloadData();
await _localLocationDataService.ReloadData();
await _localPictureDataService.ReloadData();
await _pushNotificationsService.ProcessReceivedMessages();
}
}
}
In this class I inject an I Enumerable of IQueueProcessors. These queueProcessors inject themselves the IQueueOrchestrators. If you need more information, please ask me anything.