Move `Shell::Add/RemoveView` to `PlatformView` and refine embedder API doc (#52003)

This PR moves the methods to add or remove views from `Shell` to
`PlatformView`. By design, the `Shell` is supposed to be a messenger
that glues classes together, while `PlatformView` is the operator that
embedders that do not use the embedder API should operate on. The
current design was made due to lack of knowledge to this design.

This also makes `PlatformView` aware of views, which might be a
prerequisite to https://github.com/flutter/engine/pull/51925.

This PR also adds some details to embedder API `AddView` and
`RemoveView`.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I signed the [CLA].
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc
index 48439c2..85ad6db 100644
--- a/shell/common/platform_view.cc
+++ b/shell/common/platform_view.cc
@@ -87,6 +87,17 @@
   delegate_.OnPlatformViewScheduleFrame();
 }
 
+void PlatformView::AddView(int64_t view_id,
+                           const ViewportMetrics& viewport_metrics,
+                           AddViewCallback callback) {
+  delegate_.OnPlatformViewAddView(view_id, viewport_metrics,
+                                  std::move(callback));
+}
+
+void PlatformView::RemoveView(int64_t view_id, RemoveViewCallback callback) {
+  delegate_.OnPlatformViewRemoveView(view_id, std::move(callback));
+}
+
 sk_sp<GrDirectContext> PlatformView::CreateResourceContext() const {
   FML_DLOG(WARNING) << "This platform does not set up the resource "
                        "context on the IO thread for async texture uploads.";
diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h
index bfb6489..04f85a5 100644
--- a/shell/common/platform_view.h
+++ b/shell/common/platform_view.h
@@ -51,6 +51,8 @@
 ///
 class PlatformView {
  public:
+  using AddViewCallback = std::function<void(bool added)>;
+  using RemoveViewCallback = std::function<void(bool removed)>;
   //----------------------------------------------------------------------------
   /// @brief      Used to forward events from the platform view to interested
   ///             subsystems. This forwarding is done by the shell which sets
@@ -58,6 +60,8 @@
   ///
   class Delegate {
    public:
+    using AddViewCallback = PlatformView::AddViewCallback;
+    using RemoveViewCallback = PlatformView::RemoveViewCallback;
     using KeyDataResponse = std::function<void(bool)>;
     //--------------------------------------------------------------------------
     /// @brief      Notifies the delegate that the platform view was created
@@ -84,6 +88,40 @@
     ///
     virtual void OnPlatformViewScheduleFrame() = 0;
 
+    /// @brief  Allocate resources for a new non-implicit view and inform
+    ///         Dart about the view, and on success, schedules a new frame.
+    ///
+    ///         After the operation, |callback| should be invoked with whether
+    ///         the operation is successful.
+    ///
+    ///         Adding |kFlutterImplicitViewId| or an existing view ID should
+    ///         result in failure.
+    ///
+    /// @param[in]  view_id           The view ID of the new view.
+    /// @param[in]  viewport_metrics  The initial viewport metrics for the view.
+    /// @param[in]  callback          The callback that's invoked once the shell
+    ///                               has attempted to add the view.
+    ///
+    virtual void OnPlatformViewAddView(int64_t view_id,
+                                       const ViewportMetrics& viewport_metrics,
+                                       AddViewCallback callback) = 0;
+
+    /// @brief  Deallocate resources for a removed view and inform
+    ///         Dart about the removal.
+    ///
+    ///         After the operation, |callback| should be invoked with whether
+    ///         the operation is successful.
+    ///
+    ///         Removing |kFlutterImplicitViewId| or an non-existent view ID
+    ///         should result in failure.
+    ///
+    /// @param[in]  view_id     The view ID of the view to be removed.
+    /// @param[in]  callback    The callback that's invoked once the shell has
+    ///                         attempted to remove the view.
+    ///
+    virtual void OnPlatformViewRemoveView(int64_t view_id,
+                                          RemoveViewCallback callback) = 0;
+
     //--------------------------------------------------------------------------
     /// @brief      Notifies the delegate that the specified callback needs to
     ///             be invoked after the rasterizer is done rendering the next
@@ -517,6 +555,57 @@
   ///
   void ScheduleFrame();
 
+  /// @brief  Used by embedders to notify the shell of a new non-implicit view.
+  ///
+  ///         This method notifies the shell to allocate resources and inform
+  ///         Dart about the view, and on success, schedules a new frame.
+  ///         Finally, it invokes |callback| with whether the operation is
+  ///         successful.
+  ///
+  ///         This operation is asynchronous; avoid using the view until
+  ///         |callback| returns true. Callers should prepare resources for the
+  ///         view (if any) in advance but be ready to clean up on failure.
+  ///
+  ///         The callback is called on a different thread.
+  ///
+  ///         Do not use for implicit views, which are added internally during
+  ///         shell initialization. Adding |kFlutterImplicitViewId| or an
+  ///         existing view ID will fail, indicated by |callback| returning
+  ///         false.
+  ///
+  /// @param[in]  view_id           The view ID of the new view.
+  /// @param[in]  viewport_metrics  The initial viewport metrics for the view.
+  /// @param[in]  callback          The callback that's invoked once the shell
+  ///                               has attempted to add the view.
+  ///
+  void AddView(int64_t view_id,
+               const ViewportMetrics& viewport_metrics,
+               AddViewCallback callback);
+
+  /// @brief  Used by embedders to notify the shell of a removed non-implicit
+  ///         view.
+  ///
+  ///         This method notifies the shell to deallocate resources and inform
+  ///         Dart about the removal. Finally, it invokes |callback| with
+  ///         whether the operation is successful.
+  ///
+  ///         This operation is asynchronous. The embedder should not deallocate
+  ///         resources until the |callback| is invoked.
+  ///
+  ///         The callback is called on a different thread.
+  ///
+  ///         Do not use for implicit views, which are never removed throughout
+  ///         the lifetime of the app.
+  ///         Removing |kFlutterImplicitViewId| or an
+  ///         non-existent view ID will fail, indicated by |callback| returning
+  ///         false.
+  ///
+  /// @param[in]  view_id     The view ID of the view to be removed.
+  /// @param[in]  callback    The callback that's invoked once the shell has
+  ///                         attempted to remove the view.
+  ///
+  void RemoveView(int64_t view_id, RemoveViewCallback callback);
+
   //----------------------------------------------------------------------------
   /// @brief      Used by the shell to obtain a Skia GPU context that is capable
   ///             of operating on the IO thread. The context must be in the same
diff --git a/shell/common/shell.cc b/shell/common/shell.cc
index a9a23d3..9cb5735 100644
--- a/shell/common/shell.cc
+++ b/shell/common/shell.cc
@@ -2114,9 +2114,9 @@
   return true;
 }
 
-void Shell::AddView(int64_t view_id,
-                    const ViewportMetrics& viewport_metrics,
-                    AddViewCallback callback) {
+void Shell::OnPlatformViewAddView(int64_t view_id,
+                                  const ViewportMetrics& viewport_metrics,
+                                  AddViewCallback callback) {
   TRACE_EVENT0("flutter", "Shell::AddView");
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
@@ -2135,7 +2135,8 @@
   });
 }
 
-void Shell::RemoveView(int64_t view_id, RemoveViewCallback callback) {
+void Shell::OnPlatformViewRemoveView(int64_t view_id,
+                                     RemoveViewCallback callback) {
   TRACE_EVENT0("flutter", "Shell::RemoveView");
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
diff --git a/shell/common/shell.h b/shell/common/shell.h
index a510e14..b724fa0 100644
--- a/shell/common/shell.h
+++ b/shell/common/shell.h
@@ -133,8 +133,6 @@
       const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch,
       impeller::RuntimeStageBackend runtime_stage_type)>
       EngineCreateCallback;
-  using AddViewCallback = std::function<void(bool added)>;
-  using RemoveViewCallback = std::function<void(bool removed)>;
 
   //----------------------------------------------------------------------------
   /// @brief      Creates a shell instance using the provided settings. The
@@ -304,43 +302,6 @@
   ///
   bool IsSetup() const;
 
-  /// @brief  Allocates resources for a new non-implicit view.
-  ///
-  ///         This method returns immediately and does not wait for the task on
-  ///         the UI thread to finish. This is safe because operations are
-  ///         either initiated from the UI thread (such as rendering), or are
-  ///         sent as posted tasks that are queued. In either case, it's ok for
-  ///         the engine to have views that the Dart VM doesn't.
-  ///
-  ///         The implicit view should never be added with this function.
-  ///         Instead, it is added internally on Shell initialization. Trying to
-  ///         add `kFlutterImplicitViewId` triggers an assertion.
-  ///
-  /// @param[in]  view_id           The view ID of the new view.
-  /// @param[in]  viewport_metrics  The initial viewport metrics for the view.
-  /// @param[in]  callback          The callback that's invoked once the engine
-  ///                               has attempted to add the view.
-  ///
-  void AddView(int64_t view_id,
-               const ViewportMetrics& viewport_metrics,
-               AddViewCallback callback);
-
-  /// @brief  Deallocates resources for a non-implicit view.
-  ///
-  ///         This method returns immediately and does not wait for the task on
-  ///         the UI thread to finish. This means that the Dart VM might still
-  ///         send messages regarding this view ID for a short while, even
-  ///         though this view ID is already invalid.
-  ///
-  ///         The implicit view should never be removed. Trying to remove
-  ///         `kFlutterImplicitViewId` triggers an assertion.
-  ///
-  /// @param[in]  view_id     The view ID of the view to be removed.
-  /// @param[in]  callback    The callback that's invoked once the engine has
-  ///                         attempted to remove the view.
-  ///
-  void RemoveView(int64_t view_id, RemoveViewCallback callback);
-
   //----------------------------------------------------------------------------
   /// @brief      Captures a screenshot and optionally Base64 encodes the data
   ///             of the last layer tree rendered by the rasterizer in this
@@ -601,6 +562,15 @@
   void OnPlatformViewScheduleFrame() override;
 
   // |PlatformView::Delegate|
+  void OnPlatformViewAddView(int64_t view_id,
+                             const ViewportMetrics& viewport_metrics,
+                             AddViewCallback callback) override;
+
+  // |PlatformView::Delegate|
+  void OnPlatformViewRemoveView(int64_t view_id,
+                                RemoveViewCallback callback) override;
+
+  // |PlatformView::Delegate|
   void OnPlatformViewSetViewportMetrics(
       int64_t view_id,
       const ViewportMetrics& metrics) override;
diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc
index 7754267..44f3ea7 100644
--- a/shell/common/shell_unittests.cc
+++ b/shell/common/shell_unittests.cc
@@ -79,6 +79,18 @@
   MOCK_METHOD(void, OnPlatformViewScheduleFrame, (), (override));
 
   MOCK_METHOD(void,
+              OnPlatformViewAddView,
+              (int64_t view_id,
+               const ViewportMetrics& viewport_metrics,
+               AddViewCallback callback),
+              (override));
+
+  MOCK_METHOD(void,
+              OnPlatformViewRemoveView,
+              (int64_t view_id, RemoveViewCallback callback),
+              (override));
+
+  MOCK_METHOD(void,
               OnPlatformViewSetNextFrameCallback,
               (const fml::closure& closure),
               (override));
@@ -4505,8 +4517,8 @@
   ASSERT_EQ(viewIds[0], 0ll);
 
   PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
-    shell->AddView(2, ViewportMetrics{},
-                   [](bool added) { EXPECT_TRUE(added); });
+    shell->GetPlatformView()->AddView(2, ViewportMetrics{},
+                                      [](bool added) { EXPECT_TRUE(added); });
   });
   reportLatch.Wait();
   ASSERT_TRUE(hasImplicitView);
@@ -4514,7 +4526,8 @@
   ASSERT_EQ(viewIds[1], 2ll);
 
   PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
-    shell->RemoveView(2, [](bool removed) { ASSERT_TRUE(removed); });
+    shell->GetPlatformView()->RemoveView(
+        2, [](bool removed) { ASSERT_TRUE(removed); });
   });
   reportLatch.Wait();
   ASSERT_TRUE(hasImplicitView);
@@ -4522,8 +4535,8 @@
   ASSERT_EQ(viewIds[0], 0ll);
 
   PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
-    shell->AddView(4, ViewportMetrics{},
-                   [](bool added) { EXPECT_TRUE(added); });
+    shell->GetPlatformView()->AddView(4, ViewportMetrics{},
+                                      [](bool added) { EXPECT_TRUE(added); });
   });
   reportLatch.Wait();
   ASSERT_TRUE(hasImplicitView);
@@ -4570,13 +4583,13 @@
 
   // Add view 123.
   fml::AutoResetWaitableEvent add_latch;
-  PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
-           [&shell, &add_latch] {
-             shell->AddView(123, ViewportMetrics{}, [&](bool added) {
-               EXPECT_TRUE(added);
-               add_latch.Signal();
-             });
-           });
+  PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell,
+                                                             &add_latch] {
+    shell->GetPlatformView()->AddView(123, ViewportMetrics{}, [&](bool added) {
+      EXPECT_TRUE(added);
+      add_latch.Signal();
+    });
+  });
 
   add_latch.Wait();
 
@@ -4586,13 +4599,13 @@
   ASSERT_EQ(view_ids[1], 123);
 
   // Attempt to add duplicate view ID 123. This should fail.
-  PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
-           [&shell, &add_latch] {
-             shell->AddView(123, ViewportMetrics{}, [&](bool added) {
-               EXPECT_FALSE(added);
-               add_latch.Signal();
-             });
-           });
+  PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell,
+                                                             &add_latch] {
+    shell->GetPlatformView()->AddView(123, ViewportMetrics{}, [&](bool added) {
+      EXPECT_FALSE(added);
+      add_latch.Signal();
+    });
+  });
 
   add_latch.Wait();
 
@@ -4638,7 +4651,7 @@
   fml::AutoResetWaitableEvent remove_latch;
   PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
            [&shell, &remove_latch] {
-             shell->RemoveView(123, [&](bool removed) {
+             shell->GetPlatformView()->RemoveView(123, [&](bool removed) {
                EXPECT_FALSE(removed);
                remove_latch.Signal();
              });
@@ -4690,8 +4703,8 @@
     // The construtor for ViewportMetrics{_, width, _, _, _} (only the 2nd
     // argument matters in this test).
     platform_view->SetViewportMetrics(0, ViewportMetrics{1, 10, 1, 0, 0});
-    shell->AddView(1, ViewportMetrics{1, 30, 1, 0, 0},
-                   [](bool added) { ASSERT_TRUE(added); });
+    shell->GetPlatformView()->AddView(1, ViewportMetrics{1, 30, 1, 0, 0},
+                                      [](bool added) { ASSERT_TRUE(added); });
     platform_view->SetViewportMetrics(0, ViewportMetrics{1, 20, 1, 0, 0});
   });
 
@@ -4743,9 +4756,10 @@
     // A view can be added and removed all before the isolate launches.
     // The pending add view operation is cancelled, the view is never
     // added to the Dart isolate.
-    shell->AddView(123, ViewportMetrics{1, 30, 1, 0, 0},
-                   [](bool added) { ASSERT_FALSE(added); });
-    shell->RemoveView(123, [](bool removed) { ASSERT_FALSE(removed); });
+    shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 30, 1, 0, 0},
+                                      [](bool added) { ASSERT_FALSE(added); });
+    shell->GetPlatformView()->RemoveView(
+        123, [](bool removed) { ASSERT_FALSE(removed); });
   });
 
   bool first_report = true;
@@ -4791,15 +4805,15 @@
     auto platform_view = shell->GetPlatformView();
 
     // Add the same view twice. The second time should fail.
-    shell->AddView(123, ViewportMetrics{1, 100, 1, 0, 0},
-                   [](bool added) { ASSERT_TRUE(added); });
+    shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 100, 1, 0, 0},
+                                      [](bool added) { ASSERT_TRUE(added); });
 
-    shell->AddView(123, ViewportMetrics{1, 200, 1, 0, 0},
-                   [](bool added) { ASSERT_FALSE(added); });
+    shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 200, 1, 0, 0},
+                                      [](bool added) { ASSERT_FALSE(added); });
 
     // Add another view. Previous failures should not affect this.
-    shell->AddView(456, ViewportMetrics{1, 300, 1, 0, 0},
-                   [](bool added) { ASSERT_TRUE(added); });
+    shell->GetPlatformView()->AddView(456, ViewportMetrics{1, 300, 1, 0, 0},
+                                      [](bool added) { ASSERT_TRUE(added); });
   });
 
   bool first_report = true;
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
index 1333ef3..2e2515e 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
@@ -23,6 +23,10 @@
   void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
   void OnPlatformViewDestroyed() override {}
   void OnPlatformViewScheduleFrame() override {}
+  void OnPlatformViewAddView(int64_t view_id,
+                             const ViewportMetrics& viewport_metrics,
+                             AddViewCallback callback) override {}
+  void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
   void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
   void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
   const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
index f64a4db..aad952f 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
@@ -88,6 +88,10 @@
   void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
   void OnPlatformViewDestroyed() override {}
   void OnPlatformViewScheduleFrame() override {}
+  void OnPlatformViewAddView(int64_t view_id,
+                             const ViewportMetrics& viewport_metrics,
+                             AddViewCallback callback) override {}
+  void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
   void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
   void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
   const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
index dfffa7b..7d90da6 100644
--- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
+++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
@@ -71,6 +71,10 @@
   void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
   void OnPlatformViewDestroyed() override {}
   void OnPlatformViewScheduleFrame() override {}
+  void OnPlatformViewAddView(int64_t view_id,
+                             const ViewportMetrics& viewport_metrics,
+                             AddViewCallback callback) override {}
+  void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
   void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
   void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
   const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc
index 4e7da07..26214cb 100644
--- a/shell/platform/embedder/embedder.cc
+++ b/shell/platform/embedder/embedder.cc
@@ -2229,7 +2229,7 @@
     return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
   }
 
-  flutter::Shell::AddViewCallback callback =
+  flutter::PlatformView::AddViewCallback callback =
       [c_callback = info->add_view_callback,
        user_data = info->user_data](bool added) {
         FlutterAddViewResult result = {};
@@ -2239,7 +2239,8 @@
         c_callback(&result);
       };
 
-  embedder_engine->GetShell().AddView(view_id, metrics, callback);
+  embedder_engine->GetShell().GetPlatformView()->AddView(view_id, metrics,
+                                                         callback);
   return kSuccess;
 }
 
@@ -2271,7 +2272,7 @@
     return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
   }
 
-  flutter::Shell::RemoveViewCallback callback =
+  flutter::PlatformView::RemoveViewCallback callback =
       [c_callback = info->remove_view_callback,
        user_data = info->user_data](bool removed) {
         FlutterRemoveViewResult result = {};
@@ -2281,7 +2282,8 @@
         c_callback(&result);
       };
 
-  embedder_engine->GetShell().RemoveView(info->view_id, callback);
+  embedder_engine->GetShell().GetPlatformView()->RemoveView(info->view_id,
+                                                            callback);
   return kSuccess;
 }
 
diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h
index fd6d3db..41b455c 100644
--- a/shell/platform/embedder/embedder.h
+++ b/shell/platform/embedder/embedder.h
@@ -2583,8 +2583,19 @@
 /// @brief      Adds a view.
 ///
 ///             This is an asynchronous operation. The view should not be used
-///             until the |add_view_callback| is invoked with an `added` of
-///             `true`.
+///             until the |info.add_view_callback| is invoked with an |added|
+///             value of true. The embedder should prepare resources in advance
+///             but be ready to clean up on failure.
+///
+///             A frame is scheduled if the operation succeeds.
+///
+///             The callback is invoked on a thread managed by the engine. The
+///             embedder should re-thread if needed.
+///
+///             Attempting to add the implicit view will fail and will return
+///             kInvalidArguments. Attempting to add a view with an already
+///             existing view ID will fail, and |info.add_view_callback| will be
+///             invoked with an |added| value of false.
 ///
 /// @param[in]  engine  A running engine instance.
 /// @param[in]  info    The add view arguments. This can be deallocated
@@ -2602,8 +2613,16 @@
 /// @brief      Removes a view.
 ///
 ///             This is an asynchronous operation. The view's resources must not
-///             be cleaned up until the |remove_view_callback| is invoked with
-///             a |removed| value of `true`.
+///             be cleaned up until |info.remove_view_callback| is invoked with
+///             a |removed| value of true.
+///
+///             The callback is invoked on a thread managed by the engine. The
+///             embedder should re-thread if needed.
+///
+///             Attempting to remove the implicit view will fail and will return
+///             kInvalidArguments. Attempting to remove a view with a
+///             non-existent view ID will fail, and |info.remove_view_callback|
+///             will be invoked with a |removed| value of false.
 ///
 /// @param[in]  engine  A running engine instance.
 /// @param[in]  info    The remove view arguments. This can be deallocated
diff --git a/shell/platform/embedder/platform_view_embedder_unittests.cc b/shell/platform/embedder/platform_view_embedder_unittests.cc
index 6f0e923..b76c745 100644
--- a/shell/platform/embedder/platform_view_embedder_unittests.cc
+++ b/shell/platform/embedder/platform_view_embedder_unittests.cc
@@ -23,6 +23,16 @@
   MOCK_METHOD(void, OnPlatformViewDestroyed, (), (override));
   MOCK_METHOD(void, OnPlatformViewScheduleFrame, (), (override));
   MOCK_METHOD(void,
+              OnPlatformViewAddView,
+              (int64_t view_id,
+               const ViewportMetrics& viewport_metrics,
+               AddViewCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              OnPlatformViewRemoveView,
+              (int64_t view_id, RemoveViewCallback callback),
+              (override));
+  MOCK_METHOD(void,
               OnPlatformViewSetNextFrameCallback,
               (const fml::closure& closure),
               (override));
diff --git a/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
index b61d87b..911c618 100644
--- a/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
+++ b/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
@@ -88,6 +88,13 @@
   // |flutter::PlatformView::Delegate|
   void OnPlatformViewScheduleFrame() {}
   // |flutter::PlatformView::Delegate|
+  void OnPlatformViewAddView(int64_t view_id,
+                             const flutter::ViewportMetrics& viewport_metrics,
+                             AddViewCallback callback) override {}
+  // |flutter::PlatformView::Delegate|
+  void OnPlatformViewRemoveView(int64_t view_id,
+                                RemoveViewCallback callback) override {}
+  // |flutter::PlatformView::Delegate|
   void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) {}
   // |flutter::PlatformView::Delegate|
   void OnPlatformViewSetViewportMetrics(