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. Prebid Server (S2S) Configuration
  7. Native Listing Fetch with SellwildAPIClient
  8. GDPR and Consent Management
  9. Troubleshooting

Prerequisites

RequirementMinimum Version
Flutter3.10.0
Dart SDK3.0.0
iOS13.0+
AndroidAPI 21+ (minSdk 21)
Xcode14.0+
Android Studio / GradleAGP 7.0+

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

The SDK pulls in two transitive dependencies automatically:

DependencyVersionPurpose
webview_flutter^4.4.0WebView rendering for widgets
http^1.1.0REST API calls for listings

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

The SDK loads ad content in a WebView. iOS requires the following entries in ios/Runner/Info.plist:

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

This permits the WebView to load ad creatives and tracking pixels served over HTTP. 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 WebView configuration 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"
        ...>

        <!-- Required for multi-process WebView safety on API 28+ -->
        <meta-data
            android:name="android.webkit.WebView.MetricOptOut"
            android:value="true" />

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

If your app targets API 28+ and uses multiple processes, call WebView.setDataDirectorySuffix() in your Application subclass to avoid multi-process crashes. See the Android WebView multi-process documentation for details.

Android -- Minimum SDK

Ensure your android/app/build.gradle sets a minimum SDK of 21 or higher:

groovy
android {
    defaultConfig {
        minSdkVersion 21
    }
}

Quick Start

A minimal integration requires two values: your partner code and your listings API URL.

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',
      listingsUrl: 'https://api.sellwild.com/widget/listings?partner=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.js 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 primary widget. Renders the full Sellwild marketplace experience -- listing carousel, ad placements, and header bidding -- inside a WebView.

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 WebView or widget 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',
  listingsUrl: 'https://api.sellwild.com/widget/listings?partner=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 the WebView widget, 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',
  listingsUrl: '...',
  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
        },
      ),
    );
  }
}

Prebid Server (S2S) Configuration

By default, the SDK runs Prebid.js client-side inside the WebView. For higher fill rates and to bypass WebView cookie limitations, route header bidding through Prebid Server using the prebidServer field.

dart
final config = SellwildConfig(
  partnerCode: 'weatherbug',
  listingsUrl: 'https://api.sellwild.com/widget/listings?partner=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 SDK automatically injects the s2sConfig block into Prebid.js before the auction runs. No additional JavaScript configuration is needed.

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, without the WebView. Use it when you need listing data for native UI components like SellwildListingCard or your own custom widgets.

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 listingsUrl 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',
  listingsUrl: '...',
  tcfVersion: 2,
  gppEnabled: true,
);

Native CMP Integration

If your app uses a native Consent Management Platform (OneTrust, Didomi, Usercentrics, etc.), the consent string stored in SharedPreferences (Android) or UserDefaults (iOS) is not automatically bridged to the WebView. Prebid.js looks for window.__tcfapi, which will not exist in the isolated WebView context.

Workaround: After your CMP collects consent, inject the TC string into the WebView before the widget loads. This requires accessing the WebView controller, which is not currently exposed by the SDK. If your app requires native CMP bridging, consider using Prebid Server (S2S mode) where GDPR consent is passed server-side via regs.ext.gdpr in the OpenRTB request.

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',
  listingsUrl: '...',
  iabCats: ['IAB15', 'IAB15-10'],  // Technology > Weather
);

Troubleshooting

Widget shows a blank white screen

Cause: The 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 the WebView console output.

Listings do not appear

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

Steps:

  1. Test the URL directly: curl -s "https://api.sellwild.com/widget/listings?partner=weatherbug" | python3 -m json.tool
  2. Verify the response contains a result.rs array with listing objects.
  3. Check that partnerCode matches 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 look for Prebid.js auction results in the console.

WebView crashes on Android API 28+

Cause: Multiple processes sharing the same WebView data directory.

Fix: Set a unique data directory suffix in your Application.onCreate():

kotlin
// In your Android Application class
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    WebView.setDataDirectorySuffix("sellwild_webview")
}

GDPR regions see no ads

Cause: Prebid.js cannot find a TCF consent string and suppresses all bidder calls.

Steps:

  1. If using a native CMP, verify the consent string is available before loading the widget.
  2. Consider switching to Prebid Server S2S mode, where GDPR enforcement is handled server-side.
  3. Check that tcfVersion is set to 2 in your config if you are passing TCF v2 consent.

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. The WebView 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 the widget.


SellwildConfig Complete Reference

FieldTypeDefaultDescription
partnerCodeString(required)Your Sellwild partner identifier
listingsUrlString(required)API endpoint for listing data
slugString''Partner slug for URL construction
nameString''Partner display name
apiBaseUrlStringhttps://api.sellwild.comBase URL for API calls
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?nullCustom Prebid.js bundle URL
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