Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d8fef37
Add flutter_aepoptimize plugin for Adobe Optimize SDK
akhiljain1907 Jun 20, 2026
8e03bd7
minor fix
akhiljain1907 Jun 20, 2026
107854f
fixed displayed tapped and generateInteractionXDM apis by adding prop…
akhiljain1907 Jun 30, 2026
1cda4d6
fixed proposition tracking as native AEP Offers have weak proposition…
akhiljain1907 Jun 30, 2026
e3715b8
test app changes for AEPOptimize Plugin
akhiljain1907 Jul 14, 2026
facb9c3
Merge pull request #165 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 23, 2026
8923780
Merge pull request #166 from akhiljain1907/feature/optimize-example-app
akhiljain1907 Jul 23, 2026
bfa58f4
minor changes for CI fix -
akhiljain1907 Jul 24, 2026
e500fcb
Merge pull request #168 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 24, 2026
acc612d
addressed code review comments
akhiljain1907 Jul 27, 2026
3ab307e
Merge pull request #169 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 27, 2026
73c11d2
restore proposition retention in batch tracking methods
akhiljain1907 Jul 27, 2026
42e9d59
Merge pull request #170 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 27, 2026
408a9f1
Android - retain propositions in batch tracking methods to prevent em…
akhiljain1907 Jul 27, 2026
3c58fd0
Merge pull request #171 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 27, 2026
3bd0204
added github documentation for Optimize Plugin
akhiljain1907 Jul 27, 2026
859d3d4
pass through proposition activity and placement fields for tracking
akhiljain1907 Jul 27, 2026
5d99c03
Merge pull request #172 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 28, 2026
096aadb
updated offers key to items
akhiljain1907 Jul 29, 2026
955cbf3
Merge pull request #173 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 29, 2026
e0d97fd
updated copyright year
akhiljain1907 Jul 29, 2026
2b6e30e
Added Docs for all public models and methods
akhiljain1907 Jul 29, 2026
e6240d7
Merge pull request #174 from akhiljain1907/feature/flutter-aepoptimiz…
akhiljain1907 Jul 29, 2026
44e54dc
Merge pull request #167 from adobe/feat/optimize-plugin
akhiljain1907 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,24 @@ jobs:
- name: Unit test
working-directory: ./plugins/flutter_aepuserprofile
run: flutter test


job9:
name: Optimize unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1

- uses: actions/setup-java@v1
with:
java-version: "17.x"
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.x"

- name: Install dependencies
working-directory: ./plugins/flutter_aepoptimize
run: flutter pub get

- name: Unit test
working-directory: ./plugins/flutter_aepoptimize
run: flutter test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This repository is a monorepo. It contains a collection of Adobe Experience Plat
| [EdgeBridge](plugins/flutter_aepedgebridge/README.md) | [![pub package](https://img.shields.io/pub/v/flutter_aepedgebridge.svg)](https://pub.dartlang.org/packages/flutter_aepedgebridge) |
| [UserProfile](plugins/flutter_aepuserprofile/README.md) | [![pub package](https://img.shields.io/pub/v/flutter_aepuserprofile.svg)](https://pub.dartlang.org/packages/flutter_aepuserprofile) |
| [Messaging](plugins/flutter_aepmessaging/README.md) | [![pub package](https://img.shields.io/pub/v/flutter_aepmessaging.svg)](https://pub.dartlang.org/packages/flutter_aepmessaging) |
| [Optimize](plugins/flutter_aepoptimize/README.md) | [![pub package](https://img.shields.io/pub/v/flutter_aepoptimize.svg)](https://pub.dartlang.org/packages/flutter_aepoptimize) |

> [!NOTE]
> The Flutter plugins within this repository are specifically designed to support the Android and iOS platforms only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_aepmessaging, com.adobe.marketing.mobile.flutter.flutter_aepmessaging.FlutterAEPMessagingPlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.adobe.marketing.mobile.flutter.flutter_aepoptimize.FlutterAEPOptimizePlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_aepoptimize, com.adobe.marketing.mobile.flutter.flutter_aepoptimize.FlutterAEPOptimizePlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.adobe.marketing.mobile.flutter.flutter_aepuserprofile.FlutterAEPUserProfilePlugin());
} catch (Exception e) {
Expand Down
4 changes: 2 additions & 2 deletions example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4096M
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=false

8 changes: 8 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'identity.dart';
import 'edgeIdentity.dart';
import 'edgebridge.dart';
import 'userprofile.dart';
import 'optimize.dart';


void main() async {
Expand Down Expand Up @@ -133,6 +134,13 @@ class _HomePageState extends State<HomePage> {
MaterialPageRoute(builder: (context) => MessagingPage()));
},
),
ElevatedButton(
child: const Text('OPTIMIZE'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => OptimizePage()));
},
),
]),
));
}
Expand Down
317 changes: 317 additions & 0 deletions example/lib/optimize.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_aepoptimize/flutter_aepoptimize.dart';
import 'util.dart';

class OptimizePage extends StatefulWidget {
@override
_OptimizePageState createState() => _OptimizePageState();
}

class _OptimizePageState extends State<OptimizePage> {
String _optimizeVersion = 'Unknown';
String _propositionsResult = '';
String _listenerResult = '';
String _trackingResult = '';
List<Offer> _lastOffers = [];

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

Future<void> initPlatformState() async {
late String optimizeVersion;

try {
optimizeVersion = await Optimize.extensionVersion;
} on PlatformException {
log("Failed to get Optimize extension version");
optimizeVersion = 'Unknown';
}

if (!mounted) return;

setState(() {
_optimizeVersion = optimizeVersion;
});
}

Future<void> updatePropositions() async {
final scopes = [
DecisionScope('akhil-test-mbox'),
];

try {
final result = await Optimize.updatePropositions(
scopes,
xdm: {'eventType': 'personalization.request'},
data: {'dataKey': 'dataValue'},
);

if (!mounted) return;

setState(() {
if (result != null) {
_lastOffers = result.values.expand((p) => p.offers).toList();
_propositionsResult =
'Received ${result.length} scope(s):\n${result.entries.map((e) => ' ${e.key.name}: ${e.value.offers.length} offer(s)').join('\n')}';
} else {
_propositionsResult = 'No propositions returned';
}
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_propositionsResult = 'Error: ${e.message}';
});
}
}

Future<void> updatePropositionsWithTimeout() async {
final scopes = [
DecisionScope('xcore:offer-activity:1111111111111111'),
];

try {
final result = await Optimize.updatePropositions(
scopes,
timeout: 5.0,
);

if (!mounted) return;

setState(() {
_propositionsResult = result != null
? 'Received ${result.length} scope(s) (with timeout)'
: 'No propositions returned (with timeout)';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_propositionsResult = 'Error: ${e.message}';
});
}
}

Future<void> getPropositions() async {
final scopes = [
DecisionScope('akhil-test-mbox'),
];

try {
final result = await Optimize.getPropositions(scopes);

if (!mounted) return;

setState(() {
_propositionsResult = result != null
? 'Cached ${result.length} scope(s):\n${result.entries.map((e) => ' ${e.key.name}: ${e.value.offers.length} offer(s)').join('\n')}'
: 'No cached propositions';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_propositionsResult = 'Error: ${e.message}';
});
}
}

void registerPropositionsListener() {
Optimize.onPropositionsUpdate((propositions) {
if (!mounted) return;
setState(() {
_listenerResult =
'Listener fired: ${propositions.length} scope(s) updated';
});
});

setState(() {
_listenerResult = 'Listener registered';
});
}

Future<void> clearCachedPropositions() async {
await Optimize.clearCachedPropositions();

if (!mounted) return;

setState(() {
_propositionsResult = 'Cache cleared';
});
}

Future<void> updateWithActivityPlacement() async {
final scope = DecisionScope.fromActivityAndPlacement(
activityId: 'xcore:offer-activity:1111111111111111',
placementId: 'xcore:offer-placement:2222222222222222',
);

try {
final result = await Optimize.updatePropositions([scope]);

if (!mounted) return;

setState(() {
_propositionsResult = result != null
? 'Activity/Placement: ${result.length} scope(s)'
: 'No propositions returned';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_propositionsResult = 'Error: ${e.message}';
});
}
}

// --- Offer instance tracking ---

Future<void> offerDisplayed() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
await _lastOffers.first.displayed();
if (!mounted) return;
setState(() => _trackingResult = 'offer.displayed() sent for "${_lastOffers.first.id}"');
}

Future<void> offerTapped() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
await _lastOffers.first.tapped();
if (!mounted) return;
setState(() => _trackingResult = 'offer.tapped() sent for "${_lastOffers.first.id}"');
}

Future<void> offerGenerateDisplayXdm() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
final xdm = await _lastOffers.first.generateDisplayInteractionXdm();
if (!mounted) return;
setState(() => _trackingResult = 'generateDisplayInteractionXdm:\n$xdm');
}

Future<void> offerGenerateTapXdm() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
final xdm = await _lastOffers.first.generateTapInteractionXdm();
if (!mounted) return;
setState(() => _trackingResult = 'generateTapInteractionXdm:\n$xdm');
}

// --- Batch (static) tracking ---

Future<void> batchDisplayed() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
await Optimize.displayed(_lastOffers);
if (!mounted) return;
setState(() => _trackingResult = 'Optimize.displayed() sent for ${_lastOffers.length} offer(s)');
}

Future<void> batchGenerateDisplayXdm() async {
if (_lastOffers.isEmpty) {
setState(() => _trackingResult = 'No offers — call updatePropositions first');
return;
}
final xdm = await Optimize.generateDisplayInteractionXdm(_lastOffers);
if (!mounted) return;
setState(() => _trackingResult = 'Optimize.generateDisplayInteractionXdm:\n$xdm');
}

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text("Optimize Screen")),
body: Center(
child: ListView(shrinkWrap: true, children: <Widget>[
getRichText(
'AEPOptimize extension version: ', '$_optimizeVersion\n'),
getRichText('Propositions result: ', '$_propositionsResult\n'),
getRichText('Listener status: ', '$_listenerResult\n'),
getRichText('Tracking result: ', '$_trackingResult\n'),
ElevatedButton(
child: Text("updatePropositions"),
onPressed: () => updatePropositions(),
),
ElevatedButton(
child: Text("updatePropositions (with timeout)"),
onPressed: () => updatePropositionsWithTimeout(),
),
ElevatedButton(
child: Text("updatePropositions (activity/placement)"),
onPressed: () => updateWithActivityPlacement(),
),
ElevatedButton(
child: Text("getPropositions (from cache)"),
onPressed: () => getPropositions(),
),
ElevatedButton(
child: Text("registerOnPropositionsUpdate"),
onPressed: () => registerPropositionsListener(),
),
ElevatedButton(
child: Text("clearCachedPropositions"),
onPressed: () => clearCachedPropositions(),
),
Divider(thickness: 2, height: 32),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text('Offer Tracking (uses first offer from last updatePropositions)',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
),
ElevatedButton(
child: Text("offer.displayed()"),
onPressed: () => offerDisplayed(),
),
ElevatedButton(
child: Text("offer.tapped()"),
onPressed: () => offerTapped(),
),
ElevatedButton(
child: Text("offer.generateDisplayInteractionXdm()"),
onPressed: () => offerGenerateDisplayXdm(),
),
ElevatedButton(
child: Text("offer.generateTapInteractionXdm()"),
onPressed: () => offerGenerateTapXdm(),
),
Divider(thickness: 2, height: 32),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text('Batch Tracking (all offers from last updatePropositions)',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
),
ElevatedButton(
child: Text("Optimize.displayed(offers)"),
onPressed: () => batchDisplayed(),
),
ElevatedButton(
child: Text("Optimize.generateDisplayInteractionXdm(offers)"),
onPressed: () => batchGenerateDisplayXdm(),
),
]),
));
}
Loading
Loading