Search code examples
c#androidmaui

Dependency injection in background service android maui


I created a background service in android maui like in this question: How to create a background service in .NET Maui. It's working fine. But I don't understand how to add DI services in this background? I need to add IDbContextFactory for my ef core context and IServiceScopeFactory.

If I add them in the constructor, I got an error: Error XA4213 The type 'MyBackgroundService' must provide a public default constructor.

My backgroubdService:

    [Service]
    public class AndroidBackgroundService : Service, IService
    {
        UpdateBackgroundService _updateBackgroundService; //I need this DI service

        public AndroidBackgroundService(UpdateBackgroundService updateBackgroundService) //This compile error
        {
             _updateBackgroundService = updateBackgroundService;
        }

        
        public AndroidBackgroundService()
        {
        }

        public override IBinder OnBind(Intent intent)
        {
            throw new NotImplementedException();
        }

        [return: GeneratedEnum]//we catch the actions intents to know the state of the foreground service
        public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
        {
            if (intent.Action == "START_SERVICE")
            {
                RegisterNotification();//Proceed to notify
                Run();
            }
            else if (intent.Action == "STOP_SERVICE")
            {
                StopForeground(true);//Stop the service
                StopSelfResult(startId);
            }

            return StartCommandResult.NotSticky;
        }

        public void Run()
        {
            _updateBackgroundService.Run();           
        }


        //Start and Stop Intents, set the actions for the MainActivity to get the state of the foreground service
        //Setting one action to start and one action to stop the foreground service
        public void Start()
        {
            Intent startService = new Intent(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, typeof(AndroidBackgroundService));
            startService.SetAction("START_SERVICE");
            Microsoft.Maui.ApplicationModel.Platform.CurrentActivity.StartService(startService);
        }

        public void Stop()
        {
            Intent stopIntent = new Intent(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, this.Class);
            stopIntent.SetAction("STOP_SERVICE");
            Microsoft.Maui.ApplicationModel.Platform.CurrentActivity.StartService(stopIntent);
        }

        private void RegisterNotification()
        {
            NotificationChannel channel = new NotificationChannel("ServiceChannel", "ServiceDemo", NotificationImportance.Max);
            NotificationManager manager = (NotificationManager)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity.GetSystemService(Context.NotificationService);
            manager.CreateNotificationChannel(channel);
            Notification notification = new Notification.Builder(this, "ServiceChannel")
               .SetContentTitle("Агент 2 фоновый процесс запущен")
               .SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha)
               .SetOngoing(true)
               .Build();

            StartForeground(100, notification);
        }
    }

My UpdateBackgroundService

public class UpdateBackgroundService : BaseBackgroundService
{       
    private readonly IServiceScopeFactory scopeFactory;     
    private readonly IDbContextFactory<AsterixDBContext> _DbContextFactoryAsterix;
    private readonly IDbContextFactory<Agent2DBContext> _DbContextFactory;

    public UpdateBackgroundService(IServiceScopeFactory scopeFactory, IDbContextFactory<Agent2DBContext> dbContextFactory, IDbContextFactory<AsterixDBContext> dbContextFactoryAsterix)
        : base(dbContextFactory)
    {
        this.scopeFactory = scopeFactory;
        _DbContextFactoryAsterix = dbContextFactoryAsterix;
        _DbContextFactory = dbContextFactory;
    }
    
    public Run()
    {
    ...
    }
}

MauiProgram

builder.Services.AddTransient<UpdateBackgroundService>();
#if ANDROID
     builder.Services.AddTransient<AndroidBackgroundService>();
#endif

Solution

  • Seems like a [Service] class can't have any parameters in the constructor, here is an alternative way to use dependency injection without passing parameters.

    Create a ServiceProvider class :

    public static class ServiceProvider
    {
        public static TService GetService<TService>()
            => Current.GetService<TService>();
    
        public static IServiceProvider Current
            =>
    #if WINDOWS10_0_17763_0_OR_GREATER
                MauiWinUIApplication.Current.Services;
    #elif ANDROID
                MauiApplication.Current.Services;
    #elif IOS || MACCATALYST
                MauiUIApplicationDelegate.Current.Services;
    #else
                null;
    #endif
    }
    

    Then you can simply use it in any component constructor :

    _Contexte = ServiceHelper.GetService<Contexte>();
    

    As noted by @sellotape you can also use IPlatformApplication.Current.Services, I created a static class to access it to avoid null warnings :

    // Usage example: CurrentServiceProvider.Services.GetRequiredService<Parametrage>()
    internal static class CurrentServiceProvider
    {
        public static IServiceProvider Services
        {
            get
            {
                IPlatformApplication? app = IPlatformApplication.Current;
                if (app == null)
                    throw new InvalidOperationException("Cannot resolve current application. Services should be accessed after MauiProgram initialization.");
                return app.Services;
            }
        }
    }