Flutter Plugin that Integrates Pangolin Android and iOS SDK

Pangolin Flutter SDK

Introduction

pangle_flutterIt is a Flutter plugin that integrates pangolin Android and iOS SDK. Part of the code is modified from the official example.

Official documents (login required)

Integration steps

1. Add yaml dependency

the Dependencies :
   ## Add rely 
  pangle_flutter : Latest

2. Additional configuration for Android and iOS

To configure

Instructions for use

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

1. Information flow advertising

  1. Native self-rendering information flow advertisement: The specified image size or ratio is fixed, and it is rendered by the developer according to the imageMode. Template rendering advertisement: Specify the width and height of the entire advertisement, and the SDK will automatically adapt the incoming width and height for rendering.
  2. Native self-rendering information flow ads This module is temporarily unable to customize the width and height of the entire item. You can only use PangleImgSizethe 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.
  3. At present, according to SDK Demo, only one template width and height can be passed in template advertisements at a time, and the template type used by the advertisement cannot be obtained when the advertisement is rendered. Therefore, if you choose multiple templates, the rendering effect may be poor.

pangle_flutter

pangle_flutter

2. Import the module into the project developed by iOS using pure OC

  1. Create a Swift file with any name
  2. Choose according to the prompts Create Bridging Header. If there is no prompt, please search for how to create it yourself.

OC import Swift module

Steps for usage

1. Initialization

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),
);

2. On-screen advertising

/// 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: (){},
);

3. Rewarded Video Ads

/// [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),
 );

4. Banner Ads

/// 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),
),
  • Toggle clickable state
// 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);

5. Information flow advertising

  • Get information flow data
/// 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),
 );
  • Download Data
/// 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);
    });
  },
)
  • Toggle whether clickable state
// 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);
}

6. Interstitial Ads

 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));

Parameter Description

Initial configuration

/// 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,
});

Open screen configuration

/// 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,
});

Incentive video configuration

/// 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,
});

Banner configuration

/// 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,
});

Information flow configuration

/// 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,
});

Interstitial ad

/// 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,
})

Full screen video ad

/// 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,
})

Development instructions

  1. The open-screen advertisement is placed before runApp to call the best experience

  2. 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.

  3. This solves the setState((){});problem that the advertising object disappears when the item ( ) is removed .

  4. 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.

  5. 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();
}
  1. BannerViewFeedViewThrough PlatformViewrealization. On Android, PlatformViewAPI 20 is supported at least.

contribution

  • Any better way to implement or add additional features, please submit a PR.
  • If you have any usage problems, please submit an issue.

Download Details:

Author: nullptrX

Source Code: https://github.com/nullptrX/pangle_flutter

#flutter #dart #mobile-apps

Flutter Plugin that Integrates Pangolin Android and iOS SDK
4.25 GEEK