RevenueCat integration for .NET MAUI using a slim binding approach.
There are a few other community efforts that maintain more complete bindings to RevenueCat SDKs which are commendable efforts! However, those bindings are complex, difficult to update and maintain, and don't all support all the platforms.
This library takes a much lighter touch approach by creating native library projects which expose a much simpler API to .NET/C# (so it's very easy to bind) and attempts to do most of the heavy lifting in native platform code (java/swift). The binding API surface only passes very simple types, and returns JSON strings which are deserialized in .NET back into more complex objects.
Generally the idea behind RevenueCat is you mostly have to care about what 'Entitlements' your user has at any given time, so this library doesn't attempt to provide rich data and API's around transaction details, but focuses on the fundamental concept of always raising the CustomerInfoUpdated event with the latest CustomerInfoRequest instance.
Most methods return this CustomerInfoRequest object which contains the user's entitlements. This includes calls to purchase, restore, login, etc.
My current main use case is subscriptions, so I have not done much work around consumables and one-off product purchases. I'd be happy for any help to round out the API surface and fill in any gaps related to non-subscription purchase types.
In your builder, call .UseRevenueCat(...) to register IRevenueCatManager with dependency injection, set configuration options, and have the initialization methods invoked in the appropriate application lifecycle events:
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
/* ... */
.UseRevenueCat(o => o
.WithAndroidApiKey("goog_[YOUR_GOOGLE_CLIENT_KEY]")
.WithAppleApiKey("appl_[YOUR_APPLE_CLIENT_KEY]")
.WithDebug(true));You can optionally set additional options:
string? userIdif you know it at this point (you can callLoginAsyncto set it later too)string? appStoreif you need to pass a non-default value (according to the RevenueCat SDK docs)bool debugLogto enable more debug logging at hte native RevenueCat SDK levelAction<CustomerInfoRequest> customerInfoUpdatedCallbackto have a custom callback invoked when the customer info is updated or refreshed (resulting from an API call, or from the native platform stores raising events through the RevenueCat SDK).
The CustomerInfoRequest object is the main model type which you will use in your app. This type contains a list of 'Entitlements' and their details which belong to the given user.
There's an event IRevenueCatManager.CustomerInfoUpdated(CustomerInfoUpdatedEventArgs) which is called any time relevant methods are called on the native SDK, or any time the native SDK detects a change from the server or platform billing/store API's.
This means the event/callback is called often and may be called more frequently than you expect it. It's important to gracefully handle this in your code and use the invocations to maintain the state of entitlement and its impact to your app.
Typically you will want to check the entitlements you care about for your app whenever this event/callback is invoked, and adjust the features/state of your UI appropriately.
Use the IRevenueCatManager instance (resolved through dependency injection) to interact with the SDK. This is basically a wrapper/abstraction around the underlying platform specific implementations of IRevenueCatPlatformImplementation (which you do not need to worry about directly).
Initialize(...)- this is called for you if you use theUseRevenueCat()builder method and calling it again manually has no effectTask<CustomerInfoRequest?> LoginAsync(string userId)once you know the user identifier for your purchases, you should call this. You should avoid calling it unnecessarily as it can cause multiple user account objects to be created if you do. You should also try to call it as early in the lifecycle of your app as possible.Task<CustomerInfoRequest?> GetCustomerInfoAsync(bool force)Call this to get the latest customer info. If you usefalseforforce, you may have a cached instance returned from the method. Usetrueto force updating from the server. TheCustomerInfoUpdatedevent and any callback you specify in the initialization will also be called with the updated info regardless of theforcevalue you set here.Task<Offering?> GetOfferingAsync(string offeringIdentifier)Gets the offering (and its packages/products) for a given identifier. Use this to get the information you need to display to the user when deciding on a purchase, such as the packages, prices, etc.Task<CustomerInfoRequest?> RestoreAsync()- Restores any purchases and returns the customer info which will have the entitlements relevant to the user.Task<CustomerInfoRequest?> PurchaseAsync(object? platformContext, string offeringIdentifier, string packageIdentifier)- Initiates a purchase. TheplatformContextshould be anActivity(likely the current one) on Android, and is currently unused on iOS/MacCatalyst. There's a version of this method you can call which infers the default 'current' activity of your MAUI app automatically. You also need to pass theofferingIdentifierand thepackageIdentifierthe user wants to purchase (if you only have one package, pick the first one).Task<CustomerInfoRequest?> PurchaseAsync(string offeringIdentifier, string packageIdentifier)- Same as above, but Initiates a purchase. TheplatformContextshould be anActivity(likely the current one) on Android, and is currently unused on iOS/MacCatalyst. There's a version of this method you can call which infers the default 'current' activity of your MAUI app automatically. You also need to pass theofferingIdentifierand thepackageIdentifierthe user wants to purchase (if you only have one package, pick the first one).Task<CustomerInfoRequest?> PurchaseAsync(string offeringIdentifier, string packageIdentifier)- Same as above, but automatically infers the default 'current' activity of your MAUI app automatically.