0

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Tijl .Reynhout
  • 901
  • 2
  • 9
  • 24
  • 1
    Don't store `IEnumerable ` as it is came to your constructor, make a copy with `ToList` or `ToArray`, enumerable is intended to be enumerated only once, you are violating this principle. – Alexey Rumyantsev Apr 13 '21 at 07:54
  • I changed it. :) Could this have been the cause? – Tijl .Reynhout Apr 13 '21 at 07:57
  • 1
    Definitely can, I've never met any issues with such DI use cases, when I stored in list or array. – Alexey Rumyantsev Apr 13 '21 at 08:01
  • It doesn't seem to be the cause – Tijl .Reynhout Apr 13 '21 at 09:52
  • How do you determine that singleton created many times? Regarding to this code I can only recommend to do more extensive debugging, I don't see any problems with registrations. – Alexey Rumyantsev Apr 13 '21 at 10:40
  • Because in another class I inject the IEnumerable of IQueueOrchestrator and get the one type I need by executing this linq statement: _queueOrchestrator = queueOrchestrators.Single(queueOrchestrator => queueOrchestrator.QueueType == QueueType.Incomming); This gives the following error sometimes: sequence contains more than one matching element. After adding some logging I found out that the queueorchestrator singletons were each added three times. – Tijl .Reynhout Apr 13 '21 at 11:00
  • You say *"I see that the OnStart method is sometimes called multiple times creating multiple instances of these singletons when resolving the dependencies of the ILifecycleHooksService. "* Sounds like you know where the duplicates come from, even if it is not known why that is called multiple times. How about: change your code so it either doesn't add the dependency if its already there, OR deletes any matching dependency before adding a dependency in. If you are able to fix it this way, please add an ANSWER below, showing the code that fixes it. So everyone knows it is fixed, and how to fix. – ToolmakerSteve May 17 '21 at 21:26

1 Answers1

0

I found out what was causing it. The cause is android related and is described in this stack overflow post: description of what causes this

The solution that worked for me came from this post about the same topic: solution for me

Basically the solution that worked for me was adding this to OnCreate:

// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.

if (!isTaskRoot()) {
    final Intent intent = getIntent();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
        Log.w(LOG_TAG, "Main Activity is not the root.  Finishing Main Activity instead of launching.");
        finish();
        return;       
    }
}
Tijl .Reynhout
  • 901
  • 2
  • 9
  • 24