Using Xamarin.Forms DependencyService to Access Platform-Specific APIs Safely
Learn how to hide platform‑specific calls behind a shared interface and resolve them with Xamarin.Forms DependencyService, using a battery‑level example and discussing testing trade‑offs.
31 Jan 2026, 06:12 UTC

The concrete problem is that shared Xamarin.Forms code often needs to call a native API that only exists on iOS or Android, such as reading the device’s battery level or accessing secure storage. The useful takeaway is to hide that call behind a small shared interface and resolve it with DependencyService, keeping the ViewModel platform‑agnostic while the platform projects own the native implementation.
How DependencyService works in Xamarin.Forms
Xamarin.Forms shares UI and business logic, but it cannot expose every platform‑specific class in the shared project. DependencyService is Xamarin.Forms’ built‑in service locator: code asks the locator for an implementation by type, and the locator returns the class that was registered on the running platform with the [assembly: Dependency] attribute.
This decouples the shared contract from the platform details. The shared project defines what it needs (an interface), each platform project provides how it is done, and DependencyService wires them together at runtime.
Worked example: expose the current battery level
Battery level is a good illustration because it is a simple numeric value, requires platform‑specific APIs, and can be verified without special permissions on emulators or simulators.
Shared interface in the .NET Standard library
Create a narrow interface that the ViewModel will depend on.
// Shared project, e.g., MyApp.Core/Services/IBattery.cs
public interface IBattery
{
/// Battery charge as a percentage from 0 to 100.
int GetChargePercent();
}
The interface contains only the method the shared code needs; no iOS or Android types appear.
Platform‑specific implementations and registration
Each platform project implements the interface and registers it with an assembly‑level Dependency attribute.
// iOS project, e.g., MyApp.iOS/Services/Battery_iOS.cs
using Xamarin.Forms;
using UIKit;
[assembly: Dependency(typeof(Battery_iOS))]
namespace MyApp.iOS.Services
{
public class Battery_iOS : IBattery
{
public int GetChargePercent()
{
// UIDevice.CurrentDevice.BatteryLevel is 0.0‑1.0; multiply by 100.
var level = UIDevice.CurrentDevice.BatteryLevel;
if (level < 0) // simulator may return -1.0
return 0;
return (int)(level * 100);
}
}
}
// Android project, e.g., MyApp.Droid/Services/Battery_Android.cs
using Xamarin.Forms;
using Android.Content;
using Android.OS;
[assembly: Dependency(typeof(Battery_Android))]
namespace MyApp.Droid.Services
{
public class Battery_Android : IBattery
{
public int GetChargePercent()
{
var intent = Android.App.Application.Context.RegisterReceiver(null,
new IntentFilter(Intent.ActionBatteryChanged));
int level = intent.GetIntExtra(BatteryExtraLevel, -1);
int scale = intent.GetIntExtra(BatteryExtraScale, -1);
if (level == -1 || scale == -1)
return 0;
return (int)(level * 100 / scale);
}
}
}
The [assembly: Dependency(typeof(...))] attribute makes the class discoverable. Without it, DependencyService.Get<IBattery>() returns null.
Consuming the service from shared code
In a ViewModel or code‑behind, resolve the implementation at the point of use and guard against a null return.
// Shared project ViewModel
var battery = DependencyService.Get<IBattery>();
int charge = battery != null ? battery.GetChargePercent() : 0;
// Use charge to update the UI, e.g., bind to a ProgressBar.
A quick verification step is to log whether the resolved instance is null and whether the returned integer is within the expected range. A null result usually means a missing registration or a typo in the interface name.
Trade‑offs and limitations
DependencyService follows the service locator pattern, which can make unit testing harder because the dependency is hidden inside the class and resolved at runtime. Constructor‑based dependency injection is easier to mock and gives clearer visibility of dependencies.
Overusing the pattern scatters platform‑specific logic across many small services, making the platform projects harder to maintain. Keep the interface surface small and push decisions back to shared code whenever possible.
Xamarin.Forms is largely superseded by .NET MAUI. The DependencyService pattern still exists in MAUI with similar semantics, but the project structure and initialization have changed. If you are targeting MAUI, verify that the attribute registration and resolution work as expected.
Actionable closing
Use DependencyService for narrow, hardware‑adjacent capabilities that cannot be expressed in shared code. Define a minimal interface in the shared project, implement and register it once per platform, and resolve with DependencyService.Get<T>() while checking for null. Reserve constructor injection for core logic that needs to be testable, and let DependencyService handle the true platform boundary.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.