Skip to content

Sellwild SDK -- Flutter Integration Guide

This guide covers everything needed to integrate the Sellwild SDK into a Flutter application, from installation through production deployment.


Table of Contents

  1. Prerequisites
  2. Installation
  3. Platform Configuration
  4. Quick Start
  5. Widgets Reference
  6. Remote Config
  7. Prebid Server (S2S) Configuration
  8. Native Listing Fetch with SellwildAPIClient
  9. GDPR and Consent Management
  10. Troubleshooting

Prerequisites

RequirementMinimum Version
Flutter3.10.0
Dart SDK3.0.0
iOS13.0+
AndroidAPI 23+ (minSdk 23, required by Google Mobile Ads SDK)
Xcode16.0+ (required by Prebid Mobile 3.x)
Android Studio / GradleAGP 7.0+

The native banner path uses Prebid Mobile + Google Mobile Ads. Both are wired automatically by sellwild_sdk 1.3.0 — you do not add them to your pubspec.yaml.

Verify your environment before proceeding:

bash
flutter doctor -v
dart --version

Installation

Add the Sellwild SDK to your pubspec.yaml:

yaml
dependencies:
  sellwild_sdk: ^1.3.0

The SDK pulls in transitive dependencies automatically:

DependencyVersionPurpose
webview_flutter^4.4.0WebView rendering for SellwildWidget (marketplace listings only)
http^1.1.0REST API calls for listings

On the native side, sellwild_sdk re-exports the iOS SellwildSDK pod (which depends on PrebidMobile and Google-Mobile-Ads-SDK) and the Android com.sellwild:sdk artifact (which depends on org.prebid:prebid-mobile-sdk and com.google.android.gms:play-services-ads). No extra pubspec entries are required for banner ads.

Run the install:

bash
flutter pub get

Import the SDK in your Dart code:

dart
import 'package:sellwild_sdk/sellwild_sdk.dart';

Platform Configuration

iOS -- Info.plist

Add the Google Mobile Ads application identifier to ios/Runner/Info.plist:

xml
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>

Without this entry, the GMA SDK will crash at first banner load. Use Google's official sample app ID (ca-app-pub-3940256099942544~1458002511) only during development.

SellwildWidget (marketplace listings) is rendered in WKWebView and may load ad creatives or tracking pixels served over HTTP. If you use it, add:

xml
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoadsInWebContent</key>
  <true/>
</dict>

This setting only affects WKWebView loads; it does not affect your app's own network calls.

If your app requests IDFA for attribution or frequency capping, also add:

xml
<key>NSUserTrackingUsageDescription</key>
<string>This identifier is used to deliver relevant ads and measure campaign performance.</string>

For SKAdNetwork support, add the relevant network identifiers provided by your SSPs:

xml
<key>SKAdNetworkItems</key>
<array>
  <dict>
    <key>SKAdNetworkIdentifier</key>
    <string>cstr6suwn9.skadnetwork</string>
  </dict>
  <!-- Add identifiers for each SSP -->
</array>

Android -- AndroidManifest.xml

Add the INTERNET permission (usually present by default) and the Google Mobile Ads application ID to android/app/src/main/AndroidManifest.xml:

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:usesCleartextTraffic="false"
        ...>

        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />

        <activity ...>
            <!-- existing activity config -->
        </activity>
    </application>
</manifest>

Without com.google.android.gms.ads.APPLICATION_ID, the GMA SDK throws on first banner load. Use Google's official sample app ID (ca-app-pub-3940256099942544~3347511713) only during development.

Android -- Minimum SDK

The Google Mobile Ads SDK requires minSdkVersion 23 or higher. Ensure your android/app/build.gradle is updated:

groovy
android {
    defaultConfig {
        minSdkVersion 23
    }
}

Quick Start

A minimal integration requires two values: your partner code and your slug.

Recommended: use configure()

For most integrations, call SellwildSDK.configure(partnerCode:, slug:) instead of building SellwildConfig by hand. It fetches your partner config from https://widget.sellwild.com/app/{partnerCode}/{slug}.json and populates every field below automatically:

dart
final config = await SellwildSDK.configure(
  partnerCode: 'weatherbug',
  slug: 'weatherbug-weatherbug',
);

The static example below is shown only to document each field.

dart
import 'package:flutter/material.dart';
import 'package:sellwild_sdk/sellwild_sdk.dart';

class MarketplaceScreen extends StatelessWidget {
  const MarketplaceScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final config = SellwildConfig(
      partnerCode: 'weatherbug',
      appBundleId: 'com.aws.android',
      appStoreUrl: 'https://play.google.com/store/apps/details?id=com.aws.android',
    );

    return Scaffold(
      appBar: AppBar(title: const Text('Marketplace')),
      body: SellwildWidget(
        config: config,
        onListingTap: (listing) {
          debugPrint('Tapped listing: ${listing.title} -- ${listing.url}');
        },
        onAdImpression: (zoneId) {
          debugPrint('Ad impression in zone: $zoneId');
        },
        onError: (error) {
          debugPrint('Widget error: $error');
        },
      ),
    );
  }
}

Important: Always set appBundleId and appStoreUrl. Without these fields, Prebid Mobile classifies bid requests as web traffic (ortb2.site) instead of in-app traffic (ortb2.app). DSPs that segment app and web inventory will not bid, and app-ads.txt enforcement is bypassed.


Widgets Reference

SellwildWidget

The marketplace widget. Renders the full Sellwild listing carousel inside a WKWebView (iOS) or WebView (Android). This is the only Sellwild surface that uses a WebView; banner ads (SellwildBanner) render natively as of 1.3.0.

dart
SellwildWidget(
  config: config,
  onListingTap: (SellwildListing listing) {
    // User tapped a listing. Navigate to detail screen or open URL.
    Navigator.push(context, MaterialPageRoute(
      builder: (_) => ListingDetailScreen(listing: listing),
    ));
  },
  onAdImpression: (String zoneId) {
    // Track ad impression in your analytics.
    analytics.logEvent('ad_impression', {'zone_id': zoneId});
  },
  onLoad: () {
    // Widget finished loading. Hide any external loading indicators.
  },
  onError: (Object error) {
    // Log or report errors.
    crashlytics.recordError(error);
  },
)

Constructor parameters:

ParameterTypeRequiredDescription
configSellwildConfigYesSDK configuration object
onListingTapvoid Function(SellwildListing)?NoCalled when the user taps a listing
onAdImpressionvoid Function(String)?NoCalled on ad impression with the zone ID
onLoadvoid Function()?NoCalled when the widget finishes loading
onErrorvoid Function(Object)?NoCalled on widget load errors

Layout considerations: SellwildWidget expands to fill its parent. Wrap it in a SizedBox, Expanded, or Flexible to control its dimensions:

dart
SizedBox(
  height: 400,
  child: SellwildWidget(config: config),
)

SellwildBanner

A standalone banner ad unit. Use this for ad placements outside the main widget, such as between content sections or in a sticky footer.

dart
SellwildBanner(
  config: config,
  adSize: SellwildAdSize.banner320x50,
  zoneId: '12345',
  onImpression: () {
    analytics.logEvent('banner_impression');
  },
  onClick: () {
    analytics.logEvent('banner_click');
  },
  onError: (error) {
    debugPrint('Banner error: $error');
  },
)

Constructor parameters:

ParameterTypeRequiredDescription
configSellwildConfigYesSDK configuration object
adSizeSellwildAdSizeYesIAB standard ad dimensions
zoneIdString?NoSellwild ad zone identifier
onImpressionvoid Function()?NoCalled when the ad renders
onClickvoid Function()?NoCalled when the user taps the ad
onErrorvoid Function(Object)?NoCalled on load errors

Supported ad sizes (SellwildAdSize):

Enum ValueDimensionsIAB Name
banner320x50320 x 50Mobile Banner
mrec300x250300 x 250Medium Rectangle
leaderboard728x90728 x 90Leaderboard
halfPage300x600300 x 600Half Page
wideSkyscraper160x600160 x 600Wide Skyscraper

Google Ad Manager integration: If you use GAM, pass gamTag in SellwildConfig instead of zoneId:

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  gamTag: '/12345678/weatherbug_mobile_banner',
);

SellwildBanner(
  config: config,
  adSize: SellwildAdSize.mrec300x250,
)

SellwildListingCard

A native Flutter widget for rendering a single listing. Use this when building custom listing layouts outside SellwildWidget, such as a grid or list backed by the SellwildAPIClient.

dart
SellwildListingCard(
  listing: listing,
  config: config,
  onTap: (listing) {
    Navigator.pushNamed(context, '/listing/${listing.id}');
  },
)

Constructor parameters:

ParameterTypeRequiredDescription
listingSellwildListingYesThe listing data to render
configSellwildConfigYesUsed for styling (colors, font size)
onTapvoid Function(SellwildListing)?NoCalled when the card is tapped

The card renders at a fixed width of 160px with an image, price badge, and title. Customize colors through SellwildConfig:

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  priceColor: '#FF5722',       // Price badge background
  priceFontColor: '#FFFFFF',   // Price badge text
  fontSize: 14,                // Title font size
);

Building a custom listing grid:

dart
class ListingGrid extends StatefulWidget {
  final SellwildConfig config;
  const ListingGrid({super.key, required this.config});

  @override
  State<ListingGrid> createState() => _ListingGridState();
}

class _ListingGridState extends State<ListingGrid> {
  List<SellwildListing> _listings = [];
  bool _loading = true;

  @override
  void initState() {
    super.initState();
    _loadListings();
  }

  Future<void> _loadListings() async {
    try {
      final response = await SellwildAPIClient.instance
          .fetchListings(widget.config);
      setState(() {
        _listings = response.listings;
        _loading = false;
      });
    } catch (e) {
      setState(() => _loading = false);
      debugPrint('Failed to load listings: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) return const Center(child: CircularProgressIndicator());

    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 0.7,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      padding: const EdgeInsets.all(16),
      itemCount: _listings.length,
      itemBuilder: (context, index) => SellwildListingCard(
        listing: _listings[index],
        config: widget.config,
        onTap: (listing) {
          // Handle listing tap
        },
      ),
    );
  }
}

Remote Config (the first-class path)

SellwildSDK.configure(partnerCode:, slug:) is the recommended way to integrate the SDK. It fetches a JSON document from the Sellwild CDN at app launch and returns a fully-built SellwildConfig — ad zones, refresh intervals, app identity, waterfall partners, compliance flags, and more — so you can change everything without an app update.

dart
import 'package:sellwild_sdk/sellwild_sdk.dart';

final config = await SellwildSDK.configure(
  partnerCode: 'weatherbug',
  slug: 'weatherbug-weatherbug',
);
// Hand `config` to your SellwildWidget / SellwildBanner.

The CDN URL is https://widget.sellwild.com/app/{partnerCode}/{slug}.json. Your Sellwild contact provisions the slug in the CMS.

Failure handling. On any network error, timeout, or 404 the call falls back to a SellwildConfig(partnerCode: ...) with deterministic defaults, so ads still render even with the CDN offline. Remote config is additive, never blocking.

See Configuration → Remote Config for the full CDN field reference.


Prebid Server (S2S) Configuration

As of 1.3.0, banner ads run a native Prebid Mobile auction against the configured Prebid Server endpoint. Set the prebidServer field on SellwildConfig to enable it.

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  appBundleId: 'com.aws.android',
  appStoreUrl: 'https://play.google.com/store/apps/details?id=com.aws.android',
  prebidServer: PrebidServerConfig(
    accountId: 'weatherbug',
    endpoint: 'https://prebid.sellwild.com/openrtb2/auction',
    bidders: ['appnexus', 'pubmatic', 'ix', 'rubicon', 'openx'],
    timeout: 1500,
  ),
);

PrebidServerConfig fields:

FieldTypeRequiredDefaultDescription
accountIdStringYes--Your Prebid Server account ID
endpointStringYes--Full URL to the auction endpoint
biddersList<String>Yes--Bidder codes to route server-side
timeoutintNo1500S2S auction timeout in milliseconds
syncEndpointString?NonullCookie sync endpoint (derived from endpoint if omitted)

When prebidServer is set, the native Prebid Mobile SDK is bootstrapped on first banner load and the auction runs in-process. The bid response targeting keywords are attached to the GAM AdManagerAdRequest so the cached creative resolves through your line items. No additional JavaScript configuration is required.

For a complete Prebid Server configuration reference, see the Prebid Server Configuration Guide.


Native Listing Fetch with SellwildAPIClient

SellwildAPIClient is a singleton HTTP client for fetching listing data directly. Use it when you need listing data for native UI components like SellwildListingCard or your own custom widgets — it bypasses SellwildWidget entirely.

Basic Usage

dart
final client = SellwildAPIClient.instance;

final response = await client.fetchListings(config);
for (final listing in response.listings) {
  print('${listing.title} -- ${listing.displayPrice}');
}

Response Structure

fetchListings returns a SellwildListingsResponse:

FieldTypeDescription
listingsList<SellwildListing>The listing objects
configMap<String, dynamic>Server-side widget configuration overrides
widgetCacheVersionIdString?Cache version for conditional refresh

SellwildListing Fields

FieldTypeDescription
idStringUnique listing identifier
statusStringListing status (e.g., active)
titleStringListing title
textString?Description body text
urlString?Canonical URL for the listing
categoryIdString?Category identifier
currencyString?ISO 4217 currency code (USD, EUR, GBP, etc.)
priceString?Price as a string
strikePriceString?Original price before discount
hasPhotoboolWhether the listing has at least one photo
photosList<SellwildPhoto>Photo objects with url and thumbUrl
createdDateString?ISO 8601 creation date
shippableString?Shipping availability flag
dataSourceIdString?Source identifier for multi-feed integrations
userSellwildUser?Seller information
distancedouble?Distance from search origin (when geo-sorted)

Computed properties:

  • listing.displayPrice -- returns the formatted price string, or null if the price is zero or unparseable.
  • listing.primaryPhoto -- returns the first SellwildPhoto, or null if no photos exist.

Caching

fetchListings caches responses by URL. Subsequent calls with the same config return the cached result without a network request. To force a refresh:

dart
SellwildAPIClient.instance.clearCache();
final fresh = await SellwildAPIClient.instance.fetchListings(config);

Lifecycle

The HTTP client is created once and persists for the lifetime of the app. In test environments, call dispose() to close the client and release resources:

dart
// In test tearDown:
SellwildAPIClient.instance.dispose();

Error Handling

Network and parsing errors throw SellwildException:

dart
try {
  final response = await SellwildAPIClient.instance.fetchListings(config);
} on SellwildException catch (e) {
  debugPrint('Sellwild API error: ${e.message}');
} catch (e) {
  debugPrint('Unexpected error: $e');
}

The SDK supports IAB Transparency and Consent Framework (TCF) v2. Enable it in your config:

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  tcfVersion: 2,
  gppEnabled: true,
);

Native CMP Integration

If your app uses a native Consent Management Platform (OneTrust, Didomi, Usercentrics, etc.), the CMP writes the IAB TCF v2 string to standard storage (SharedPreferences / UserDefaults) under the IABTCF_* keys. Prebid Mobile reads these keys natively and forwards them to Prebid Server in regs.ext.gdpr and user.ext.consent on the OpenRTB request.

The SellwildWidget (marketplace surface) loads window.__tcfapi from inside the WebView; if your CMP does not expose a JavaScript locator there, listings still render but Prebid.js inside the widget will be GDPR-suppressed. Banner ads (native path) are unaffected.

Prebid Server GDPR Handling

When using Prebid Server S2S mode, the server enforces GDPR based on the regs.ext.gdpr field in the OpenRTB bid request. The Sellwild Prebid Server instance at prebid.sellwild.com is configured with gdpr.default-value: 1, meaning GDPR enforcement is applied by default unless the request explicitly opts out. See the Prebid Server Configuration Guide for details on how consent signals propagate.

IAB Category Declaration

Declare IAB content categories for your app to improve ad relevance and brand safety:

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  iabCats: ['IAB15', 'IAB15-10'],  // Technology > Weather
);

Troubleshooting

SellwildWidget shows a blank white screen

Cause: The marketplace WebView failed to load https://widget.sellwild.com/partner.js.

Steps:

  1. Verify network connectivity. Open the URL in a device browser to confirm it loads.
  2. On iOS, confirm NSAllowsArbitraryLoadsInWebContent is set in Info.plist.
  3. Check onError callback output for specific error messages.
  4. Enable debug mode (debug: true in SellwildConfig) and inspect device logs (Xcode console / adb logcat).

Listings do not appear

Cause: The listings endpoint returned no results or an unexpected response format.

Steps:

  1. Look up the CDN-resolved listings endpoint — curl -s "https://widget.sellwild.com/app/{partnerCode}/{slug}.json" | python3 -m json.tool | grep LISTINGS — then curl that URL directly to inspect the response.
  2. Verify the response contains a result.rs array with listing objects.
  3. Check that partnerCode and slug match an active partner account.

onListingTap is never called

Cause: The web widget sends a URL-based LISTING_CLICK event via window.open(). The SDK constructs a minimal SellwildListing stub with the URL. If neither a full listing object nor a URL is present in the bridge message, the callback is skipped.

Steps:

  1. Confirm the onListingTap callback is set on the SellwildWidget.
  2. Tap a listing and check debug output for bridge messages.
  3. The listing.url field on the stub will contain the target URL. Use it to navigate.

Ads are not filling (no impressions)

Cause: Header bidding requires correct in-app traffic signals.

Steps:

  1. Set appBundleId and appStoreUrl in SellwildConfig. Without these, bid requests are classified as web traffic and most SSPs will not bid.
  2. If using Prebid Server, verify the endpoint is reachable: curl -I https://prebid.sellwild.com/openrtb2/auction
  3. Check that the bidder codes in PrebidServerConfig.bidders match your server-side configuration.
  4. Enable debug: true and inspect Prebid Mobile / GMA logs in Xcode console or adb logcat.

GDPR regions see no ads

Cause: Prebid Mobile reads TCF v2 consent from the standard IABTCF_* storage keys; if those keys are missing or signal "no consent", bidders are suppressed.

Steps:

  1. Verify your CMP writes IABTCF_TCString and related keys before the first banner loads.
  2. Confirm tcfVersion: 2 is set in your SellwildConfig if you want to advertise TCF v2 support.
  3. Inspect regs.ext.gdpr and user.ext.consent in the OpenRTB request via Prebid Server logs.

Price badge shows $ for non-USD listings

Cause: The SellwildListingCard maps currency codes to symbols. Supported codes: USD, EUR, GBP, CAD, AUD. All other currencies default to $.

Fix: If you need additional currency symbols, render your own price display using listing.currency and listing.price.

Build fails with "Minimum OS version" error on iOS

Fix: Ensure your ios/Podfile specifies iOS 13.0 or higher:

ruby
platform :ios, '13.0'

Hot reload does not update the widget

Expected behavior. SellwildWidget content is loaded in initState() and is not rebuilt on hot reload. Use hot restart (flutter run --restart) or navigate away and back to reload it.


SellwildConfig Complete Reference

FieldTypeDefaultDescription
partnerCodeString(required)Your Sellwild partner identifier
slugString''Partner slug for URL construction
nameString''Partner display name
titleString?nullWidget header title
linkTextString?'View all'Text for the "view all" link
buyNowTextString?'Buy now'Text for the buy button
titleColorString'#000000'Widget title color (hex)
titleSizeint16Widget title font size in px
linkColorString'#0066cc'Link text color (hex)
fontSizeint13Listing title font size in px
fontColorString'#ffffff'Listing title font color (hex)
priceColorString'#333333'Price badge background color (hex)
priceFontColorString'#ffffff'Price badge text color (hex)
marginBottomint10Bottom margin in px
colorsList<String>['#333333']Theme accent colors (hex)
overlayTitleboolfalseOverlay title on listing image
watermarkboolfalseShow watermark
watermarkTitleString'Powered by Sellwild'Watermark text
adTypeString?null (defaults to PrebidOnly)Ad system selection
bannerZidString?nullTop banner zone ID
bottomBannerZidString?nullBottom banner zone ID
mobileBannerZidString?nullMobile-specific banner zone ID
mobileZidsList<String>[]Additional mobile zone IDs
hideBannerTopboolfalseHide the top banner placement
hideBannerBottomboolfalseHide the bottom banner placement
gamTagString?nullGoogle Ad Manager ad unit path
gptProxyUrlString?nullGPT script proxy URL
disableGptboolfalseDisable GPT entirely
adDisableDisplayboolfalseDisable all display ads
adRefreshMaxint0Max ad refreshes (0 = unlimited)
adRefreshMaxMobileint0Max ad refreshes on mobile (0 = use adRefreshMax)
adRefreshIntervalDuration30 secondsTime between ad refreshes
maxFailedAuctionsint3Stop refreshing after N consecutive no-fills
prebidSrcString?nullReserved for SellwildWidget (marketplace) only
floorMultiplierdouble1.0Bid floor multiplier
gppEnabledboolfalseEnable IAB Global Privacy Platform
tcfVersionint0TCF version (0 = disabled, 2 = TCF v2)
iabCatsList<String>[]IAB content category codes
boltiveboolfalseEnable Boltive ad quality
boltiveClientIdString''Boltive client identifier
lotameboolfalseEnable Lotame data enrichment
appBundleIdString?nullApp bundle ID for ortb2.app.bundle
appStoreUrlString?nullApp store URL for ortb2.app.storeurl
prebidServerPrebidServerConfig?nullPrebid Server S2S configuration
debugboolfalseEnable debug logging

Sellwild SDK Documentation