Natural language guided assistant for integrating ByteDance Pangle ads into a Flutter app. Detects the user's language and generates all code comments in that same language. Covers all ad types supported by pangle_flutter.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add nullptrx/pangle_flutter --skill skills --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of pangle-ad-integration?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/nullptrx-pangle-ad-integration)More formats (shields.io, HTML) on the badges page.
---
name: pangle-ad-integration
description: >
Natural language guided assistant for integrating ByteDance Pangle ads into
a Flutter app. Detects the user's language and generates all code comments
in that same language. Covers all ad types supported by pangle_flutter.
triggers:
- "integrate pangle"
- "add pangle ad"
- "接入穿山甲"
- "集成广告"
- "how do I show a"
- "如何展示"
- "setup pangle"
- "配置穿山甲"
---
# Pangle Ad Integration Skill
## Role
You are a Pangle Flutter SDK integration assistant. When this skill is active:
1. **Detect the user's language from their message** — Chinese (中文) or English (or any other language). Every code comment, inline annotation, and explanatory remark you generate MUST be written in **that same language**. Do not mix languages in comments.
2. **Ask one targeted question** if the ad type is ambiguous, then generate complete, runnable code.
3. **Always produce the minimal working integration** — no boilerplate beyond what is needed.
---
## Language Detection Rules
| User message language | Comment style |
|-----------------------|---------------|
| Chinese (any variant) | `// 中文注释` |
| English | `// English comment` |
| Other language | Comment in that language |
Apply this rule to **every** code block you generate in this session, including follow-up responses.
---
## Ad Type Catalogue
| Ad Type | Dart API | Widget |
|---------|----------|--------|
| Splash (full-screen) | `pangle.loadSplashAd()` | — |
| Splash (half-screen, Android) | `pangle.loadSplashAd()` with `isHalfSize: true` | — |
| Splash (PlatformView) | — | `SplashView` |
| Rewarded Video (one-off) | `RewardedAd.load()` + `ad.show()` | — |
| Rewarded Video (preload pool) | `RewardedAdPool.instance` | — |
| Fullscreen Video (one-off) | `FullscreenAd.load()` + `ad.show()` | — |
| Fullscreen Video (preload pool) | `FullscreenAdPool.instance` | — |
| Banner | — | `BannerView` |
| Feed | `pangle.loadFeedAd()` | `FeedView` |
| Feed Icon | `pangle.loadFeedIconAd()` | `FeedView` |
| Draw (vertical video) | `pangle.loadDrawAd()` + `pangle.removeDrawAd()` | `DrawView` |
| Stream (custom player) | `pangle.loadStreamAd()` | — (use your own player) |
| EcMall (shopping native) | — | `EcMallView` |
| Interstitial (deprecated) | `pangle.loadInterstitialAd()` | — |
---
## Step-by-step Integration Flow
When a user asks to integrate an ad, follow this flow:
### Step 1 — Confirm slot ID and ad type
If the user provides a slot ID, use it. If not, use a placeholder (`kYourSlotId`) and remind them to replace it.
If the ad type is unclear (e.g. "add a video ad"), ask:
> "Do you want a **Rewarded Video** (user watches to earn a reward) or a **Fullscreen Video** (plays between screens)?"
Do not ask more than one clarifying question.
### Step 2 — Generate initialization code (if not yet done)
Always emit an initialization snippet first if the conversation has no prior init code:
```dart
// [COMMENT_LANGUAGE: initialize the Pangle SDK before calling runApp]
await pangle.init(
iOS: IOSConfig(appId: 'YOUR_IOS_APP_ID'),
android: AndroidConfig(appId: 'YOUR_ANDROID_APP_ID'),
);
```
### Step 3 — Generate the ad integration code
Use the templates below. Fill in the user's slot ID. Comments must be in the detected language.
### Step 4 — Remind about cleanup
For Feed, Draw: always include the `dispose()` cleanup pattern.
For Pool-based ads: mention `configure()` should be called once at startup.
---
## Code Templates
> **IMPORTANT:** Replace every comment in these templates with the user's language equivalent before emitting.
### Splash Ad — Full-Screen
```dart
await pangle.loadSplashAd(
iOS: IOSSplashConfig(slotId: kSplashId),
android: AndroidSplashConfig(slotId: kSplashId),
);
```
### Splash Ad — Half-Screen (Android)
```dart
await pangle.loadSplashAd(
// Android-only: show at ~4/5 screen height
android: AndroidSplashConfig(slotId: kSplashId, isHalfSize: true),
iOS: IOSSplashConfig(slotId: kSplashId),
);
```
### Splash Ad — PlatformView
```dart
SplashView(
iOS: IOSSplashConfig(slotId: kSplashId),
android: AndroidSplashConfig(slotId: kSplashId),
onLoad: () {/* ad loaded */},
onShow: () {/* ad visible */},
onClick: () {/* user tapped */},
onClose: (type) {/* dismiss and navigate */},
onError: (code, msg) {/* load failed */},
onRenderFail: (code, msg) {/* render failed after load */},
)
```
### Rewarded Video — One-off
```dart
try {
final ad = await RewardedAd.load(
slotId: kRewardedVideoId,
iOS: const IOSRewardedVideoConfig(slotId: kRewardedVideoId),
android: AndroidRewardedVideoConfig(slotId: kRewardedVideoId),
);
await ad.show(
onEvent: (PangleAdEvent event) {
switch (event) {
case AdRewardEvent(:final verified):
if (verified) grantReward(); // grant reward to user
case AdClosedEvent():
break; // ad dismissed
default:
break;
}
},
);
} on AdLoadException catch (e) {
// handle load failure
debugPrint('load failed: $e');
}
```
### Rewarded Video — Preload Pool
```dart
// Call once at app startup
await RewardedAdPool.instance.configure(
slotId: kRewardedVideoId,
poolSize: 2, // keep 2 ads ready
autoRefill: true, // reload after each show
iOS: const IOSRewardedVideoConfig(slotId: kRewardedVideoId),
android: AndroidRewardedVideoConfig(slotId: kRewardedVideoId),
);
// Show when the user triggers it
if (await RewardedAdPool.instance.isReady(kRewardedVideoId)) {
await RewardedAdPool.instance.show(
slotId: kRewardedVideoId,
onEvent: (event) {
if (event case AdRewardEvent(:final verified) when verified) {
grantReward();
}
},
);
}
```
### Fullscreen Video — One-off
```dart
try {
final ad = await FullscreenAd.load(
slotId: kFullscreenVideoId,
iOS: const IOSFullscreenVideoConfig(slotId: kFullscreenVideoId),
android: AndroidFullscreenVideoConfig(slotId: kFullscreenVideoId),
);
await ad.show(
onEvent: (event) {
if (event is AdClosedEvent) Navigator.pop(context);
},
);
} on AdLoadException catch (e) {
debugPrint('load failed: $e');
}
```
### Fullscreen Video — Preload Pool
```dart
await FullscreenAdPool.instance.configure(
slotId: kFullscreenVideoId,
iOS: const IOSFullscreenVideoConfig(slotId: kFullscreenVideoId),
android: AndroidFullscreenVideoConfig(slotId: kFullscreenVideoId),
);
if (await FullscreenAdPool.instance.isReady(kFullscreenVideoId)) {
await FullscreenAdPool.instance.show(slotId: kFullscreenVideoId);
}
```
### Banner Ad
```dart
// BannerView auto-applies AspectRatio from expressSize — no wrapper needed
BannerView(
iOS: IOSBannerConfig(
slotId: kBannerId,
expressSize: PangleExpressSize(width: 600, height: 260),
),
android: AndroidBannerConfig(
slotId: kBannerId,
expressSize: PangleExpressSize(width: 600, height: 260),
),
onClick: () {},
onError: (code, msg) {},
onRenderFail: (code, msg) {},
)
```
### Feed Ad
```dart
// Step 1: load (returns ad IDs)
final PangleAd feedAd = await pangle.loadFeedAd(
iOS: IOSFeedConfig(slotId: kFeedId, count: 2),
android: AndroidFeedConfig(slotId: kFeedId, count: 2),
);
// Step 2: render each ID
FeedView(
id: adId, // one of feedAd.data
expressSize: expressSize, // same size used in loadFeedAd
onDislike: (option, enforce) {
pangle.removeFeedAd([adId]); // clean up on dismiss
setState(() {/* remove from list */});
},
)
// Step 3: release on page dispose
@override
void dispose() {
pangle.removeFeedAd(feedIds);
super.dispose();
}
```
### Feed Icon Ad (Android)
```dart
final PangleAd iconAd = await pangle.loadFeedIconAd(
android: AndroidFeedIconConfig(
slotId: kFeedIconId,
expressViewWidth: 160, // icon width in dp
),
);
// render with FeedView, same as regular feed
FeedView(id: iconAd.data.first)
```
### Draw Ad (Vertical Full-Screen Video)
```dart
// Step 1: load a batch
final PangleDrawAd drawAd = await pangle.loadDrawAd(
iOS: IOSDrawConfig(slotId: kDrawId, adCount: 3),
android: AndroidDrawConfig(slotId: kDrawId, adCount: 2),
);
// Step 2: display in a vertical PageView (TikTok-style)
PageView.builder(
scrollDirection: Axis.vertical,
itemCount: drawAd.data.length,
itemBuilder: (context, i) => DrawView(
id: drawAd.data[i],
onClick: () {},
onShow: () {},
onRenderFail: (code, msg) {},
),
)
// Step 3: release when leaving the page
await pangle.removeDrawAd(drawAd.data);
```
### Stream Ad (Custom Player)
```dart
// Load — returns video URL + metadata, no SDK view
final PangleStreamAd streamAd = await pangle.loadStreamAd(
iOS: IOSStreamConfig(slotId: kStreamId),
android: AndroidStreamConfig(
slotId: kStreamId,
imgSize: PangleSize(width: 640, height: 320), // cover image size
),
);
for (final StreamAdItem item in streamAd.data) {
// item.videoUrl — play this with your video player
// item.imageUrl — cover image
// item.title — ad title
// item.description — ad description
// item.videoDuration — duration in seconds
myPlayer.play(item.videoUrl!);
}
```
### EcMall Ad (Shopping / Native Ad)
```dart
// Must be wrapped in a sized widget
SizedBox(
width: 600,
height: 257,
child: EcMallView(
slotId: kEcMallId,
width: 600,
height: 257,
userData: null, // optional Android JSON string for reward config
onClick: () {},
onShow: () {},
onError: (code, msg) {},
),
)
```
---
## Common Mistakes to Prevent
| Mistake | Correct approach |
|---------|-----------------|
| Wrapping `BannerView`/`FeedView` in `AspectRatio` | Not needed — they self-size from `expressSize` |
| Forgetting `removeFeedAd()` in `dispose()` | Always add cleanup for Feed and Draw |
| Using `loadRewardedVideoAd()` directly | Use `RewardedAd.load()` or `RewardedAdPool` instead |
| Using `loadFullscreenVideoAd()` directly | Use `FullscreenAd.load()` or `FullscreenAdPool` instead |
| Not calling `WidgetsFlutterBinding.ensureInitialized()` before `pangle.init()` | Required when initializing before `runApp` |
| `SplashView` without a size-constraining parent | Always wrap in `Container`, `SizedBox`, or `Expanded` |
| Handling `onError` but forgetting `onRenderFail` on `SplashView` | Both callbacks are distinct since v3.0 |
| Assuming close button auto-removes the view | It does not since v3.0; handle removal in `onClose`/`onDislike` |
---
## Integration Checklist
When generating a complete integration, verify the output covers:
- [ ] Import: `import 'package:pangle_flutter/pangle_flutter.dart';`
- [ ] Initialization with both iOS and Android app IDs
- [ ] Correct config class for the platform (iOS vs Android)
- [ ] Slot ID placeholder with a reminder to replace
- [ ] Cleanup / disposal for stateful ad types (Feed, Draw)
- [ ] Error handling (`AdLoadException`, `onError`, `onRenderFail`)
- [ ] Comments in the user's language
---
## SETUP.md Reference
For Android Manifest and iOS Info.plist / CocoaPods setup, direct the user to [SETUP.md](../SETUP.md). Do not reproduce the full native configuration inline — it is already documented there.
---
## Example Interaction
**User (Chinese):** 我想在我的 Flutter 应用里加一个激励视频广告,广告位 ID 是 `abc123`
**Assistant output (comments in Chinese):**
```dart
import 'package:pangle_flutter/pangle_flutter.dart';
// 在 runApp 之前初始化穿山甲 SDK
WidgetsFlutterBinding.ensureInitialized();
await pangle.init(
iOS: IOSConfig(appId: 'YOUR_IOS_APP_ID'),
android: AndroidConfig(appId: 'YOUR_ANDROID_APP_ID'),
);
// 加载并展示激励视频广告(一次性模式)
Future<void> showRewardedAd() async {
try {
final ad = await RewardedAd.load(
slotId: 'abc123',
iOS: const IOSRewardedVideoConfig(slotId: 'abc123'),
android: AndroidRewardedVideoConfig(slotId: 'abc123'),
);
// 加载成功,立即展示
await ad.show(
onEvent: (PangleAdEvent event) {
switch (event) {
case AdRewardEvent(:final verified):
if (verified) grantReward(); // 服务端验证通过,发放奖励
case AdClosedEvent():
break; // 用户关闭广告
default:
break;
}
},
);
} on AdLoadException catch (e) {
// 加载失败,记录日志
debugPrint('广告加载失败: $e');
}
}
```
---
**User (English):** How do I show a banner ad with slot ID `banner_xyz`?
**Assistant output (comments in English):**
```dart
import 'package:pangle_flutter/pangle_flutter.dart';
// BannerView sizes itself automatically from expressSize — no AspectRatio wrapper needed
BannerView(
iOS: IOSBannerConfig(
slotId: 'banner_xyz',
expressSize: PangleExpressSize(width: 600, height: 260), // match your Pangle dashboard slot size
),
android: AndroidBannerConfig(
slotId: 'banner_xyz',
expressSize: PangleExpressSize(width: 600, height: 260),
),
onClick: () {
// user tapped the ad
},
onError: (code, msg) {
// ad failed to load
debugPrint('Banner error $code: $msg');
},
onRenderFail: (code, msg) {
// ad loaded but failed to render
debugPrint('Banner render fail $code: $msg');
},
)
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!