Repository: flutter
Issue: PlatformException when showing native screens through PlatformView in Flutter as a module in native app, but works fine running the Flutter module directly
Labels: platform-ios,a: platform-views,has reproducible steps,P2,team-ios,triaged-ios,found in release: 3.16,found in release: 3.18

### Steps to reproduce

I'm trying to integrate Flutter as a module in an iOS app and show native screen i have in the ios/Runner folder from Flutter using PlatformView.

So as a test I fired up a side project and from a Flutter app showing a native ios screen is pretty straightforward and it all works as expected. Then I turned itinto a module by adding `module` property in the `pubspec.yaml` file as : 
```
  module:
    androidX: true
    androidPackage: com.test.embeded
    iosBundleIdentifier: com.test.embeded
```



I then created a new ios app native project with the flutter project integrated as a module, set up the `FlutterEngine` and everything else.

### Expected results

I was expecting to see native screens inside the PlatformView when i run the Xcode project as I do when the Flutter project (the module)  from VSCode.

### Actual results

I get PlatformException when navigating to the PlatformView when Flutter is a module inside the native iOS app.

```
flutter: [MAIN] -> PlatformDispatcher.instance.onError ⚠️: PlatformException(unregistered_view_type, A UIKitView widget is trying to create a PlatformView with an unregistered type: < <platform-view-type> >, If you are the author of the PlatformView, make sure `registerViewFactory` is invoked.
See: https://docs.flutter.dev/development/platform-integration/platform-views#on-the-platform-side-1 for more details.
If you are not the author of the PlatformView, make sure to call `GeneratedPluginRegistrant.register`., null)
flutter: [MAIN] -> PlatformDispatcher.instance.onError ⚠️: #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:651:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:322:18)
<asynchronous suspension>
#2      PlatformViewsService.initUiKitView (package:flutter/src/services/platform_views.dart:243:5)
<asynchronous suspension>
#3      _DarwinViewState._createNewUiKitView (package:flutter/src/widgets/platform_view.dart:695:36)
<asynchronous suspension>
```


### Code sample

# Flutter as a module.

main.dart:
```
import 'package:flutter/material.dart';
import 'package:native_in_fluter/home.dart';

@pragma('vm:entry-point')
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const Home(title: 'Flutter home screen'),
    );
  }
}
```

home.dart:
```
import 'dart:developer' as console;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:native_in_fluter/native_viewer.dart';
import 'dart:io';
import 'package:flutter/cupertino.dart';

class Home extends StatefulWidget {
  final String title;
  const Home({super.key, required this.title});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late MethodChannel channel; // = const MethodChannel('test_platform_channel');
  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    channel = const MethodChannel('native_viewer_platform_channel');
    channel.setMethodCallHandler((call) {
      // console.log('Home setMethodCallHandler set');
      switch (call.method) {
        case 'swift_to_flutter':
          console.log(
              'swift_to_flutter received in Home with args ${call.arguments}');

          break;
        default:
      }
      return call.arguments;
    });
  }

  @override
  Widget build(BuildContext context) {
    dynamic backButton =
        Platform.isIOS ? CupertinoIcons.back : Icons.arrow_back;

    Map<String, dynamic> params = <String, dynamic>{};
    String nativeViewTitle = '';
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
        leading: IconButton(
            icon: Icon(backButton),
            color: Colors.redAccent,
            onPressed: () {
              channel.invokeMethod('close');
              // Navigator.pop(context);
            }),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                nativeViewTitle = 'Screen A';
                params.clear();
                params = {
                  'screen': 'viewController',
                  'param 1': 'vc button A',
                  'param 2': 'vc button B'
                };
                pushNativeView(
                    context: context,
                    nativeViewTitle: nativeViewTitle,
                    params: params);
                callNative();
              },
              child: const Text(
                'Navigate to ios screen A',
              ),
            ),
            ElevatedButton(
              onPressed: () {
                nativeViewTitle = 'Screen B';
                params.clear();
                params = {
                  'screen': 'viewController2',
                  'param 1': 'vc2 button A',
                  'param 2': 'vc2 button B'
                };
                pushNativeView(
                    context: context,
                    nativeViewTitle: nativeViewTitle,
                    params: params);
                callNative();
                // channel.invokeMethod(
                //     "navigate_to-screen_b", ["screen_b"]).catchError((e) {
                //   console.log('invokeMethod b error: $e');
                // });
              },
              child: const Text(
                'Navigate to ios screen B',
              ),
            )
          ],
        ),
      ),
    );
  }

  // void callNative() {
  //   channel.invokeMethod("test", []).catchError((e) {
  //     console.log('invokeMethod test error: $e');
  //   });
  // }

  void callNative() async {
    await channel.invokeMethod("test", []).catchError((e) {
      console.log('invokeMethod test error: $e');
    });
  }

  void pushNativeView(
      {required BuildContext context,
      required String nativeViewTitle,
      required Map<String, dynamic> params}) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => NativeViewer(
            nativeScreen: "scgline uno",
            title: nativeViewTitle,
            viewParams: params),
      ),
    );
  }
}
```
PlatformView screen:
```
import 'dart:io';
import 'dart:developer' as console;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:native_in_fluter/adaptive_platform_view.dart';

class NativeViewer extends StatefulWidget {
  final String title;
  final String nativeScreen;
  final Map<String, dynamic> viewParams;
  
  const NativeViewer(
      {super.key,
      required this.title,
      required this.viewParams,
      required this.nativeScreen});

  @override
  State<NativeViewer> createState() => _NativeViewerState();
}

class _NativeViewerState extends State<NativeViewer> {
  late MethodChannel channel; // = const MethodChannel('test_platform_channel');
  @override
  void initState() {
    super.initState();
    channel = const MethodChannel('native_viewer_platform_channel');
    channel.setMethodCallHandler((call) {
      console.log('NativeViewer setMethodCallHandler set');
      switch (call.method) {
        case 'swift_to_flutter':
          console.log(
              'swift_to_flutter received in NativeViewer with args ${call.arguments}');

          break;
        default:
      }
      return call.arguments;
    });
  }

  @override
  Widget build(BuildContext context) {
    dynamic backButton =
        Platform.isIOS ? CupertinoIcons.back : Icons.arrow_back;
    // This is used in the platform side to register the view.
    const String viewType = '<platform-view-type>';
    // Pass parameters to the platform side.
    final Map<String, dynamic> creationParams = widget.viewParams;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(
          widget.title,
          // style: const TextStyle(fontSize: 30, color: Colors.white),
        ),
        leading: IconButton(
            icon: Icon(backButton),
            // color: Colors.redAccent,
            onPressed: () {
              Navigator.pop(context);
            }),
      ),
      body: AdatptivePlatformView(
        nativeScreen: widget.nativeScreen,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      ),
    );
  }
}
```

adaptive PlatformView:
```
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class AdatptivePlatformView extends StatelessWidget {
  final String nativeScreen;
  final String viewType;
  final dynamic creationParams;
  final TextDirection? layoutDirection;
  final MessageCodec<dynamic>? creationParamsCodec;
  const AdatptivePlatformView(
      {super.key,
      required this.viewType,
      this.creationParams,
      this.creationParamsCodec,
      this.layoutDirection, 
      required this.nativeScreen});

  @override
  Widget build(BuildContext context) {
    return Platform.isAndroid
        ? AndroidView(
            viewType: viewType,
            layoutDirection: TextDirection.ltr,
            creationParams: creationParams,
            creationParamsCodec: const StandardMessageCodec(),
          )
        : UiKitView(
            viewType: viewType,
            layoutDirection: TextDirection.ltr,
            creationParams: creationParams,
            creationParamsCodec: const StandardMessageCodec(),
          );
  }
}
```

AppDelegate:

```
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    var nativeViewerPlatformCahannel: FlutterMethodChannel?
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      let controller : FlutterViewController = window?.rootViewController as! FlutterViewController;
      nativeViewerPlatformCahannel = FlutterMethodChannel(name: "native_viewer_platform_channel",binaryMessenger: controller.binaryMessenger);
      methodChannel?.invokeMethod("swift_to_flutter", arguments: ["AppDelegate method channel instantiated"]);
      GeneratedPluginRegistrant.register(with: self);
      // MARK: NATIVE in Flutter setup

      weak var registrar = self.registrar(forPlugin: "plugin-name");
      let factory = DynamicNativeViewFactory(messenger: registrar!.messenger())
      self.registrar(forPlugin: "<plugin-name>")!.register(
          factory,
          withId: "<platform-view-type>")
          
      
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

```

Factory:
```
import Flutter
import UIKit


class DynamicNativeViewFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger
    
    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }

    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        return DynamicNativeView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger)
    }

    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
          return FlutterStandardMessageCodec.sharedInstance()
    }
}

class DynamicNativeView: NSObject, FlutterPlatformView {
    private var _view: UIView = UIView();
    

    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        if let params = args as? [String:AnyObject] {
            var screen: String = params["screen"] as! String;
            
            if let vc = ViewControllerGetter(rawValue: screen)?.getViewController(with: params) {
                _view = vc.view;
            }
        }
        super.init()
    }

    func view() -> UIView {
        return _view
    }
}
```

vc getter:
```
import Foundation

enum ViewControllerGetter: String {
    case viewController
    case viewController2
    
    func getViewController (with params: [String: AnyObject]?) -> UIViewController? {
        switch self {
        case .viewController:
            if let vcParams = params {
                let vc = ViewController();
                vc.buttonTitleA = params!["param 1"] as! String;
                vc.buttonTitleB = params!["param 2"] as! String;
                return vc
            }
        case .viewController2:
            if let vcParams = params {
                let vc = ViewController2();
                vc.buttonTitleA = params!["param 1"] as! String;
                vc.buttonTitleB = params!["param 2"] as! String;
                return vc
            }

        }
        return nil
    }
}
```


# iOS app

AppDelegate:
```
import UIKit
import Flutter
import FlutterPluginRegistrant


@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    lazy var flutterEngine = FlutterEngine(name: "my flutter engine");


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        flutterEngine.run(withEntrypoint: "main", libraryURI: "package:native_in_fluter/main.dart");
        
        GeneratedPluginRegistrant.register(with: flutterEngine);

        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}

```
ViewController:
```
import UIKit
import Flutter

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let buttonA = UIButton(type:UIButton.ButtonType.custom)
        buttonA.addTarget(self, action: #selector(showFlutterScreen), for: .touchUpInside)
        buttonA.setTitle("Show flutter", for: UIControl.State.normal)
        buttonA.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        buttonA.backgroundColor = UIColor.systemGray5
        buttonA.setTitleColor(UIColor.systemBlue, for: .normal)// title.tintColor = UIColor.systemBlue
        buttonA.layer.cornerRadius = 8;
        self.view.addSubview(buttonA)

    }
    
    @objc func showFlutterScreen() {

        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
        let methodChannelA = FlutterMethodChannel(name: "native_viewer_platform_channel",
                                                  binaryMessenger: flutterEngine.binaryMessenger);


        methodChannelA.setMethodCallHandler({ //[weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            print("\(String (describing: call.method)) method invocation received on \"native_viewer_platform_channel\" method channel with arguments:  \(String(describing: call.arguments))" );

            switch(call.method) {
            case "close":
                self.dismissViewController(vc: flutterViewController)
            default:
                print("Unrecognized method name: \(call.method)")
            }
        })
        presentViewController(vc: flutterViewController);
        
      }


    func presentViewController(vc: FlutterViewController){
        vc.modalPresentationStyle = .overCurrentContext;
        present(vc, animated: true);
    }
    
    func dismissViewController(vc: FlutterViewController){
        vc.dismiss(animated: true);
    }




}

```


### Screenshots or Video

<details open>
<summary>Screenshots / Video demonstration</summary>

[Upload media here]

</details>


### Logs

<details open><summary>Logs</summary>

```console
[Paste your logs here]
```

</details>


### Flutter Doctor output

[✓] Flutter (Channel stable, 3.16.2, on macOS 14.1.1 23B2082 darwin-arm64, locale en-US)
    • Flutter version 3.16.2 on channel stable at /Users/vincenzo.calia/Documents/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 9e1c857886 (5 weeks ago), 2023-11-30 11:51:18 -0600
    • Engine revision cf7a9d0800
    • Dart version 3.2.2
    • DevTools version 2.28.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/vincenzo.calia/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • ANDROID_SDK_ROOT = /Users/vincenzo.calia/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15A507
    • CocoaPods version 1.14.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)

[✓] VS Code (version 1.85.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.80.0

[✓] VS Code (version 1.84.2)
    • VS Code at /Users/vincenzo.calia/Downloads/Visual Studio Code.app/Contents
    • Flutter extension version 3.80.0

[✓] Connected device (3 available)
    • iPhone 15 Pro (mobile) • 87E520D0-E3B9-4AF6-BCA9-12B4A5482101 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-17-0 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 14.1.1 23B2082 darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 120.0.6099.129
    ! Error: Browsing on the local area network for iPhone. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources
    • All expected network resources are available.

• No issues found!