From 503d964ef99c0776053f8fb156d98c109aedc494 Mon Sep 17 00:00:00 2001 From: Alan Trope Date: Fri, 22 May 2026 10:03:13 -0300 Subject: [PATCH 1/4] Add support for hybrid composition in Android ads - Introduced _GoogleMobileAdsHybridPlatformViewLink and _GoogleMobileAdsAndroidPlatformView classes to manage platform views based on hybrid composition settings. - Updated AdWidget to include useInitHybridAndroidView parameter, allowing control over hybrid view initialization. - Enhanced _AdWidgetState to check for hybrid composition support on Android and adjust view rendering accordingly. --- .../lib/src/ad_containers.dart | 244 ++++++++++++------ 1 file changed, 169 insertions(+), 75 deletions(-) diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index f86044a0e..9fe62fddd 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -688,6 +688,106 @@ abstract class AdWithoutView extends Ad { } } +class _GoogleMobileAdsHybridPlatformViewLink extends StatelessWidget { + const _GoogleMobileAdsHybridPlatformViewLink({ + required this.viewType, + required this.adId, + required this.useHcpp, + required this.useInitHybridAndroidView, + required this.platformViewKey, + }); + + final String viewType; + final int adId; + final bool useHcpp; + final bool useInitHybridAndroidView; + final Key platformViewKey; + + @override + Widget build(BuildContext context) { + return PlatformViewLink( + key: platformViewKey, + viewType: viewType, + surfaceFactory: ( + BuildContext context, + PlatformViewController controller, + ) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + final AndroidViewController controller = + useHcpp && useInitHybridAndroidView + ? PlatformViewsService.initHybridAndroidView( + id: params.id, + viewType: viewType, + layoutDirection: TextDirection.ltr, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ) + : PlatformViewsService.initSurfaceAndroidView( + id: params.id, + viewType: viewType, + layoutDirection: TextDirection.ltr, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + ); + + return controller + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); + }, + ); + } +} + +class _GoogleMobileAdsAndroidPlatformView extends StatelessWidget { + const _GoogleMobileAdsAndroidPlatformView({ + required this.viewType, + required this.adId, + required this.useHybridComposition, + required this.useInitHybridAndroidView, + required this.hcppSupported, + required this.platformViewKey, + }); + + final String viewType; + final int adId; + final bool useHybridComposition; + final bool useInitHybridAndroidView; + final bool? hcppSupported; + final Key platformViewKey; + + @override + Widget build(BuildContext context) { + if (!useHybridComposition) { + return AndroidView( + key: platformViewKey, + viewType: viewType, + creationParams: adId, + creationParamsCodec: const StandardMessageCodec(), + clipBehavior: Clip.none, + ); + } + + if (hcppSupported == null) { + return const SizedBox.shrink(); + } + + return _GoogleMobileAdsHybridPlatformViewLink( + viewType: viewType, + adId: adId, + useHcpp: hcppSupported!, + useInitHybridAndroidView: useInitHybridAndroidView, + platformViewKey: platformViewKey, + ); + } +} + /// Displays an [Ad] as a Flutter widget. /// /// This widget takes ads inheriting from [AdWithView] @@ -704,6 +804,7 @@ class AdWidget extends StatefulWidget { Key? key, required this.ad, this.useHybridComposition = false, + this.useInitHybridAndroidView = true, }) : super(key: key); /// Ad to be displayed as a widget. @@ -712,6 +813,10 @@ class AdWidget extends StatefulWidget { /// Use Hybrid composition or Virtual Display final bool useHybridComposition; + /// When hybrid composition is enabled and HCPP is supported, controls + /// whether [PlatformViewsService.initHybridAndroidView] is used. + final bool useInitHybridAndroidView; + @override _AdWidgetState createState() => _AdWidgetState(); } @@ -719,6 +824,10 @@ class AdWidget extends StatefulWidget { class _AdWidgetState extends State { bool _adIdAlreadyMounted = false; bool _adLoadNotCalled = false; + bool? _hcppSupported; + late final Key _platformViewKey = ValueKey( + instanceManager.adIdFor(widget.ad), + ); @override void initState() { @@ -732,6 +841,15 @@ class _AdWidgetState extends State { } else { _adLoadNotCalled = true; } + + if (defaultTargetPlatform == TargetPlatform.android && + widget.useHybridComposition) { + HybridAndroidViewController.checkIfSupported().then((supported) { + if (mounted) { + setState(() => _hcppSupported = supported); + } + }); + } } @override @@ -768,46 +886,23 @@ class _AdWidgetState extends State { ]); } final viewType = '${instanceManager.channel.name}/ad_widget'; - if (defaultTargetPlatform == TargetPlatform.android) { - return widget.useHybridComposition - ? PlatformViewLink( - viewType: viewType, - surfaceFactory: - (BuildContext context, PlatformViewController controller) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: - const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, - onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: viewType, - layoutDirection: TextDirection.ltr, - creationParams: instanceManager.adIdFor(widget.ad), - creationParamsCodec: StandardMessageCodec(), - ) - ..addOnPlatformViewCreatedListener( - params.onPlatformViewCreated, - ) - ..create(); - }, - ) - : AndroidView( - viewType: viewType, - creationParams: instanceManager.adIdFor(widget.ad), - creationParamsCodec: const StandardMessageCodec(), - clipBehavior: Clip.none, - ); - } - - return UiKitView( - viewType: viewType, - creationParams: instanceManager.adIdFor(widget.ad), - creationParamsCodec: StandardMessageCodec(), - ); + final adId = instanceManager.adIdFor(widget.ad)!; + + return switch (defaultTargetPlatform) { + TargetPlatform.android => _GoogleMobileAdsAndroidPlatformView( + viewType: viewType, + adId: adId, + useHybridComposition: widget.useHybridComposition, + useInitHybridAndroidView: widget.useInitHybridAndroidView, + hcppSupported: _hcppSupported, + platformViewKey: _platformViewKey, + ), + _ => UiKitView( + viewType: viewType, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + ), + }; } } @@ -833,6 +928,10 @@ class _FluidAdWidgetState extends State { bool _adIdAlreadyMounted = false; bool _adLoadNotCalled = false; double _height = 0; + bool? _hcppSupported; + late final Key _platformViewKey = ValueKey( + instanceManager.adIdFor(widget.ad), + ); @override void initState() { @@ -846,6 +945,14 @@ class _FluidAdWidgetState extends State { } else { _adLoadNotCalled = true; } + + if (defaultTargetPlatform == TargetPlatform.android) { + HybridAndroidViewController.checkIfSupported().then((supported) { + if (mounted) { + setState(() => _hcppSupported = supported); + } + }); + } } @override @@ -888,41 +995,28 @@ class _FluidAdWidgetState extends State { }); }; - Widget platformView; - double height; - if (defaultTargetPlatform == TargetPlatform.android) { - platformView = PlatformViewLink( - viewType: '${instanceManager.channel.name}/ad_widget', - surfaceFactory: - (BuildContext context, PlatformViewController controller) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: - const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, - onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: '${instanceManager.channel.name}/ad_widget', - layoutDirection: TextDirection.ltr, - creationParams: instanceManager.adIdFor(widget.ad), - creationParamsCodec: StandardMessageCodec(), - ) - ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) - ..create(); - }, - ); - height = _height / MediaQuery.of(context).devicePixelRatio; - } else { - platformView = UiKitView( - viewType: '${instanceManager.channel.name}/ad_widget', - creationParams: instanceManager.adIdFor(widget.ad), - creationParamsCodec: StandardMessageCodec(), - ); - height = _height; - } + final viewType = '${instanceManager.channel.name}/ad_widget'; + final adId = instanceManager.adIdFor(widget.ad)!; + + final Widget platformView = switch (defaultTargetPlatform) { + TargetPlatform.android => _GoogleMobileAdsAndroidPlatformView( + viewType: viewType, + adId: adId, + useHybridComposition: true, + useInitHybridAndroidView: true, + hcppSupported: _hcppSupported, + platformViewKey: _platformViewKey, + ), + _ => UiKitView( + viewType: viewType, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + ), + }; + + final height = defaultTargetPlatform == TargetPlatform.android + ? _height / MediaQuery.of(context).devicePixelRatio + : _height; return Container( height: max(1, height), From 301463af84a1c2044cd385e96614888c0c4db536 Mon Sep 17 00:00:00 2001 From: Alan Trope Date: Fri, 22 May 2026 10:05:38 -0300 Subject: [PATCH 2/4] Refactor ad container code for improved readability - Reformatted surfaceFactory and platform view initialization code for better clarity and consistency. - Ensured proper indentation and alignment in the _AdWidgetState and _FluidAdWidgetState classes to enhance maintainability. --- .../lib/src/ad_containers.dart | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index 9fe62fddd..0669795d9 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -708,17 +708,19 @@ class _GoogleMobileAdsHybridPlatformViewLink extends StatelessWidget { return PlatformViewLink( key: platformViewKey, viewType: viewType, - surfaceFactory: ( - BuildContext context, - PlatformViewController controller, - ) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, + surfaceFactory: + (BuildContext context, PlatformViewController controller) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: + const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, onCreatePlatformView: (PlatformViewCreationParams params) { + print( + 'onCreatePlatformView google_mobile_ads: $useHcpp, $useInitHybridAndroidView', + ); final AndroidViewController controller = useHcpp && useInitHybridAndroidView ? PlatformViewsService.initHybridAndroidView( @@ -890,18 +892,18 @@ class _AdWidgetState extends State { return switch (defaultTargetPlatform) { TargetPlatform.android => _GoogleMobileAdsAndroidPlatformView( - viewType: viewType, - adId: adId, - useHybridComposition: widget.useHybridComposition, - useInitHybridAndroidView: widget.useInitHybridAndroidView, - hcppSupported: _hcppSupported, - platformViewKey: _platformViewKey, - ), + viewType: viewType, + adId: adId, + useHybridComposition: widget.useHybridComposition, + useInitHybridAndroidView: widget.useInitHybridAndroidView, + hcppSupported: _hcppSupported, + platformViewKey: _platformViewKey, + ), _ => UiKitView( - viewType: viewType, - creationParams: adId, - creationParamsCodec: StandardMessageCodec(), - ), + viewType: viewType, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + ), }; } } @@ -1000,18 +1002,18 @@ class _FluidAdWidgetState extends State { final Widget platformView = switch (defaultTargetPlatform) { TargetPlatform.android => _GoogleMobileAdsAndroidPlatformView( - viewType: viewType, - adId: adId, - useHybridComposition: true, - useInitHybridAndroidView: true, - hcppSupported: _hcppSupported, - platformViewKey: _platformViewKey, - ), + viewType: viewType, + adId: adId, + useHybridComposition: true, + useInitHybridAndroidView: true, + hcppSupported: _hcppSupported, + platformViewKey: _platformViewKey, + ), _ => UiKitView( - viewType: viewType, - creationParams: adId, - creationParamsCodec: StandardMessageCodec(), - ), + viewType: viewType, + creationParams: adId, + creationParamsCodec: StandardMessageCodec(), + ), }; final height = defaultTargetPlatform == TargetPlatform.android From 42922a30ffc4851e9d6e34ff77ee4fd418894569 Mon Sep 17 00:00:00 2001 From: Alan Trope Date: Fri, 22 May 2026 10:08:07 -0300 Subject: [PATCH 3/4] Enhance logging for hybrid composition in ad containers - Updated logging statements in _GoogleMobileAdsHybridPlatformViewLink and _AdWidgetState to include ad IDs for better traceability during platform view creation and hybrid composition checks. - Improved clarity of log messages to facilitate debugging and monitoring of hybrid composition support in Android ads. --- packages/google_mobile_ads/lib/src/ad_containers.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index 0669795d9..84378b7dc 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -719,7 +719,7 @@ class _GoogleMobileAdsHybridPlatformViewLink extends StatelessWidget { }, onCreatePlatformView: (PlatformViewCreationParams params) { print( - 'onCreatePlatformView google_mobile_ads: $useHcpp, $useInitHybridAndroidView', + '[_GoogleMobileAdsHybridPlatformViewLink $adId] onCreatePlatformView google_mobile_ads hcpp: $useHcpp, $useInitHybridAndroidView', ); final AndroidViewController controller = useHcpp && useInitHybridAndroidView @@ -846,7 +846,13 @@ class _AdWidgetState extends State { if (defaultTargetPlatform == TargetPlatform.android && widget.useHybridComposition) { + print( + '[AdWidget ${instanceManager.adIdFor(widget.ad)}] google_mobile_ads init check hcpp', + ); HybridAndroidViewController.checkIfSupported().then((supported) { + print( + '[AdWidget ${instanceManager.adIdFor(widget.ad)}] google_mobile_ads init check hcpp: $supported', + ); if (mounted) { setState(() => _hcppSupported = supported); } From 9c4fce5c2ea61202ff541d54d7278661cbe54809 Mon Sep 17 00:00:00 2001 From: Alan Trope Date: Mon, 25 May 2026 16:32:08 -0300 Subject: [PATCH 4/4] Refactor logging to use debugPrint for better performance - Replaced print statements with debugPrint in _GoogleMobileAdsHybridPlatformViewLink and _AdWidgetState to enhance logging efficiency and reduce performance overhead during platform view creation and hybrid composition checks. --- packages/google_mobile_ads/lib/src/ad_containers.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index 84378b7dc..90df1f4f0 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -718,7 +718,7 @@ class _GoogleMobileAdsHybridPlatformViewLink extends StatelessWidget { ); }, onCreatePlatformView: (PlatformViewCreationParams params) { - print( + debugPrint( '[_GoogleMobileAdsHybridPlatformViewLink $adId] onCreatePlatformView google_mobile_ads hcpp: $useHcpp, $useInitHybridAndroidView', ); final AndroidViewController controller = @@ -846,11 +846,11 @@ class _AdWidgetState extends State { if (defaultTargetPlatform == TargetPlatform.android && widget.useHybridComposition) { - print( + debugPrint( '[AdWidget ${instanceManager.adIdFor(widget.ad)}] google_mobile_ads init check hcpp', ); HybridAndroidViewController.checkIfSupported().then((supported) { - print( + debugPrint( '[AdWidget ${instanceManager.adIdFor(widget.ad)}] google_mobile_ads init check hcpp: $supported', ); if (mounted) {