blob: 6733b29953b0110c4afb16c6475731cd5cca206b [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library plugin_platform_interface;
import 'package:meta/meta.dart';
/// Base class for platform interfaces.
///
/// Provides a static helper method for ensuring that platform interfaces are
/// implemented using `extends` instead of `implements`.
///
/// Platform interface classes are expected to have a private static token object which will be
/// be passed to [verify] along with a platform interface object for verification.
///
/// Sample usage:
///
/// ```dart
/// abstract class UrlLauncherPlatform extends PlatformInterface {
/// UrlLauncherPlatform() : super(token: _token);
///
/// static UrlLauncherPlatform _instance = MethodChannelUrlLauncher();
///
/// static final Object _token = Object();
///
/// static UrlLauncherPlatform get instance => _instance;
///
/// /// Platform-specific plugins should set this with their own platform-specific
/// /// class that extends [UrlLauncherPlatform] when they register themselves.
/// static set instance(UrlLauncherPlatform instance) {
/// PlatformInterface.verify(instance, _token);
/// _instance = instance;
/// }
///
/// }
/// ```
///
/// Mockito mocks of platform interfaces will fail the verification, in test code only it is possible
/// to include the [MockPlatformInterfaceMixin] for the verification to be temporarily disabled. See
/// [MockPlatformInterfaceMixin] for a sample of using Mockito to mock a platform interface.
abstract class PlatformInterface {
/// Constructs a PlatformInterface, for use only in constructors of abstract
/// derived classes.
///
/// @param token The same, non-`const` `Object` that will be passed to `verify`.
PlatformInterface({required Object token}) {
_instanceTokens[this] = token;
}
/// Expando mapping instances of PlatformInterface to their associated tokens.
/// The reason this is not simply a private field of type `Object?` is because
/// as of the implementation of field promotion in Dart
/// (https://github.com/dart-lang/language/issues/2020), it is a runtime error
/// to invoke a private member that is mocked in another library. The expando
/// approach prevents [_verify] from triggering this runtime exception when
/// encountering an implementation that uses `implements` rather than
/// `extends`. This in turn allows [_verify] to throw an [AssertionError] (as
/// documented).
static final Expando<Object> _instanceTokens = Expando<Object>();
/// Ensures that the platform instance was constructed with a non-`const` token
/// that matches the provided token and throws [AssertionError] if not.
///
/// This is used to ensure that implementers are using `extends` rather than
/// `implements`.
///
/// Subclasses of [MockPlatformInterfaceMixin] are assumed to be valid in debug
/// builds.
///
/// This is implemented as a static method so that it cannot be overridden
/// with `noSuchMethod`.
static void verify(PlatformInterface instance, Object token) {
_verify(instance, token, preventConstObject: true);
}
/// Performs the same checks as `verify` but without throwing an
/// [AssertionError] if `const Object()` is used as the instance token.
///
/// This method will be deprecated in a future release.
static void verifyToken(PlatformInterface instance, Object token) {
_verify(instance, token, preventConstObject: false);
}
static void _verify(
PlatformInterface instance,
Object token, {
required bool preventConstObject,
}) {
if (instance is MockPlatformInterfaceMixin) {
bool assertionsEnabled = false;
assert(() {
assertionsEnabled = true;
return true;
}());
if (!assertionsEnabled) {
throw AssertionError(
'`MockPlatformInterfaceMixin` is not intended for use in release builds.');
}
return;
}
if (preventConstObject &&
identical(_instanceTokens[instance], const Object())) {
throw AssertionError('`const Object()` cannot be used as the token.');
}
if (!identical(token, _instanceTokens[instance])) {
throw AssertionError(
'Platform interfaces must not be implemented with `implements`');
}
}
}
/// A [PlatformInterface] mixin that can be combined with fake or mock objects,
/// such as test's `Fake` or mockito's `Mock`.
///
/// It passes the [PlatformInterface.verify] check even though it isn't
/// using `extends`.
///
/// This class is intended for use in tests only.
///
/// Sample usage (assuming `UrlLauncherPlatform` extends [PlatformInterface]):
///
/// ```dart
/// class UrlLauncherPlatformMock extends Mock
/// with MockPlatformInterfaceMixin
/// implements UrlLauncherPlatform {}
/// ```
@visibleForTesting
abstract class MockPlatformInterfaceMixin implements PlatformInterface {}