Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/draft-release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ jobs:
- name: Setup XCode
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: "26.0"
xcode-version: "26.2"

- name: Install .NET MAUI
run: dotnet workload install maui --skip-manifest-update
run: dotnet workload install maui --version 10.0.103

- name: Get current version
id: version-file
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ jobs:
- name: Setup XCode
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: "26.0"
xcode-version: "26.2"

- name: Install .NET MAUI
run: dotnet workload install maui --skip-manifest-update
run: dotnet workload install maui --version 10.0.103

- name: Restore dependencies
run: dotnet restore
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-from-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ jobs:
- name: Setup XCode
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: "26.0"
xcode-version: "26.2"

- name: Install .NET MAUI
run: dotnet workload install maui --skip-manifest-update
run: dotnet workload install maui --version 10.0.103

- name: Restore dependencies
run: dotnet restore
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- `MParticleOptions.LogLevel` is now forwarded to the native SDKs on both platforms. Previously it was ignored (Android never called `MParticleOptions.Builder.logLevel`, and iOS never assigned `MParticleOptions.logLevel`), so the native SDKs stayed at their default log level and `VERBOSE` diagnostics (including batch upload logs) were never emitted.

## [4.2.0] - 2026-05-15

This release upgrades the iOS binding to mParticle Apple SDK 9 and contains breaking changes. See [MIGRATING.md](./MIGRATING.md) for the full 4.x → 5.0 upgrade guide.
Expand Down
3 changes: 2 additions & 1 deletion Kits/rokt/SampleApp/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sdk="clr-namespace:mParticle.MAUI;assembly=mParticle.Maui.Sdk"
xmlns:rokt="clr-namespace:mParticle.MAUI.Rokt;assembly=mParticle.Maui.Kits.Rokt"
x:Class="SampleApp.MainPage">

<ScrollView>
Expand Down Expand Up @@ -54,7 +55,7 @@
Clicked="OnShowRoktShoppableAdsClicked"
HorizontalOptions="Fill" />

<sdk:RoktEmbeddedView
<rokt:RoktEmbeddedView
x:Name="Location1"/>

<Label
Expand Down
1 change: 1 addition & 0 deletions Kits/rokt/SampleApp/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using mParticle.MAUI;
using mParticle.MAUI.Rokt;
using mParticle.MAUI.Rokt.Payments;
using System.Collections.Generic;

Expand Down Expand Up @@ -26,10 +27,10 @@
secret = "ENTER_YOUR_API_SECRET";
#endif

OnUserIdentified _identityStateListener = null;

Check warning on line 30 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
_identityStateListener = newUser =>
{
MParticle.Instance.Identity.RemoveIdentityStateListener(_identityStateListener);

Check warning on line 33 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'listener' in 'void IdentityApi.RemoveIdentityStateListener(OnUserIdentified listener)'.
if (newUser != null)
{
Console.WriteLine("New User Identified\n" + newUser.ToString());
Expand Down Expand Up @@ -209,10 +210,10 @@
},
UserAliasHandler = (previousUser, newUser) =>
{
if (newUser.GetUserIdentities().GetValueOrDefault(UserIdentity.CustomerId).Equals("SomeCustomer123") &&

Check warning on line 213 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
newUser.GetUserIdentities().GetValueOrDefault(UserIdentity.Email).Equals("MyEmail@myemail.xyz") &&

Check warning on line 214 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
newUser.GetUserIdentities().GetValueOrDefault(UserIdentity.Other).Equals("OtherVal") &&

Check warning on line 215 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
newUser.GetUserIdentities().GetValueOrDefault(UserIdentity.Microsoft).Equals("FB"))

Check warning on line 216 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
Console.WriteLine("Incorrect User Identities");
}
Expand Down Expand Up @@ -240,7 +241,7 @@
{
if (previousUser.GetUserAttributes().ContainsKey(ConstantUserAttribute))
{
newUser.GetUserAttributes().TryAdd(ConstantUserAttribute, previousUser.GetUserAttributes().GetValueOrDefault(ConstantUserAttribute));

Check warning on line 244 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'bool Dictionary<string, string>.TryAdd(string key, string value)'.
}
},
})
Expand Down Expand Up @@ -276,7 +277,7 @@
{
{"Location1", Location1}
},
config: null

Check warning on line 280 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
);
}

Expand All @@ -303,7 +304,7 @@
{
{"Location1", Location1}
},
config: null

Check warning on line 307 in Kits/rokt/SampleApp/MainPage.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
);
}

Expand Down
1 change: 1 addition & 0 deletions Kits/rokt/SampleApp/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using mParticle.MAUI;
using mParticle.MAUI.Rokt;

namespace SampleApp;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using mParticle.MAUI.Rokt;
#if __IOS__
using System.Linq;
using Foundation;
using CoreBinding = global::mParticle.MAUI.iOSBinding;
#endif

namespace mParticle.MAUI.Rokt.Payments;

/// <summary>
/// Adds Shoppable Ads support to the Rokt API. This capability only exists when the
/// <c>mParticle.Maui.Kits.Rokt.Payments</c> package is referenced, because it depends on a
/// natively registered Rokt payment extension (see <see cref="RoktPaymentExtension"/>).
/// </summary>
public static class RoktShoppableAdsExtensions
{
/// <summary>
/// Displays a Shoppable Ads overlay placement.
/// </summary>
/// <remarks>
/// Implemented on iOS (requires iOS 15+ and a payment extension registered via
/// <see cref="RoktPaymentExtension.Register(string, string)"/>). On Android this is a
/// no-op until native support lands.
/// </remarks>
/// <param name="rokt">The Rokt API instance.</param>
/// <param name="identifier">The view name / placement identifier.</param>
/// <param name="attributes">Optional attributes for targeting.</param>
/// <param name="config">Optional display configuration (color mode, caching).</param>
public static void SelectShoppableAds(
this RoktApi rokt,
string identifier,
Dictionary<string, string> attributes = null,

Check warning on line 34 in Kits/rokt/Sdk/MParticle.Maui.Rokt.Payments/RoktShoppableAdsExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 34 in Kits/rokt/Sdk/MParticle.Maui.Rokt.Payments/RoktShoppableAdsExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
RoktConfig config = null)

Check warning on line 35 in Kits/rokt/Sdk/MParticle.Maui.Rokt.Payments/RoktShoppableAdsExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 35 in Kits/rokt/Sdk/MParticle.Maui.Rokt.Payments/RoktShoppableAdsExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
// Route through the receiver's native handle so behavior matches the sibling
// RoktApi methods: a null handle means the SDK is not initialized (NoOp receiver).
var handle = rokt?.NativeHandle;
if (handle == null)
{
Console.WriteLine(RoktApi.SdkNotInitializedWarning);
return;
}
#if __IOS__
if (handle is not CoreBinding.MPRokt native)
{
Console.WriteLine(RoktApi.SdkNotInitializedWarning);
return;
}

var nsAttributes = ConvertToNSDictionary(attributes) ?? new NSDictionary<NSString, NSString>();
var nsConfig = ConvertToMpRoktConfig(config);

native.SelectShoppableAds(identifier, nsAttributes, nsConfig, null);
#else
Console.WriteLine("[mParticle MAUI SDK] SelectShoppableAds is not yet supported on Android.");
#endif
Comment thread
cursor[bot] marked this conversation as resolved.
}

#if __IOS__
private static CoreBinding.RoktConfig ConvertToMpRoktConfig(RoktConfig config)
{
if (config == null)
return null;

var builder = new CoreBinding.RoktConfigBuilder()
.ColorMode(ConvertToMpRoktColorMode(config.ColorMode));

if (config.CacheDuration.HasValue || (config.CacheAttributes != null && config.CacheAttributes.Any()))
{
var cacheDuration = config.CacheDuration.HasValue
? config.CacheDuration.Value
: CoreBinding.RoktCacheConfig.MaxCacheDuration;
var cacheAttributes = ConvertToNSDictionary(config.CacheAttributes);
var cacheConfig = new CoreBinding.RoktCacheConfig(cacheDuration, cacheAttributes);
builder = builder.CacheConfig(cacheConfig);
}

return builder.Build();
}

private static CoreBinding.RoktColorMode ConvertToMpRoktColorMode(RoktColorMode colorMode)
{
switch (colorMode)
{
case RoktColorMode.Light:
return CoreBinding.RoktColorMode.Light;
case RoktColorMode.Dark:
return CoreBinding.RoktColorMode.Dark;
case RoktColorMode.System:
default:
return CoreBinding.RoktColorMode.System;
}
}

private static NSDictionary<NSString, NSString> ConvertToNSDictionary(Dictionary<string, string> dictionary)
{
if (dictionary == null || !dictionary.Any())
return new NSDictionary<NSString, NSString>();

return NSDictionary<NSString, NSString>.FromObjectsAndKeys(dictionary.Values.ToArray(), dictionary.Keys.ToArray());
}
#endif
}
14 changes: 3 additions & 11 deletions Kits/rokt/Sdk/MParticle.Maui.Rokt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ After `MParticle.Instance.Initialize(options)`, the Rokt API is available via
Available methods:

- `SelectPlacements(identifier, attributes, embeddedViews, config)`
- `SelectShoppableAds(identifier, attributes, config)`
- `Events(identifier, onEvent)` for placement-specific event subscription
- `GlobalEvents(onEvent)` for all Rokt events

> `SelectShoppableAds(...)` is not part of this package. It ships as an extension in
> `mParticle.Maui.Kits.Rokt.Payments`, which registers the native payment extension it requires.

Example placement selection:

```csharp
Expand All @@ -46,16 +48,6 @@ MParticle.Instance.Rokt.SelectPlacements(
);
```

Example shoppable ads selection:

```csharp
MParticle.Instance.Rokt.SelectShoppableAds(
identifier: "StgRoktShoppableAds",
attributes: attributes,
config: null
);
```

Example event subscriptions:

```csharp
Expand Down
38 changes: 38 additions & 0 deletions Kits/rokt/Sdk/MParticle.Maui.Rokt/RoktExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using mParticle.MAUI;

namespace mParticle.MAUI.Rokt;

/// <summary>
/// Adds the Rokt entry point to the mParticle SDK. The <c>Rokt</c> accessor only exists
/// when the <c>mParticle.Maui.Kits.Rokt</c> package is referenced; the core SDK does not
/// expose any Rokt surface on its own.
/// </summary>
public static class MParticleRoktExtensions
{
extension(MParticleSDK sdk)
{
/// <summary>
/// Returns an implementation of the Rokt API bound to the current mParticle instance.
/// </summary>
public RoktApi Rokt
{
get
{
if (sdk == null)
{
return new NoOpRoktApi();
}
#if __IOS__
var native = sdk.GetBindingInstance() as mParticle.MAUI.iOSBinding.MParticle;
var rokt = native?.Rokt;
return rokt == null ? new NoOpRoktApi() : new IosRoktApi(rokt);
#elif __ANDROID__
var native = sdk.GetBindingInstance() as mParticle.MAUI.AndroidBinding.MParticle;
return native == null ? new NoOpRoktApi() : new AndroidRoktApi(native);
#else
return new NoOpRoktApi();
#endif
}
Comment thread
denischilik marked this conversation as resolved.
}
}
}
Loading
Loading