File: Connectivity\Connectivity.shared.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Networking;
 
namespace Microsoft.Maui.Networking
{
	/// <summary>
	/// The Connectivity API lets you monitor for changes in the device's network conditions, check the current network access, and determine how it is currently connected.
	/// </summary>
	public interface IConnectivity
	{
		/// <summary>
		/// Gets the active connectivity types for the device.
		/// </summary>
		IEnumerable<ConnectionProfile> ConnectionProfiles { get; }
 
		/// <summary>
		/// Gets the current state of network access.
		/// </summary>
		NetworkAccess NetworkAccess { get; }
 
		/// <summary>
		/// Occurs when network access or profile has changed.
		/// </summary>
		event EventHandler<ConnectivityChangedEventArgs> ConnectivityChanged;
	}
 
#nullable enable
	/// <summary>
	/// The Connectivity APIs lets you monitor for changes in the device's network conditions, check the current network access, and how it is currently connected.
	/// </summary>
	public static class Connectivity
	{
		/// <summary>
		/// Gets the current state of network access.
		/// </summary>
		/// <remarks>
		/// <para>Even when <see cref="NetworkAccess.Internet"/> is returned, full internet access is not guaranteed.</para>
		/// <para>Can throw <see cref="PermissionException"/> on Android if <c>ACCESS_NETWORK_STATE</c> is not set in manifest.</para>
		/// </remarks>
		public static NetworkAccess NetworkAccess => Current.NetworkAccess;
 
		/// <summary>
		/// Gets the active connectivity types for the device.
		/// </summary>
		/// <remarks>Can throw <see cref="PermissionException"/> on Android if <c>ACCESS_NETWORK_STATE</c> is not set in manifest.</remarks>
		public static IEnumerable<ConnectionProfile> ConnectionProfiles => Current.ConnectionProfiles.Distinct();
 
		/// <summary>
		/// Occurs when network access or profile has changed.
		/// </summary>
		/// <remarks>Can throw <see cref="PermissionException"/> on Android if <c>ACCESS_NETWORK_STATE</c> is not set in manifest.</remarks>
		public static event EventHandler<ConnectivityChangedEventArgs> ConnectivityChanged
		{
			add => Current.ConnectivityChanged += value;
			remove => Current.ConnectivityChanged -= value;
		}
 
		static IConnectivity? currentImplementation;
 
		/// <summary>
		/// Provides the default implementation for static usage of this API.
		/// </summary>
		public static IConnectivity Current =>
			currentImplementation ??= new ConnectivityImplementation();
 
		internal static void SetCurrent(IConnectivity? implementation) =>
			currentImplementation = implementation;
	}
#nullable disable
 
	partial class ConnectivityImplementation : IConnectivity
	{
		event EventHandler<ConnectivityChangedEventArgs> ConnectivityChangedInternal;
 
		// a cache so that events aren't fired unnecessarily
		// this is mainly an issue on Android, but we can stiil do this everywhere
		NetworkAccess currentAccess;
		List<ConnectionProfile> currentProfiles;
 
		public event EventHandler<ConnectivityChangedEventArgs> ConnectivityChanged
		{
			add
			{
				if (ConnectivityChangedInternal is null)
				{
					SetCurrent();
					StartListeners();
				}
				ConnectivityChangedInternal += value;
			}
			remove
			{
				ConnectivityChangedInternal -= value;
				if (ConnectivityChangedInternal is null)
					StopListeners();
			}
		}
 
		void SetCurrent()
		{
			currentAccess = NetworkAccess;
			currentProfiles = new List<ConnectionProfile>(ConnectionProfiles);
		}
 
		void OnConnectivityChanged(NetworkAccess access, IEnumerable<ConnectionProfile> profiles)
			=> OnConnectivityChanged(new ConnectivityChangedEventArgs(access, profiles));
 
		void OnConnectivityChanged()
			=> OnConnectivityChanged(NetworkAccess, ConnectionProfiles);
 
		void OnConnectivityChanged(ConnectivityChangedEventArgs e)
		{
			if (currentAccess != e.NetworkAccess || !currentProfiles.SequenceEqual(e.ConnectionProfiles))
			{
				SetCurrent();
				MainThread.BeginInvokeOnMainThread(() => ConnectivityChangedInternal?.Invoke(null, e));
			}
		}
	}
 
	/// <summary>
	/// The current connectivity information from the change event.
	/// </summary>
	public class ConnectivityChangedEventArgs : EventArgs
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ConnectivityChangedEventArgs"/> class.
		/// </summary>
		/// <param name="access">The current access of the network.</param>
		/// <param name="connectionProfiles">The connection profiles changing correspondingto this event.</param>
		public ConnectivityChangedEventArgs(NetworkAccess access, IEnumerable<ConnectionProfile> connectionProfiles)
		{
			NetworkAccess = access;
			ConnectionProfiles = connectionProfiles;
		}
 
		/// <summary>
		/// Gets the current state of network access.
		/// </summary>
		/// <remarks>
		/// <para>Even when <see cref="NetworkAccess.Internet"/> is returned, full internet access is not guaranteed.</para>
		/// <para>Can throw <see cref="PermissionException"/> on Android if <c>ACCESS_NETWORK_STATE</c> is not set in manifest.</para>
		/// </remarks>
		public NetworkAccess NetworkAccess { get; }
 
		/// <summary>
		/// Gets the active connectivity profiles for the device.
		/// </summary>
		public IEnumerable<ConnectionProfile> ConnectionProfiles { get; }
 
		/// <summary>
		/// Returns a string representation of the current values of <see cref="ConnectivityChangedEventArgs"/>.
		/// </summary>
		/// <returns>A string representation of this instance in the format of <c>NetworkAccess: {value}, ConnectionProfiles: [{value1}, {value2}]</c>.</returns>
		public override string ToString() =>
			$"{nameof(NetworkAccess)}: {NetworkAccess}, " +
			$"{nameof(ConnectionProfiles)}: [{string.Join(", ", ConnectionProfiles)}]";
	}
}