Dependency Service do czego jest nam potrzebny?

Dependency Service do czego jest nam potrzebny?

Chciałbym dzisiaj wam przybliżyć czym jest Dependency Service i do czego jest nam potrzebny.

Czym jest Dependency Service

Xamarin Forms jest technologią multi-platformową w której większość kodu aplikacji definiujemy w bibliotece PCL lub Shared. Niestety wymusza to na nas pewne ograniczenia. Największym jest brak możliwość dostępu do natywnych funkcjonalność danej platformy.

Mam tutaj na myśli usługi takie jak

  • Dostęp do plików
  • Poczta
  • Dostęp do hardware np. żyroskop
  • Notyfikacje
  • i wiele wiele innych

Każda z takich usług występuje na każdej platformie czy to będzie iOS czy Android, a może Windows. Niestety różnice na każdej z nich pociągają również różnice w sposobie implementacji i różnice w API. Stąd potrzebujemy czegoś co pozwoli nam używać różnych implementacji specyficznych dla każdej z platform i zarazem pozwoli nam wywołać ten kod z naszego Core aplikacji.

Dependency Service diagram

Z pomocą przychodzi nam Dependency Service.
Jak widzicie na załączonym obrazku nasz serwis chce uzyskać dostęp do informacji o baterii. Definiujemy interfejs tworzymy jego implementację na każdej z platform i wywołujemy poprzez Dependency Service. Wygląda prosto i takie jest też w rzeczywistości.

Przejdźmy dalej…

Dependency Service – Service Locator?

Sam w sobie Dependency Service nie jest niczym nowym. Pod spodem definiuje on wzorzec Service Locator (przynajmniej tak mi się wydaje :)).

Możemy dojść do takiego wniosku analizując kod źródłowy (tak od jakiegoś czasu mamy dostęp do kodu źródłowego Xamarin Forms na GitHub).

Ma on zdefiniowane podstawowe metody

  • Get
  • Register

Poniżej przykład metody Register

		public static void Register<T>() where T : class
		{
			Type type = typeof(T);
			if (!DependencyTypes.Contains(type))
				DependencyTypes.Add(type);
		}

		public static void Register<T, TImpl>() where T : class where TImpl : class, T
		{
			Type targetType = typeof(T);
			Type implementorType = typeof(TImpl);
			if (!DependencyTypes.Contains(targetType))
				DependencyTypes.Add(targetType);

			DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType };
		}

Ciekawsze rzeczy dzieją się na etapie tworzenia nowej instancji

		static void Initialize()
		{
			if (s_initialized)
			{
				return;
			}

			Assembly[] assemblies = Device.GetAssemblies();
			if (Internals.Registrar.ExtraAssemblies != null)
			{
				assemblies = assemblies.Union(Internals.Registrar.ExtraAssemblies).ToArray();
			}

			Initialize(assemblies);
		}

		internal static void Initialize(Assembly[] assemblies)
		{
			if (s_initialized)
			{
				return;
			}

			Type targetAttrType = typeof(DependencyAttribute);

			// Don't use LINQ for performance reasons
			// Naive implementation can easily take over a second to run
			foreach (Assembly assembly in assemblies)
			{
				Attribute[] attributes = assembly.GetCustomAttributes(targetAttrType).ToArray();
				if (attributes.Length == 0)
					continue;

				foreach (DependencyAttribute attribute in attributes)
				{
					if (!DependencyTypes.Contains(attribute.Implementor))
					{
						DependencyTypes.Add(attribute.Implementor);
					}
				}
			}

			s_initialized = true;
		}

Nie będę tutaj wchodził już w szczegóły ponieważ wyjdą one później.

Tworzymy własną implementację

Chciałbym wam pokazać jak zaimplementować „użycie” Dependency Service. Zrobimy to na przykładzie z baterią. Cały kod źródłowy jest do pobrania z GitHub

Interfejs na początek

Trzeba stworzyć nowy interfejs w naszym przykładzie jest to IBatteryService.

    public interface IBatteryService
    {
        int RemainingChargePercent { get; }

        Models.BatteryStatus Status { get; }

        Models.PowerSource PowerSource { get; }
    }

Implementacja

Kolejny krok to stworzenie implementacji na każdej z platform. W tym przykładzie użyjemy tylko Android.

    public class BatteryService : BatteryStatus.Infrastructure.IBatteryService
    {
        public int RemainingChargePercent
        {
            get
            {
                try
                {
                    using (var filter = new IntentFilter(Intent.ActionBatteryChanged))
                    {
                        using (var battery = Application.Context.RegisterReceiver(null, filter))
                        {
                            var level = battery.GetIntExtra(BatteryManager.ExtraLevel, -1);
                            var scale = battery.GetIntExtra(BatteryManager.ExtraScale, -1);

                            return (int)Math.Floor(level * 100D / scale);
                        }
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine("Ensure you have android.permission.BATTERY_STATS");
                    throw;
                }
            }
        }
...

Jest to tylko fragment implementacji dający nam tylko pogląd jak to powinno wyglądać.

Rejestracja

Gdy mamy już gotową implementację musimy ją zarejestrować żeby Dependency Service mógł go później użyć. Robimy to w prosty sposób poprzez nadanie atrybutu na namespace.

[assembly:Xamarin.Forms.Dependency(typeof(BatteryStatus.Droid.Infrastructure.BatteryService))]
namespace BatteryStatus.Droid.Infrastructure

Wywołanie\Użycie

W naszym przykładzie użyjemy naszego serwisu w ViewModel. Robimy to poprzez użycie metody Get z Dependency Service.

        public BatteryStatusViewModel()
        {
            var batteryService = DependencyService.Get<IBatteryService>();

            if (batteryService == null)
                return;

            this.BatteryStatus = batteryService.Status.ToString();
            this.PowerSource = batteryService.PowerSource.ToString();
            this.RemainingCharge = batteryService.RemainingChargePercent;
        }

Podsumowanie

Uff. Dobrnęliśmy do końca całości.
Użycie Dependency Service daje nam bardzo potężne możliwości, oraz dostęp do wielu natywnych funkcjonalności platform.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *