From b4e72c4d1bec55934173c933207fdd2d3b00c961 Mon Sep 17 00:00:00 2001 From: Markus Korn Date: Sun, 18 Feb 2024 15:07:48 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20refactor(main.dart):=20import=20?= =?UTF-8?q?'dart:async'=20and=20'package:xandr/load=5Fmode.dart'=20to=20su?= =?UTF-8?q?pport=20asynchronous=20operations=20and=20load=20mode=20functio?= =?UTF-8?q?nality=20=E2=9C=A8=20feat(main.dart):=20add=20ScrollController?= =?UTF-8?q?=20and=20StreamController=20to=20manage=20scroll=20events=20and?= =?UTF-8?q?=20check=20if=20ad=20is=20in=20viewport=20=F0=9F=94=A7=20refact?= =?UTF-8?q?or(main.dart):=20add=20listener=20to=20=5FscrollController=20to?= =?UTF-8?q?=20add=20its=20position=20to=20=5FcheckIfAdIsInViewport=20strea?= =?UTF-8?q?m=20=E2=9C=A8=20feat(main.dart):=20add=20more=20Text=20widgets?= =?UTF-8?q?=20and=20a=20new=20AdBanner=20with=20loadMode=20set=20to=20Load?= =?UTF-8?q?Mode.whenInViewport=20to=20demonstrate=20ad=20loading=20when=20?= =?UTF-8?q?in=20viewport=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20im?= =?UTF-8?q?port=20'dart:async'=20and=20'package:xandr/load=5Fmode.dart'=20?= =?UTF-8?q?to=20support=20asynchronous=20operations=20and=20load=20mode=20?= =?UTF-8?q?functionality=20=E2=9C=A8=20feat(ad=5Fbanner.dart):=20add=20loa?= =?UTF-8?q?dMode=20parameter=20to=20AdBanner=20and=20=5FHostAdBannerView?= =?UTF-8?q?=20to=20determine=20when=20the=20ad=20is=20loaded=20=E2=9C=A8?= =?UTF-8?q?=20feat(ad=5Fbanner.dart):=20add=20loadAd=20method=20to=20trigg?= =?UTF-8?q?er=20ad=20loading=20via=20MethodChannel=20=E2=9C=A8=20feat(ad?= =?UTF-8?q?=5Fbanner.dart):=20add=20=5FcheckViewport=20method=20to=20check?= =?UTF-8?q?=20if=20the=20ad=20is=20in=20the=20viewport=20and=20load=20it?= =?UTF-8?q?=20if=20it=20is=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20?= =?UTF-8?q?call=20loadAd=20in=20initState=20if=20loadMode=20is=20LoadWhenC?= =?UTF-8?q?reated=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20call=20?= =?UTF-8?q?=5FcheckViewport=20in=20initState=20if=20loadMode=20is=20WhenIn?= =?UTF-8?q?Viewport=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20add=20o?= =?UTF-8?q?nDoneLoading=20callback=20to=20=5FHostAdBannerView=20to=20updat?= =?UTF-8?q?e=20=5Floading=20and=20=5Floaded=20states=20when=20ad=20loading?= =?UTF-8?q?=20is=20done=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20cal?= =?UTF-8?q?l=20loadAd=20in=20=5FHostAdBannerView's=20onPlatformViewCreated?= =?UTF-8?q?=20if=20loadMode=20is=20LoadWhenCreated=20=F0=9F=94=A7=20refact?= =?UTF-8?q?or(ad=5Fbanner.dart):=20add=20loadMode=20to=20creationParams=20?= =?UTF-8?q?to=20pass=20it=20to=20the=20native=20code=20=F0=9F=94=A7=20refa?= =?UTF-8?q?ctor(ad=5Fbanner.dart):=20add=20widgetId=20to=20=5FHostAdBanner?= =?UTF-8?q?View=20to=20pass=20it=20to=20the=20native=20code=20=F0=9F=94=A7?= =?UTF-8?q?=20refactor(ad=5Fbanner.dart):=20complete=20widgetId=20in=20=5F?= =?UTF-8?q?HostAdBannerView's=20onPlatformViewCreated=20if=20it's=20not=20?= =?UTF-8?q?completed=20yet=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20?= =?UTF-8?q?call=20onDoneLoading=20in=20=5FHostAdBannerView's=20onPlatformV?= =?UTF-8?q?iewCreated=20when=20ad=20loading=20is=20done=20=F0=9F=94=A7=20r?= =?UTF-8?q?efactor(ad=5Fbanner.dart):=20call=20loadAd=20in=20=5FHostAdBann?= =?UTF-8?q?erView's=20onPlatformViewCreated=20if=20loadMode=20is=20LoadWhe?= =?UTF-8?q?nCreated=20=F0=9F=94=A7=20refactor(ad=5Fbanner.dart):=20add=20l?= =?UTF-8?q?oadMode=20to=20=5FHostAdBannerView's=20creationParams=20to=20pa?= =?UTF-8?q?ss=20it=20to=20the=20native=20code=20=F0=9F=94=A7=20refactor(ad?= =?UTF-8?q?=5Fbanner.dart):=20add=20loadMode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/xandr/example/lib/main.dart | 68 +++++++++++++++ packages/xandr/lib/ad_banner.dart | 84 ++++++++++++++++++- packages/xandr/lib/load_mode.dart | 54 ++++++++++++ packages/xandr/lib/xandr.dart | 6 ++ .../de/thekorn/xandr/BannerViewContainer.kt | 30 +++++-- .../de/thekorn/xandr/BannerViewFactory.kt | 9 +- .../main/kotlin/de/thekorn/xandr/Xandr.g.kt | 21 +++++ .../kotlin/de/thekorn/xandr/XandrPlugin.kt | 10 +++ .../thekorn/xandr/models/BannerViewOptions.kt | 3 +- .../de/thekorn/xandr/models/FlutterState.kt | 28 +++++++ .../xandr_android/lib/src/messages.g.dart | 29 +++++++ packages/xandr_android/lib/xandr_android.dart | 7 ++ packages/xandr_android/pigeons/messages.dart | 5 ++ .../lib/src/interface.dart | 5 ++ .../lib/src/method_channel.dart | 5 ++ 15 files changed, 346 insertions(+), 18 deletions(-) create mode 100644 packages/xandr/lib/load_mode.dart diff --git a/packages/xandr/example/lib/main.dart b/packages/xandr/example/lib/main.dart index 1fa07cc..44fe4f6 100644 --- a/packages/xandr/example/lib/main.dart +++ b/packages/xandr/example/lib/main.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:xandr/ad_banner.dart'; import 'package:xandr/ad_size.dart'; +import 'package:xandr/load_mode.dart'; import 'package:xandr/xandr.dart'; import 'package:xandr/xandr_builder.dart'; @@ -34,12 +37,25 @@ class XandrExample extends StatefulWidget { class _XandrExampleState extends State { late final XandrController _controller; + final ScrollController _scrollController = ScrollController(); + final StreamController _checkIfAdIsInViewport = + StreamController(); + + @override + void dispose() { + _scrollController.dispose(); + _checkIfAdIsInViewport.close(); + super.dispose(); + } @override void initState() { super.initState(); _controller = XandrController(); + _scrollController.addListener(() { + _checkIfAdIsInViewport.add(_scrollController.position); + }); } @override @@ -57,6 +73,7 @@ class _XandrExampleState extends State { if (snapshot.hasData) { debugPrint('Xandr SDK initialized, success=${snapshot.hasData}'); return SingleChildScrollView( + controller: _scrollController, child: Column( children: [ const Text( @@ -114,6 +131,31 @@ class _XandrExampleState extends State { //customKeywords: useDemoAds, resizeWhenLoaded: true, ), + const Text( + 'Lorem Ipsum is simply dummy text of the printing and ' + 'typesetting industry. Lorem Ipsum has been the boo ' + 'standard dummy text ever since the 1500s, when an aha ' + 'printer took a galley of type and scrambled it to v'), + const Text( + 'Lorem Ipsum is simply dummy text of the printing and ' + 'typesetting industry. Lorem Ipsum has been the boo ' + 'standard dummy text ever since the 1500s, when an aha ' + 'printer took a galley of type and scrambled it to v'), + const Text( + 'Lorem Ipsum is simply dummy text of the printing and ' + 'typesetting industry. Lorem Ipsum has been the boo ' + 'standard dummy text ever since the 1500s, when an aha ' + 'printer took a galley of type and scrambled it to v'), + const Text( + 'Lorem Ipsum is simply dummy text of the printing and ' + 'typesetting industry. Lorem Ipsum has been the boo ' + 'standard dummy text ever since the 1500s, when an aha ' + 'printer took a galley of type and scrambled it to v'), + const Text( + 'Lorem Ipsum is simply dummy text of the printing and ' + 'typesetting industry. Lorem Ipsum has been the boo ' + 'standard dummy text ever since the 1500s, when an aha ' + 'printer took a galley of type and scrambled it to v'), const Text( 'Lorem Ipsum is simply dummy text of the printing and ' 'typesetting industry. Lorem Ipsum has been the boo ' @@ -153,6 +195,32 @@ class _XandrExampleState extends State { 'typesetting industry. Lorem Ipsum has been the boo ' 'standard dummy text ever since the 1500s, when an aha ' 'printer took a galley of type and scrambled it to g'), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Align( + alignment: Alignment.topLeft, + child: Text( + 'load when in viewport:', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), + AdBanner( + controller: _controller, + //placementID: '17058950', + inventoryCode: 'bunte_webdesktop_home_homepage_hor_1', + adSizes: const [ + AdSize(1, 1), + AdSize(728, 90), + ], //[AdSize(300, 250)], + resizeAdToFitContainer: true, + loadMode: LoadMode.whenInViewport( + _checkIfAdIsInViewport.stream, + -100, + ), + ), ], ), ); diff --git a/packages/xandr/lib/ad_banner.dart b/packages/xandr/lib/ad_banner.dart index 83f3fa7..4c52d7e 100644 --- a/packages/xandr/lib/ad_banner.dart +++ b/packages/xandr/lib/ad_banner.dart @@ -1,6 +1,10 @@ +import 'dart:async'; + +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:xandr/ad_size.dart'; +import 'package:xandr/load_mode.dart'; import 'package:xandr/xandr.dart'; import 'package:xandr_android/xandr_android.dart'; @@ -25,6 +29,7 @@ class AdBanner extends StatefulWidget { this.resizeAdToFitContainer = false, this.loadsInBackground, this.shouldServePSAs, + LoadMode? loadMode, double? width, double? height, }) : assert(adSizes.isNotEmpty, 'adSizes must not be empty'), @@ -33,7 +38,8 @@ class AdBanner extends StatefulWidget { 'placementID or inventoryCode must not be null', ), width = width ?? adSizes.first.width.toDouble(), - height = height ?? adSizes.first.height.toDouble(); + height = height ?? adSizes.first.height.toDouble(), + loadMode = loadMode ?? LoadMode.whenCreated(); /// The placement ID for the ad banner. final String? placementID; @@ -86,6 +92,9 @@ class AdBanner extends StatefulWidget { /// They are not enabled by default. final bool? shouldServePSAs; + /// The load mode for the ad banner, determines when the ad is loaded. + final LoadMode loadMode; + @override State createState() => _AdBannerState(); } @@ -93,12 +102,54 @@ class AdBanner extends StatefulWidget { class _AdBannerState extends State { double _width = 1; double _height = 1; + bool _loading = false; + bool _loaded = false; + final Completer _widgetId = Completer(); @override void initState() { - super.initState(); + context.toString(); + if (widget.loadMode is LoadWhenCreated) { + _loading = true; + } else if (widget.loadMode is WhenInViewport) { + (widget.loadMode as WhenInViewport).checkIfInViewport.listen((_) { + _checkViewport((widget.loadMode as WhenInViewport).pixelOffset); + }); + } _height = widget.height; _width = widget.width; + super.initState(); + } + + /// trigger ad loading via [MethodChannel] + void loadAd() { + if (!_loaded && !_loading) { + setState(() { + _loading = true; + }); + _widgetId.future.then((value) => widget.controller.loadAd(value)); + } + } + + /// function used only with the [WhenInViewport] loadMode + void _checkViewport(int pixelOffset) { + final object = context.findRenderObject(); + + if (object == null || !object.attached) { + return; + } + + final viewport = RenderAbstractViewport.of(object); + final vpHeight = viewport.paintBounds.height; + final vpOffset = viewport.getOffsetToReveal(object, 0); + + final deltaTop = vpOffset.offset - Scrollable.of(context).position.pixels; + + if ((vpHeight - deltaTop) > pixelOffset) { + if (!_loading) { + loadAd(); + } + } } void changeSize(double width, double height) { @@ -108,6 +159,13 @@ class _AdBannerState extends State { }); } + void onDoneLoading({required bool success}) { + setState(() { + _loading = false; + _loaded = success; + }); + } + @override Widget build(BuildContext context) { return SizedBox( @@ -128,6 +186,9 @@ class _AdBannerState extends State { resizeAdToFitContainer: widget.resizeAdToFitContainer, loadsInBackground: widget.loadsInBackground, shouldServePSAs: widget.shouldServePSAs, + loadMode: widget.loadMode, + onDoneLoading: onDoneLoading, + widgetId: _widgetId, delegate: BannerAdEventDelegate( onBannerAdLoaded: (event) { debugPrint('>>>> onBannerAdLoaded: $event'); @@ -163,6 +224,8 @@ enum ClickThroughAction { } } +typedef _DoneLoadingCallback = void Function({required bool success}); + class _HostAdBannerView extends StatelessWidget { _HostAdBannerView({ required String? placementID, @@ -176,11 +239,15 @@ class _HostAdBannerView extends StatelessWidget { required int layoutHeight, required int layoutWidth, required bool resizeAdToFitContainer, + required LoadMode loadMode, + required _DoneLoadingCallback onDoneLoading, + required this.widgetId, ClickThroughAction? clickThroughAction, bool? loadsInBackground, bool? shouldServePSAs, this.delegate, - }) : creationParams = { + }) : _onDoneLoading = onDoneLoading, + creationParams = { 'placementID': placementID, 'inventoryCode': inventoryCode, 'autoRefreshInterval': autoRefreshInterval.inMilliseconds, @@ -191,6 +258,7 @@ class _HostAdBannerView extends StatelessWidget { 'layoutHeight': layoutHeight, 'layoutWidth': layoutWidth, 'resizeAdToFitContainer': resizeAdToFitContainer, + 'loadWhenCreated': loadMode is LoadWhenCreated, } { if (clickThroughAction != null) { creationParams['clickThroughAction'] = clickThroughAction.toString(); @@ -207,6 +275,8 @@ class _HostAdBannerView extends StatelessWidget { final Map creationParams; final XandrController controller; final BannerAdEventDelegate? delegate; + final _DoneLoadingCallback _onDoneLoading; + final Completer widgetId; @override Widget build(BuildContext context) { @@ -215,14 +285,22 @@ class _HostAdBannerView extends StatelessWidget { viewType: 'de.thekorn.xandr/ad_banner', onPlatformViewCreated: (id) { debugPrint('Created banner view: $id'); + + if (!widgetId.isCompleted) { + widgetId.complete(id); + } controller.listen(id, (event) { if (event is BannerAdLoadedEvent) { + _onDoneLoading(success: true); delegate?.onBannerAdLoaded?.call(event); } else if (event is BannerAdLoadedErrorEvent) { + _onDoneLoading(success: false); delegate?.onBannerAdLoadedError?.call(event); } else if (event is NativeBannerAdLoadedEvent) { + _onDoneLoading(success: true); delegate?.onNativeBannerAdLoaded?.call(event); } else if (event is NativeBannerAdLoadedErrorEvent) { + _onDoneLoading(success: false); delegate?.onNativeBannerAdLoadedError?.call(event); } }); diff --git a/packages/xandr/lib/load_mode.dart b/packages/xandr/lib/load_mode.dart new file mode 100644 index 0000000..f261426 --- /dev/null +++ b/packages/xandr/lib/load_mode.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +/// Represents the load mode for a specific operation. +class LoadMode { + LoadMode._(); + + /// factory method to create a class [LoadWhenCreated] + factory LoadMode.whenCreated() = LoadWhenCreated; + + /// factory method to create a class [WhenInViewport] + factory LoadMode.whenInViewport( + Stream checkIfInViewport, + int pixelOffset, + ) = WhenInViewport; +} + +/// Use this class to automatically load the ad when it's created in the native +/// code. +/// +/// Use the factory method [LoadMode.whenCreated] to create it. +class LoadWhenCreated extends LoadMode { + /// load the ad immediately when it's created. + LoadWhenCreated() : super._(); +} + +/// Use this class to when you want to asynchronously load the ad and +/// additionally check if it's in the viewport (+- pixelOffset). +/// +/// [checkIfInViewport] pass null events, to trigger checking if the +/// BannerAdView is in the viewport (+- pixelOffset). +/// For example, the events could be generated by a [NotificationListener], +/// so that on every scroll, you would check if the BannerAdView is in the +/// viewport. [pixelOffset] number of pixels that is used to calculate when to +/// load the ad. NOTE: Bear in mind, that the BannerAdView should be added to +/// the widget tree. So dynamic ListViews with a builder method could not +/// catch -1000px offsets, because the BannerAdView is not rendered/created yet. +/// Example values: +/// If set to 0, the ad will load when the widget will enter the viewport. +/// If set to -100, the ad will load -100 px before appearing on the screen. +/// If set to 100, the ad will load when 100 px of the ad could be shown. +/// +/// Use the factory method [LoadMode.whenInViewport] to create it. +class WhenInViewport extends LoadMode { + /// load ad when it's in the viewport + WhenInViewport(this.checkIfInViewport, int? pixelOffset) + : pixelOffset = pixelOffset ?? 0, + super._(); + + /// stream which should get new events when scrolling changes + final Stream checkIfInViewport; + + /// pixel offset determining when to load the ad + final int pixelOffset; +} diff --git a/packages/xandr/lib/xandr.dart b/packages/xandr/lib/xandr.dart index 5c396bd..e5e2864 100644 --- a/packages/xandr/lib/xandr.dart +++ b/packages/xandr/lib/xandr.dart @@ -36,6 +36,12 @@ class XandrController { return _platform.init(memberId); } + /// loads an ad. + Future loadAd(int widgetId) async { + debugPrint('loadAd'); + return _platform.loadAd(widgetId); + } + /// loads an interstitial ad. Future loadInterstitialAd({ String? placementID, diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewContainer.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewContainer.kt index dc6c9fe..f77d294 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewContainer.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewContainer.kt @@ -2,6 +2,7 @@ package de.thekorn.xandr import android.app.Activity import android.view.View +import androidx.core.content.ContextCompat import com.appnexus.opensdk.ANClickThroughAction import com.appnexus.opensdk.BannerAdView import de.thekorn.xandr.listeners.XandrAdListener @@ -12,7 +13,7 @@ import io.flutter.plugin.platform.PlatformView import kotlinx.coroutines.ExperimentalCoroutinesApi class BannerViewContainer( - activity: Activity, + private var activity: Activity, private var state: FlutterState, private var widgetId: Int, private val bannerViewOptions: BannerViewOptions? @@ -93,22 +94,35 @@ class BannerViewContainer( "Return view, xandr-initialized=${state.isInitialized.isCompleted}" ) - this.banner.adListener = XandrAdListener( - widgetId, - this.state.flutterApi, - this.bannerViewOptions - ) - state.isInitialized.invokeOnCompletion { Log.d( "Xandr.BannerView", "load add, xandr-initialized=${state.isInitialized.getCompleted()}" ) - this.banner.loadAd() + bannerViewOptions?.loadWhenCreated?.let { loadWhenCreated -> + if (loadWhenCreated) { + loadAd() + } + } } return this.banner } + fun loadAd() { + Log.d("Xandr.BannerView", "loadAd; id=$widgetId") + this.banner.adListener = null + + this.banner.adListener = XandrAdListener( + widgetId, + this.state.flutterApi, + this.bannerViewOptions + ) + this.banner.setBackgroundColor( + ContextCompat.getColor(activity, android.R.color.transparent) + ) + this.banner.loadAd() + } + override fun dispose() { this.banner.destroy() } diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewFactory.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewFactory.kt index eeb2f6c..e9d0d28 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewFactory.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/BannerViewFactory.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import de.thekorn.xandr.models.FlutterState import de.thekorn.xandr.models.toBannerAdViewOptions +import io.flutter.Log import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory @@ -13,12 +14,8 @@ class BannerViewFactory( private val state: FlutterState ) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + override fun create(context: Context, id: Int, args: Any?): PlatformView { - return BannerViewContainer( - activity, - this.state, - id, - (args as? Map<*, *>)?.toBannerAdViewOptions() - ) + return state.getOrCreateBannerView(activity, id, args); } } diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/Xandr.g.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/Xandr.g.kt index b8260cc..7a26b20 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/Xandr.g.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/Xandr.g.kt @@ -48,6 +48,7 @@ class FlutterError ( /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface XandrHostApi { fun init(memberId: Long, callback: (Result) -> Unit) + fun loadAd(widgetId: Long, callback: (Result) -> Unit) fun loadInterstitialAd(widgetId: Long, placementID: String?, inventoryCode: String?, customKeywords: Map?, callback: (Result) -> Unit) fun showInterstitialAd(autoDismissDelay: Long?, callback: (Result) -> Unit) @@ -79,6 +80,26 @@ interface XandrHostApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.xandr_android.XandrHostApi.loadAd", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val widgetIdArg = args[0].let { if (it is Int) it.toLong() else it as Long } + api.loadAd(widgetIdArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.xandr_android.XandrHostApi.loadInterstitialAd", codec) if (api != null) { diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/XandrPlugin.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/XandrPlugin.kt index 0fdfdd8..be47263 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/XandrPlugin.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/XandrPlugin.kt @@ -87,6 +87,16 @@ class XandrPlugin : FlutterPlugin, ActivityAware, XandrHostApi { } } + override fun loadAd(widgetId: Long, callback: (Result) -> Unit) { + Log.d("Xandr", "loadAd got called, with widgetId=$widgetId") + this.flutterState.isInitialized.invokeOnCompletion { + val ad = this.flutterState.getBannerView(widgetId.toInt()) + Log.d("Xandr", "loadAd found ad for widgetId=$widgetId, ad=$ad") + ad.loadAd() + callback(Result.success(true)) + } + } + override fun loadInterstitialAd( widgetId: Long, placementID: String?, diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/BannerViewOptions.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/BannerViewOptions.kt index 3d1441f..b766ee7 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/BannerViewOptions.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/BannerViewOptions.kt @@ -57,6 +57,7 @@ fun Map<*, *>.toBannerAdViewOptions(): BannerViewOptions { clickThroughAction = this["clickThroughAction"] as String?, autoRefreshInterval = this["autoRefreshInterval"] as Int?, resizeWhenLoaded = this["resizeWhenLoaded"] as Boolean?, - allowNativeDemand = this["allowNativeDemand"] as Boolean? + allowNativeDemand = this["allowNativeDemand"] as Boolean?, + loadWhenCreated = this["loadWhenCreated"] as Boolean? ) } diff --git a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/FlutterState.kt b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/FlutterState.kt index 21db3c7..246635f 100644 --- a/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/FlutterState.kt +++ b/packages/xandr_android/android/src/main/kotlin/de/thekorn/xandr/models/FlutterState.kt @@ -2,8 +2,11 @@ package de.thekorn.xandr.models import XandrFlutterApi import XandrHostApi +import android.app.Activity import android.content.Context +import de.thekorn.xandr.BannerViewContainer import de.thekorn.xandr.XandrPlugin +import io.flutter.Log import io.flutter.plugin.common.BinaryMessenger import kotlin.properties.Delegates import kotlinx.coroutines.CompletableDeferred @@ -14,6 +17,8 @@ class FlutterState( ) { val isInitialized: CompletableDeferred = CompletableDeferred() + private val flutterBannerAdViews = mutableMapOf() + var memberId by Delegates.notNull() lateinit var flutterApi: XandrFlutterApi @@ -25,4 +30,27 @@ class FlutterState( fun stopListening() { XandrHostApi.setUp(this.binaryMessenger, null) } + + fun getOrCreateBannerView(activity: Activity, id: Int, args: Any?): BannerViewContainer { + if (!flutterBannerAdViews.containsKey(id)) { + Log.d("Xandr.BannerViewFactory", "Create new FlutterBannerAdView for id=$id") + flutterBannerAdViews[id] = BannerViewContainer( + activity, + this, + id, + (args as? Map<*, *>)?.toBannerAdViewOptions() + ) + } + Log.d("Xandr.BannerViewFactory", "Return existing FlutterBannerAdView for id=$id") + return flutterBannerAdViews[id]!! + } + + fun getBannerView(id: Int): BannerViewContainer { + if (flutterBannerAdViews.containsKey(id)) { + Log.d("Xandr.BannerViewFactory", "Create new FlutterBannerAdView for id=$id") + return flutterBannerAdViews[id]!! + } + Log.e("Xandr.BannerViewFactory", "Banner for widgetId=$id not found!") + throw RuntimeException("Unable to find Banner for widgetId=$id") + } } diff --git a/packages/xandr_android/lib/src/messages.g.dart b/packages/xandr_android/lib/src/messages.g.dart index 45a269f..8ac1c28 100644 --- a/packages/xandr_android/lib/src/messages.g.dart +++ b/packages/xandr_android/lib/src/messages.g.dart @@ -66,6 +66,35 @@ class XandrHostApi { } } + Future loadAd({required int widgetId}) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.xandr_android.XandrHostApi.loadAd'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([widgetId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + Future loadInterstitialAd({ required int widgetId, String? placementID, diff --git a/packages/xandr_android/lib/xandr_android.dart b/packages/xandr_android/lib/xandr_android.dart index d785169..bb2a1e5 100644 --- a/packages/xandr_android/lib/xandr_android.dart +++ b/packages/xandr_android/lib/xandr_android.dart @@ -28,6 +28,13 @@ class XandrAndroid extends XandrPlatform { return _api.init(memberId: memberId); } + @override + Future loadAd(int widgetId) async { + return _api.loadAd( + widgetId: widgetId, + ); + } + @override Future loadInterstitialAd( String? placementID, diff --git a/packages/xandr_android/pigeons/messages.dart b/packages/xandr_android/pigeons/messages.dart index a107777..9df01d2 100644 --- a/packages/xandr_android/pigeons/messages.dart +++ b/packages/xandr_android/pigeons/messages.dart @@ -24,6 +24,11 @@ abstract class XandrHostApi { required int memberId, }); + @async + bool loadAd({ + required int widgetId, + }); + @async bool loadInterstitialAd({ required int widgetId, diff --git a/packages/xandr_platform_interface/lib/src/interface.dart b/packages/xandr_platform_interface/lib/src/interface.dart index bba088f..dea2b86 100644 --- a/packages/xandr_platform_interface/lib/src/interface.dart +++ b/packages/xandr_platform_interface/lib/src/interface.dart @@ -45,6 +45,11 @@ abstract class XandrPlatform extends PlatformInterface { throw UnimplementedError('init() has not been implemented.'); } + /// loads an ad. + Future loadAd(int widgetId) async { + throw UnimplementedError('loadAd() has not been implemented.'); + } + /// loads an interstitial ad. Future loadInterstitialAd( String? placementID, diff --git a/packages/xandr_platform_interface/lib/src/method_channel.dart b/packages/xandr_platform_interface/lib/src/method_channel.dart index de521a4..ea6f831 100644 --- a/packages/xandr_platform_interface/lib/src/method_channel.dart +++ b/packages/xandr_platform_interface/lib/src/method_channel.dart @@ -13,6 +13,11 @@ class MethodChannelXandr extends XandrPlatform { return (await methodChannel.invokeMethod('init', [memberId]))!; } + @override + Future loadAd(int widgetId) async { + return (await methodChannel.invokeMethod('loadAd'))!; + } + @override Future loadInterstitialAd( String? placementID,