Xamarin forms хочу использовать один проект с платформ зависимым кодом для Android и Ios
создал новый solution Xamarin.forms и начал разбираться с библиотекой Shiny, т.к. нужна работа с BluetoothLE и Beacon маяками. Подключил модуль Shiny.BluetoothLE и написал сканер Bluetooth устройств, не понятны были некоторые моменты работы библиотеки, поэтому для изучения решил подключить исходники проекта (а не nuget либу) Shiny.BluetoothLE. https://github.com/shinyorg/shiny/tree/master/src/Shiny.BluetoothLE
Я создал проект и решил по частям добавлять код из Shiny.BluetoothLE.
для компиляции проекта под разные платформы используется "MSBuild.Sdk.Extras"
добавил global.json в solution
{
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "3.0.23"
}
}
Настройки проекта:
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;monoandroid10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<TargetFrameworks Condition=" $(OS) == 'WINDOWS_NT' ">$(UwpTargetFramework);$(TargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**\Platforms\**\*.cs" />
<None Include="**\Platforms\**\*.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
<PackageReference Include="Shiny.BluetoothLE.Common" Version="2.1.0.2769" />
<PackageReference Include="Shiny.Core" Version="2.1.0.2769" />
<Compile Include="**\Platforms\Shared\**\*.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('monoandroid')) ">
<PackageReference Include="Shiny.BluetoothLE.Common" Version="2.1.0.2769" />
<PackageReference Include="Shiny.Core" Version="2.1.0.2769" />
<Compile Include="**\Platforms\Shared\**\*.cs" />
<Compile Include="**\Platforms\Android\**\*.cs" />
</ItemGroup>
</Project>
Вначале решил добиться работоспособности условной компиляции и внедрение через DI нужных реализаций сервиса IBleManager (под каждую платформу свою).
Добавил IBleManager, затем его реализацию BleManager для Android (пока добавил только 1 платформу) и его зависимость ManagerContext.
КОД УРЕЗАННЫЙ ДЛЯ ДЕМОНСТРАЦИИ.
using System;
using Shiny;
public interface IBleManager
{
IObservable<AccessState> RequestAccess();
}
using System;
using Infrastructure.BluetoothLE.Platforms.Android.Internals;
using Shiny;
public class BleManager : IBleManager
{
readonly ManagerContext _context;
public BleManager(ManagerContext context) => this._context = context;
public IObservable<AccessState> RequestAccess()
{
throw new NotImplementedException();
}
}
using System;
using System.Reactive.Linq;
using Android.Bluetooth;
using Android.Bluetooth.LE;
using Microsoft.Extensions.Logging;
using Shiny;
using Shiny.BluetoothLE;
using Shiny.Infrastructure;
public class ManagerContext : IShinyStartupTask
{
readonly ShinyCoreServices services;
readonly ILogger logger;
public ManagerContext(ShinyCoreServices services,
BleConfiguration config,
ILogger<ManagerContext> logger)
{
this.Configuration = config;
this.services = services;
this.logger = logger;
}
public void Start()
{
var builder = new ScanSettings.Builder(); //Android.Bluetooth.LE зависимость
}
public IServiceProvider Services => this.services.Services;
public AccessState Status => this.Manager.GetAccessState();
public IObservable<AccessState> StatusChanged() => ShinyBleAdapterStateBroadcastReceiver
.WhenStateChanged()
.StartWith(this.Status);
public BleConfiguration Configuration { get; }
public BluetoothManager Manager { get; }
public IAndroidContext Android => this.services.Android;
}
затем зарегистрировать все зависимости в DI.
ShinyModule - модуль для регистрации по аналогии с Autofac.
ShinyModule и вся библиотека Shiny использует Microsoft.Extensions.DependencyInjection в качестве DI
#if !NETSTANDARD
using Infrastructure.BluetoothLE.Platforms.Android;
using Infrastructure.BluetoothLE.Platforms.Android.Internals;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Shiny;
namespace Infrastructure.BluetoothLE.Platforms.Shared
{
public class BleShinyModule : ShinyModule
{
readonly BleConfiguration config;
public BleShinyModule(BleConfiguration config) => this.config = config ?? new BleConfiguration();
public override void Register(IServiceCollection services)
{
services.AddSingleton(this.config);
services.TryAddSingleton<ManagerContext>();
services.TryAddSingleton<IBleManager, BleManager>();
}
}
}
#endif
using Microsoft.Extensions.DependencyInjection;
using Shiny;
namespace Infrastructure.BluetoothLE.Platforms.Shared
{
public static class ServiceCollectionExtensions
{
public static bool UseBleClient_Infr(this IServiceCollection builder, BleConfiguration? config = null)
{
#if NETSTANDARD
return false;
#else
builder.RegisterModule(new BleShinyModule(config));
return true;
#endif
}
}
}
Вызов из Xamarin.Forms проекта.
Shiny вызывает ConfigureServices класса Startup для регистрации зависимостей
public class Startup : ShinyStartup
{
public override void ConfigureServices(IServiceCollection services, IPlatform platform)
{
services.AddSingleton<IDialogs, Dialogs>();
// register all of the shiny stuff you want to use
var r1= services.UseBleClient(); //Shiny
var r2= services.UseBleClient_Infr(); //моя реализация
//r1 - true, IBleManager регистрируется нормально
//r2 - false !!!!
}
}
Запускаю Debug на Android устройстве и срабатывает ветка NETSTANDARD условной компиляции и ничего не регистрируется.
Хотя регистрируяShiny.BluetoothLE из nuget пакета возвращается true и все работает.
Если оставить только код
builder.RegisterModule(new BleShinyModule(config));
return true;
и снять условие #if !NETSTANDARD с BleShinyModule То будет ошибка: BleShinyModule.cs(2, 44): [CS0234] Тип или имя пространства имен "Android" не существует в пространстве имен "Infrastructure.BluetoothLE.Platforms" (возможно, отсутствует ссылка на сборку).
От чего зависит условная компиляция? где-то нужно выставлять флаги под что компилировать?
Я думал что при запуске на Android автоматически будет выбран вариант Android.
Т.е. я как-то не верно использую MSBuild.Sdk.Extras для компиляции разных вариантов или условную компиляцию?
Почему аналогичный код работает у Shiny?
И более глобальный вопрос) такой подход является нормальным для реализации специфических вешей для каждой платформы в одном проекте или лучше как то еще делать?
