pangle_flutter
It is a Flutter plugin that integrates pangolin Android and iOS SDK. Part of the code is modified from the official example.
the Dependencies :
## Add rely
pangle_flutter : Latest
At present, iOS information flow ads and banner ads are still in preview, and some functions cannot be used normally (such as click event delivery problems, slow rendering), and it is not recommended to be used in a formal environment
PangleImgSize
the value in the middle to specify the image width and height ratio. Template rendering ads can specify the desired width and height, but it must correspond to the width and height specified in the background of the ad.Create Bridging Header
. If there is no prompt, please search for how to create it yourself.Import 'Package: pangle_flutter / pangle_flutter.dart' ;
/// If the initialization before runApp method invocation, this code is added the following
WidgetsFlutterBinding . EnsureInitialized ();
/// Initialization, not all parameters are listed, there will be detailed instructions later
/// [kAppId] The appID obtained after applying for pangolin advertising space
await pangle. heat (
iOS : IOSConfig (appId : kAppId),
android : AndroidConfig (appId : kAppId),
);
/// Full-screen type
/// [kSplashId] On-screen advertising ID, corresponding to CodeId of Android, corresponding to slotID of iOS
await pangle. loadSplashAd (
iOS: IOSSplashConfig(slotId: kSplashId, isExpress: false),
android: AndroidSplashConfig(slotId: kSplashId, isExpress: false),
);
/// Custom type
/// Same as Widget usage
SplashView(
iOS: IOSSplashConfig(slotId: kSplashId, isExpress: false),
android: AndroidSplashConfig(slotId: kSplashId, isExpress: false),
backgroundColor : Colors .white,
/// Advertising display
onShow : ()(),
/// Ad acquisition failed
onError : ()(),
/// The ad was clicked
onClick : ()(),
/// The ad is clicked and skipped
onSkip : ()(),
/// The advertisement countdown ends
onTimeOver: (){},
);
/// [kRewardedVideoId] Rewarded video ad ID, corresponding to Android’s CodeId, corresponding to iOS’s slotID
pangle. loadRewardVideoAd (
iOS: IOSRewardedVideoConfig(slotId: kRewardedVideoId),
android: AndroidRewardedVideoConfig(slotId: kRewardedVideoId),
);
/// Banner is implemented by PlatformView, and the method of use is the same as Widget
/// [kBannerId] Banner advertising ID, corresponding to Android CodeId, corresponding to iOS slotID
BannerView(
iOS : IOSBannerAdConfig (slotId : kBannerId),
android : AndroidBannerAdConfig (slotId : kBannerId),
),
// Because iOS’s EXPRESS type advertisements use WebView rendering internally, and there are some click event conflicts between WebView and FlutterView, this solution is provided
final _bannerKey = GlobalKey < BannerViewState > ();
// Externally control whether the ad slot can be clicked
_bannerKey .currentState. setUserInteractionEnabled (enable);
/// Information flow realization logic
/// First make a network request and get information flow data
///
/// PangleFeedAd corresponding fields:
/// [code] response code, 0 success, -1 failure
/// [message] When an error occurs, debug information
/// [count] Get the number of information streams, generally the same as the count passed in above, the final result is based on this count
/// [data] (string list) The key id used to display information stream ads
PangleFeedAd feedAd = await pangle. loadFeedAd (
iOS: IOSFeedAdConfig(slotId: kFeedId, count: 2),
android: AndroidFeedAdConfig(slotId: kFeedId, count: 2),
);
/// How to use
/// Your data model
class Item {
/// Add field
final String feedId;
}
final items = <Item>[];
final feedAdDatas = feedAd.data;
final items = Item(feedId: feedAdDatas[0]);
items.insert(Random().nextInt(items.length), item);
/// Widget使用
FeedView(
id : item.feedId,
onRemove: () {
setState(() {
items.removeAt(index);
});
},
)
// Because iOS’s EXPRESS type ads are rendered internally by WebView, and there is a partial click event conflict between WebView and FlutterView, this solution is provided
// 1\. Inherit GlobalObjectKey to implement your own key
class _ItemKey extends GlobalObjectKey < FeedViewState > {
_ItemKey ( Object value) : super (value);
}
// 2\. Provide your own key
FeedView for FeedView (
key: _ItemKey(item.feedId),
...
)
// 3\. Provide a key for the Widget that needs to calculate the position, such as
final _titleKey = GlobalKey ();
AppBar (key : _titleKey)
final _naviKey = GlobalKey ();
BottomNavigationBar (key : _naviKey)
// 4\. Provide a ScrollController for the FeedView container, Such as
final _controller = ScrollController ();
ListView (controller : _controller)
// 5\. Listen to controller scroll events and dynamically switch the clickable state
@override
void initState () {
super . InitState ();
_loadFeedAd();
_controller.addListener(_onScroll);
}
@override
void dispose() {
_controller.removeListener(_onScroll);
super.dispose();
}
_onScroll() {
if (!Platform.isIOS) {
return;
}
RenderBox titleBox = _titleKey.currentContext.findRenderObject();
var titleSize = titleBox.size;
var titleOffset = titleBox.localToGlobal(Offset.zero);
final minAvailableHeigt = titleOffset.dy + titleSize.height;
RenderBox naviBox = _naviKey.currentContext.findRenderObject();
var naviOffset = naviBox.localToGlobal(Offset.zero);
final maxAvailableHeight = naviOffset.dy;
/// Check whether the width, height and offset of each item meet the click requirements
for (var value in feedIds) {
_switchUserInteraction(maxAvailableHeight, minAvailableHeigt, value);
}
}
void _switchUserInteraction(
double maxAvailableHeight,
double minAvailableHeigt,
String id,
) {
var itemKey = _ItemKey(id);
RenderBox renderBox = itemKey.currentContext.findRenderObject();
var size = renderBox.size;
var offset = renderBox.localToGlobal(Offset.zero);
/// The bottom coordinate is not lower than NavigationBar, and the top coordinate is not higher than AppBar
var available = offset.dy + size.height < maxAvailableHeight &&
offset.dy > minAvailableHeigt;
itemKey.currentState.setUserInteractionEnabled(available);
}
final result = await pangle. loadInterstitialAd (
iOS: IOSInterstitialAdConfig(
slotId : kInterstitialId,
isExpress: true,
/// The width and height are the width and height of the advertising space you applied for, please assign values according to the actual situation
imgSize : PangleImgSize .interstitial600_400,
),
android: AndroidInterstitialAdConfig(
slotId : kInterstitialId,
),
);
print(jsonEncode(result));
/// Register the ad config for iOS
///
/// [appId] the unique identifier of the App
/// [logLevel] optional. default none
/// [coppa] optional. Coppa 0 adult, 1 child
/// [isPaidApp] optional. Set whether the app is a paid app, the default is a non-paid app.
IOSConfig({
@required this.appId,
this.logLevel,
this.coppa,
this.isPaidApp,
});
/// Register the ad config for Android
///
/// [appId] Mandatory parameter, set the AppId of the application
/// [debug] Open the test phase, you can troubleshoot the problem through the log, remove the call when going online, the default is false
/// [allowShowNotify] Whether to allow sdk to display notification bar prompts
/// [allowShowPageWhenScreenLock] Whether to support the display of advertising landing pages in the lock screen scene
/// [supportMultiProcess] Optional parameter, set whether to support multiple processes: true support, false not stand by. The default is false is not supported
/// [directDownloadNetworkType] Optional parameters allow direct download network state set, the default is only a WiFi
/// [isPaidApp] Optional parameter set whether charging users: true user billing, false non- Billing users. The default is false for non-billing users. This parameter can only be passed in with the user’s consent.
/// [useTextureView] optional parameter to set whether to use texture to play the video: true to use, false to not use. The default is false and not used (surface is used)
///[titleBarTheme] optional parameter, set the landing page theme, the default is light
AndroidConfig({
@required this.appId,
this.debug,
this.allowShowNotify,
this.allowShowPageWhenScreenLock,
this.supportMultiProcess,
this.directDownloadNetworkType = AndroidDirectDownloadNetworkType.kWiFi /// 多个值,用 & 连接
this.isPaidApp,
this.useTextureView,
this.titleBarTheme = AndroidTitleBarTheme.light,
});
/// The splash ad config for iOS
///
/// [slotId] The unique identifier of splash ad.
/// [tolerateTimeout] optional. Maximum allowable load timeout, default 3s, unit s.
/// [hideSkipButton] optional. Whether hide skip button, default NO. If you hide the skip button, you need to customize the countdown.
/// [isExpress] optional. experimental. 个性化模板广告.
/// [expressSize] optional. 模板宽高
IOSSplashConfig({
@required this.slotId,
this.tolerateTimeout,
this.hideSkipButton,
this.isExpress,
this.expressSize,
});
/// The splash ad config for Android
///
/// [slotId] The unique identifier of splash ad.
/// [tolerateTimeout] optional. Maximum allowable load timeout, default 3s, unit s.
/// [hideSkipButton] optional. Whether hide skip button, default NO. If you hide the skip button, you need to customize the countdown.
/// [isSupportDeepLink] optional. Whether to support deeplink. default true.
/// [isExpress] optional. experimental. 个性化模板广告.
/// [expressSize] optional. 模板宽高
AndroidSplashConfig({
@required this.slotId,
this.tolerateTimeout,
this.hideSkipButton,
this.isSupportDeepLink,
this.isExpress,
this.expressSize,
});
/// The rewarded video ad config for Android
///
/// [slotId] The unique identifier of rewarded video ad.
/// [userId] required.
// Third-party game user_id identity.
// Mainly used in the reward issuance, it is the callback pass-through parameter from server-to-server.
// It is the unique identifier of each user.
// In the non-server callback mode, it will also be pass-through when the video is finished playing.
// Only the string can be passed in this case, not nil.
/// [rewardName] optional. reward name.
/// [rewardAmount] optional. number of rewards.
/// [extra] optional. serialized string.
/// [isExpress] optional. 个性化模板广告.
/// [loadingType] optional. 加载广告的类型,默认[LoadingType.normal]
IOSRewardedVideoConfig({
@required this.slotId,
this.userId,
this.rewardName,
this.rewardAmount,
this.extra,
this.isExpress,
this.loadingType,
});
/// The rewarded video ad config for Android
///
/// [slotId] The unique identifier of rewarded video ad.
/// [userId] required.
// Third-party game user_id identity.
// Mainly used in the reward issuance, it is the callback pass-through parameter from server-to-server.
// It is the unique identifier of each user.
// In the non-server callback mode, it will also be pass-through when the video is finished playing.
// Only the string can be passed in this case, not nil.
/// [rewardName] optional. reward name.
/// [rewardAmount] optional. number of rewards.
/// [extra] optional. serialized string.
/// [isVertical] optional. Whether video is vertical orientation. Vertical, if true. Otherwise, horizontal.
/// [isSupportDeepLink]optional. Whether to support deeplink. default true.
/// [isExpress] optional. Personalized template ads.
AndroidRewardedVideoConfig({
@required this.slotId,
this.userId,
this.rewardName,
this.rewardAmount,
this.extra,
this.isVertical,
this.isSupportDeepLink,
this.isExpress,
});
/// The feed ad config for iOS
///
/// [slotId] required. The unique identifier of a feed ad.
/// [imgSize] required. Image size.
/// [isExpress] optional. Personalized template Advertising.
/// [expressSize] optional. Template width
/// [isUserInteractionEnabled] Whether the ad slot is clickable, true is
ok , false is not /// [interval] The carousel interval, in seconds, is set in the range of 30~120s
IOSBannerAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.banner600_150,
this.count,
this.isExpress,
this.expressSize,
this.isUserInteractionEnabled = true,
this.interval,
});
/// The feed ad config for Android
///
/// [slotId] required. The unique identifier of a feed ad.
/// [imgSize] required. Image size.
/// [isSupportDeepLink] optional. Whether to support deeplink.
/// [isExpress] optional. 个性化模板广告.
/// [expressSize] optional. 模板宽高
/// [interval] The carousel interval, in seconds, is set in the range of 30~120s
AndroidBannerAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.banner600_150,
this.isSupportDeepLink,
this.isExpress,
this.expressSize,
this.interval,
});
/// The feed ad config for iOS
///
/// [slotId] required. The unique identifier of a feed ad.
/// [imgSize] required. Image size.
/// [tag] optional. experimental. Mark it.
/// [count] It is recommended to request no more than 3 ads. The maximum is 10\. default 3
/// [isSupportDeepLink] optional. Whether to support deeplink.
/// [isExpress] optional. 个性化模板广告.
/// [expressSize] optional. 模板宽高
IOSFeedAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.feed690_388,
this.tag,
this.count,
this.isSupportDeepLink,
this.isExpress,
this.expressSize,
});
/// The feed ad config for iOS
///
/// [slotId] required. The unique identifier of a feed ad.
/// [imgSize] required. Image size.
/// [tag] optional. experimental. Mark it.
/// [count] It is recommended to request no more than 3 ads. The maximum is 10\. default 3
/// [isSupportDeepLink] optional. Whether to support deeplink.
/// [isExpress] optional. 个性化模板广告.
/// [expressSize] optional. 模板宽高
AndroidFeedAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.feed690_388,
this.tag,
this.count,
this.isSupportDeepLink,
this.isExpress,
this.expressSize,
});
/// The interstitial ad config for iOS
///
/// [slotId] required. The unique identifier of a interstitial ad.
/// [imgSize] required. Image size.
/// [isExpress] optional. Personalized template Advertising.
/// [expressSize] optional. Template width and height
IOSInterstitialAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.interstitial600_400,
this.isExpress,
this.expressSize,
})
/// The interstitial ad config for Android
///
/// [slotId] required. The unique identifier of a interstitial ad.
/// [imgSize] required. Image size.
/// [isSupportDeepLink] optional. Whether to support deeplink. default true.
/// [isExpress] optional. experimental. 个性化模板广告
/// [expressSize] optional. 模板宽高
AndroidInterstitialAdConfig({
@required this.slotId,
this.imgSize = PangleImgSize.interstitial600_400,
this.isSupportDeepLink,
this.isExpress,
this.expressSize,
})
/// The full screen video ad config for iOS
///
/// [slotId] required. The unique identifier of a full screen video ad.
/// [loadingType] optional. Type of loading ads, default [PangleLoadingType.normal ]
/// [isExpress] optional. Personalized template ads
IOSFullscreenVideoConfig({
@required this.slotId,
this.loadingType = PangleLoadingType.normal,
this.isExpress = true,
})
/// The full screen video ad config for Android
///
/// [slotId] required. The unique identifier of a full screen video ad.
/// [isSupportDeepLink] optional. Whether to support deeplink. default true.
// / [orientation] Set the desired video playback direction, default [PangleOrientation.veritical]
/// [loadingType] optional. Type of loading advertisement, default [PangleLoadingType.normal]
/// [isExpress] optional. Personalized template advertisement
// / [expressSize] optional. Template width and height
AndroidFullscreenVideoConfig({
@required this.slotId,
this.isSupportDeepLink = true,
this.orientation = PangleOrientation.veritical,
this.loadingType = PangleLoadingType.normal,
this.isExpress = true,
})
The open-screen advertisement is placed before runApp to call the best experience
Information flow advertisements were previously loaded after the PlatformView was successfully created, and changed to load when created. Then use the hashCode of the obtained FeedAd object as the key global cache, and find the corresponding advertising object through the id passed in by the FeedView in Flutter.
This solves the setState((){});
problem that the advertising object disappears when the item ( ) is removed .
The click event of the iOS information flow advertisement needs to be passed in rootViewController
, (UIApplication.shared.delegate?.window??.rootViewController)!
and the problem is not found yet.
Interstitial ads do not display problem
// open_ad_sdk:
// com.bytedance.sdk.openadsdk.utils.a: 28 has life cycle monitoring,
// under certain circumstances when FlutterActivity jumps to the interface, it will not call back onStart(), onStop()
// if used ttAdManager.requestPermissionIfNecessary(context) will not be called.
//The above situation causes onActivityStarted to go less once, so the show method below does not work.
public void onActivityStarted( Activity var1) {
if ( this . a . incrementAndGet() > 0 ) {
this . b . set( false );
}
this.b();
}
...
public void onActivityStopped(Activity var1) {
if (this.a.decrementAndGet() == 0) {
this.b.set(true);
}
}
// com.bytedance.sdk.openadsdk.core.cb: 306 has a life cycle judgment, and show() cannot be executed
if ( ! this . k . isShowing() && ! com . bytedance . sdk . openadsdk . core . i . c() . a()) {
this . k . show();
}
BannerView
, FeedView
Through PlatformView
realization. On Android, PlatformView
API 20 is supported at least.Author: nullptrX
Source Code: https://github.com/nullptrX/pangle_flutter
#flutter #dart #mobile-apps