-
Notifications
You must be signed in to change notification settings - Fork 26
Paywalls #649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Paywalls #649
Changes from 52 commits
Commits
Show all changes
78 commits
Select commit
Hold shift + click to select a range
53e264a
Add RevenueCatUI package
facumenzella f9683a5
revert code
facumenzella 0a407d2
Add minimum
facumenzella 151ef83
Moved files to RevenueCat/Android
facumenzella 45ad7d0
Moved files from Runtime to Scripts
facumenzella c4ac9de
Use SimpleJSON
facumenzella e20e4ce
delete extra .meta
facumenzella cdd8ea2
Updated bool IsSupported(); doc
facumenzella be566dd
Added pure code options, removed GameObjecft
facumenzella c550a2e
Split callback
facumenzella d1d075e
added debug logs
facumenzella 1a0e59c
Remove StubPaywallPresenter.cs
facumenzella 8b7784b
Delete RevenueCatUICallbackHandler.cs
facumenzella 69096d4
Add missing pieces
facumenzella c191052
Merge branch 'main' into feat/add-revenuecatui
facumenzella 1ee5dcc
ready
facumenzella 2cbfef6
remove reference to ui package
facumenzella 160ed40
rename to com.revenuecat.unity.ui
facumenzella e03df34
Merge branch 'main' into feat/add-revenuecatui
facumenzella c3bd377
Update Subtester/Assets/Scenes/Main.unity
facumenzella 0ad7ba1
Remove extra Stub.meta
facumenzella a8591ee
Remove README
facumenzella ffca566
Rename .asmdef files
facumenzella 5417e16
Remove customer center code
facumenzella 9ec1edf
Merge branch 'feat/add-revenuecatui' of github.com:RevenueCat/purchas…
facumenzella 6a43d86
remove _isSupportedCache
facumenzella 0a829d0
Update Subtester/Packages/manifest.json
facumenzella 18b9ba1
renamed editor asmdef and deleted extra files
facumenzella 21ac4df
Merge branch 'feat/add-revenuecatui' of github.com:RevenueCat/purchas…
facumenzella 17aed4a
revert Subtester
facumenzella d3cca45
Update Subtester/Assets/Plugins/Android/mainTemplate.gradle
facumenzella f51be9e
Update Subtester/ProjectSettings/AndroidResolverDependencies.xml
facumenzella 1e1ee01
Make properties internal
facumenzella 0143afe
Merge branch 'feat/add-revenuecatui' of github.com:RevenueCat/purchas…
facumenzella 2693a44
minimal paywalls presentation
vegaro 97dec08
clean up
vegaro 980a0e5
Merge branch 'main' into paywalls-android-poc
vegaro b9bb1e0
clean up
vegaro b048332
clean up
vegaro cfbc8d9
move into androidlib
vegaro 1c4c865
add local dependency
vegaro f072bde
updated mainTemplate.gradle
vegaro 2457709
update PurchasesListener
vegaro 44bc909
gitignore
vegaro eb27acf
add log to AndroidPaywallPresenter
vegaro 5da75f8
add displayCloseButton
vegaro 56258da
fix compilation of Subtester when exporting package
vegaro be4ef13
use componentActivity
vegaro 649cff4
presentPaywallIfNeeded
vegaro e55f375
add close button to log
vegaro 26961f3
add PresentPaywallIfNeeded to PurchasesListener
vegaro 07afd3d
update AndroidResolverDependencies
vegaro dde2af5
implements PaywallResultHandler
vegaro 96d73f6
rename to PaywallTrampolineActivity and made transparent
vegaro cf1a839
remove unneeded comment
vegaro 09651b4
TODOs on logs
vegaro 8065057
remove UnityBridge
vegaro 82643ac
Paywalls iOS (#675)
facumenzella 52a8260
Merge branch 'main' into paywalls-android-poc
vegaro 40f1404
small PR comments
vegaro 830e978
Merge branch 'paywalls-android-poc' of github.com:RevenueCat/purchase…
vegaro 588ea5d
Another TODO
vegaro 79dfe20
static functions in trampoline activity
vegaro bb5af22
create PaywallUnityOptions
vegaro 34b2f4a
add PaywallUnityOptions to signature
vegaro bba5ea9
remove gameObjectName from parameters
vegaro 903aad4
fix versions
vegaro bed291d
Extract paywall result strings to constants in PaywallTrampolineActiv…
vegaro 3ec3b42
fix indentation
vegaro cba2097
change default display close button
vegaro aabecf5
AndroidApplication.currentActivity
vegaro bcc83a6
better handling of exceptions on AndroidJavaClass
vegaro b36f845
remove MonoBehaviour
vegaro e032186
remove ?? "default"
vegaro ed13583
Merge branch 'main' into paywalls-android-poc
vegaro c4fa9e7
fix PurchasesListener
vegaro 1601b1b
update version
vegaro 08005f2
Merge branch 'main' into paywalls-android-poc
vegaro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,3 +27,4 @@ fastlane/.env | |
| # CircleCI folders | ||
| vendor/ | ||
| .bundle/ | ||
| RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
17 changes: 17 additions & 0 deletions
17
RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools"> | ||
|
|
||
| <application> | ||
| <activity | ||
| android:name=".PaywallProxyActivity" | ||
| android:exported="false" | ||
| android:theme="@android:style/Theme.NoTitleBar" | ||
| tools:node="merge" /> | ||
| </application> | ||
|
|
||
| <!-- No special permissions required --> | ||
|
|
||
| </manifest> | ||
|
|
||
|
|
||
49 changes: 49 additions & 0 deletions
49
RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| apply plugin: 'com.android.library' | ||
|
|
||
| dependencies { | ||
| implementation fileTree(dir: 'libs', include: ['*.jar']) | ||
| implementation 'com.revenuecat.purchases:purchases-hybrid-common:17.7.0' | ||
| implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:17.7.0' | ||
vegaro marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| implementation 'androidx.activity:activity:1.8.2' | ||
| } | ||
|
|
||
| android { | ||
| namespace 'com.revenuecat.purchasesunity.ui' | ||
|
|
||
| compileSdk getProperty("unity.compileSdkVersion") as int | ||
| buildToolsVersion = getProperty("unity.buildToolsVersion") | ||
|
|
||
| compileOptions { | ||
| sourceCompatibility JavaVersion.valueOf(getProperty("unity.javaCompatabilityVersion")) | ||
| targetCompatibility JavaVersion.valueOf(getProperty("unity.javaCompatabilityVersion")) | ||
| } | ||
|
|
||
| sourceSets { | ||
| main { | ||
| manifest.srcFile 'AndroidManifest.xml' | ||
| java.srcDirs = ['src/main/java'] | ||
| res.srcDirs = ['src/main/res'] | ||
| assets.srcDirs = ['src/main/assets'] | ||
| jniLibs.srcDirs = ['src/main/jniLibs'] | ||
| } | ||
| } | ||
|
|
||
| def unityLib = project(':unityLibrary').extensions.getByName('android') | ||
|
|
||
| defaultConfig { | ||
| consumerProguardFiles "consumer-proguard.pro" | ||
| minSdkVersion unityLib.defaultConfig.minSdkVersion.mApiLevel | ||
| targetSdkVersion unityLib.defaultConfig.targetSdkVersion.mApiLevel | ||
| } | ||
|
|
||
| lintOptions { | ||
| abortOnError false | ||
| } | ||
|
|
||
| buildTypes { | ||
| release { | ||
| minifyEnabled false | ||
| proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules. pro' | ||
| } | ||
| } | ||
| } | ||
1 change: 1 addition & 0 deletions
1
RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/proguard-android-optimize.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| -keep class com.unity3d.player.** { *; } |
134 changes: 134 additions & 0 deletions
134
...CatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| package com.revenuecat.purchasesunity.ui; | ||
|
|
||
| import android.content.Intent; | ||
| import android.os.Bundle; | ||
| import android.util.Log; | ||
|
|
||
| import androidx.annotation.Nullable; | ||
| import androidx.activity.ComponentActivity; | ||
|
|
||
| import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; | ||
| import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; | ||
| import com.revenuecat.purchases.PresentedOfferingContext; | ||
|
|
||
| public class PaywallProxyActivity extends ComponentActivity { | ||
| public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; | ||
| public static final String EXTRA_METHOD = "rc_proxy_method"; | ||
| public static final String EXTRA_OFFERING_ID = "rc_offering_id"; | ||
| public static final String EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON = "rc_should_display_dismiss_button"; | ||
| public static final String EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER = "rc_required_entitlement_identifier"; | ||
|
|
||
| private static final String TAG = "PurchasesUnity"; | ||
|
|
||
| private String gameObject; | ||
| private String method; | ||
|
|
||
| @Override | ||
| protected void onCreate(@Nullable Bundle savedInstanceState) { | ||
| super.onCreate(savedInstanceState); | ||
|
|
||
| final Intent source = getIntent(); | ||
| gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); | ||
| method = source.getStringExtra(EXTRA_METHOD); | ||
| String offeringId = source.getStringExtra(EXTRA_OFFERING_ID); | ||
| boolean shouldDisplayDismissButton = source.getBooleanExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, false); | ||
| String requiredEntitlementIdentifier = source.getStringExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER); | ||
|
|
||
| if (gameObject == null || method == null) { | ||
| Log.w(TAG, "Missing gameObject/method extras; finishing."); | ||
| finish(); | ||
| return; | ||
| } | ||
|
|
||
| PaywallActivityLauncher launcher = new PaywallActivityLauncher( | ||
| this, | ||
| result -> { | ||
| try { | ||
| if (result != null) { | ||
| sendPaywallResult(result); | ||
| } | ||
| } finally { | ||
| finish(); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| if (requiredEntitlementIdentifier != null) { | ||
| Log.d(TAG, "Using launchIfNeeded for entitlement '" + requiredEntitlementIdentifier + "'"); | ||
| launchPaywallIfNeeded(launcher, requiredEntitlementIdentifier, offeringId, shouldDisplayDismissButton); | ||
| } else { | ||
| Log.d(TAG, "No entitlement check required, presenting paywall directly"); | ||
| launchPaywall(launcher, offeringId, shouldDisplayDismissButton); | ||
| } | ||
| } | ||
|
|
||
| private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requiredEntitlementIdentifier, String offeringId, boolean shouldDisplayDismissButton) { | ||
| Log.d(TAG, "Launching paywall if needed with PaywallActivityLauncher"); | ||
| Log.d(TAG, "Options - entitlement: " + requiredEntitlementIdentifier + ", offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); | ||
|
|
||
| if (offeringId != null) { | ||
| Log.d(TAG, "Using launchIfNeeded with offering ID"); | ||
| launcher.launchIfNeeded( | ||
| requiredEntitlementIdentifier, | ||
| offeringId, | ||
| new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data | ||
| null, // fontProvider | ||
| shouldDisplayDismissButton, | ||
| false, // edgeToEdge | ||
| paywallDisplayResult -> { | ||
| if (!paywallDisplayResult) { | ||
| Log.d(TAG, "PaywallDisplayCallback: paywall not needed"); | ||
| sendResult("NOT_PRESENTED"); | ||
| finish(); | ||
| } | ||
| // If paywallDisplayResult is true, the paywall will be shown and result will come through normal callback | ||
| } | ||
| ); | ||
| } else { | ||
| Log.w(TAG, "launchIfNeeded requires an offering ID, falling back to regular launch"); | ||
| launchPaywall(launcher, offeringId, shouldDisplayDismissButton); | ||
| } | ||
| } | ||
|
|
||
| private void launchPaywall(PaywallActivityLauncher launcher, String offeringId, boolean shouldDisplayDismissButton) { | ||
| Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); | ||
| Log.d(TAG, "Options - offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); | ||
|
|
||
| if (offeringId != null) { | ||
| Log.d(TAG, "Launching paywall with offering ID"); | ||
| launcher.launch(offeringId, | ||
| new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data | ||
| null, // fontProvider | ||
| shouldDisplayDismissButton); | ||
| } else { | ||
| Log.d(TAG, "Launching paywall with standard method"); | ||
| launcher.launch( | ||
| null, // offering (Offering object, not String) | ||
| null, // fontProvider | ||
| shouldDisplayDismissButton | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| private void sendResult(String resultName) { | ||
| Log.d(TAG, "Sending result: " + resultName); | ||
| runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); | ||
| } | ||
|
|
||
| private void sendPaywallResult(PaywallResult result) { | ||
| final String resultName; | ||
| if (result instanceof PaywallResult.Purchased) { | ||
| resultName = "purchased"; | ||
| } else if (result instanceof PaywallResult.Restored) { | ||
| resultName = "restored"; | ||
| } else if (result instanceof PaywallResult.Cancelled) { | ||
| resultName = "cancelled"; | ||
| } else if (result instanceof PaywallResult.Error) { | ||
| resultName = "error"; | ||
| } else { | ||
| resultName = "cancelled"; | ||
| } | ||
|
|
||
| sendResult(resultName); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These kinds of activities are often called "trampoline" activities. Maybe we can use that name for ours as well,
PaywallTrampolineActivity?This blog post by Marcello Galhardo has some details on the pattern, one of which is the tip to use the
@android:style/Theme.NoDisplaytheme. We discussed that we should ensure no UI glitches and transition animations happen. Maybe that theme is all we need?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice blogpost! It looks like "trampoline" activities are finished as soon as they are created:
But in our case, we want the Activity to send the message through the Unity bridge. If we finish the Activity we won't be able to send that. We need the activity to stay alive to receive PaywallResult from the launcher basically.
Also from the docs of
Theme.NoDisplayI could create a static callback manager that implements
PaywallResultHandlerfor the results of the PaywallActivity, but I think there's risk of leaking the trampoline Activity? What do you think? @JayShortwayThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm good point, but I don't think we can assume that actually. Depending on the device and memory constraints, activities on the backstack might be killed. We can test this by enabling Don't keep activities in the developer options.
Maybe we need a callback manager after all? Why do you think there's a risk of leaking the trampoline Activity (just curious)?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm now I am actually not sure. My thinking was:
launcheris created inPaywallTrampolineActivity. ThePaywallResultHandleris a static callback managerPaywallTrampolineActivityis finished beforeonResume.launcherpreventPaywallTrampolineActivityfrom being garbage collected because it holds a reference to thePaywallResultHandlerwhich is a static class that's not destroyed?Like can the
PaywallResultHandlerlive longer than the launcher?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if
launcherholds a reference toPaywallResultHandler(and not the other way around),PaywallResultHandlercan live longer.I wonder how it works if we enable Don't keep activities, and have
PaywallTrampolineActivitylaunch the paywall. My assumption is that the framework handles delivering the paywall result back toPaywallTrampolineActivity, even if that was killed in the meantime. We can also check if finishing thePaywallTrampolineActivityafter launching the paywall has any effect on this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will double check. That will definitely make this a trampoline, which is beneficial
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw @JayShortway I did try this (without Don't keep activities):
and finishing the activity right at the end of
onCreate(after launching the paywall), and thePaywallResultHandlerdoesn't get called, so it looks like the result handler can't survive the ActivityThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I did test "Don't keep activities" and as soon as the paywall is seen, the app restarts. It looks like
UnityPlayerActivitygets killed and the app is restarted