Merge branch 'master' into update_nnbd
diff --git a/.cirrus.yml b/.cirrus.yml
index 756eb6e..4762407 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -163,6 +163,7 @@
     - name: build-ipas+drive-examples
       env:
         PATH: $PATH:/usr/local/bin
+        PLUGINS_TO_SKIP_XCTESTS: "battery/battery,camera,connectivity/connectivity,device_info/device_info,espresso,google_maps_flutter/google_maps_flutter,google_sign_in/google_sign_in,image_picker/image_picker,in_app_purchase,integration_test,ios_platform_images,local_auth,package_info,path_provider/path_provider,quick_actions,sensors,shared_preferences/shared_preferences,url_launcher/url_launcher,video_player/video_player,webview_flutter,wifi_info_flutter/wifi_info_flutter"
         matrix:
           PLUGIN_SHARDING: "--shardIndex 0 --shardCount 4"
           PLUGIN_SHARDING: "--shardIndex 1 --shardCount 4"
@@ -179,6 +180,7 @@
         - flutter channel $CHANNEL
         - ./script/incremental_build.sh build-examples --ipa --enable-experiment=non-nullable
         - ./script/incremental_build.sh drive-examples --enable-experiment=non-nullable
+        - ./script/incremental_build.sh xctest --target RunnerUITests --skip $PLUGINS_TO_SKIP_XCTESTS --enable-experiment=non-nullable
 task:
   # don't run on release tags since it creates O(n^2) tasks where n is the number of plugins
   only_if: $CIRRUS_TAG == ''
diff --git a/.gitignore b/.gitignore
index 88ce490..d756050 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@
 generated_plugin_registrant.dart
 GeneratedPluginRegistrant.h
 GeneratedPluginRegistrant.m
+generated_plugin_registrant.cc
 GeneratedPluginRegistrant.java
 GeneratedPluginRegistrant.swift
 build/
diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md
index 1913f97..ab878a6 100644
--- a/packages/android_alarm_manager/CHANGELOG.md
+++ b/packages/android_alarm_manager/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.4.5+16
+
+* Remove unnecessary workaround from test.
+
 ## 0.4.5+15
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart
index 4ba6977..b4d907c 100644
--- a/packages/android_alarm_manager/example/lib/main.dart
+++ b/packages/android_alarm_manager/example/lib/main.dart
@@ -104,11 +104,7 @@
 
   @override
   Widget build(BuildContext context) {
-    // TODO(jackson): This has been deprecated and should be replaced
-    // with `headline4` when it's available on all the versions of
-    // Flutter that we test.
-    // ignore: deprecated_member_use
-    final textStyle = Theme.of(context).textTheme.display1;
+    final textStyle = Theme.of(context).textTheme.headline4;
     return Scaffold(
       appBar: AppBar(
         title: Text(widget.title),
diff --git a/packages/android_alarm_manager/example/test_driver/integration_test.dart b/packages/android_alarm_manager/example/test_driver/integration_test.dart
index 4e32b48..ed54518 100644
--- a/packages/android_alarm_manager/example/test_driver/integration_test.dart
+++ b/packages/android_alarm_manager/example/test_driver/integration_test.dart
@@ -7,39 +7,14 @@
 import 'dart:io';
 
 import 'package:flutter_driver/flutter_driver.dart';
-import 'package:vm_service_client/vm_service_client.dart';
-
-Future<StreamSubscription<VMIsolateRef>> resumeIsolatesOnPause(
-    FlutterDriver driver) async {
-  final VM vm = await driver.serviceClient.getVM();
-  for (VMIsolateRef isolateRef in vm.isolates) {
-    final VMIsolate isolate = await isolateRef.load();
-    if (isolate.isPaused) {
-      await isolate.resume();
-    }
-  }
-  return driver.serviceClient.onIsolateRunnable
-      .asBroadcastStream()
-      .listen((VMIsolateRef isolateRef) async {
-    final VMIsolate isolate = await isolateRef.load();
-    if (isolate.isPaused) {
-      await isolate.resume();
-    }
-  });
-}
 
 Future<void> main() async {
   final FlutterDriver driver = await FlutterDriver.connect();
-  // flutter drive causes isolates to be paused on spawn. The background isolate
-  // for this plugin will need to be resumed for the test to pass.
-  final StreamSubscription<VMIsolateRef> subscription =
-      await resumeIsolatesOnPause(driver);
   final String data = await driver.requestData(
     null,
     timeout: const Duration(minutes: 1),
   );
   await driver.close();
-  await subscription.cancel();
   final Map<String, dynamic> result = jsonDecode(data);
   exit(result['result'] == 'true' ? 0 : 1);
 }
diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml
index e12bedd..8032732 100644
--- a/packages/android_alarm_manager/pubspec.yaml
+++ b/packages/android_alarm_manager/pubspec.yaml
@@ -4,7 +4,7 @@
 # 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.4.5+15
+version: 0.4.5+16
 homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager
 
 dependencies:
diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md
index ebf654f..f6ff34d 100644
--- a/packages/camera/CHANGELOG.md
+++ b/packages/camera/CHANGELOG.md
@@ -1,3 +1,15 @@
+## 0.5.8+14
+
+* Changed the order of the setters for `mediaRecorder` in `MediaRecorderBuilder.java` to make it more readable.
+
+## 0.5.8+13
+
+* Added Dartdocs for all public APIs.
+
+## 0.5.8+12
+
+* Added information of video not working correctly on Android emulators to `README.md`.
+
 ## 0.5.8+11
 
 * Fix rare nullptr exception on Android.
diff --git a/packages/camera/README.md b/packages/camera/README.md
index c56ed0d..39d3ed8 100644
--- a/packages/camera/README.md
+++ b/packages/camera/README.md
@@ -41,6 +41,8 @@
 minSdkVersion 21
 ```
 
+It's important to note that the `MediaRecorder` class is not working properly on emulators, as stated in the documentation: https://developer.android.com/reference/android/media/MediaRecorder. Specifically, when recording a video with sound enabled and trying to play it back, the duration won't be correct and you will only see the first frame.
+
 ### Handling Lifecycle states
 
 As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so:
diff --git a/packages/camera/analysis_options.yaml b/packages/camera/analysis_options.yaml
deleted file mode 100644
index 8e4af76..0000000
--- a/packages/camera/analysis_options.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# This is a temporary file to allow us to land a new set of linter rules in a
-# series of manageable patches instead of one gigantic PR. It disables some of
-# the new lints that are already failing on this plugin, for this plugin. It
-# should be deleted and the failing lints addressed as soon as possible.
-
-include: ../../analysis_options.yaml
-
-analyzer:
-  errors:
-    public_member_api_docs: ignore
diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
index 57dc6e6..b2309c8 100644
--- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
+++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
@@ -49,18 +49,18 @@
   public MediaRecorder build() throws IOException {
     MediaRecorder mediaRecorder = recorderFactory.makeMediaRecorder();
 
-    // There's a specific order that mediaRecorder expects. Do not change the order
-    // of these function calls.
-    if (enableAudio) {
-      mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-      mediaRecorder.setAudioEncodingBitRate(recordingProfile.audioBitRate);
-    }
+    // There's a fixed order that mediaRecorder expects. Only change these functions accordingly.
+    // You can find the specifics here: https://developer.android.com/reference/android/media/MediaRecorder.
+    if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
     mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
     mediaRecorder.setOutputFormat(recordingProfile.fileFormat);
-    if (enableAudio) mediaRecorder.setAudioEncoder(recordingProfile.audioCodec);
+    if (enableAudio) {
+      mediaRecorder.setAudioEncoder(recordingProfile.audioCodec);
+      mediaRecorder.setAudioEncodingBitRate(recordingProfile.audioBitRate);
+      mediaRecorder.setAudioSamplingRate(recordingProfile.audioSampleRate);
+    }
     mediaRecorder.setVideoEncoder(recordingProfile.videoCodec);
     mediaRecorder.setVideoEncodingBitRate(recordingProfile.videoBitRate);
-    if (enableAudio) mediaRecorder.setAudioSamplingRate(recordingProfile.audioSampleRate);
     mediaRecorder.setVideoFrameRate(recordingProfile.videoFrameRate);
     mediaRecorder.setVideoSize(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);
     mediaRecorder.setOutputFile(outputFilePath);
diff --git a/packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
index f60e85d..622b49b 100644
--- a/packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
+++ b/packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
@@ -69,13 +69,13 @@
 
     InOrder inOrder = inOrder(recorder);
     inOrder.verify(recorder).setAudioSource(MediaRecorder.AudioSource.MIC);
-    inOrder.verify(recorder).setAudioEncodingBitRate(recorderProfile.audioBitRate);
     inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE);
     inOrder.verify(recorder).setOutputFormat(recorderProfile.fileFormat);
     inOrder.verify(recorder).setAudioEncoder(recorderProfile.audioCodec);
+    inOrder.verify(recorder).setAudioEncodingBitRate(recorderProfile.audioBitRate);
+    inOrder.verify(recorder).setAudioSamplingRate(recorderProfile.audioSampleRate);
     inOrder.verify(recorder).setVideoEncoder(recorderProfile.videoCodec);
     inOrder.verify(recorder).setVideoEncodingBitRate(recorderProfile.videoBitRate);
-    inOrder.verify(recorder).setAudioSamplingRate(recorderProfile.audioSampleRate);
     inOrder.verify(recorder).setVideoFrameRate(recorderProfile.videoFrameRate);
     inOrder
         .verify(recorder)
diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart
index ce9fd94..91c6525 100644
--- a/packages/camera/lib/camera.dart
+++ b/packages/camera/lib/camera.dart
@@ -13,7 +13,17 @@
 
 final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera');
 
-enum CameraLensDirection { front, back, external }
+/// The direction the camera is facing.
+enum CameraLensDirection {
+  /// Front facing camera (a user looking at the screen is seen by the camera).
+  front,
+
+  /// Back facing camera (a user looking at the screen is not seen by the camera).
+  back,
+
+  /// External camera which may not be mounted to the device.
+  external,
+}
 
 /// Affect the quality of video recording and image capture:
 ///
@@ -38,6 +48,9 @@
   max,
 }
 
+/// Signature for a callback receiving the a camera image.
+///
+/// This is used by [CameraController.startImageStream].
 // ignore: inference_failure_on_function_return_type
 typedef onLatestImageAvailable = Function(CameraImage image);
 
@@ -91,10 +104,15 @@
   }
 }
 
+/// Properties of a camera device.
 class CameraDescription {
+  /// Creates a new camera description with the given properties.
   CameraDescription({this.name, this.lensDirection, this.sensorOrientation});
 
+  /// The name of the camera device.
   final String name;
+
+  /// The direction the camera is facing.
   final CameraLensDirection lensDirection;
 
   /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.
@@ -126,19 +144,27 @@
 
 /// This is thrown when the plugin reports an error.
 class CameraException implements Exception {
+  /// Creates a new camera exception with the given error code and description.
   CameraException(this.code, this.description);
 
+  /// Error code.
+  // TODO(bparrishMines): Document possible error codes.
+  // https://github.com/flutter/flutter/issues/69298
   String code;
+
+  /// Textual description of the error.
   String description;
 
   @override
   String toString() => '$runtimeType($code, $description)';
 }
 
-// Build the UI texture view of the video data with textureId.
+/// A widget showing a live camera preview.
 class CameraPreview extends StatelessWidget {
+  /// Creates a preview widget for the given camera controller.
   const CameraPreview(this.controller);
 
+  /// The controller for the camera that the preview is shown for.
   final CameraController controller;
 
   @override
@@ -151,6 +177,7 @@
 
 /// The state of a [CameraController].
 class CameraValue {
+  /// Creates a new camera controller state.
   const CameraValue({
     this.isInitialized,
     this.errorDescription,
@@ -161,6 +188,7 @@
     bool isRecordingPaused,
   }) : _isRecordingPaused = isRecordingPaused;
 
+  /// Creates a new camera controller state for an uninitialzed controller.
   const CameraValue.uninitialized()
       : this(
           isInitialized: false,
@@ -187,6 +215,10 @@
   /// True when camera [isRecordingVideo] and recording is paused.
   bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused;
 
+  /// Description of an error state.
+  ///
+  /// This is null while the controller is not in an error state.
+  /// When [hasError] is true this contains the error description.
   final String errorDescription;
 
   /// The size of the preview in pixels.
@@ -199,8 +231,15 @@
   /// Can only be called when [initialize] is done.
   double get aspectRatio => previewSize.height / previewSize.width;
 
+  /// Whether the controller is in an error state.
+  ///
+  /// When true [errorDescription] describes the error.
   bool get hasError => errorDescription != null;
 
+  /// Creates a modified copy of the object.
+  ///
+  /// Explicitly specified fields get the specified value, all other fields get
+  /// the same value of the current object.
   CameraValue copyWith({
     bool isInitialized,
     bool isRecordingVideo,
@@ -241,13 +280,22 @@
 ///
 /// To show the camera preview on the screen use a [CameraPreview] widget.
 class CameraController extends ValueNotifier<CameraValue> {
+  /// Creates a new camera controller in an uninitialized state.
   CameraController(
     this.description,
     this.resolutionPreset, {
     this.enableAudio = true,
   }) : super(const CameraValue.uninitialized());
 
+  /// The properties of the camera device controlled by this controller.
   final CameraDescription description;
+
+  /// The resolution this controller is targeting.
+  ///
+  /// This resolution preset is not guaranteed to be available on the device,
+  /// if unavailable a lower resolution will be used.
+  ///
+  /// See also: [ResolutionPreset].
   final ResolutionPreset resolutionPreset;
 
   /// Whether to include audio when recording a video.
diff --git a/packages/camera/lib/new/camera.dart b/packages/camera/lib/new/camera.dart
deleted file mode 100644
index 08b085f..0000000
--- a/packages/camera/lib/new/camera.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-export 'src/camera_controller.dart';
-export 'src/camera_testing.dart';
-export 'src/common/camera_interface.dart';
-export 'src/common/native_texture.dart';
-export 'src/support_android/camera.dart';
-export 'src/support_android/camera_info.dart';
diff --git a/packages/camera/lib/new/src/camera_controller.dart b/packages/camera/lib/new/src/camera_controller.dart
deleted file mode 100644
index 4296f39..0000000
--- a/packages/camera/lib/new/src/camera_controller.dart
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter/foundation.dart';
-
-import 'common/camera_interface.dart';
-
-/// Controls a device camera.
-///
-/// Use [CameraController.availableCameras] to get a list of available cameras.
-///
-/// This class is used as a simple interface to control a camera on Android or
-/// iOS.
-///
-/// Only one instance of [CameraController] can be active at a time. If you call
-/// [initialize] on a [CameraController] while another is active, the old
-/// controller will be disposed before initializing the new controller.
-///
-/// Example using [CameraController]:
-///
-/// ```dart
-/// final List<CameraDescription> cameras = async CameraController.availableCameras();
-/// final CameraController controller = CameraController(description: cameras[0]);
-/// controller.initialize();
-/// controller.start();
-/// ```
-class CameraController {
-  /// Default constructor.
-  ///
-  /// Use [CameraController.availableCameras] to get a list of available
-  /// cameras.
-  ///
-  /// This will choose the best [CameraConfigurator] for the current device.
-  factory CameraController({@required CameraDescription description}) {
-    return CameraController._(
-      description: description,
-      configurator: _createDefaultConfigurator(description),
-      api: _getCameraApi(description),
-    );
-  }
-
-  CameraController._({
-    @required this.description,
-    @required this.configurator,
-    @required this.api,
-  })  : assert(description != null),
-        assert(configurator != null),
-        assert(api != null);
-
-  /// Constructor for defining your own [CameraConfigurator].
-  ///
-  /// Use [CameraController.availableCameras] to get a list of available
-  /// cameras.
-  factory CameraController.customConfigurator({
-    @required CameraDescription description,
-    @required CameraConfigurator configurator,
-  }) {
-    return CameraController._(
-      description: description,
-      configurator: configurator,
-      api: _getCameraApi(description),
-    );
-  }
-
-  static const String _isNotInitializedMessage = 'Initialize was not called.';
-  static const String _isDisposedMessage = 'This controller has been disposed.';
-
-  // Keep only one active instance of CameraController.
-  static CameraController _instance;
-
-  bool _isDisposed = false;
-
-  /// Details for the camera this controller accesses.
-  final CameraDescription description;
-
-  /// Configurator used to control the camera.
-  final CameraConfigurator configurator;
-
-  /// Api used by the [configurator].
-  final CameraApi api;
-
-  bool get isDisposed => _isDisposed;
-
-  /// Retrieves a list of available cameras for the current device.
-  ///
-  /// This will choose the best [CameraAPI] for the current device.
-  static Future<List<CameraDescription>> availableCameras() async {
-    throw UnimplementedError('$defaultTargetPlatform not supported');
-  }
-
-  /// Initializes the camera on the device.
-  ///
-  /// You must call [dispose] when you are done using the camera, otherwise it
-  /// will remain locked and be unavailable to other applications.
-  ///
-  /// Only one instance of [CameraController] can be active at a time. If you
-  /// call [initialize] on a [CameraController] while another is active, the old
-  /// controller will be disposed before initializing the new controller.
-  Future<void> initialize() {
-    if (_instance == this) {
-      return Future<void>.value();
-    }
-
-    final Completer<void> completer = Completer<void>();
-
-    if (_instance != null) {
-      _instance
-          .dispose()
-          .then((_) => configurator.initialize())
-          .then((_) => completer.complete());
-    }
-    _instance = this;
-
-    return completer.future;
-  }
-
-  /// Begins the flow of data between the inputs and outputs connected to the camera instance.
-  Future<void> start() {
-    assert(!_isDisposed, _isDisposedMessage);
-    assert(_instance != this, _isNotInitializedMessage);
-
-    return configurator.start();
-  }
-
-  /// Stops the flow of data between the inputs and outputs connected to the camera instance.
-  Future<void> stop() {
-    assert(!_isDisposed, _isDisposedMessage);
-    assert(_instance != this, _isNotInitializedMessage);
-
-    return configurator.stop();
-  }
-
-  /// Deallocate all resources and disables further use of the controller.
-  Future<void> dispose() {
-    _instance = null;
-    _isDisposed = true;
-    return configurator.dispose();
-  }
-
-  static CameraConfigurator _createDefaultConfigurator(
-    CameraDescription description,
-  ) {
-    final CameraApi api = _getCameraApi(description);
-    switch (api) {
-      case CameraApi.android:
-        throw UnimplementedError();
-      case CameraApi.iOS:
-        throw UnimplementedError();
-      case CameraApi.supportAndroid:
-        throw UnimplementedError();
-    }
-
-    return null; // Unreachable code
-  }
-
-  static CameraApi _getCameraApi(CameraDescription description) {
-    return CameraApi.iOS;
-
-    // TODO(bparrishMines): Uncomment this when platform specific code is added.
-    /*
-    throw ArgumentError.value(
-      description.runtimeType,
-      'description.runtimeType',
-      'Failed to get $CameraApi from',
-    );
-    */
-  }
-}
diff --git a/packages/camera/lib/new/src/camera_testing.dart b/packages/camera/lib/new/src/camera_testing.dart
deleted file mode 100644
index 8022216..0000000
--- a/packages/camera/lib/new/src/camera_testing.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/foundation.dart';
-import 'package:flutter/services.dart';
-
-import 'common/camera_channel.dart';
-
-@visibleForTesting
-class CameraTesting {
-  CameraTesting._();
-
-  static final MethodChannel channel = CameraChannel.channel;
-  static int get nextHandle => CameraChannel.nextHandle;
-  static set nextHandle(int handle) => CameraChannel.nextHandle = handle;
-}
diff --git a/packages/camera/lib/new/src/common/camera_channel.dart b/packages/camera/lib/new/src/common/camera_channel.dart
deleted file mode 100644
index 12036b8..0000000
--- a/packages/camera/lib/new/src/common/camera_channel.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/services.dart';
-
-typedef CameraCallback = void Function(dynamic result);
-
-// Non exported class
-class CameraChannel {
-  static final Map<int, dynamic> callbacks = <int, CameraCallback>{};
-
-  static final MethodChannel channel = const MethodChannel(
-    'flutter.plugins.io/camera',
-  )..setMethodCallHandler(
-      (MethodCall call) async {
-        assert(call.method == 'handleCallback');
-
-        final int handle = call.arguments['handle'];
-        if (callbacks[handle] != null) callbacks[handle](call.arguments);
-      },
-    );
-
-  static int nextHandle = 0;
-
-  static void registerCallback(int handle, CameraCallback callback) {
-    assert(handle != null);
-    assert(CameraCallback != null);
-
-    assert(!callbacks.containsKey(handle));
-    callbacks[handle] = callback;
-  }
-
-  static void unregisterCallback(int handle) {
-    assert(handle != null);
-    callbacks.remove(handle);
-  }
-}
diff --git a/packages/camera/lib/new/src/common/camera_interface.dart b/packages/camera/lib/new/src/common/camera_interface.dart
deleted file mode 100644
index 99ead09..0000000
--- a/packages/camera/lib/new/src/common/camera_interface.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-/// Available APIs compatible with [CameraController].
-enum CameraApi {
-  /// [Camera2](https://developer.android.com/reference/android/hardware/camera2/package-summary)
-  android,
-
-  /// [AVFoundation](https://developer.apple.com/av-foundation/)
-  iOS,
-
-  /// [Camera](https://developer.android.com/reference/android/hardware/Camera)
-  supportAndroid,
-}
-
-/// Location of the camera on the device.
-enum LensDirection { front, back, unknown }
-
-/// Abstract class used to create a common interface to describe a camera from different platform APIs.
-///
-/// This provides information such as the [name] of the camera and [direction]
-/// the lens face.
-abstract class CameraDescription {
-  /// Location of the camera on the device.
-  LensDirection get direction;
-
-  /// Identifier for this camera.
-  String get name;
-}
-
-/// Abstract class used to create a common interface across platform APIs.
-abstract class CameraConfigurator {
-  /// Texture id that can be used to send camera frames to a [Texture] widget.
-  ///
-  /// You must call [addPreviewTexture] first or this will only return null.
-  int get previewTextureId;
-
-  /// Initializes the camera on the device.
-  Future<void> initialize();
-
-  /// Begins the flow of data between the inputs and outputs connected to the camera instance.
-  ///
-  /// This will start updating the texture with id: [previewTextureId].
-  Future<void> start();
-
-  /// Stops the flow of data between the inputs and outputs connected to the camera instance.
-  Future<void> stop();
-
-  /// Dispose all resources and disables further use of this configurator.
-  Future<void> dispose();
-
-  /// Retrieves a valid texture Id to be used with a [Texture] widget.
-  Future<int> addPreviewTexture();
-}
diff --git a/packages/camera/lib/new/src/common/camera_mixins.dart b/packages/camera/lib/new/src/common/camera_mixins.dart
deleted file mode 100644
index bb27e48..0000000
--- a/packages/camera/lib/new/src/common/camera_mixins.dart
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'camera_channel.dart';
-
-mixin NativeMethodCallHandler {
-  /// Identifier for an object on the native side of the plugin.
-  ///
-  /// Only used internally and for debugging.
-  final int handle = CameraChannel.nextHandle++;
-}
-
-mixin CameraMappable {
-  /// Creates a description of the object compatible with [PlatformChannel]s.
-  ///
-  /// Only used as an internal method and for debugging.
-  Map<String, dynamic> asMap();
-}
diff --git a/packages/camera/lib/new/src/common/native_texture.dart b/packages/camera/lib/new/src/common/native_texture.dart
deleted file mode 100644
index 1deb7e3..0000000
--- a/packages/camera/lib/new/src/common/native_texture.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter/foundation.dart';
-
-import 'camera_channel.dart';
-import 'camera_mixins.dart';
-
-/// Used to allocate a buffer for displaying a preview camera texture.
-///
-/// This is used to for a developer to have a control over the
-/// `TextureRegistry.SurfaceTextureEntry` (Android) and FlutterTexture (iOS).
-/// This gives direct access to the textureId and can be reused with separate
-/// camera instances.
-///
-/// The [textureId] can be passed to a [Texture] widget.
-class NativeTexture with CameraMappable {
-  NativeTexture._({@required int handle, @required this.textureId})
-      : _handle = handle,
-        assert(handle != null),
-        assert(textureId != null);
-
-  final int _handle;
-
-  bool _isClosed = false;
-
-  /// Id that can be passed to a [Texture] widget.
-  final int textureId;
-
-  static Future<NativeTexture> allocate() async {
-    final int handle = CameraChannel.nextHandle++;
-
-    final int textureId = await CameraChannel.channel.invokeMethod<int>(
-      '$NativeTexture#allocate',
-      <String, dynamic>{'textureHandle': handle},
-    );
-
-    return NativeTexture._(handle: handle, textureId: textureId);
-  }
-
-  /// Deallocate this texture.
-  Future<void> release() {
-    if (_isClosed) return Future<void>.value();
-
-    _isClosed = true;
-    return CameraChannel.channel.invokeMethod<void>(
-      '$NativeTexture#release',
-      <String, dynamic>{'handle': _handle},
-    );
-  }
-
-  @override
-  Map<String, dynamic> asMap() {
-    return <String, dynamic>{'handle': _handle};
-  }
-}
diff --git a/packages/camera/lib/new/src/support_android/camera.dart b/packages/camera/lib/new/src/support_android/camera.dart
deleted file mode 100644
index d78753d..0000000
--- a/packages/camera/lib/new/src/support_android/camera.dart
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import '../common/camera_channel.dart';
-import '../common/camera_mixins.dart';
-import '../common/native_texture.dart';
-import 'camera_info.dart';
-
-/// The Camera class used to set image capture settings, start/stop preview, snap pictures, and retrieve frames for encoding for video.
-///
-/// This class is a client for the Camera service, which manages the actual
-/// camera hardware.
-///
-/// This exposes the deprecated Android
-/// [Camera](https://developer.android.com/reference/android/hardware/Camera)
-/// API. This should only be used with Android sdk versions less than 21.
-class Camera with NativeMethodCallHandler {
-  Camera._();
-
-  bool _isClosed = false;
-
-  /// Retrieves the number of physical cameras available on this device.
-  static Future<int> getNumberOfCameras() {
-    return CameraChannel.channel.invokeMethod<int>(
-      'Camera#getNumberOfCameras',
-    );
-  }
-
-  /// Creates a new [Camera] object to access a particular hardware camera.
-  ///
-  /// If the same camera is opened by other applications, this will throw a
-  /// [PlatformException].
-  ///
-  /// You must call [release] when you are done using the camera, otherwise it
-  /// will remain locked and be unavailable to other applications.
-  ///
-  /// Your application should only have one [Camera] object active at a time for
-  /// a particular hardware camera.
-  static Camera open(int cameraId) {
-    final Camera camera = Camera._();
-
-    CameraChannel.channel.invokeMethod<int>(
-      'Camera#open',
-      <String, dynamic>{'cameraId': cameraId, 'cameraHandle': camera.handle},
-    );
-
-    return camera;
-  }
-
-  /// Retrieves information about a particular camera.
-  ///
-  /// If [getNumberOfCameras] returns N, the valid id is 0 to N-1.
-  static Future<CameraInfo> getCameraInfo(int cameraId) async {
-    final Map<String, dynamic> infoMap =
-        await CameraChannel.channel.invokeMapMethod<String, dynamic>(
-      'Camera#getCameraInfo',
-      <String, dynamic>{'cameraId': cameraId},
-    );
-
-    return CameraInfo.fromMap(infoMap);
-  }
-
-  /// Sets the [NativeTexture] to be used for live preview.
-  ///
-  /// This method must be called before [startPreview].
-  ///
-  /// The one exception is that if the preview native texture is not set (or
-  /// set to null) before [startPreview] is called, then this method may be
-  /// called once with a non-null parameter to set the preview texture.
-  /// (This allows camera setup and surface creation to happen in parallel,
-  /// saving time.) The preview native texture may not otherwise change while
-  /// preview is running.
-  set previewTexture(NativeTexture texture) {
-    assert(!_isClosed);
-
-    CameraChannel.channel.invokeMethod<void>(
-      'Camera#previewTexture',
-      <String, dynamic>{'handle': handle, 'nativeTexture': texture?.asMap()},
-    );
-  }
-
-  /// Starts capturing and drawing preview frames to the screen.
-  ///
-  /// Preview will not actually start until a surface is supplied with
-  /// [previewTexture].
-  Future<void> startPreview() {
-    assert(!_isClosed);
-
-    return CameraChannel.channel.invokeMethod<void>(
-      'Camera#startPreview',
-      <String, dynamic>{'handle': handle},
-    );
-  }
-
-  /// Stops capturing and drawing preview frames to the [previewTexture], and resets the camera for a future call to [startPreview].
-  Future<void> stopPreview() {
-    assert(!_isClosed);
-
-    return CameraChannel.channel.invokeMethod<void>(
-      'Camera#stopPreview',
-      <String, dynamic>{'handle': handle},
-    );
-  }
-
-  /// Disconnects and releases the Camera object resources.
-  ///
-  /// You must call this as soon as you're done with the Camera object.
-  Future<void> release() {
-    if (_isClosed) return Future<void>.value();
-
-    _isClosed = true;
-    return CameraChannel.channel.invokeMethod<void>(
-      'Camera#release',
-      <String, dynamic>{'handle': handle},
-    );
-  }
-}
diff --git a/packages/camera/lib/new/src/support_android/camera_info.dart b/packages/camera/lib/new/src/support_android/camera_info.dart
deleted file mode 100644
index 033fecf..0000000
--- a/packages/camera/lib/new/src/support_android/camera_info.dart
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/foundation.dart';
-
-import '../common/camera_interface.dart';
-
-/// The direction that the camera faces.
-enum Facing { back, front }
-
-/// Information about a camera.
-///
-/// Retrieved from [Camera.getCameraInfo].
-class CameraInfo implements CameraDescription {
-  const CameraInfo({
-    @required this.id,
-    @required this.facing,
-    @required this.orientation,
-  })  : assert(id != null),
-        assert(facing != null),
-        assert(orientation != null);
-
-  factory CameraInfo.fromMap(Map<String, dynamic> map) {
-    return CameraInfo(
-      id: map['id'],
-      orientation: map['orientation'],
-      facing: Facing.values.firstWhere(
-        (Facing facing) => facing.toString() == map['facing'],
-      ),
-    );
-  }
-
-  /// Identifier for a particular camera.
-  final int id;
-
-  /// The direction that the camera faces.
-  final Facing facing;
-
-  /// The orientation of the camera image.
-  ///
-  /// The value is the angle that the camera image needs to be rotated clockwise
-  /// so it shows correctly on the display in its natural orientation.
-  /// It should be 0, 90, 180, or 270.
-  ///
-  /// For example, suppose a device has a naturally tall screen. The back-facing
-  /// camera sensor is mounted in landscape. You are looking at the screen. If
-  /// the top side of the camera sensor is aligned with the right edge of the
-  /// screen in natural orientation, the value should be 90. If the top side of
-  /// a front-facing camera sensor is aligned with the right of the screen, the
-  /// value should be 270.
-  final int orientation;
-
-  @override
-  String get name => id.toString();
-
-  @override
-  LensDirection get direction {
-    switch (facing) {
-      case Facing.front:
-        return LensDirection.front;
-      case Facing.back:
-        return LensDirection.back;
-    }
-
-    return null;
-  }
-}
diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml
index 7a2b187..de7d8b5 100644
--- a/packages/camera/pubspec.yaml
+++ b/packages/camera/pubspec.yaml
@@ -2,8 +2,7 @@
 description: A Flutter plugin for getting information about and controlling the
   camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
   and streaming image buffers to dart.
-version: 0.5.8+11
-
+version: 0.5.8+14
 homepage: https://github.com/flutter/plugins/tree/master/packages/camera
 
 dependencies:
diff --git a/packages/camera/test/camera_test.dart b/packages/camera/test/camera_test.dart
deleted file mode 100644
index fbb9556..0000000
--- a/packages/camera/test/camera_test.dart
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:camera/new/camera.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:camera/new/src/camera_testing.dart';
-import 'package:camera/new/src/common/native_texture.dart';
-
-void main() {
-  TestWidgetsFlutterBinding.ensureInitialized();
-
-  group('Camera', () {
-    final List<MethodCall> log = <MethodCall>[];
-
-    setUpAll(() {
-      CameraTesting.channel
-          .setMockMethodCallHandler((MethodCall methodCall) async {
-        log.add(methodCall);
-        switch (methodCall.method) {
-          case 'NativeTexture#allocate':
-            return 15;
-        }
-
-        throw ArgumentError.value(
-          methodCall.method,
-          'methodCall.method',
-          'No method found for',
-        );
-      });
-    });
-
-    setUp(() {
-      log.clear();
-      CameraTesting.nextHandle = 0;
-    });
-
-    group('$CameraController', () {
-      test('Initializing a second controller closes the first', () {
-        final MockCameraDescription description = MockCameraDescription();
-        final MockCameraConfigurator configurator = MockCameraConfigurator();
-
-        final CameraController controller1 =
-            CameraController.customConfigurator(
-          description: description,
-          configurator: configurator,
-        );
-
-        controller1.initialize();
-
-        final CameraController controller2 =
-            CameraController.customConfigurator(
-          description: description,
-          configurator: configurator,
-        );
-
-        controller2.initialize();
-
-        expect(
-          () => controller1.start(),
-          throwsA(isInstanceOf<AssertionError>()),
-        );
-
-        expect(
-          () => controller1.stop(),
-          throwsA(isInstanceOf<AssertionError>()),
-        );
-
-        expect(controller1.isDisposed, isTrue);
-      });
-    });
-
-    group('$NativeTexture', () {
-      test('allocate', () async {
-        final NativeTexture texture = await NativeTexture.allocate();
-
-        expect(texture.textureId, 15);
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$NativeTexture#allocate',
-            arguments: <String, dynamic>{'textureHandle': 0},
-          )
-        ]);
-      });
-    });
-  });
-}
-
-class MockCameraDescription extends CameraDescription {
-  @override
-  LensDirection get direction => LensDirection.unknown;
-
-  @override
-  String get name => 'none';
-}
-
-class MockCameraConfigurator extends CameraConfigurator {
-  @override
-  Future<int> addPreviewTexture() => Future<int>.value(7);
-
-  @override
-  Future<void> dispose() => Future<void>.value();
-
-  @override
-  Future<void> initialize() => Future<void>.value();
-
-  @override
-  int get previewTextureId => 7;
-
-  @override
-  Future<void> start() => Future<void>.value();
-
-  @override
-  Future<void> stop() => Future<void>.value();
-}
diff --git a/packages/camera/test/support_android/support_android_test.dart b/packages/camera/test/support_android/support_android_test.dart
deleted file mode 100644
index 399acd3..0000000
--- a/packages/camera/test/support_android/support_android_test.dart
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:camera/new/src/support_android/camera_info.dart';
-import 'package:camera/new/src/support_android/camera.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:camera/new/src/camera_testing.dart';
-
-void main() {
-  TestWidgetsFlutterBinding.ensureInitialized();
-
-  group('Support Android Camera', () {
-    group('$Camera', () {
-      final List<MethodCall> log = <MethodCall>[];
-      setUpAll(() {
-        CameraTesting.channel
-            .setMockMethodCallHandler((MethodCall methodCall) async {
-          log.add(methodCall);
-          switch (methodCall.method) {
-            case 'Camera#getNumberOfCameras':
-              return 3;
-            case 'Camera#open':
-              return null;
-            case 'Camera#getCameraInfo':
-              return <dynamic, dynamic>{
-                'id': 3,
-                'orientation': 90,
-                'facing': Facing.front.toString(),
-              };
-            case 'Camera#startPreview':
-              return null;
-            case 'Camera#stopPreview':
-              return null;
-            case 'Camera#release':
-              return null;
-          }
-
-          throw ArgumentError.value(
-            methodCall.method,
-            'methodCall.method',
-            'No method found for',
-          );
-        });
-      });
-
-      setUp(() {
-        log.clear();
-        CameraTesting.nextHandle = 0;
-      });
-
-      test('getNumberOfCameras', () async {
-        final int result = await Camera.getNumberOfCameras();
-
-        expect(result, 3);
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#getNumberOfCameras',
-            arguments: null,
-          )
-        ]);
-      });
-
-      test('open', () {
-        Camera.open(14);
-
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#open',
-            arguments: <String, dynamic>{
-              'cameraId': 14,
-              'cameraHandle': 0,
-            },
-          )
-        ]);
-      });
-
-      test('getCameraInfo', () async {
-        final CameraInfo info = await Camera.getCameraInfo(14);
-
-        expect(info.id, 3);
-        expect(info.orientation, 90);
-        expect(info.facing, Facing.front);
-
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#getCameraInfo',
-            arguments: <String, dynamic>{'cameraId': 14},
-          )
-        ]);
-      });
-
-      test('startPreview', () {
-        final Camera camera = Camera.open(0);
-
-        log.clear();
-        camera.startPreview();
-
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#startPreview',
-            arguments: <String, dynamic>{
-              'handle': 0,
-            },
-          )
-        ]);
-      });
-
-      test('stopPreview', () {
-        final Camera camera = Camera.open(0);
-
-        log.clear();
-        camera.stopPreview();
-
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#stopPreview',
-            arguments: <String, dynamic>{
-              'handle': 0,
-            },
-          )
-        ]);
-      });
-
-      test('release', () {
-        final Camera camera = Camera.open(0);
-
-        log.clear();
-        camera.release();
-
-        expect(log, <Matcher>[
-          isMethodCall(
-            '$Camera#release',
-            arguments: <String, dynamic>{
-              'handle': 0,
-            },
-          )
-        ]);
-      });
-    });
-  });
-}
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index 5135fdd..a2e0c1b 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,22 @@
+## 1.0.5
+
+Overhaul lifecycle management in GoogleMapsPlugin.
+
+GoogleMapController is now uniformly driven by implementing `DefaultLifecycleObserver`. That observer is registered to a lifecycle from one of three sources:
+
+1. For v2 plugin registration, `GoogleMapsPlugin` obtains the lifecycle via `ActivityAware` methods.
+2. For v1 plugin registration, if the activity implements `LifecycleOwner`, it's lifecycle is used directly.
+3. For v1 plugin registration, if the activity does not implement `LifecycleOwner`, a proxy lifecycle is created and driven via `ActivityLifecycleCallbacks`.
+
+## 1.0.4
+
+* Cleanup of Android code:
+* A few minor formatting changes and additions of `@Nullable` annotations.
+* Removed pass-through of `activityHashCode` to `GoogleMapController`.
+* Replaced custom lifecycle state ints with `androidx.lifecycle.Lifecycle.State` enum.
+* Fixed a bug where the Lifecycle object was being leaked `onDetachFromActivity`, by nulling out the field.
+* Moved GoogleMapListener to its own file. Declaring multiple top level classes in the same file is discouraged.
+
 ## 1.0.3
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
index f12e3f2..a1d7da0 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
+++ b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
@@ -8,7 +8,7 @@
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.3.2'
+        classpath 'com.android.tools.build:gradle:3.5.0'
     }
 }
 
@@ -33,6 +33,7 @@
     }
 
     dependencies {
+        implementation "androidx.annotation:annotation:1.1.0"
         implementation 'com.google.android.gms:play-services-maps:17.0.0'
         androidTestImplementation 'androidx.test:runner:1.2.0'
         androidTestImplementation 'androidx.test:rules:1.2.0'
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
index 97af63c..93a3c3e 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
@@ -4,16 +4,12 @@
 
 package io.flutter.plugins.googlemaps;
 
-import android.app.Application;
 import android.content.Context;
 import android.graphics.Rect;
-import androidx.lifecycle.Lifecycle;
 import com.google.android.gms.maps.GoogleMapOptions;
 import com.google.android.gms.maps.model.CameraPosition;
 import com.google.android.gms.maps.model.LatLngBounds;
 import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.PluginRegistry;
-import java.util.concurrent.atomic.AtomicInteger;
 
 class GoogleMapBuilder implements GoogleMapOptionsSink {
   private final GoogleMapOptions options = new GoogleMapOptions();
@@ -32,23 +28,10 @@
   GoogleMapController build(
       int id,
       Context context,
-      AtomicInteger state,
       BinaryMessenger binaryMessenger,
-      Application application,
-      Lifecycle lifecycle,
-      PluginRegistry.Registrar registrar,
-      int activityHashCode) {
+      LifecycleProvider lifecycleProvider) {
     final GoogleMapController controller =
-        new GoogleMapController(
-            id,
-            context,
-            state,
-            binaryMessenger,
-            application,
-            lifecycle,
-            registrar,
-            activityHashCode,
-            options);
+        new GoogleMapController(id, context, binaryMessenger, lifecycleProvider, options);
     controller.init();
     controller.setMyLocationEnabled(myLocationEnabled);
     controller.setMyLocationButtonEnabled(myLocationButtonEnabled);
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
index 58e8bb0..33cacff 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
@@ -4,17 +4,8 @@
 
 package io.flutter.plugins.googlemaps;
 
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.CREATED;
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.DESTROYED;
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.PAUSED;
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.RESUMED;
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.STARTED;
-import static io.flutter.plugins.googlemaps.GoogleMapsPlugin.STOPPED;
-
 import android.Manifest;
 import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Application;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -45,7 +36,6 @@
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.PluginRegistry;
 import io.flutter.plugin.platform.PlatformView;
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
@@ -53,12 +43,10 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /** Controller of a single GoogleMaps MapView instance. */
 final class GoogleMapController
-    implements Application.ActivityLifecycleCallbacks,
-        DefaultLifecycleObserver,
+    implements DefaultLifecycleObserver,
         ActivityPluginBinding.OnSaveInstanceStateListener,
         GoogleMapOptionsSink,
         MethodChannel.MethodCallHandler,
@@ -68,7 +56,6 @@
 
   private static final String TAG = "GoogleMapController";
   private final int id;
-  private final AtomicInteger activityState;
   private final MethodChannel methodChannel;
   private final GoogleMapOptions options;
   @Nullable private MapView mapView;
@@ -83,13 +70,8 @@
   private boolean disposed = false;
   private final float density;
   private MethodChannel.Result mapReadyResult;
-  private final int
-      activityHashCode; // Do not use directly, use getActivityHashCode() instead to get correct hashCode for both v1 and v2 embedding.
-  private final Lifecycle lifecycle;
   private final Context context;
-  private final Application
-      mApplication; // Do not use direclty, use getApplication() instead to get correct application object for both v1 and v2 embedding.
-  private final PluginRegistry.Registrar registrar; // For v1 embedding only.
+  private final LifecycleProvider lifecycleProvider;
   private final MarkersController markersController;
   private final PolygonsController polygonsController;
   private final PolylinesController polylinesController;
@@ -102,25 +84,17 @@
   GoogleMapController(
       int id,
       Context context,
-      AtomicInteger activityState,
       BinaryMessenger binaryMessenger,
-      Application application,
-      Lifecycle lifecycle,
-      PluginRegistry.Registrar registrar,
-      int registrarActivityHashCode,
+      LifecycleProvider lifecycleProvider,
       GoogleMapOptions options) {
     this.id = id;
     this.context = context;
-    this.activityState = activityState;
     this.options = options;
     this.mapView = new MapView(context, options);
     this.density = context.getResources().getDisplayMetrics().density;
     methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.io/google_maps_" + id);
     methodChannel.setMethodCallHandler(this);
-    mApplication = application;
-    this.lifecycle = lifecycle;
-    this.registrar = registrar;
-    this.activityHashCode = registrarActivityHashCode;
+    this.lifecycleProvider = lifecycleProvider;
     this.markersController = new MarkersController(methodChannel);
     this.polygonsController = new PolygonsController(methodChannel, density);
     this.polylinesController = new PolylinesController(methodChannel, density);
@@ -133,44 +107,7 @@
   }
 
   void init() {
-    switch (activityState.get()) {
-      case STOPPED:
-        mapView.onCreate(null);
-        mapView.onStart();
-        mapView.onResume();
-        mapView.onPause();
-        mapView.onStop();
-        break;
-      case PAUSED:
-        mapView.onCreate(null);
-        mapView.onStart();
-        mapView.onResume();
-        mapView.onPause();
-        break;
-      case RESUMED:
-        mapView.onCreate(null);
-        mapView.onStart();
-        mapView.onResume();
-        break;
-      case STARTED:
-        mapView.onCreate(null);
-        mapView.onStart();
-        break;
-      case CREATED:
-        mapView.onCreate(null);
-        break;
-      case DESTROYED:
-        // Nothing to do, the activity has been completely destroyed.
-        break;
-      default:
-        throw new IllegalArgumentException(
-            "Cannot interpret " + activityState.get() + " as an activity state");
-    }
-    if (lifecycle != null) {
-      lifecycle.addObserver(this);
-    } else {
-      getApplication().registerActivityLifecycleCallbacks(this);
-    }
+    lifecycleProvider.getLifecycle().addObserver(this);
     mapView.getMapAsync(this);
   }
 
@@ -535,7 +472,10 @@
     methodChannel.setMethodCallHandler(null);
     setGoogleMapListener(null);
     destroyMapViewIfNecessary();
-    getApplication().unregisterActivityLifecycleCallbacks(this);
+    Lifecycle lifecycle = lifecycleProvider.getLifecycle();
+    if (lifecycle != null) {
+      lifecycle.removeObserver(this);
+    }
   }
 
   private void setGoogleMapListener(@Nullable GoogleMapListener listener) {
@@ -556,73 +496,16 @@
   // does. This will override it when available even with the annotation commented out.
   public void onInputConnectionLocked() {
     // TODO(mklim): Remove this empty override once https://github.com/flutter/flutter/issues/40126 is fixed in stable.
-  };
+  }
 
   // @Override
   // The minimum supported version of Flutter doesn't have this method on the PlatformView interface, but the maximum
   // does. This will override it when available even with the annotation commented out.
   public void onInputConnectionUnlocked() {
     // TODO(mklim): Remove this empty override once https://github.com/flutter/flutter/issues/40126 is fixed in stable.
-  };
-
-  // Application.ActivityLifecycleCallbacks methods
-  @Override
-  public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onCreate(savedInstanceState);
   }
 
-  @Override
-  public void onActivityStarted(Activity activity) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onStart();
-  }
-
-  @Override
-  public void onActivityResumed(Activity activity) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onResume();
-  }
-
-  @Override
-  public void onActivityPaused(Activity activity) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onPause();
-  }
-
-  @Override
-  public void onActivityStopped(Activity activity) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onStop();
-  }
-
-  @Override
-  public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    mapView.onSaveInstanceState(outState);
-  }
-
-  @Override
-  public void onActivityDestroyed(Activity activity) {
-    if (disposed || activity.hashCode() != getActivityHashCode()) {
-      return;
-    }
-    destroyMapViewIfNecessary();
-  }
-
-  // DefaultLifecycleObserver and OnSaveInstanceStateListener
+  // DefaultLifecycleObserver
 
   @Override
   public void onCreate(@NonNull LifecycleOwner owner) {
@@ -666,6 +549,7 @@
 
   @Override
   public void onDestroy(@NonNull LifecycleOwner owner) {
+    owner.getLifecycle().removeObserver(this);
     if (disposed) {
       return;
     }
@@ -876,22 +760,6 @@
         permission, android.os.Process.myPid(), android.os.Process.myUid());
   }
 
-  private int getActivityHashCode() {
-    if (registrar != null && registrar.activity() != null) {
-      return registrar.activity().hashCode();
-    } else {
-      return activityHashCode;
-    }
-  }
-
-  private Application getApplication() {
-    if (registrar != null && registrar.activity() != null) {
-      return registrar.activity().getApplication();
-    } else {
-      return mApplication;
-    }
-  }
-
   private void destroyMapViewIfNecessary() {
     if (mapView == null) {
       return;
@@ -916,16 +784,3 @@
     this.buildingsEnabled = buildingsEnabled;
   }
 }
-
-interface GoogleMapListener
-    extends GoogleMap.OnCameraIdleListener,
-        GoogleMap.OnCameraMoveListener,
-        GoogleMap.OnCameraMoveStartedListener,
-        GoogleMap.OnInfoWindowClickListener,
-        GoogleMap.OnMarkerClickListener,
-        GoogleMap.OnPolygonClickListener,
-        GoogleMap.OnPolylineClickListener,
-        GoogleMap.OnCircleClickListener,
-        GoogleMap.OnMapClickListener,
-        GoogleMap.OnMapLongClickListener,
-        GoogleMap.OnMarkerDragListener {}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
index b6bc7e5..e56adbb 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
@@ -4,41 +4,23 @@
 
 package io.flutter.plugins.googlemaps;
 
-import android.app.Application;
 import android.content.Context;
-import androidx.lifecycle.Lifecycle;
 import com.google.android.gms.maps.model.CameraPosition;
 import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.PluginRegistry;
 import io.flutter.plugin.common.StandardMessageCodec;
 import io.flutter.plugin.platform.PlatformView;
 import io.flutter.plugin.platform.PlatformViewFactory;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class GoogleMapFactory extends PlatformViewFactory {
 
-  private final AtomicInteger mActivityState;
   private final BinaryMessenger binaryMessenger;
-  private final Application application;
-  private final int activityHashCode;
-  private final Lifecycle lifecycle;
-  private final PluginRegistry.Registrar registrar; // V1 embedding only.
+  private final LifecycleProvider lifecycleProvider;
 
-  GoogleMapFactory(
-      AtomicInteger state,
-      BinaryMessenger binaryMessenger,
-      Application application,
-      Lifecycle lifecycle,
-      PluginRegistry.Registrar registrar,
-      int activityHashCode) {
+  GoogleMapFactory(BinaryMessenger binaryMessenger, LifecycleProvider lifecycleProvider) {
     super(StandardMessageCodec.INSTANCE);
-    mActivityState = state;
     this.binaryMessenger = binaryMessenger;
-    this.application = application;
-    this.activityHashCode = activityHashCode;
-    this.lifecycle = lifecycle;
-    this.registrar = registrar;
+    this.lifecycleProvider = lifecycleProvider;
   }
 
   @SuppressWarnings("unchecked")
@@ -64,14 +46,6 @@
     if (params.containsKey("circlesToAdd")) {
       builder.setInitialCircles(params.get("circlesToAdd"));
     }
-    return builder.build(
-        id,
-        context,
-        mActivityState,
-        binaryMessenger,
-        application,
-        lifecycle,
-        registrar,
-        activityHashCode);
+    return builder.build(id, context, binaryMessenger, lifecycleProvider);
   }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java
new file mode 100644
index 0000000..518d45c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import com.google.android.gms.maps.GoogleMap;
+
+interface GoogleMapListener
+    extends GoogleMap.OnCameraIdleListener,
+        GoogleMap.OnCameraMoveListener,
+        GoogleMap.OnCameraMoveStartedListener,
+        GoogleMap.OnInfoWindowClickListener,
+        GoogleMap.OnMarkerClickListener,
+        GoogleMap.OnPolygonClickListener,
+        GoogleMap.OnPolylineClickListener,
+        GoogleMap.OnCircleClickListener,
+        GoogleMap.OnMapClickListener,
+        GoogleMap.OnMapLongClickListener,
+        GoogleMap.OnMarkerDragListener {}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
index f434a05..69ba8cb 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
@@ -5,17 +5,18 @@
 package io.flutter.plugins.googlemaps;
 
 import android.app.Activity;
-import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
 import android.os.Bundle;
 import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.annotation.Nullable;
 import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.embedding.engine.plugins.activity.ActivityAware;
 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
 import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Plugin for controlling a set of GoogleMap views to be shown as overlays on top of the Flutter
@@ -23,38 +24,41 @@
  * the map. A Texture drawn using GoogleMap bitmap snapshots can then be shown instead of the
  * overlay.
  */
-public class GoogleMapsPlugin
-    implements Application.ActivityLifecycleCallbacks,
-        FlutterPlugin,
-        ActivityAware,
-        DefaultLifecycleObserver {
-  static final int CREATED = 1;
-  static final int STARTED = 2;
-  static final int RESUMED = 3;
-  static final int PAUSED = 4;
-  static final int STOPPED = 5;
-  static final int DESTROYED = 6;
-  private final AtomicInteger state = new AtomicInteger(0);
-  private int registrarActivityHashCode;
-  private FlutterPluginBinding pluginBinding;
-  private Lifecycle lifecycle;
+public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware {
+
+  @Nullable private Lifecycle lifecycle;
 
   private static final String VIEW_TYPE = "plugins.flutter.io/google_maps";
 
   @SuppressWarnings("deprecation")
-  public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
-    if (registrar.activity() == null) {
+  public static void registerWith(
+      final io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
+    final Activity activity = registrar.activity();
+    if (activity == null) {
       // When a background flutter view tries to register the plugin, the registrar has no activity.
       // We stop the registration process as this plugin is foreground only.
       return;
     }
-    final GoogleMapsPlugin plugin = new GoogleMapsPlugin(registrar.activity());
-    registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin);
-    registrar
-        .platformViewRegistry()
-        .registerViewFactory(
-            VIEW_TYPE,
-            new GoogleMapFactory(plugin.state, registrar.messenger(), null, null, registrar, -1));
+    if (activity instanceof LifecycleOwner) {
+      registrar
+          .platformViewRegistry()
+          .registerViewFactory(
+              VIEW_TYPE,
+              new GoogleMapFactory(
+                  registrar.messenger(),
+                  new LifecycleProvider() {
+                    @Override
+                    public Lifecycle getLifecycle() {
+                      return ((LifecycleOwner) activity).getLifecycle();
+                    }
+                  }));
+    } else {
+      registrar
+          .platformViewRegistry()
+          .registerViewFactory(
+              VIEW_TYPE,
+              new GoogleMapFactory(registrar.messenger(), new ProxyLifecycleProvider(activity)));
+    }
   }
 
   public GoogleMapsPlugin() {}
@@ -63,136 +67,119 @@
 
   @Override
   public void onAttachedToEngine(FlutterPluginBinding binding) {
-    pluginBinding = binding;
+    binding
+        .getPlatformViewRegistry()
+        .registerViewFactory(
+            VIEW_TYPE,
+            new GoogleMapFactory(
+                binding.getBinaryMessenger(),
+                new LifecycleProvider() {
+                  @Nullable
+                  @Override
+                  public Lifecycle getLifecycle() {
+                    return lifecycle;
+                  }
+                }));
   }
 
   @Override
-  public void onDetachedFromEngine(FlutterPluginBinding binding) {
-    pluginBinding = null;
-  }
+  public void onDetachedFromEngine(FlutterPluginBinding binding) {}
 
   // ActivityAware
 
   @Override
   public void onAttachedToActivity(ActivityPluginBinding binding) {
     lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
-    lifecycle.addObserver(this);
-    pluginBinding
-        .getPlatformViewRegistry()
-        .registerViewFactory(
-            VIEW_TYPE,
-            new GoogleMapFactory(
-                state,
-                pluginBinding.getBinaryMessenger(),
-                binding.getActivity().getApplication(),
-                lifecycle,
-                null,
-                binding.getActivity().hashCode()));
   }
 
   @Override
   public void onDetachedFromActivity() {
-    lifecycle.removeObserver(this);
-  }
-
-  @Override
-  public void onDetachedFromActivityForConfigChanges() {
-    this.onDetachedFromActivity();
+    lifecycle = null;
   }
 
   @Override
   public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
-    lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
-    lifecycle.addObserver(this);
-  }
-
-  // DefaultLifecycleObserver methods
-
-  @Override
-  public void onCreate(@NonNull LifecycleOwner owner) {
-    state.set(CREATED);
+    onAttachedToActivity(binding);
   }
 
   @Override
-  public void onStart(@NonNull LifecycleOwner owner) {
-    state.set(STARTED);
+  public void onDetachedFromActivityForConfigChanges() {
+    onDetachedFromActivity();
   }
 
-  @Override
-  public void onResume(@NonNull LifecycleOwner owner) {
-    state.set(RESUMED);
-  }
+  /**
+   * This class provides a {@link LifecycleOwner} for the activity driven by {@link
+   * ActivityLifecycleCallbacks}.
+   *
+   * <p>This is used in the case where a direct Lifecycle/Owner is not available.
+   */
+  private static final class ProxyLifecycleProvider
+      implements ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider {
 
-  @Override
-  public void onPause(@NonNull LifecycleOwner owner) {
-    state.set(PAUSED);
-  }
+    private final LifecycleRegistry lifecycle = new LifecycleRegistry(this);
+    private final int registrarActivityHashCode;
 
-  @Override
-  public void onStop(@NonNull LifecycleOwner owner) {
-    state.set(STOPPED);
-  }
-
-  @Override
-  public void onDestroy(@NonNull LifecycleOwner owner) {
-    state.set(DESTROYED);
-  }
-
-  // Application.ActivityLifecycleCallbacks methods
-
-  @Override
-  public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    private ProxyLifecycleProvider(Activity activity) {
+      this.registrarActivityHashCode = activity.hashCode();
+      activity.getApplication().registerActivityLifecycleCallbacks(this);
     }
-    state.set(CREATED);
-  }
 
-  @Override
-  public void onActivityStarted(Activity activity) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    @Override
+    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      lifecycle.handleLifecycleEvent(Event.ON_CREATE);
     }
-    state.set(STARTED);
-  }
 
-  @Override
-  public void onActivityResumed(Activity activity) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    @Override
+    public void onActivityStarted(Activity activity) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      lifecycle.handleLifecycleEvent(Event.ON_START);
     }
-    state.set(RESUMED);
-  }
 
-  @Override
-  public void onActivityPaused(Activity activity) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    @Override
+    public void onActivityResumed(Activity activity) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      lifecycle.handleLifecycleEvent(Event.ON_RESUME);
     }
-    state.set(PAUSED);
-  }
 
-  @Override
-  public void onActivityStopped(Activity activity) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    @Override
+    public void onActivityPaused(Activity activity) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      lifecycle.handleLifecycleEvent(Event.ON_PAUSE);
     }
-    state.set(STOPPED);
-  }
 
-  @Override
-  public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
-
-  @Override
-  public void onActivityDestroyed(Activity activity) {
-    if (activity.hashCode() != registrarActivityHashCode) {
-      return;
+    @Override
+    public void onActivityStopped(Activity activity) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      lifecycle.handleLifecycleEvent(Event.ON_STOP);
     }
-    activity.getApplication().unregisterActivityLifecycleCallbacks(this);
-    state.set(DESTROYED);
-  }
 
-  private GoogleMapsPlugin(Activity activity) {
-    this.registrarActivityHashCode = activity.hashCode();
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+      if (activity.hashCode() != registrarActivityHashCode) {
+        return;
+      }
+      activity.getApplication().unregisterActivityLifecycleCallbacks(this);
+      lifecycle.handleLifecycleEvent(Event.ON_DESTROY);
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+      return lifecycle;
+    }
   }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java
new file mode 100644
index 0000000..154de20
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java
@@ -0,0 +1,14 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+
+interface LifecycleProvider {
+
+  @Nullable
+  Lifecycle getLifecycle();
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
index 04b5f6f..94b9347 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
+++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
@@ -3,39 +3,36 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.app.Application;
 import android.content.Context;
-import androidx.lifecycle.LifecycleOwner;
+import androidx.activity.ComponentActivity;
 import androidx.test.core.app.ApplicationProvider;
 import com.google.android.gms.maps.GoogleMap;
 import io.flutter.plugin.common.BinaryMessenger;
-import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 
 @RunWith(RobolectricTestRunner.class)
 public class GoogleMapControllerTest {
 
   private Context context;
-  private Application application;
+  private ComponentActivity activity;
   private GoogleMapController googleMapController;
 
   @Mock BinaryMessenger mockMessenger;
   @Mock GoogleMap mockGoogleMap;
-  @Mock LifecycleOwner lifecycleOwner;
 
   @Before
   public void before() {
     MockitoAnnotations.initMocks(this);
     context = ApplicationProvider.getApplicationContext();
-    application = ApplicationProvider.getApplicationContext();
+    activity = Robolectric.setupActivity(ComponentActivity.class);
     googleMapController =
-        new GoogleMapController(
-            0, context, new AtomicInteger(1), mockMessenger, application, null, null, 0, null);
+        new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null);
     googleMapController.init();
   }
 
@@ -51,7 +48,7 @@
   public void OnDestroyReleaseTheMap() throws InterruptedException {
     googleMapController.onMapReady(mockGoogleMap);
     assertTrue(googleMapController != null);
-    googleMapController.onDestroy(lifecycleOwner);
+    googleMapController.onDestroy(activity);
     assertNull(googleMapController.getView());
   }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index 5a9aae8..dadedb8 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -1,7 +1,7 @@
 name: google_maps_flutter
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter
-version: 1.0.3
+version: 1.0.5
 
 dependencies:
   flutter:
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
index e4918ab..9307aff 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.0+6
+
+* Ensure a single `InfoWindow` is shown at a time. [Issue](https://github.com/flutter/flutter/issues/67380).
+
 ## 0.1.0+5
 
 * Update `package:google_maps` to `^3.4.5`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
index a0428ad..fea6164 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
@@ -105,6 +105,7 @@
   ///
   /// See also [hideMarkerInfoWindow] and [isInfoWindowShown].
   void showMarkerInfoWindow(MarkerId markerId) {
+    _hideAllMarkerInfoWindow();
     MarkerController markerController = _markerIdToController[markerId];
     markerController?.showInfoWindow();
   }
@@ -145,4 +146,11 @@
       markerId,
     ));
   }
+
+  void _hideAllMarkerInfoWindow() {
+    _markerIdToController.values
+        .where((controller) =>
+            controller == null ? false : controller.infoWindowShown)
+        .forEach((controller) => controller.hideInfoWindow());
+  }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
index a44d5c1..9f88ff1 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: google_maps_flutter_web
 description: Web platform implementation of google_maps_flutter
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
-version: 0.1.0+5
+version: 0.1.0+6
 
 flutter:
   plugin:
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/markers_integration.dart b/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/markers_integration.dart
index a813ff8..cad8cd8 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/markers_integration.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/markers_integration.dart
@@ -100,6 +100,35 @@
       expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
     });
 
+    // https://github.com/flutter/flutter/issues/67380
+    testWidgets('only single InfoWindow is visible',
+        (WidgetTester tester) async {
+      final markers = {
+        Marker(
+          markerId: MarkerId('1'),
+          infoWindow: InfoWindow(title: "Title", snippet: "Snippet"),
+        ),
+        Marker(
+          markerId: MarkerId('2'),
+          infoWindow: InfoWindow(title: "Title", snippet: "Snippet"),
+        ),
+      };
+      controller.addMarkers(markers);
+
+      expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
+      expect(controller.markers[MarkerId('2')].infoWindowShown, isFalse);
+
+      controller.showMarkerInfoWindow(MarkerId('1'));
+
+      expect(controller.markers[MarkerId('1')].infoWindowShown, isTrue);
+      expect(controller.markers[MarkerId('2')].infoWindowShown, isFalse);
+
+      controller.showMarkerInfoWindow(MarkerId('2'));
+
+      expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
+      expect(controller.markers[MarkerId('2')].infoWindowShown, isTrue);
+    });
+
     // https://github.com/flutter/flutter/issues/64938
     testWidgets('markers with icon:null work', (WidgetTester tester) async {
       final markers = {
diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 33ee468..0fb0ce4 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.7+13
+
+* Update documentation of `getImage()` about HEIC images.
+
 ## 0.6.7+12
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart
index ff9aa2c..82c199e 100755
--- a/packages/image_picker/image_picker/lib/image_picker.dart
+++ b/packages/image_picker/image_picker/lib/image_picker.dart
@@ -76,6 +76,9 @@
   /// The `source` argument controls where the image comes from. This can
   /// be either [ImageSource.camera] or [ImageSource.gallery].
   ///
+  /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
+  /// in addition to a size modification, of which the usage is explained below.
+  ///
   /// If specified, the image will be at most `maxWidth` wide and
   /// `maxHeight` tall. Otherwise the image will be returned at it's
   /// original width and height.
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index baecbd2..711d2db 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for selecting images from the Android and iOS image
   library, and taking new pictures with the camera.
 homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker
-version: 0.6.7+12
+version: 0.6.7+13
 
 flutter:
   plugin:
diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
index e82e620..1ed4502 100644
--- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
+++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.1.3
+
+* Update documentation of `pickImage()` regarding HEIC images.
+
 ## 1.1.2
 
 * Update documentation of `pickImage()` regarding compression support for specific image types.
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
index f33c80b..cbd6041 100644
--- a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
@@ -120,6 +120,9 @@
   /// The `source` argument controls where the image comes from. This can
   /// be either [ImageSource.camera] or [ImageSource.gallery].
   ///
+  /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
+  /// in addition to a size modification, of which the usage is explained below.
+  ///
   /// If specified, the image will be at most `maxWidth` wide and
   /// `maxHeight` tall. Otherwise the image will be returned at it's
   /// original width and height.
diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
index 1630048..39a6528 100644
--- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml
+++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 1.1.2
+version: 1.1.3
 
 dependencies:
   flutter:
diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md
index 2193d01..6207da9 100644
--- a/packages/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.4+16
+
+* Add Dartdocs to all public APIs.
+
 ## 0.3.4+15
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/in_app_purchase/analysis_options.yaml b/packages/in_app_purchase/analysis_options.yaml
deleted file mode 100644
index 8e4af76..0000000
--- a/packages/in_app_purchase/analysis_options.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# This is a temporary file to allow us to land a new set of linter rules in a
-# series of manageable patches instead of one gigantic PR. It disables some of
-# the new lints that are already failing on this plugin, for this plugin. It
-# should be deleted and the failing lints addressed as soon as possible.
-
-include: ../../analysis_options.yaml
-
-analyzer:
-  errors:
-    public_member_api_docs: ignore
diff --git a/packages/in_app_purchase/example/lib/consumable_store.dart b/packages/in_app_purchase/example/lib/consumable_store.dart
index 12121a9..3c2bec9 100644
--- a/packages/in_app_purchase/example/lib/consumable_store.dart
+++ b/packages/in_app_purchase/example/lib/consumable_store.dart
@@ -5,22 +5,31 @@
 import 'dart:async';
 import 'package:shared_preferences/shared_preferences.dart';
 
-// This is just a development prototype for locally storing consumables. Do not
-// use this.
+/// A store of consumable items.
+///
+/// This is a development prototype tha stores consumables in the shared
+/// preferences. Do not use this in real world apps.
 class ConsumableStore {
   static const String _kPrefKey = 'consumables';
   static Future<void> _writes = Future.value();
 
+  /// Adds a consumable with ID `id` to the store.
+  ///
+  /// The consumable is only added after the returned Future is complete.
   static Future<void> save(String id) {
     _writes = _writes.then((void _) => _doSave(id));
     return _writes;
   }
 
+  /// Consumes a consumable with ID `id` from the store.
+  ///
+  /// The consumable was only consumed after the returned Future is complete.
   static Future<void> consume(String id) {
     _writes = _writes.then((void _) => _doConsume(id));
     return _writes;
   }
 
+  /// Returns the list of consumables from the store.
   static Future<List<String>> load() async {
     return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ??
         [];
diff --git a/packages/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/example/lib/main.dart
index 352ad3b..b285b6e 100644
--- a/packages/in_app_purchase/example/lib/main.dart
+++ b/packages/in_app_purchase/example/lib/main.dart
@@ -13,10 +13,10 @@
   // [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
   // as part of initializing the app.
   InAppPurchaseConnection.enablePendingPurchases();
-  runApp(MyApp());
+  runApp(_MyApp());
 }
 
-const bool kAutoConsume = true;
+const bool _kAutoConsume = true;
 
 const String _kConsumableId = 'consumable';
 const List<String> _kProductIds = <String>[
@@ -25,12 +25,12 @@
   'subscription'
 ];
 
-class MyApp extends StatefulWidget {
+class _MyApp extends StatefulWidget {
   @override
   _MyAppState createState() => _MyAppState();
 }
 
-class _MyAppState extends State<MyApp> {
+class _MyAppState extends State<_MyApp> {
   final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
   StreamSubscription<List<PurchaseDetails>> _subscription;
   List<String> _notFoundIds = [];
@@ -257,7 +257,7 @@
                       if (productDetails.id == _kConsumableId) {
                         _connection.buyConsumable(
                             purchaseParam: purchaseParam,
-                            autoConsume: kAutoConsume || Platform.isIOS);
+                            autoConsume: _kAutoConsume || Platform.isIOS);
                       } else {
                         _connection.buyNonConsumable(
                             purchaseParam: purchaseParam);
@@ -374,7 +374,7 @@
           }
         }
         if (Platform.isAndroid) {
-          if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) {
+          if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) {
             await InAppPurchaseConnection.instance
                 .consumePurchase(purchaseDetails);
           }
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
index ebbd90a..0c8b348 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
@@ -12,6 +12,7 @@
 import 'sku_details_wrapper.dart';
 import 'enum_converters.dart';
 
+/// Method identifier for the OnPurchaseUpdated method channel method.
 @visibleForTesting
 const String kOnPurchasesUpdated =
     'PurchasesUpdatedListener#onPurchasesUpdated(int, List<Purchase>)';
@@ -51,6 +52,9 @@
 class BillingClient {
   bool _enablePendingPurchases = false;
 
+  /// Creates a billing client.
+  ///
+  /// The `onPurchasesUpdated` parameter must not be null.
   BillingClient(PurchasesUpdatedListener onPurchasesUpdated) {
     assert(onPurchasesUpdated != null);
     channel.setMethodCallHandler(callHandler);
@@ -273,6 +277,7 @@
         }));
   }
 
+  /// The method call handler for [channel].
   @visibleForTesting
   Future<void> callHandler(MethodCall call) async {
     switch (call.method) {
@@ -309,36 +314,47 @@
   // WARNING: Changes to this class need to be reflected in our generated code.
   // Run `flutter packages pub run build_runner watch` to rebuild and watch for
   // further changes.
+  /// The requested feature is not supported by Play Store on the current device.
   @JsonValue(-2)
   featureNotSupported,
 
+  /// The play Store service is not connected now - potentially transient state.
   @JsonValue(-1)
   serviceDisconnected,
 
+  /// Success.
   @JsonValue(0)
   ok,
 
+  /// The user pressed back or canceled a dialog.
   @JsonValue(1)
   userCanceled,
 
+  /// The network connection is down.
   @JsonValue(2)
   serviceUnavailable,
 
+  /// The billing API version is not supported for the type requested.
   @JsonValue(3)
   billingUnavailable,
 
+  /// The requested product is not available for purchase.
   @JsonValue(4)
   itemUnavailable,
 
+  /// Invalid arguments provided to the API.
   @JsonValue(5)
   developerError,
 
+  /// Fatal error during the API action.
   @JsonValue(6)
   error,
 
+  /// Failure to purchase since item is already owned.
   @JsonValue(7)
   itemAlreadyOwned,
 
+  /// Failure to consume since item is not owned.
   @JsonValue(8)
   itemNotOwned,
 }
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
index 1e81895..966c891 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
@@ -13,6 +13,7 @@
 /// Use these in `@JsonSerializable()` classes by annotating them with
 /// `@BillingResponseConverter()`.
 class BillingResponseConverter implements JsonConverter<BillingResponse, int> {
+  /// Default const constructor.
   const BillingResponseConverter();
 
   @override
@@ -28,6 +29,7 @@
 /// Use these in `@JsonSerializable()` classes by annotating them with
 /// `@SkuTypeConverter()`.
 class SkuTypeConverter implements JsonConverter<SkuType, String> {
+  /// Default const constructor.
   const SkuTypeConverter();
 
   @override
@@ -52,6 +54,7 @@
 /// `@PurchaseStateConverter()`.
 class PurchaseStateConverter
     implements JsonConverter<PurchaseStateWrapper, int> {
+  /// Default const constructor.
   const PurchaseStateConverter();
 
   @override
@@ -63,6 +66,9 @@
   int toJson(PurchaseStateWrapper object) =>
       _$PurchaseStateWrapperEnumMap[object];
 
+  /// Converts the purchase state stored in `object` to a [PurchaseStatus].
+  ///
+  /// [PurchaseStateWrapper.unspecified_state] is mapped to [PurchaseStatus.error].
   PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) {
     switch (object) {
       case PurchaseStateWrapper.pending:
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
index 0d4b74f..8bdd738 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
@@ -24,6 +24,7 @@
 @JsonSerializable()
 @PurchaseStateConverter()
 class PurchaseWrapper {
+  /// Creates a purchase wrapper with the given purchase details.
   @visibleForTesting
   PurchaseWrapper(
       {@required this.orderId,
@@ -38,6 +39,7 @@
       @required this.isAcknowledged,
       @required this.purchaseState});
 
+  /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details.
   factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map);
 
   @override
@@ -132,6 +134,7 @@
 // For now, we keep them separated classes to be consistent with Android's BillingClient implementation.
 @JsonSerializable()
 class PurchaseHistoryRecordWrapper {
+  /// Creates a [PurchaseHistoryRecordWrapper] with the given record details.
   @visibleForTesting
   PurchaseHistoryRecordWrapper({
     @required this.purchaseTime,
@@ -142,6 +145,7 @@
     @required this.developerPayload,
   });
 
+  /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details.
   factory PurchaseHistoryRecordWrapper.fromJson(Map map) =>
       _$PurchaseHistoryRecordWrapperFromJson(map);
 
@@ -197,11 +201,13 @@
 @JsonSerializable()
 @BillingResponseConverter()
 class PurchasesResultWrapper {
+  /// Creates a [PurchasesResultWrapper] with the given purchase result details.
   PurchasesResultWrapper(
       {@required this.responseCode,
       @required this.billingResult,
       @required this.purchasesList});
 
+  /// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details.
   factory PurchasesResultWrapper.fromJson(Map<String, dynamic> map) =>
       _$PurchasesResultWrapperFromJson(map);
 
@@ -240,9 +246,11 @@
 @JsonSerializable()
 @BillingResponseConverter()
 class PurchasesHistoryResult {
+  /// Creates a [PurchasesHistoryResult] with the provided history.
   PurchasesHistoryResult(
       {@required this.billingResult, @required this.purchaseHistoryRecordList});
 
+  /// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details.
   factory PurchasesHistoryResult.fromJson(Map<String, dynamic> map) =>
       _$PurchasesHistoryResultFromJson(map);
 
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
index 4d6a930..db65e20 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
@@ -19,6 +19,7 @@
 @JsonSerializable()
 @SkuTypeConverter()
 class SkuDetailsWrapper {
+  /// Creates a [SkuDetailsWrapper] with the given purchase details.
   @visibleForTesting
   SkuDetailsWrapper({
     @required this.description,
@@ -47,6 +48,7 @@
   factory SkuDetailsWrapper.fromJson(Map map) =>
       _$SkuDetailsWrapperFromJson(map);
 
+  /// Textual description of the product.
   final String description;
 
   /// Trial period in ISO 8601 format.
@@ -78,6 +80,8 @@
 
   /// Applies to [SkuType.subs], formatted in ISO 8601.
   final String subscriptionPeriod;
+
+  /// The product's title.
   final String title;
 
   /// The [SkuType] of the product.
@@ -143,6 +147,7 @@
 /// Returned by [BillingClient.querySkuDetails].
 @JsonSerializable()
 class SkuDetailsResponseWrapper {
+  /// Creates a [SkuDetailsResponseWrapper] with the given purchase details.
   @visibleForTesting
   SkuDetailsResponseWrapper(
       {@required this.billingResult, this.skuDetailsList});
diff --git a/packages/in_app_purchase/lib/src/channel.dart b/packages/in_app_purchase/lib/src/channel.dart
index b105070..a0b92b5 100644
--- a/packages/in_app_purchase/lib/src/channel.dart
+++ b/packages/in_app_purchase/lib/src/channel.dart
@@ -4,8 +4,13 @@
 
 import 'package:flutter/services.dart';
 
+/// Method channel for the plugin's platform<-->Dart calls (all but the
+/// ios->Dart calls which are carried over the [callbackChannel]).
 const MethodChannel channel =
     MethodChannel('plugins.flutter.io/in_app_purchase');
 
+/// Method channel for the plugin's ios->Dart calls.
+// This is in a separate channel due to historic reasons only.
+// TODO(cyanglaz): Remove this. https://github.com/flutter/flutter/issues/69225
 const MethodChannel callbackChannel =
     MethodChannel('plugins.flutter.io/in_app_purchase_callback');
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
index da6fc74..a244ab1 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
@@ -18,14 +18,25 @@
 /// This translates various `StoreKit` calls and responses into the
 /// generic plugin API.
 class AppStoreConnection implements InAppPurchaseConnection {
+  /// Returns the singleton instance of the [AppStoreConnection] that should be
+  /// used across the app.
   static AppStoreConnection get instance => _getOrCreateInstance();
   static AppStoreConnection _instance;
   static SKPaymentQueueWrapper _skPaymentQueueWrapper;
   static _TransactionObserver _observer;
 
+  /// Creates an [AppStoreConnection] object.
+  ///
+  /// This constructor should only be used for testing, for any other purpose
+  /// get the connection from the [instance] getter.
+  @visibleForTesting
+  AppStoreConnection();
+
   Stream<List<PurchaseDetails>> get purchaseUpdatedStream =>
       _observer.purchaseUpdatedController.stream;
 
+  /// Callback handler for transaction status changes.
+  @visibleForTesting
   static SKTransactionObserverWrapper get observer => _observer;
 
   static AppStoreConnection _getOrCreateInstance() {
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
index 581a7bd..b980bbd 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
@@ -32,6 +32,8 @@
     _purchaseUpdatedController = StreamController.broadcast();
     ;
   }
+
+  /// Returns the singleton instance of the [GooglePlayConnection].
   static GooglePlayConnection get instance => _getOrCreateInstance();
   static GooglePlayConnection _instance;
 
@@ -39,6 +41,9 @@
       _purchaseUpdatedController.stream;
   static StreamController<List<PurchaseDetails>> _purchaseUpdatedController;
 
+  /// The [BillingClient] that's abstracted by [GooglePlayConnection].
+  ///
+  /// This field should not be used out of test code.
   @visibleForTesting
   final BillingClient billingClient;
 
@@ -161,6 +166,10 @@
         'The method <refreshPurchaseVerificationData> only works on iOS.');
   }
 
+  /// Resets the connection instance.
+  ///
+  /// The next call to [instance] will create a new instance. Should only be
+  /// used in tests.
   @visibleForTesting
   static void reset() => _instance = null;
 
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
index ba3932f..f07ff96 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
@@ -269,7 +269,13 @@
 }
 
 /// Which platform the request is on.
-enum IAPSource { GooglePlay, AppStore }
+enum IAPSource {
+  /// Google's Play Store.
+  GooglePlay,
+
+  /// Apple's App Store.
+  AppStore
+}
 
 /// Captures an error from the underlying purchase platform.
 ///
@@ -279,6 +285,7 @@
 /// * [ProductDetailsResponse] for error when querying product details.
 /// * [PurchaseDetails] for error happened in purchase.
 class IAPError {
+  /// Creates a new IAP error object with the given error details.
   IAPError(
       {@required this.source,
       @required this.code,
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/product_details.dart b/packages/in_app_purchase/lib/src/in_app_purchase/product_details.dart
index 9808bba..bb9e268 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/product_details.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/product_details.dart
@@ -12,6 +12,7 @@
 /// This class unifies the BillingClient's [SkuDetailsWrapper] and StoreKit's [SKProductWrapper]. You can use the common attributes in
 /// This class for simple operations. If you would like to see the detailed representation of the product, instead,  use [skuDetails] on Android and [skProduct] on iOS.
 class ProductDetails {
+  /// Creates a new product details object with the provided details.
   ProductDetails(
       {@required this.id,
       @required this.title,
@@ -66,6 +67,7 @@
 ///
 /// A list of [ProductDetails] can be obtained from the this response.
 class ProductDetailsResponse {
+  /// Creates a new [ProductDetailsResponse] with the provided response details.
   ProductDetailsResponse(
       {@required this.productDetails, @required this.notFoundIDs, this.error});
 
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
index 2321bd0..708b42c 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
@@ -10,9 +10,15 @@
 import './in_app_purchase_connection.dart';
 import './product_details.dart';
 
+/// [IAPError.code] code for failed purchases.
 final String kPurchaseErrorCode = 'purchase_error';
+
+/// [IAPError.code] code used when a query for previouys transaction has failed.
 final String kRestoredPurchaseErrorCode = 'restore_transactions_failed';
+
+/// [IAPError.code] code used when a consuming a purchased item fails.
 final String kConsumptionFailedErrorCode = 'consume_purchase_failed';
+
 final String _kPlatformIOS = 'ios';
 final String _kPlatformAndroid = 'android';
 
@@ -51,12 +57,16 @@
   /// Indicates the source of the purchase.
   final IAPSource source;
 
+  /// Creates a [PurchaseVerificationData] object with the provided information.
   PurchaseVerificationData(
       {@required this.localVerificationData,
       @required this.serverVerificationData,
       @required this.source});
 }
 
+/// Status for a [PurchaseDetails].
+///
+/// This is the type for [PurchaseDetails.status].
 enum PurchaseStatus {
   /// The purchase process is pending.
   ///
@@ -76,6 +86,7 @@
 
 /// The parameter object for generating a purchase.
 class PurchaseParam {
+  /// Creates a new purchase parameter object with the given data.
   PurchaseParam(
       {@required this.productDetails,
       this.applicationUserName,
@@ -170,6 +181,7 @@
   // The value is either '_kPlatformIOS' or '_kPlatformAndroid'.
   String _platform;
 
+  /// Creates a new PurchaseDetails object with the provided data.
   PurchaseDetails({
     @required this.purchaseID,
     @required this.productID,
@@ -233,6 +245,7 @@
 ///
 /// An instance of this class is returned in [InAppPurchaseConnection.queryPastPurchases].
 class QueryPurchaseDetailsResponse {
+  /// Creates a new [QueryPurchaseDetailsResponse] object with the provider information.
   QueryPurchaseDetailsResponse({@required this.pastPurchases, this.error});
 
   /// A list of successfully fetched past purchases.
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart
index 49cfb78..6218870 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart
@@ -14,6 +14,7 @@
 /// `@SKTransactionStatusConverter()`.
 class SKTransactionStatusConverter
     implements JsonConverter<SKPaymentTransactionStateWrapper, int> {
+  /// Default const constructor.
   const SKTransactionStatusConverter();
 
   @override
@@ -23,6 +24,7 @@
               .cast<SKPaymentTransactionStateWrapper, dynamic>(),
           json);
 
+  /// Converts an [SKPaymentTransactionStateWrapper] to a [PurchaseStatus].
   PurchaseStatus toPurchaseStatus(SKPaymentTransactionStateWrapper object) {
     switch (object) {
       case SKPaymentTransactionStateWrapper.purchasing:
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
index bb731ac..ce38759 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
@@ -203,6 +203,7 @@
 /// [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc).
 @JsonSerializable(nullable: true)
 class SKError {
+  /// Creates a new [SKError] object with the provided information.
   SKError(
       {@required this.code, @required this.domain, @required this.userInfo});
 
@@ -258,6 +259,7 @@
 /// initiate a payment.
 @JsonSerializable(nullable: true)
 class SKPaymentWrapper {
+  /// Creates a new [SKPaymentWrapper] with the provided information.
   SKPaymentWrapper(
       {@required this.productIdentifier,
       this.applicationUsername,
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart
index cb7ca03..65f6ff8 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart
@@ -98,6 +98,7 @@
 /// [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction?language=objc).
 @JsonSerializable(nullable: true)
 class SKPaymentTransactionWrapper {
+  /// Creates a new [SKPaymentTransactionWrapper] with the provided information.
   SKPaymentTransactionWrapper({
     @required this.payment,
     @required this.transactionState,
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
index 8f4c815..aa76971 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
@@ -18,6 +18,7 @@
 /// Contains information about a list of products and a list of invalid product identifiers.
 @JsonSerializable()
 class SkProductResponseWrapper {
+  /// Creates an [SkProductResponseWrapper] with the given product details.
   SkProductResponseWrapper(
       {@required this.products, @required this.invalidProductIdentifiers});
 
@@ -67,12 +68,21 @@
 // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition
 // in the [SKProductPeriodUnit], this need to be updated to match.
 enum SKSubscriptionPeriodUnit {
+  /// An interval lasting one day.
   @JsonValue(0)
   day,
+
+  /// An interval lasting one month.
   @JsonValue(1)
+
+  /// An interval lasting one week.
   week,
   @JsonValue(2)
+
+  /// An interval lasting one month.
   month,
+
+  /// An interval lasting one year.
   @JsonValue(3)
   year,
 }
@@ -83,6 +93,7 @@
 /// It is used as a property in [SKProductDiscountWrapper] and [SKProductWrapper].
 @JsonSerializable(nullable: true)
 class SKProductSubscriptionPeriodWrapper {
+  /// Creates an [SKProductSubscriptionPeriodWrapper] for a `numberOfUnits`x`unit` period.
   SKProductSubscriptionPeriodWrapper(
       {@required this.numberOfUnits, @required this.unit});
 
@@ -143,6 +154,7 @@
 /// It is used as a property in [SKProductWrapper].
 @JsonSerializable(nullable: true)
 class SKProductDiscountWrapper {
+  /// Creates an [SKProductDiscountWrapper] with the given discount details.
   SKProductDiscountWrapper(
       {@required this.price,
       @required this.priceLocale,
@@ -206,6 +218,7 @@
 /// should be stored for use when making a payment.
 @JsonSerializable(nullable: true)
 class SKProductWrapper {
+  /// Creates an [SKProductWrapper] with the given product details.
   SKProductWrapper({
     @required this.productIdentifier,
     @required this.localizedTitle,
@@ -304,6 +317,7 @@
 //                 https://github.com/flutter/flutter/issues/26610
 @JsonSerializable()
 class SKPriceLocaleWrapper {
+  /// Creates a new price locale for `currencySymbol` and `currencyCode`.
   SKPriceLocaleWrapper(
       {@required this.currencySymbol, @required this.currencyCode});
 
diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml
index 31c8350..eda865e 100644
--- a/packages/in_app_purchase/pubspec.yaml
+++ b/packages/in_app_purchase/pubspec.yaml
@@ -1,7 +1,7 @@
 name: in_app_purchase
 description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
 homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
-version: 0.3.4+15
+version: 0.3.4+16
 
 dependencies:
   async: ^2.0.8
diff --git a/packages/integration_test/CHANGELOG.md b/packages/integration_test/CHANGELOG.md
index 07ee671..534d706 100644
--- a/packages/integration_test/CHANGELOG.md
+++ b/packages/integration_test/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.2+2
+
+* Broaden the constraint on vm_service.
+
 ## 0.9.2+1
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml
index 44aa2c5..337fc55 100644
--- a/packages/integration_test/pubspec.yaml
+++ b/packages/integration_test/pubspec.yaml
@@ -1,6 +1,6 @@
 name: integration_test
 description: Runs tests that use the flutter_test API as integration tests.
-version: 0.9.2+1
+version: 0.9.2+2
 homepage: https://github.com/flutter/plugins/tree/master/packages/integration_test
 
 environment:
@@ -15,7 +15,10 @@
   flutter_test:
     sdk: flutter
   path: ^1.6.4
-  vm_service: ^4.2.0
+  # TODO(dnfield): This is a problem - flutter_driver and flutter_tools depend
+  # on this packkage, and so does integration_test. When this gets rev'd in the
+  # SDK, it has to be rev'd here too so integration tests in the SDK can use it.
+  vm_service: ">= 4.2.0 <6.0.0"
 
 dev_dependencies:
   pedantic: ^1.8.0
diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md
index 6b1ccc7..ceb2a19 100644
--- a/packages/path_provider/path_provider/CHANGELOG.md
+++ b/packages/path_provider/path_provider/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.6.23
+
+* Check in windows/ directory for example/
+
 ## 1.6.22
 
 * Switch to guava-android dependency instead of full guava.
diff --git a/packages/path_provider/path_provider/example/windows/.gitignore b/packages/path_provider/path_provider/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/path_provider/path_provider/example/windows/CMakeLists.txt b/packages/path_provider/path_provider/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt b/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.cc b/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..a6177ab
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,7 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {}
diff --git a/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.h b/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/path_provider/path_provider/example/windows/flutter/generated_plugins.cmake b/packages/path_provider/path_provider/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/path_provider/path_provider/example/windows/runner/CMakeLists.txt b/packages/path_provider/path_provider/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/path_provider/path_provider/example/windows/runner/Runner.rc b/packages/path_provider/path_provider/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..9d72c23
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "io.flutter.plugins" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 io.flutter.plugins. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/path_provider/path_provider/example/windows/runner/flutter_window.cpp b/packages/path_provider/path_provider/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/path_provider/path_provider/example/windows/runner/flutter_window.h b/packages/path_provider/path_provider/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/path_provider/path_provider/example/windows/runner/main.cpp b/packages/path_provider/path_provider/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/path_provider/path_provider/example/windows/runner/resource.h b/packages/path_provider/path_provider/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/path_provider/path_provider/example/windows/runner/resources/app_icon.ico b/packages/path_provider/path_provider/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/path_provider/path_provider/example/windows/runner/run_loop.cpp b/packages/path_provider/path_provider/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/path_provider/path_provider/example/windows/runner/run_loop.h b/packages/path_provider/path_provider/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/path_provider/path_provider/example/windows/runner/runner.exe.manifest b/packages/path_provider/path_provider/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/path_provider/path_provider/example/windows/runner/utils.cpp b/packages/path_provider/path_provider/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/path_provider/path_provider/example/windows/runner/utils.h b/packages/path_provider/path_provider/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/path_provider/path_provider/example/windows/runner/win32_window.cpp b/packages/path_provider/path_provider/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/path_provider/path_provider/example/windows/runner/win32_window.h b/packages/path_provider/path_provider/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/path_provider/path_provider/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml
index c583efa..3fda0dc 100644
--- a/packages/path_provider/path_provider/pubspec.yaml
+++ b/packages/path_provider/path_provider/pubspec.yaml
@@ -1,7 +1,7 @@
 name: path_provider
 description: Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
 homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider
-version: 1.6.22
+version: 1.6.23
 
 flutter:
   plugin:
diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md
index b7bc07e..e142c51 100644
--- a/packages/path_provider/path_provider_windows/CHANGELOG.md
+++ b/packages/path_provider/path_provider_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.4+2
+
+* Check in windows/ directory for example/
+
 ## 0.0.4+1
 
 * Add getPath to the stub, so that the analyzer won't complain about
diff --git a/packages/path_provider/path_provider_windows/example/windows/.gitignore b/packages/path_provider/path_provider_windows/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/path_provider/path_provider_windows/example/windows/CMakeLists.txt b/packages/path_provider/path_provider_windows/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt b/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.cc b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..a6177ab
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,7 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {}
diff --git a/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugins.cmake b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/CMakeLists.txt b/packages/path_provider/path_provider_windows/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc b/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..944329a
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.example" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.cpp b/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.h b/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/main.cpp b/packages/path_provider/path_provider_windows/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/resource.h b/packages/path_provider/path_provider_windows/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/resources/app_icon.ico b/packages/path_provider/path_provider_windows/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.cpp b/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.h b/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/runner.exe.manifest b/packages/path_provider/path_provider_windows/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/utils.cpp b/packages/path_provider/path_provider_windows/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/utils.h b/packages/path_provider/path_provider_windows/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.cpp b/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.h b/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/path_provider/path_provider_windows/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/path_provider/path_provider_windows/pubspec.yaml b/packages/path_provider/path_provider_windows/pubspec.yaml
index a04366c..8aa1af5 100644
--- a/packages/path_provider/path_provider_windows/pubspec.yaml
+++ b/packages/path_provider/path_provider_windows/pubspec.yaml
@@ -1,7 +1,7 @@
 name: path_provider_windows
 description: Windows implementation of the path_provider plugin
 homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_windows
-version: 0.0.4+1
+version: 0.0.4+2
 
 flutter:
   plugin:
diff --git a/packages/share/CHANGELOG.md b/packages/share/CHANGELOG.md
index 3644547..883cd34 100644
--- a/packages/share/CHANGELOG.md
+++ b/packages/share/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.5+4
+
+* Fix iPad share window not showing when `origin` is null.
+
 ## 0.6.5+3
 
 * Replace deprecated `Environment.getExternalStorageDirectory()` call on Android.
diff --git a/packages/share/example/ios/Runner.xcodeproj/project.pbxproj b/packages/share/example/ios/Runner.xcodeproj/project.pbxproj
index 730c0d4..639666b 100644
--- a/packages/share/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/share/example/ios/Runner.xcodeproj/project.pbxproj
@@ -10,10 +10,7 @@
 		28918A213BCB94C5470742D8 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85392794417D70A970945C83 /* libPods-Runner.a */; };
 		2D9222511EC45DE6007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D9222501EC45DE6007564B0 /* GeneratedPluginRegistrant.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
-		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
-		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
-		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		683426AE2538D314009B194C /* FLTShareExampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 683426AD2538D314009B194C /* FLTShareExampleUITests.m */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -21,6 +18,16 @@
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		683426B02538D314009B194C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+			remoteInfo = Runner;
+		};
+/* End PBXContainerItemProxy section */
+
 /* Begin PBXCopyFilesBuildPhase section */
 		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
 			isa = PBXCopyFilesBuildPhase;
@@ -28,8 +35,6 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -42,14 +47,15 @@
 		2D92224F1EC45DE6007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		2D9222501EC45DE6007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
+		683426AB2538D314009B194C /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		683426AD2538D314009B194C /* FLTShareExampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTShareExampleUITests.m; sourceTree = "<group>"; };
+		683426AF2538D314009B194C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
 		85392794417D70A970945C83 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
-		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -59,12 +65,17 @@
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+		683426A82538D314009B194C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		97C146EB1CF9000F007C117D /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
 				28918A213BCB94C5470742D8 /* libPods-Runner.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -81,6 +92,15 @@
 			name = Pods;
 			sourceTree = "<group>";
 		};
+		683426AC2538D314009B194C /* RunnerUITests */ = {
+			isa = PBXGroup;
+			children = (
+				683426AD2538D314009B194C /* FLTShareExampleUITests.m */,
+				683426AF2538D314009B194C /* Info.plist */,
+			);
+			path = RunnerUITests;
+			sourceTree = "<group>";
+		};
 		8CA31EF57239BF20619316D9 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
@@ -92,9 +112,7 @@
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
-				3B80C3931E831B6300D905FE /* App.framework */,
 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -107,6 +125,7 @@
 			children = (
 				9740EEB11CF90186004384FC /* Flutter */,
 				97C146F01CF9000F007C117D /* Runner */,
+				683426AC2538D314009B194C /* RunnerUITests */,
 				97C146EF1CF9000F007C117D /* Products */,
 				16DDF472245BCC3E62219493 /* Pods */,
 				8CA31EF57239BF20619316D9 /* Frameworks */,
@@ -117,6 +136,7 @@
 			isa = PBXGroup;
 			children = (
 				97C146EE1CF9000F007C117D /* Runner.app */,
+				683426AB2538D314009B194C /* RunnerUITests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -148,6 +168,24 @@
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
+		683426AA2538D314009B194C /* RunnerUITests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 683426B42538D314009B194C /* Build configuration list for PBXNativeTarget "RunnerUITests" */;
+			buildPhases = (
+				683426A72538D314009B194C /* Sources */,
+				683426A82538D314009B194C /* Frameworks */,
+				683426A92538D314009B194C /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				683426B12538D314009B194C /* PBXTargetDependency */,
+			);
+			name = RunnerUITests;
+			productName = RunnerUITests;
+			productReference = 683426AB2538D314009B194C /* RunnerUITests.xctest */;
+			productType = "com.apple.product-type.bundle.ui-testing";
+		};
 		97C146ED1CF9000F007C117D /* Runner */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@@ -179,6 +217,11 @@
 				LastUpgradeCheck = 1100;
 				ORGANIZATIONNAME = "The Chromium Authors";
 				TargetAttributes = {
+					683426AA2538D314009B194C = {
+						CreatedOnToolsVersion = 11.7;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 97C146ED1CF9000F007C117D;
+					};
 					97C146ED1CF9000F007C117D = {
 						CreatedOnToolsVersion = 7.3.1;
 					};
@@ -198,11 +241,19 @@
 			projectRoot = "";
 			targets = (
 				97C146ED1CF9000F007C117D /* Runner */,
+				683426AA2538D314009B194C /* RunnerUITests */,
 			);
 		};
 /* End PBXProject section */
 
 /* Begin PBXResourcesBuildPhase section */
+		683426A92538D314009B194C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		97C146EC1CF9000F007C117D /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -223,9 +274,12 @@
 			files = (
 			);
 			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+				"${PODS_ROOT}/../Flutter/Flutter.framework",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -244,7 +298,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
 		};
 		5F8AC0B5B699C537B657C107 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -281,6 +335,14 @@
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
+		683426A72538D314009B194C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				683426AE2538D314009B194C /* FLTShareExampleUITests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		97C146EA1CF9000F007C117D /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -293,6 +355,14 @@
 		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		683426B12538D314009B194C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 97C146ED1CF9000F007C117D /* Runner */;
+			targetProxy = 683426B02538D314009B194C /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin PBXVariantGroup section */
 		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
 			isa = PBXVariantGroup;
@@ -313,9 +383,53 @@
 /* End PBXVariantGroup section */
 
 /* Begin XCBuildConfiguration section */
+		683426B22538D314009B194C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = "";
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				INFOPLIST_FILE = RunnerUITests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_TARGET_NAME = Runner;
+			};
+			name = Debug;
+		};
+		683426B32538D314009B194C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = "";
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				INFOPLIST_FILE = RunnerUITests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_TARGET_NAME = Runner;
+			};
+			name = Release;
+		};
 		97C147031CF9000F007C117D /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
@@ -372,7 +486,6 @@
 		};
 		97C147041CF9000F007C117D /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
@@ -466,6 +579,15 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
+		683426B42538D314009B194C /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				683426B22538D314009B194C /* Debug */,
+				683426B32538D314009B194C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
diff --git a/packages/share/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/share/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index 3bb3697..0000000
--- a/packages/share/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1100"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-      <BuildActionEntries>
-         <BuildActionEntry
-            buildForTesting = "YES"
-            buildForRunning = "YES"
-            buildForProfiling = "YES"
-            buildForArchiving = "YES"
-            buildForAnalyzing = "YES">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-               BuildableName = "Runner.app"
-               BlueprintName = "Runner"
-               ReferencedContainer = "container:Runner.xcodeproj">
-            </BuildableReference>
-         </BuildActionEntry>
-      </BuildActionEntries>
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <Testables>
-      </Testables>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>
diff --git a/packages/share/example/ios/RunnerUITests/FLTShareExampleUITests.m b/packages/share/example/ios/RunnerUITests/FLTShareExampleUITests.m
new file mode 100644
index 0000000..8e18d03
--- /dev/null
+++ b/packages/share/example/ios/RunnerUITests/FLTShareExampleUITests.m
@@ -0,0 +1,48 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <XCTest/XCTest.h>
+#import <os/log.h>
+
+static const NSInteger kSecondsToWaitWhenFindingElements = 30;
+
+@interface FLTShareExampleUITests : XCTestCase
+
+@end
+
+@implementation FLTShareExampleUITests
+
+- (void)setUp {
+  self.continueAfterFailure = NO;
+}
+
+- (void)testShareWithEmptyOrigin {
+  XCUIApplication* app = [[XCUIApplication alloc] init];
+  [app launch];
+
+  XCUIElement* shareWithEmptyOriginButton = [app.buttons
+      elementMatchingPredicate:[NSPredicate
+                                   predicateWithFormat:@"label == %@", @"Share With Empty Origin"]];
+  if (![shareWithEmptyOriginButton waitForExistenceWithTimeout:kSecondsToWaitWhenFindingElements]) {
+    os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription);
+    XCTFail(@"Failed due to not able to find shareWithEmptyOriginButton with %@ seconds",
+            @(kSecondsToWaitWhenFindingElements));
+  }
+
+  XCTAssertNotNil(shareWithEmptyOriginButton);
+  [shareWithEmptyOriginButton tap];
+
+  // Find the share popup.
+  XCUIElement* activityListView = [app.otherElements
+      elementMatchingPredicate:[NSPredicate
+                                   predicateWithFormat:@"identifier == %@", @"ActivityListView"]];
+  if (![activityListView waitForExistenceWithTimeout:kSecondsToWaitWhenFindingElements]) {
+    os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription);
+    XCTFail(@"Failed due to not able to find activityListView with %@ seconds",
+            @(kSecondsToWaitWhenFindingElements));
+  }
+  XCTAssertNotNil(activityListView);
+}
+
+@end
diff --git a/packages/share/example/ios/RunnerUITests/Info.plist b/packages/share/example/ios/RunnerUITests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/packages/share/example/ios/RunnerUITests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/packages/share/example/lib/main.dart b/packages/share/example/lib/main.dart
index d6f1a16..7ebce3c 100644
--- a/packages/share/example/lib/main.dart
+++ b/packages/share/example/lib/main.dart
@@ -86,6 +86,15 @@
                       );
                     },
                   ),
+                  const Padding(padding: EdgeInsets.only(top: 12.0)),
+                  Builder(
+                    builder: (BuildContext context) {
+                      return RaisedButton(
+                        child: const Text('Share With Empty Origin'),
+                        onPressed: () => _onShareWithEmptyOrigin(context),
+                      );
+                    },
+                  ),
                 ],
               ),
             ),
@@ -120,4 +129,8 @@
           sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
     }
   }
+
+  _onShareWithEmptyOrigin(BuildContext context) async {
+    await Share.share("text");
+  }
 }
diff --git a/packages/share/ios/Classes/FLTSharePlugin.m b/packages/share/ios/Classes/FLTSharePlugin.m
index 837623a..024ede9 100644
--- a/packages/share/ios/Classes/FLTSharePlugin.m
+++ b/packages/share/ios/Classes/FLTSharePlugin.m
@@ -168,9 +168,8 @@
   UIActivityViewController *activityViewController =
       [[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
   activityViewController.popoverPresentationController.sourceView = controller.view;
-  if (!CGRectIsEmpty(origin)) {
-    activityViewController.popoverPresentationController.sourceRect = origin;
-  }
+  activityViewController.popoverPresentationController.sourceRect = origin;
+
   [controller presentViewController:activityViewController animated:YES completion:nil];
 }
 
diff --git a/packages/share/ios/share.podspec b/packages/share/ios/share.podspec
index 73d6030..786e1c7 100644
--- a/packages/share/ios/share.podspec
+++ b/packages/share/ios/share.podspec
@@ -17,7 +17,6 @@
   s.source_files = 'Classes/**/*'
   s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
-  
   s.platform = :ios, '8.0'
   s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
 end
diff --git a/packages/share/pubspec.yaml b/packages/share/pubspec.yaml
index 5a65901..5bd79ef 100644
--- a/packages/share/pubspec.yaml
+++ b/packages/share/pubspec.yaml
@@ -5,7 +5,7 @@
 # 0.6.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.6.5+3
+version: 0.6.5+4
 
 flutter:
   plugin:
diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md
index 268e1ab..c4c64fe 100644
--- a/packages/shared_preferences/shared_preferences/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.12+3
+
+* Check in windows/ directory for example/
+
 ## 0.5.12+2
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/shared_preferences/shared_preferences/example/windows/.gitignore b/packages/shared_preferences/shared_preferences/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/shared_preferences/shared_preferences/example/windows/CMakeLists.txt b/packages/shared_preferences/shared_preferences/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt b/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.cc b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..a6177ab
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,7 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.h b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugins.cmake b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/CMakeLists.txt b/packages/shared_preferences/shared_preferences/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc b/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..9d72c23
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "io.flutter.plugins" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 io.flutter.plugins. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.cpp b/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.h b/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/main.cpp b/packages/shared_preferences/shared_preferences/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/resource.h b/packages/shared_preferences/shared_preferences/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/resources/app_icon.ico b/packages/shared_preferences/shared_preferences/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.cpp b/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.h b/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/runner.exe.manifest b/packages/shared_preferences/shared_preferences/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/utils.cpp b/packages/shared_preferences/shared_preferences/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/utils.h b/packages/shared_preferences/shared_preferences/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.cpp b/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.h b/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml
index 703ff43..9ddacf9 100644
--- a/packages/shared_preferences/shared_preferences/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences/pubspec.yaml
@@ -5,7 +5,7 @@
 # 0.5.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.5.12+2
+version: 0.5.12+3
 
 flutter:
   plugin:
diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
index 7f82e5b..026095c 100644
--- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.1+2
+
+* Check in windows/ directory for example/
+
 ## 0.0.1+1
 
 * Add iOS stub for compatibility with 1.17 and earlier.
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/.gitignore b/packages/shared_preferences/shared_preferences_windows/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/CMakeLists.txt b/packages/shared_preferences/shared_preferences_windows/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.cc b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..a6177ab
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,7 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugins.cmake b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/CMakeLists.txt b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..944329a
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.example" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.cpp b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.h b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/main.cpp b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resource.h b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resources/app_icon.ico b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.cpp b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.h b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/runner.exe.manifest b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.cpp b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.h b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.cpp b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.h b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
index f1ce8ec..b106f87 100644
--- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
@@ -1,7 +1,7 @@
 name: shared_preferences_windows
 description: Windows implementation of shared_preferences
 homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_windows
-version: 0.0.1+1
+version: 0.0.1+2
 
 flutter:
   plugin:
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index c74b428..3b5fff1 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 * Migrate to null safety.
 
+## 5.7.9
+
+* Check in windows/ directory for example/
+
 ## 5.7.8
 
 * Fixed a situation where an app would crash if the url_launcher’s `launch` method can’t find an app to open the provided url. It will now throw a clear Dart PlatformException.
diff --git a/packages/url_launcher/url_launcher/example/windows/.gitignore b/packages/url_launcher/url_launcher/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/url_launcher/url_launcher/example/windows/CMakeLists.txt b/packages/url_launcher/url_launcher/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..ddfcf7c
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,12 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+#include <url_launcher_windows/url_launcher_plugin.h>
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+  UrlLauncherPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("UrlLauncherPlugin"));
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..411af46
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  url_launcher_windows
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/CMakeLists.txt b/packages/url_launcher/url_launcher/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc b/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..9d72c23
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "io.flutter.plugins" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 io.flutter.plugins. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.cpp b/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.h b/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/main.cpp b/packages/url_launcher/url_launcher/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/resource.h b/packages/url_launcher/url_launcher/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/resources/app_icon.ico b/packages/url_launcher/url_launcher/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/run_loop.cpp b/packages/url_launcher/url_launcher/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/run_loop.h b/packages/url_launcher/url_launcher/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/runner.exe.manifest b/packages/url_launcher/url_launcher/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/utils.cpp b/packages/url_launcher/url_launcher/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/utils.h b/packages/url_launcher/url_launcher/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/win32_window.cpp b/packages/url_launcher/url_launcher/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/url_launcher/url_launcher/example/windows/runner/win32_window.h b/packages/url_launcher/url_launcher/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/url_launcher/url_launcher/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md
index 30657d8..a8748a3 100644
--- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 * Migrate to null-safety.
 
+## 0.0.1+2
+
+* Check in windows/ directory for example/
+
 ## 0.0.1+1
 
 * Update README to reflect endorsement.
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/.gitignore b/packages/url_launcher/url_launcher_windows/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..abf9040
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(example LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..c7a8c76
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..ddfcf7c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,12 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+#include <url_launcher_windows/url_launcher_plugin.h>
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+  UrlLauncherPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("UrlLauncherPlugin"));
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9846246
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..411af46
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  url_launcher_windows
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..977e38b
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "run_loop.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc b/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..944329a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.example" "\0"
+            VALUE "FileDescription", "A new Flutter project." "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0"
+            VALUE "OriginalFilename", "example.exe" "\0"
+            VALUE "ProductName", "example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.cpp b/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..c422723
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,64 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(RunLoop* run_loop,
+                             const flutter::DartProject& project)
+    : run_loop_(run_loop), project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opporutunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.h b/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..b663ddd
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.h
@@ -0,0 +1,39 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "run_loop.h"
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
+  // Flutter view running |project|.
+  explicit FlutterWindow(RunLoop* run_loop,
+                         const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The run loop driving events for this window.
+  RunLoop* run_loop_;
+
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/main.cpp b/packages/url_launcher/url_launcher_windows/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fc17fec
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/main.cpp
@@ -0,0 +1,36 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "run_loop.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  RunLoop run_loop;
+
+  flutter::DartProject project(L"data");
+  FlutterWindow window(&run_loop, project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  run_loop.Run();
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/resource.h b/packages/url_launcher/url_launcher_windows/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/resources/app_icon.ico b/packages/url_launcher/url_launcher_windows/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.cpp b/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.cpp
new file mode 100644
index 0000000..2d6636a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.cpp
@@ -0,0 +1,66 @@
+#include "run_loop.h"
+
+#include <windows.h>
+
+#include <algorithm>
+
+RunLoop::RunLoop() {}
+
+RunLoop::~RunLoop() {}
+
+void RunLoop::Run() {
+  bool keep_running = true;
+  TimePoint next_flutter_event_time = TimePoint::clock::now();
+  while (keep_running) {
+    std::chrono::nanoseconds wait_duration =
+        std::max(std::chrono::nanoseconds(0),
+                 next_flutter_event_time - TimePoint::clock::now());
+    ::MsgWaitForMultipleObjects(
+        0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
+        QS_ALLINPUT);
+    bool processed_events = false;
+    MSG message;
+    // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+    // won't return again for items left in the queue after PeekMessage.
+    while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+      processed_events = true;
+      if (message.message == WM_QUIT) {
+        keep_running = false;
+        break;
+      }
+      ::TranslateMessage(&message);
+      ::DispatchMessage(&message);
+      // Allow Flutter to process messages each time a Windows message is
+      // processed, to prevent starvation.
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+    // If the PeekMessage loop didn't run, process Flutter messages.
+    if (!processed_events) {
+      next_flutter_event_time =
+          std::min(next_flutter_event_time, ProcessFlutterMessages());
+    }
+  }
+}
+
+void RunLoop::RegisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.insert(flutter_instance);
+}
+
+void RunLoop::UnregisterFlutterInstance(
+    flutter::FlutterEngine* flutter_instance) {
+  flutter_instances_.erase(flutter_instance);
+}
+
+RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
+  TimePoint next_event_time = TimePoint::max();
+  for (auto instance : flutter_instances_) {
+    std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
+    if (wait_duration != std::chrono::nanoseconds::max()) {
+      next_event_time =
+          std::min(next_event_time, TimePoint::clock::now() + wait_duration);
+    }
+  }
+  return next_event_time;
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.h b/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.h
new file mode 100644
index 0000000..5f2c4a9
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.h
@@ -0,0 +1,38 @@
+#ifndef RUNNER_RUN_LOOP_H_
+#define RUNNER_RUN_LOOP_H_
+
+#include <flutter/flutter_engine.h>
+
+#include <chrono>
+#include <set>
+
+// A runloop that will service events for Flutter instances as well
+// as native messages.
+class RunLoop {
+ public:
+  RunLoop();
+  ~RunLoop();
+
+  // Prevent copying
+  RunLoop(RunLoop const&) = delete;
+  RunLoop& operator=(RunLoop const&) = delete;
+
+  // Runs the run loop until the application quits.
+  void Run();
+
+  // Registers the given Flutter instance for event servicing.
+  void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+  // Unregisters the given Flutter instance from event servicing.
+  void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
+
+ private:
+  using TimePoint = std::chrono::steady_clock::time_point;
+
+  // Processes all currently pending messages for registered Flutter instances.
+  TimePoint ProcessFlutterMessages();
+
+  std::set<flutter::FlutterEngine*> flutter_instances_;
+};
+
+#endif  // RUNNER_RUN_LOOP_H_
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/runner.exe.manifest b/packages/url_launcher/url_launcher_windows/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.cpp b/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..37501e5
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.cpp
@@ -0,0 +1,22 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.h b/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.h
new file mode 100644
index 0000000..d792603
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/utils.h
@@ -0,0 +1,8 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.cpp b/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c63ad01
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.cpp
@@ -0,0 +1,236 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE:
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.h b/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..4ae64a1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.h
@@ -0,0 +1,95 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index acac564..36d5132 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,7 +1,19 @@
+## 2.0.0-nullsafety.1
+
+* Merge master.
+
 ## 2.0.0-nullsafety
 
 * Migration to null safety.
 
+## 0.11.1+4
+
+* Add `toString()` to `Caption`.
+
+## 0.11.1+3
+
+* Android: Upgrade ExoPlayer to 2.12.1.
+
 ## 0.11.1+2
 
 * Update android compileSdkVersion to 29.
diff --git a/packages/video_player/video_player/android/build.gradle b/packages/video_player/video_player/android/build.gradle
index 50636ae..08a240b 100644
--- a/packages/video_player/video_player/android/build.gradle
+++ b/packages/video_player/video_player/android/build.gradle
@@ -9,7 +9,7 @@
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.3.0'
+        classpath 'com.android.tools.build:gradle:3.5.0'
     }
 }
 
@@ -44,9 +44,9 @@
     }
 
     dependencies {
-        implementation 'com.google.android.exoplayer:exoplayer-core:2.9.6'
-        implementation 'com.google.android.exoplayer:exoplayer-hls:2.9.6'
-        implementation 'com.google.android.exoplayer:exoplayer-dash:2.9.6'
-        implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.9.6'
+        implementation 'com.google.android.exoplayer:exoplayer-core:2.12.1'
+        implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.1'
+        implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.1'
+        implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.12.1'
     }
 }
diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
index 8f8c898..33c2f42 100644
--- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
+++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
@@ -9,23 +9,20 @@
 import android.view.Surface;
 import com.google.android.exoplayer2.C;
 import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
 import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
 import com.google.android.exoplayer2.PlaybackParameters;
 import com.google.android.exoplayer2.Player;
 import com.google.android.exoplayer2.Player.EventListener;
 import com.google.android.exoplayer2.SimpleExoPlayer;
 import com.google.android.exoplayer2.audio.AudioAttributes;
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
 import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
 import com.google.android.exoplayer2.source.dash.DashMediaSource;
 import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
 import com.google.android.exoplayer2.source.hls.HlsMediaSource;
 import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
 import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelector;
 import com.google.android.exoplayer2.upstream.DataSource;
 import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
@@ -70,8 +67,7 @@
     this.textureEntry = textureEntry;
     this.options = options;
 
-    TrackSelector trackSelector = new DefaultTrackSelector();
-    exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
+    exoPlayer = new SimpleExoPlayer.Builder(context).build();
 
     Uri uri = Uri.parse(dataSource);
 
@@ -89,7 +85,8 @@
     }
 
     MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context);
-    exoPlayer.prepare(mediaSource);
+    exoPlayer.setMediaSource(mediaSource);
+    exoPlayer.prepare();
 
     setupVideoPlayer(eventChannel, textureEntry);
   }
@@ -131,18 +128,18 @@
         return new SsMediaSource.Factory(
                 new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
                 new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
-            .createMediaSource(uri);
+            .createMediaSource(MediaItem.fromUri(uri));
       case C.TYPE_DASH:
         return new DashMediaSource.Factory(
                 new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
                 new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
-            .createMediaSource(uri);
+            .createMediaSource(MediaItem.fromUri(uri));
       case C.TYPE_HLS:
-        return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
+        return new HlsMediaSource.Factory(mediaDataSourceFactory)
+            .createMediaSource(MediaItem.fromUri(uri));
       case C.TYPE_OTHER:
-        return new ExtractorMediaSource.Factory(mediaDataSourceFactory)
-            .setExtractorsFactory(new DefaultExtractorsFactory())
-            .createMediaSource(uri);
+        return new ProgressiveMediaSource.Factory(mediaDataSourceFactory)
+            .createMediaSource(MediaItem.fromUri(uri));
       default:
         {
           throw new IllegalStateException("Unsupported type: " + type);
@@ -174,7 +171,7 @@
         new EventListener() {
 
           @Override
-          public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
+          public void onPlaybackStateChanged(final int playbackState) {
             if (playbackState == Player.STATE_BUFFERING) {
               sendBufferingUpdate();
             } else if (playbackState == Player.STATE_READY) {
diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
index 1beb79c..b17509d 100644
--- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
+++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
@@ -8,7 +8,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.LongSparseArray;
-import io.flutter.embedding.engine.loader.FlutterLoader;
+import io.flutter.FlutterInjector;
 import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.EventChannel;
@@ -60,6 +60,7 @@
 
   @Override
   public void onAttachedToEngine(FlutterPluginBinding binding) {
+
     if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
       try {
         HttpsURLConnection.setDefaultSSLSocketFactory(new CustomSSLSocketFactory());
@@ -73,14 +74,13 @@
       }
     }
 
-    @SuppressWarnings("deprecation")
-    final FlutterLoader flutterLoader = FlutterLoader.getInstance();
+    final FlutterInjector injector = FlutterInjector.instance();
     this.flutterState =
         new FlutterState(
             binding.getApplicationContext(),
             binding.getBinaryMessenger(),
-            flutterLoader::getLookupKeyForAsset,
-            flutterLoader::getLookupKeyForAsset,
+            injector.flutterLoader()::getLookupKeyForAsset,
+            injector.flutterLoader()::getLookupKeyForAsset,
             binding.getTextureRegistry());
     flutterState.startListening(this, binding.getBinaryMessenger());
   }
@@ -138,7 +138,6 @@
               "asset:///" + assetLookupKey,
               null,
               options);
-      videoPlayers.put(handle.id(), player);
     } else {
       player =
           new VideoPlayer(
@@ -148,8 +147,8 @@
               arg.getUri(),
               arg.getFormatHint(),
               options);
-      videoPlayers.put(handle.id(), player);
     }
+    videoPlayers.put(handle.id(), player);
 
     TextureMessage result = new TextureMessage();
     result.setTextureId(handle.id());
diff --git a/packages/video_player/video_player/example/android/app/build.gradle b/packages/video_player/video_player/example/android/app/build.gradle
index 2ab52e1..13fabc7 100644
--- a/packages/video_player/video_player/example/android/app/build.gradle
+++ b/packages/video_player/video_player/example/android/app/build.gradle
@@ -34,7 +34,7 @@
     defaultConfig {
         applicationId "io.flutter.plugins.videoplayerexample"
         minSdkVersion 16
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/video_player/video_player/example/android/build.gradle b/packages/video_player/video_player/example/android/build.gradle
index 112aa2a..498448e 100644
--- a/packages/video_player/video_player/example/android/build.gradle
+++ b/packages/video_player/video_player/example/android/build.gradle
@@ -5,7 +5,7 @@
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.3.0'
+        classpath 'com.android.tools.build:gradle:3.5.0'
     }
 }
 
diff --git a/packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties
index 2819f02..296b146 100644
--- a/packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
diff --git a/packages/video_player/video_player/lib/src/closed_caption_file.dart b/packages/video_player/video_player/lib/src/closed_caption_file.dart
index f9d8040..eae9bf5 100644
--- a/packages/video_player/video_player/lib/src/closed_caption_file.dart
+++ b/packages/video_player/video_player/lib/src/closed_caption_file.dart
@@ -54,4 +54,13 @@
   /// and an empty [text] string.
   static const Caption none =
       Caption(number: 0, start: Duration.zero, end: Duration.zero, text: '');
+
+  @override
+  String toString() {
+    return '$runtimeType('
+        'number: $number, '
+        'start: $start, '
+        'end: $end, '
+        'text: $text)';
+  }
 }
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index ee9c9fe..cfefaad 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -4,7 +4,7 @@
 # 0.10.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 2.0.0-nullsafety
+version: 2.0.0-nullsafety.1
 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
 
 flutter:
diff --git a/packages/video_player/video_player/test/closed_caption_file_test.dart b/packages/video_player/video_player/test/closed_caption_file_test.dart
new file mode 100644
index 0000000..148c082
--- /dev/null
+++ b/packages/video_player/video_player/test/closed_caption_file_test.dart
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:video_player/src/closed_caption_file.dart';
+
+void main() {
+  group('ClosedCaptionFile', () {
+    test('toString()', () {
+      final Caption caption = const Caption(
+        number: 1,
+        start: Duration(seconds: 1),
+        end: Duration(seconds: 2),
+        text: 'caption',
+      );
+
+      expect(
+          caption.toString(),
+          'Caption('
+          'number: 1, '
+          'start: 0:00:01.000000, '
+          'end: 0:00:02.000000, '
+          'text: caption'
+          ')');
+    });
+  });
+}
diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart
index 7dec747..d9701ba 100644
--- a/packages/video_player/video_player/test/video_player_test.dart
+++ b/packages/video_player/video_player/test/video_player_test.dart
@@ -567,7 +567,7 @@
           'VideoPlayerValue(duration: 0:00:05.000000, '
           'size: Size(400.0, 300.0), '
           'position: 0:00:01.000000, '
-          'caption: Instance of \'Caption\', '
+          'caption: Caption(number: null, start: null, end: null, text: foo), '
           'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], '
           'isInitialized: true, '
           'isPlaying: true, '
diff --git a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
index d85be8b..37711dc 100644
--- a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
+++ b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.1
+
+* Fixed method channel name in android implementation. [Issue](https://github.com/flutter/flutter/issues/69073).
+
 ## 1.0.0
 
 * Initial release of the plugin. This plugin retrieves information about a device's connection to wifi.
diff --git a/packages/wifi_info_flutter/wifi_info_flutter/android/src/main/java/io/flutter/plugins/wifi_info_flutter/WifiInfoFlutterPlugin.java b/packages/wifi_info_flutter/wifi_info_flutter/android/src/main/java/io/flutter/plugins/wifi_info_flutter/WifiInfoFlutterPlugin.java
index 407bbc0..ea22e0b 100644
--- a/packages/wifi_info_flutter/wifi_info_flutter/android/src/main/java/io/flutter/plugins/wifi_info_flutter/WifiInfoFlutterPlugin.java
+++ b/packages/wifi_info_flutter/wifi_info_flutter/android/src/main/java/io/flutter/plugins/wifi_info_flutter/WifiInfoFlutterPlugin.java
@@ -33,7 +33,7 @@
   }
 
   private void setupChannels(BinaryMessenger messenger, Context context) {
-    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/wifi_flutter_info");
+    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/wifi_info_flutter");
     final WifiManager wifiManager =
         (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
 
diff --git a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
index 567a091..74ee9f0 100644
--- a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
+++ b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
 name: wifi_info_flutter
 description: A new flutter plugin project.
-version: 1.0.0
+version: 1.0.1
 homepage: https://github.com/flutter/plugins/tree/master/packages/wifi_info_flutter/wifi_info_flutter
 
 environment: