[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-platform-views-in-flutter-integrare-componenti-nativi-android-e-ios-nella-ui":3,"comments-article-platform-views-in-flutter-integrare-componenti-nativi-android-e-ios-nella-ui":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},131,"Platform Views in Flutter: integrare componenti nativi Android e iOS nella UI","platform-views-in-flutter-integrare-componenti-nativi-android-e-ios-nella-ui","Quando un widget Flutter non basta, puoi incorporare una view nativa direttamente nell'albero dei widget. Guida pratica ad AndroidView, UiKitView, PlatformViewLink, alle modalità di composizione su Android e ai costi reali in termini di performance.","## Perché le Platform Views\n\nFlutter disegna ogni pixel della propria interfaccia con il suo motore di rendering: i widget non sono view native, sono primitive grafiche composte su una canvas. Questo è il punto di forza del framework, ma crea un problema quando serve mostrare un componente che esiste **solo** in forma nativa: un SDK di mappe proprietario, un player DRM, un lettore di documenti, un controllo di firma digitale, un widget di un provider di pagamenti o un `WebView`.\n\nLa soluzione si chiama **Platform View**: un meccanismo che permette di incorporare una `android.view.View` o una `UIView` all'interno dell'albero dei widget Flutter, come se fosse un widget qualsiasi. È lo stesso meccanismo su cui sono costruiti pacchetti come `webview_flutter` e `google_maps_flutter`.\n\nIn questa guida vediamo come funziona davvero, come implementare una platform view da zero su Android e iOS, come gestire gesti e comunicazione bidirezionale e — soprattutto — quali costi comporta.\n\n## Quando usarle (e quando evitarle)\n\nLe platform views sono potenti ma costose: ogni view nativa rompe la pipeline di rendering di Flutter e obbliga il motore a comporre il frame insieme al sistema operativo. Prima di usarle chiediti:\n\n- **Posso ricostruire la UI in Flutter?** Nel 90% dei casi sì, con risultati migliori in termini di performance e coerenza visiva.\n- **Mi serve solo logica nativa, non UI?** Allora usa un platform channel (meglio se generato con Pigeon) o FFI: non serve una platform view.\n- **Mi serve solo un flusso di pixel** (es. frame di una camera o di un decoder video)? Il widget `Texture` è molto più efficiente: il nativo scrive su una `SurfaceTexture`\u002F`CVPixelBuffer` e Flutter la compone come texture GPU, senza interleaving di view.\n\nLe platform views restano la scelta giusta quando il componente nativo gestisce da sé input, scroll, accessibilità e rendering complesso — tipicamente un SDK di terze parti che non puoi riscrivere.\n\n## Le modalità di composizione su Android\n\nSu Android esistono tre strategie, con trade-off diversi. Conoscerle serve a capire perché a volte la tastiera, i gesti o le trasformazioni non si comportano come previsto.\n\n### Hybrid Composition (HC)\n\nLa view nativa viene aggiunta davvero alla gerarchia di view dell'Activity. Input, tastiera e accessibilità funzionano in modo nativo e perfetto. Il prezzo è che Flutter deve convertire la propria UI in una `FlutterImageView` quando una platform view è presente, con un costo di sincronizzazione tra thread UI e raster. API: `PlatformViewsService.initExpensiveAndroidView`.\n\n### Texture Layer Hybrid Composition (TLHC)\n\nLa view nativa viene renderizzata su una texture che Flutter compone nella propria scena. È la modalità consigliata e di default nelle versioni recenti del framework: costa molto meno di HC e mantiene buona parte dei vantaggi. Alcune view \"esotiche\" (quelle che disegnano su `SurfaceView` propri o usano hardware overlay) non sono compatibili e il framework ricade automaticamente su Hybrid Composition. API: `PlatformViewsService.initSurfaceAndroidView`.\n\n### Virtual Display (VD)\n\nLa modalità storica: la view viene renderizzata in un display virtuale off-screen e il risultato è copiato in una texture. Funziona ovunque ma ha problemi noti con tastiera, accessibilità e gestione del testo. API: `PlatformViewsService.initAndroidView`. Va considerata solo come fallback.\n\nSu **iOS** il discorso è più semplice: esiste una sola modalità, basata su hybrid composition, esposta dal widget `UiKitView`. Il flag `io.flutter.embedded_views_preview` nell'`Info.plist`, richiesto dalle vecchie versioni, non serve più da anni.\n\n## Il lato Dart: AndroidView, UiKitView e PlatformViewLink\n\nLa versione più semplice usa direttamente i widget `AndroidView` e `UiKitView`:\n\n```dart\nimport 'package:flutter\u002Ffoundation.dart';\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'package:flutter\u002Fservices.dart';\n\nclass NativeBadgeView extends StatelessWidget {\n  const NativeBadgeView({super.key, required this.text});\n\n  final String text;\n\n  static const String viewType = 'it.example\u002Fnative_badge';\n\n  @override\n  Widget build(BuildContext context) {\n    final creationParams = \u003CString, dynamic>{'text': text};\n\n    switch (defaultTargetPlatform) {\n      case TargetPlatform.android:\n        return AndroidView(\n          viewType: viewType,\n          layoutDirection: TextDirection.ltr,\n          creationParams: creationParams,\n          creationParamsCodec: const StandardMessageCodec(),\n        );\n      case TargetPlatform.iOS:\n        return UiKitView(\n          viewType: viewType,\n          layoutDirection: TextDirection.ltr,\n          creationParams: creationParams,\n          creationParamsCodec: const StandardMessageCodec(),\n        );\n      default:\n        return const SizedBox.shrink();\n    }\n  }\n}\n```\n\nSe vuoi **controllo esplicito sulla modalità di composizione** su Android, usa `PlatformViewLink`:\n\n```dart\nimport 'package:flutter\u002Fgestures.dart';\nimport 'package:flutter\u002Frendering.dart';\n\nWidget buildAndroidBadge(Map\u003CString, dynamic> creationParams) {\n  return PlatformViewLink(\n    viewType: NativeBadgeView.viewType,\n    surfaceFactory: (context, controller) {\n      return AndroidViewSurface(\n        controller: controller as AndroidViewController,\n        hitTestBehavior: PlatformViewHitTestBehavior.opaque,\n        gestureRecognizers: const \u003CFactory\u003COneSequenceGestureRecognizer>>{},\n      );\n    },\n    onCreatePlatformView: (params) {\n      return PlatformViewsService.initSurfaceAndroidView(\n        id: params.id,\n        viewType: NativeBadgeView.viewType,\n        layoutDirection: TextDirection.ltr,\n        creationParams: creationParams,\n        creationParamsCodec: const StandardMessageCodec(),\n        onFocus: () => params.onFocusChanged(true),\n      )\n        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)\n        ..create();\n    },\n  );\n}\n```\n\nSostituendo `initSurfaceAndroidView` con `initExpensiveAndroidView` ottieni Hybrid Composition pura: utile se la view nativa usa un `SurfaceView` interno (player video, renderer OpenGL) e con TLHC vedi artefatti o schermate nere.\n\n## Il lato Android: factory e PlatformView\n\nSu Android devi fornire una `PlatformViewFactory` e registrarla con un identificativo di tipo (`viewType`) identico a quello usato in Dart.\n\n```kotlin\nimport android.content.Context\nimport android.view.View\nimport android.widget.TextView\nimport io.flutter.plugin.common.BinaryMessenger\nimport io.flutter.plugin.common.MethodCall\nimport io.flutter.plugin.common.MethodChannel\nimport io.flutter.plugin.common.StandardMessageCodec\nimport io.flutter.plugin.platform.PlatformView\nimport io.flutter.plugin.platform.PlatformViewFactory\n\nclass NativeBadgeFactory(\n    private val messenger: BinaryMessenger,\n) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {\n\n    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {\n        @Suppress(\"UNCHECKED_CAST\")\n        val params = args as? Map\u003CString, Any?> ?: emptyMap()\n        return NativeBadgeView(context, viewId, params, messenger)\n    }\n}\n\nclass NativeBadgeView(\n    context: Context,\n    viewId: Int,\n    params: Map\u003CString, Any?>,\n    messenger: BinaryMessenger,\n) : PlatformView, MethodChannel.MethodCallHandler {\n\n    private val textView = TextView(context).apply {\n        text = params[\"text\"] as? String ?: \"\"\n        textSize = 18f\n    }\n\n    private val channel = MethodChannel(messenger, \"it.example\u002Fnative_badge_$viewId\")\n\n    init {\n        channel.setMethodCallHandler(this)\n        textView.setOnClickListener { channel.invokeMethod(\"onTap\", null) }\n    }\n\n    override fun getView(): View = textView\n\n    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {\n        when (call.method) {\n            \"setText\" -> {\n                textView.text = call.argument\u003CString>(\"text\").orEmpty()\n                result.success(null)\n            }\n            else -> result.notImplemented()\n        }\n    }\n\n    override fun dispose() {\n        channel.setMethodCallHandler(null)\n    }\n}\n```\n\nLa registrazione avviene nel plugin (o nella `MainActivity`, se la view vive solo nell'app):\n\n```kotlin\nclass NativeBadgePlugin : FlutterPlugin {\n    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {\n        binding.platformViewRegistry.registerViewFactory(\n            \"it.example\u002Fnative_badge\",\n            NativeBadgeFactory(binding.binaryMessenger),\n        )\n    }\n\n    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) = Unit\n}\n```\n\n**Attenzione al `dispose()`**: è il punto in cui devi rilasciare listener, canali e risorse native. Dimenticarlo è una delle cause più comuni di memory leak nelle app che usano platform views in liste.\n\n## Il lato iOS: FlutterPlatformViewFactory\n\n```swift\nimport Flutter\nimport UIKit\n\nclass NativeBadgeFactory: NSObject, FlutterPlatformViewFactory {\n    private let messenger: FlutterBinaryMessenger\n\n    init(messenger: FlutterBinaryMessenger) {\n        self.messenger = messenger\n        super.init()\n    }\n\n    func create(withFrame frame: CGRect,\n                viewIdentifier viewId: Int64,\n                arguments args: Any?) -> FlutterPlatformView {\n        NativeBadgeView(frame: frame, viewId: viewId, args: args, messenger: messenger)\n    }\n\n    func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {\n        FlutterStandardMessageCodec.sharedInstance()\n    }\n}\n\nclass NativeBadgeView: NSObject, FlutterPlatformView {\n    private let label: UILabel\n    private let channel: FlutterMethodChannel\n\n    init(frame: CGRect, viewId: Int64, args: Any?, messenger: FlutterBinaryMessenger) {\n        label = UILabel(frame: frame)\n        channel = FlutterMethodChannel(\n            name: \"it.example\u002Fnative_badge_\\(viewId)\",\n            binaryMessenger: messenger\n        )\n        super.init()\n\n        if let params = args as? [String: Any] {\n            label.text = params[\"text\"] as? String\n        }\n        label.font = .systemFont(ofSize: 18)\n\n        channel.setMethodCallHandler { [weak self] call, result in\n            guard call.method == \"setText\" else { return result(FlutterMethodNotImplemented) }\n            let args = call.arguments as? [String: Any]\n            self?.label.text = args?[\"text\"] as? String\n            result(nil)\n        }\n    }\n\n    func view() -> UIView { label }\n}\n```\n\nE la registrazione nel plugin:\n\n```swift\npublic static func register(with registrar: FlutterPluginRegistrar) {\n    let factory = NativeBadgeFactory(messenger: registrar.messenger())\n    registrar.register(factory, withId: \"it.example\u002Fnative_badge\")\n}\n```\n\nNota il `viewId` nel nome del canale: avere un canale per istanza evita di confondere i messaggi quando la stessa platform view compare più volte nella schermata.\n\n## Controllare la view dal lato Dart\n\nUna volta creata la view, usa il callback `onPlatformViewCreated` per costruire il controller Dart:\n\n```dart\nclass NativeBadgeController {\n  NativeBadgeController(int viewId)\n      : _channel = MethodChannel('it.example\u002Fnative_badge_$viewId');\n\n  final MethodChannel _channel;\n\n  Future\u003Cvoid> setText(String text) =>\n      _channel.invokeMethod\u003Cvoid>('setText', {'text': text});\n\n  void onTap(VoidCallback callback) {\n    _channel.setMethodCallHandler((call) async {\n      if (call.method == 'onTap') callback();\n    });\n  }\n}\n```\n\nPer progetti reali conviene generare questi canali con **Pigeon**, ottenendo API tipizzate e nessun errore di battitura nei nomi dei metodi.\n\n## Gesti: chi vince il tocco?\n\nPer impostazione predefinita, l'arena dei gesti di Flutter può \"rubare\" i tocchi alla view nativa: se la platform view si trova dentro un `ListView` o un `PageView`, lo scroll di Flutter ha la precedenza. Puoi cambiare questo comportamento passando dei gesture recognizer:\n\n```dart\nUiKitView(\n  viewType: NativeBadgeView.viewType,\n  gestureRecognizers: \u003CFactory\u003COneSequenceGestureRecognizer>>{\n    Factory\u003COneSequenceGestureRecognizer>(() => EagerGestureRecognizer()),\n  },\n)\n```\n\n`EagerGestureRecognizer` fa vincere subito la view nativa: tutti i tocchi arrivano al componente nativo. Se invece vuoi delegare solo gesti specifici, inserisci i recognizer corrispondenti (`VerticalDragGestureRecognizer`, `ScaleGestureRecognizer`, ecc.). È il modo corretto per far convivere, ad esempio, una mappa nativa con pinch-to-zoom dentro una pagina scrollabile.\n\n## Insidie e buone pratiche\n\n- **Non metterle in liste lunghe.** Ogni platform view comporta overhead di composizione. Dieci `WebView` dentro una `ListView.builder` sono una ricetta per i jank.\n- **Trasformazioni e opacità limitate.** Rotazioni, `Opacity`, `ClipPath` complessi e `BackdropFilter` applicati sopra una platform view possono non essere rispettati o costare molto. Progetta la UI in modo che la view nativa sia un rettangolo piatto.\n- **Golden test impossibili.** Le platform views non vengono renderizzate nei test di widget: nei golden test appaiono come aree vuote. Isolale dietro un'astrazione e sostituiscile con un placeholder nei test.\n- **Attenzione al ciclo di vita.** Rilascia le risorse native in `dispose()` lato nativo e cancella gli handler dei canali lato Dart, altrimenti i leak si accumulano a ogni navigazione.\n- **Verifica con Impeller.** Il motore di rendering corrente gestisce le platform views, ma alcune combinazioni con `SurfaceView` o effetti grafici possono comportarsi diversamente rispetto a Skia: testa su dispositivi reali, non solo sull'emulatore.\n- **Accessibilità.** Con TLHC e HC l'albero di semantica nativo viene unito a quello di Flutter; con Virtual Display no. È un altro motivo per evitare la modalità VD.\n\n## In sintesi\n\nLe platform views sono la via d'uscita quando un SDK nativo non ha equivalenti in Flutter. Il pattern è sempre lo stesso: una factory registrata con un `viewType`, un widget `AndroidView`\u002F`UiKitView` (o `PlatformViewLink` per il controllo fine della composizione) e un `MethodChannel` per istanza per la comunicazione bidirezionale.\n\nUsate con parsimonia, risolvono problemi altrimenti bloccanti. Usate come scorciatoia per non riscrivere la UI, diventano rapidamente il collo di bottiglia delle performance della tua app: prima di aggiungerne una, chiediti sempre se un widget Flutter, un `Texture` o un semplice platform channel non siano la risposta migliore.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F271ed78b-30bd-4d6b-9b4c-418e5d52a495.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1639200413098-017ed1f7df45?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTIyOTYxMjN8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Jonathan Borba","https:\u002F\u002Funsplash.com\u002F@jonathanborba","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-couple-of-samsung-phones-sitting-next-to-each-other-SXOy8t1rNiA",null,"published","2026-10-18T04:02:04+00:00","Platform Views in Flutter: UI native Android e iOS","Come integrare view native Android e iOS in Flutter con AndroidView, UiKitView e PlatformViewLink: codice Kotlin e Swift, gesti, modalità di composizione.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1792316896829]