Nesting views
This section describes how to nest the screens displayed by the Purchasely SDK into your own views
Overview
Purchasely SDK provides the capability to display paywalls that occupy the full screen.
However, for develops who want to customize the size of their paywalls,such as displaying them in a half-screen format, the SDK allows nesting of the paywall view within their app's views. This feature provides enhanced flexibility, allowing for a more seamless integration and a tailored user experience.
Currently, this functionality is available for native technologies (Swift and Kotlin) as well as Flutter and React Native.
Swift
Purchasely provides a UIViewController instance, you can display it directly using the present() method. This UIViewController contains a UIView instance that you can use to integrate it in your own UIView.
The preloaded presentation also provides the property swiftUIView to display Purchasely Screen with your SwiftUI View
import Purchasely
var controller: UIViewController?
// Preload the presentation then read its controller
PLYPresentationBuilder.forPlacementId("onboarding").build().preload { presentation, error in
guard let presentation = presentation, error == nil else {
print("Error while fetching presentation: \(error?.localizedDescription ?? "unknown")")
return
}
let purchaselyController = presentation.controller
// Option 1 - Display the controller directly
self.present(purchaselyController, animated: true, completion: nil)
// Option 2 - Display Purchasely UIView inside your own
let targetView = UIView()
let purchaselyView = purchaselyController?.view
targetView.addSubview(purchaselyView)
purchaselyView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
purchaselyView!.topAnchor.constraint(equalTo: targetView.topAnchor),
purchaselyView!.bottomAnchor.constraint(equalTo: targetView.bottomAnchor),
purchaselyView!.leadingAnchor.constraint(equalTo: targetView.leadingAnchor),
purchaselyView!.trailingAnchor.constraint(equalTo: targetView.trailingAnchor)
])
// Option 3 - Display with SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("This is SwiftUI View")
.padding()
presentation.swiftUIView
.frame(height: 400)
}
}
}
}Kotlin
Purchasely provides a View instance that you can add to your layout hierarchy. It is up to you to decide how to display it inside your own View, Fragment or Activity.
To use it with Jetpack Compose, you directly use the component AndroidView
// Option 1 - Preload the presentation then add its view to your layout
PLYPresentation {
placementId("onboarding")
}.preload { loaded, error ->
if (error != null || loaded == null) return@preload
val purchaselyView = loaded.buildView(context) { outcome -> }
findViewById<FrameLayout>(R.id.container).addView(purchaselyView)
}
// Option 2 - Get the view asynchronously
PLYPresentation {
placementId("onboarding")
onCloseRequested {
// TODO remove view from your layout
}
}.preload { presentation, error ->
if(error != null) {
Log.d("Purchasely", "Error fetching paywall", error)
return@preload
}
when(presentation?.type) {
PLYPresentationType.NORMAL,
PLYPresentationType.FALLBACK -> {
val purchaselyView = presentation.buildView(context) { outcome -> }
// Display Purchasely paywall by adding purchaselyView to your layout
findViewById<FrameLayout>(R.id.container).addView(purchaselyView)
}
else -> {
//No presentation, it means an error was triggered
}
}
}
// Option 3 - Add the view inside your Jetpack Compose Component
// Preload the presentation first, then build the view inside AndroidView.
// `loaded` is the preloaded PLYPresentation.
AndroidView(
modifier = Modifier
.fillMaxSize()
.padding(0.dp, 5.dp), // Occupy the max size in the Compose UI tree
factory = { context ->
loaded.buildView(context) { outcome ->
// remove this component to close Purchasely Screen,
}
}
)Flutter
Flutter developers can nest Purchasely Screen using the PLYPresentationView widget built from a PresentationRequest.
Full example below:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:purchasely_flutter/native_view_widget.dart';
import 'package:purchasely_flutter/purchasely_flutter.dart';
class PresentationScreen extends StatelessWidget {
PresentationScreen({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
// Wrap with SafeArea
child: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: _buildPresentationView(),
)
],
),
),
);
}
Widget _buildPresentationView() {
final request = PLYPresentationBuilder.placement('onboarding')
// you can also target a specific screen with PLYPresentationBuilder.screen('my_paywall_1')
.onDismissed((outcome) {
print('Presentation result:${outcome.purchaseResult} - plan:${outcome.plan}');
})
.build();
return PLYPresentationView(request: request);
}
}Since PLYPresentationView is a StatelessWidget, you can easily add it to your widget tree for display. For instance, you can embed it within a Center widget inside a Scaffold in your Flutter app to present the paywall view to the user.
React Native
React Native developers can nest a Purchasely Screen with the PLYPresentationView component shipped in react-native-purchasely (no need to copy any file into your project). Build a PresentationRequest, preload() it, then pass it to the component through the request prop — the native view resolves the loaded presentation by the request's requestId, so there is no second network fetch. Because it is a regular React component, you can place it anywhere in your view tree (for example inside a half-height container) to size the paywall as you like.
Display the PLYPresentationView
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import Purchasely, {
PLYPresentationView,
ProductResult,
type PLYPresentationViewResult,
type PLYPresentationRequest,
} from 'react-native-purchasely';
const PaywallScreen = ({ navigation }: { navigation: any }) => {
const [request, setRequest] = useState<PLYPresentationRequest | null>(null);
// Preload the presentation before rendering the view
useEffect(() => {
const preload = async () => {
const req = Purchasely.presentation.placement('ACCOUNT').build();
// you can also target a specific screen with Purchasely.presentation.screen('my_paywall_1')
await req.preload();
setRequest(req);
};
preload();
}, []);
// Called when the nested paywall is closed
const onPresentationClosed = (result: PLYPresentationViewResult) => {
switch (result.result) {
case ProductResult.PRODUCT_RESULT_PURCHASED:
case ProductResult.PRODUCT_RESULT_RESTORED:
if (result.plan != null) {
console.log('User purchased ' + result.plan.name);
}
break;
case ProductResult.PRODUCT_RESULT_CANCELLED:
console.log('User cancelled');
break;
}
// Remove the component from your tree to close the Purchasely Screen
navigation.goBack();
};
if (request == null) {
return null; // or render your own loading indicator
}
return (
<View style={{ flex: 1 }}>
<PLYPresentationView
request={request}
flex={1}
onPresentationClosed={onPresentationClosed}
/>
</View>
);
};
export default PaywallScreen;
The embedded view reports{ result, plan }
onPresentationClosedreceives aPLYPresentationViewResult:resultis aProductResult(PRODUCT_RESULT_PURCHASED/PRODUCT_RESULT_RESTORED/PRODUCT_RESULT_CANCELLED) andplanis the purchased / restored plan (ornullwhen the user simply closed the screen). This is the couple the native embedded view emits — not the 5-fieldPLYPresentationOutcomereturned by a full-screendisplay().
If you don't preload a request, you can pass a placementId directly (or a presentation you preloaded yourself via the presentation prop); the view falls back to these when no request is set.
<PLYPresentationView
placementId="ACCOUNT"
flex={1}
onPresentationClosed={(result) => console.log('Closed:', result.result, result.plan)}
/>By following these guidelines, you can effectively nest the Purchasely paywall view within your app's interface, allowing for a customized display that fits your app's design and user experience requirements.