Skip to main content

Recommendations

Recommendations is data only: the SDK fetches recommendations and renders nothing — you build the list UI. There is no native view and no dispose(); each fetch is an independent request/response round trip.


Sample App

The plugin's example app, shipped with the teads_flutter package, includes a working screen for this format. Use it as a reference while you set up this placement.

Example​

final recommendations = await Recommendations.fetch(
RecommendationsPlacementConfig.url(
widgetId: 'SDK_1', // Provided by your Teads account manager
articleUrl: 'https://example.com/article/123',
),
);

ListView.builder(
itemCount: recommendations.length,
itemBuilder: (context, index) {
final rec = recommendations[index];
return ListTile(
leading: rec.thumbnail?.url != null ? Image.network(rec.thumbnail!.url!) : null,
title: Text(rec.content ?? ''),
subtitle: Text(rec.sourceName ?? ''),
trailing: rec.shouldDisplayDisclosureIcon && rec.disclosure?.iconUrl != null
? GestureDetector(
// launchUrl comes from package:url_launcher.
onTap: () {
final clickUrl = rec.disclosure!.clickUrl;
if (clickUrl != null) launchUrl(Uri.parse(clickUrl));
},
child: Image.network(rec.disclosure!.iconUrl!),
)
: null,
onTap: () => _onRecommendationTap(rec),
);
},
)

Call Recommendations.release(recommendations) once a list is no longer shown, to free the native entries that registerClick resolves against. It is optional, since that cache is also bounded.

Request types​

RecommendationsPlacementConfig has two mutually exclusive named constructors, mirroring the native request shapes:

// URL-based
RecommendationsPlacementConfig.url(
widgetId: 'SDK_1',
articleUrl: 'https://example.com/article/123',
externalId: 'optional-external-id',
);

// Platform-based: for an app or portal rather than one article.
// One of contentUrl / portalUrl / bundleUrl must be set, and lang is required
// by the server.
RecommendationsPlacementConfig.platform(
widgetId: 'SDK_1',
portalUrl: 'https://example.com',
lang: 'en',
);

Handling a tap​

Future<void> _onRecommendationTap(TeadsRecommendation rec) async {
final url = await Recommendations.registerClick(rec);
if (url == null) return;
// Open `url` however your app opens links — an external browser for a paid item,
// your own reader UI for an organic one (`rec.isPaid` tells you which).
}

registerClick returns null if the item has no click handle, or if the native side no longer has it cached.

Call registerClick only at the moment of an actual tap

registerClick resolves the destination URL and reports the click — for an organic recommendation it fires the click-tracking request as a side effect. Calling it eagerly (for example right after fetch, to pre-compute URLs) reports a phantom click for every organic item whether the user taps it or not. This is why TeadsRecommendation has no clickUrl field.

AdChoices and disclosure icons — required​

Two icons are required to ship a recommendations widget:

  • A clickable AdChoices icon above the list, at least 30×30 points, hidden until recommendations load. When tapped, open the URL from Recommendations.adChoicesUrl.
  • The per-item disclosure icon on paid items: show rec.disclosure when rec.shouldDisplayDisclosureIcon is true (as in the example above), opening rec.disclosure!.clickUrl when tapped.
// launchUrl comes from package:url_launcher.
final adChoicesUrl = await Recommendations.adChoicesUrl;

IconButton(
icon: const Icon(Icons.info_outline),
iconSize: 30,
onPressed: adChoicesUrl == null ? null : () => launchUrl(Uri.parse(adChoicesUrl)),
)

To see paid items (and so the disclosure icons) while testing, enable test mode and testRTB before fetching — testRTB does nothing outside test mode:

await TeadsSdk.setTestMode(true);
await Recommendations.setTestRtb(true);

Both are process-wide — remove them before release.

Available fields​

TeadsRecommendation exposes only the fields present, with the same meaning, on both platforms:

FieldTypeNotes
contentString?The recommendation's title
authorString?
sourceNameString?Publisher/source name to display
positionString?Position within the fetched set
publishDateDateTime?
isPaidbooltrue for a paid (3rd-party) item, false for organic
isVideoboolAlways false on iOS — that is native's own behavior
sameSourcebool
shouldDisplayDisclosureIconboolShow disclosure when true
thumbnailTeadsRecommendationThumbnail?url, width, height
disclosureTeadsRecommendationDisclosure?iconUrl, clickUrl
reqIdString?Needed for viewability reporting

Android-only extras (description, advertiserName, ctaText, and others) are deliberately dropped rather than sent as null on iOS.

Viewability​

Because you render the list, impression reporting is yours to drive too. See Recommendations Viewability — it is required for revenue on recommendation placements.

Configuration​

Every RecommendationsPlacementConfig field and Recommendations member is in Settings.

Checklist​