Skip to content
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

Feature Request: Split the RevenueCat XCFramework into Separate UI and Core Frameworks #4056

Open
jesus-mg-ios opened this issue Jul 16, 2024 · 13 comments
Assignees

Comments

@jesus-mg-ios
Copy link

Description

Dear RevenueCat Team,

I am writing to request an enhancement to the RevenueCat SDK. Specifically, I propose splitting the existing XCFramework into two distinct frameworks:

  1. RevenueCatCore.xcframework: This framework would include all core functionalities related to in-app purchases and subscription management, excluding any UI components.
  2. RevenueCatUI.xcframework: This framework would exclusively contain the UI components, providing a clear separation from the core logic.

Benefits

  • Modularity: Developers who do not require UI components can integrate only the core functionalities, leading to a more modular and lightweight integration.
  • Flexibility: Allows developers to implement custom UI solutions while still leveraging RevenueCat's robust backend.
  • Performance: Reduces the size of the binary for projects that only need core functionalities, potentially improving app performance.

Implementation Considerations

  • Ensure that the split maintains backward compatibility or provide a clear migration path.
  • Update documentation to reflect the new structure and guide developers on how to integrate both frameworks if needed.

Thank you for considering this request. I believe this enhancement would greatly benefit the developer community by providing more flexibility and efficiency in integrating RevenueCat into their projects.

@RCGitBot
Copy link
Contributor

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

@aboedo
Copy link
Member

aboedo commented Jul 17, 2024

This is definitely something that we intend to do.

Note that our current xcframework actually doesn't include the RevenueCatUI.framework, so we're already kinda doing it 😅

Meaning that RevenueCatUI is currently (and sadly) not included in any way in the .xcframework we include in releases

@jesus-mg-ios
Copy link
Author

jesus-mg-ios commented Jul 17, 2024

Thanks for your comment @aboedo, so this is weird because if I get the xcframework and get the binary passing through the strings command in Mac I get things like "PaywallColor", or "https://api-paywalls.revenuecat.com" "@"UIColor"16@?0@"UITraitCollection"8" "PaywallViewMode" "blurredBackgroundImage" ... and so on ... that for the name should be UI. Could you double check it?

@aboedo
Copy link
Member

aboedo commented Jul 17, 2024

Yeah, I can see that being very confusing.

Those entities are all a part of the main RevenueCat SDK instead of RevenueCat UI.

Our thinking at the time was to have the split be:

  • RevenueCat: contains all of the logic, networking, entities, etc to power RevenueCat
  • RevenueCatUI: contains only the templates themselves

The distinction is that basically RevenueCat still is responsible for handling and validating the data, and RevenueCatUI is only responsible for rendering the data. So that technically you could use the same entities and data that RevenueCat uses to display your own Paywalls independent from the rest of our system.

This also makes it easier for us to reuse a lot of the tooling and automation we build for data validation, and to ensure that we're batching and grouping requests as efficiently as possible for RevenueCat without duplicating for RevenueCatUI.

Hope that makes sense!

@jesus-mg-ios
Copy link
Author

jesus-mg-ios commented Jul 17, 2024

Thank you for the clarification. However, I must say that the current approach doesn't make sense to me, as the binary is growing with entities and components that I won't use.

If UI components are leaking into the core SDK, it indicates a coupling that should be avoided. The core framework should handle all data processing, validation, and networking, returning raw or processed data to the consuming framework without any UI-specific dependencies. UI-related strings and entities should reside exclusively within the RevenueCatUI framework.

To maintain a clean separation and prevent unnecessary bloat, consider creating an intermediate module for shared entities or tools. This structure could be:

  • RevenueCatCore: Manages logic, networking, data validation, etc.
  • RevenueCatShared: Contains shared entities, utilities, and data models.
  • RevenueCatUI: Handles rendering and UI-specific logic, consuming data and entities from RevenueCatCore and RevenueCatShared.
    This approach ensures each module maintains a single responsibility, making the SDK more modular, maintainable, and efficient. It would also allow developers to integrate only the parts they need, reducing the overall binary size and improving performance.

Does not mean to have 3 frameworks. You can include the shared in the core, linked statically and then for people using the UI RevenueCatUI and RevenueCatCore(with the shared entities). But for me, the paywall mode and color shouldn't be inside RevenueCatCore.

Thank you for considering these suggestions.

@cnordvik
Copy link

Just adding my +1 here as this was the first thing we checked when considering RevenueCat, the impact of download size.

I see things like a 100KB RevenueCat_RevenueCatUI.bundle/background.jpg and the total impact of adding the SDK without doing anything is 2.0MB (8.31%) increase in download size and 6.4MB (9.85%) increase in install size. This is on an a very mature app with millions of users and has WatchApp, extensions and a fair number of third party SDKs. So this impact is pretty significant considering that our needs are basically to just sync our subscriptions to the RevenueCat backend.

CleanShot 2024-07-30 at 09 52 51

@jesus-mg-ios
Copy link
Author

Just out of curiosity, is the tool you used made in-house or can I find it somewhere? @cnordvik

@cnordvik
Copy link

Just out of curiosity, is the tool you used made in-house or can I find it somewhere? @cnordvik

We use EmergeTools for the size comparison on PRs.

@aboedo
Copy link
Member

aboedo commented Aug 12, 2024

Hey folks, sorry for the radio silence here, I missed notifications for the thread.

Fully agree on keeping the UI separate from the core, but that is what we're doing:

The RevenueCat framework does not have any UI components or strings files. It only contains some structs definitions for types that the RevenueCatUI framework might use.

However, this is only the definition of the structs. If you do not use the Paywalls system, then other than those definitions (which should be very small as it's just a handful of Swift files, since it's only the entities related to the data, not views or anything actually visual), there should be no impact to an app using only RevenueCat.

The reason we still packed those in was so that we could reuse the very optimized networking logic in RevenueCat. Granted, we could have split things into a separate Core, as suggested. We decided against it at the time because when keeping it all together, we could also optimize the requests themselves, and do things like ensuring that the Paywall information is packed in the same network request that gets Offerings information. Again, if you don't use Paywalls, then no extra information is added.

But if you do use Paywalls, then by packing all the information in a single request we save one round trip, and we can retain control over how the relevant entities are cached in a way that is consistent between the RevenueCat framework and RevenueCatUI.

We also make those entities public, in the hopes that if you wanted to for example build your own Paywalls system, you would be able to do so and leverage all of our entities if you want to remotely control components, just like we did.

Meaning that our RevenueCatUI system would become "one possible implementation" of how to do UI with RevenueCat. We have a long ways to go before that dream is really fulfilled, admittedly, but that was a part of the consideration with keeping the structs in the main SDK, but still ensuring that all of the strings, images, and any UI components are still kept in the RevenueCatUI SDK.

Hope this helps shed some light on things!

We do have some other optimizations planned for SDK footprint, though, stay tuned!

@aboedo aboedo self-assigned this Aug 12, 2024
@jesus-mg-ios
Copy link
Author

jesus-mg-ios commented Aug 13, 2024

Thank you for your explanation, @aboedo. However, your point of view doesn't align with the data provided by cnordvik and myself. There are colors and other elements from UIKit that need to be considered. Additionally, some customers don't want the paywall system (referring to us as your customers), and we have constraints related to app size that directly affect downloads and, consequently, the conversion rate. As you know, there's a warning message when the app exceeds 200MB during installation. While it's true that RevenueCat itself isn't 200MB, we still need to use other third-party frameworks, like Firebase, which are actively working to reduce their framework sizes.

In my opinion, the solution you're using has alternative ways to decouple your UI entities from the rest one, as you're currently using them in the UI Layer.

"We decided against it at the time because when keeping it all together, we could also optimize the requests themselves, and do things like ensuring that the Paywall information is packed in the same network request that gets Offerings information"

Regarding this point where you mentioned "optimize the requests themselves," I assume the endpoint is combining everything into one. In this case, you could pass a generic parameter decodable from the RevenueCat UI, leaving all the paywall logic to the UI itself. This way, the DTO entities, like Offerings, could reside within the core framework (specifically in the data layer). You could even have two implementations: one with generics and another returning the offerings.

Well, I don't know how the framework structure is, but I'm sure that you could decode data in the same request passing one DTO or another, and the DTO itself mustn't be sticky to the request using generics.

@aboedo
Copy link
Member

aboedo commented Aug 14, 2024

Thanks for the input. I agree that it's not impossible, but it's also not an insignificant amount of work and we do have quite a few other things to take care of, balancing can be hard.

I do want to clarify here, though, that the impact of having Paywalls-related entities in our main SDK should be very small: it's ~184.1 kb, as you can see in this Emerge tools X-Ray:
https://www.emergetools.com/app/example/ios/examp_7TSGKjCkcgfF
Color information within that is less than 50kb.

image

This was tested by integrating a brand new app through SPM and archiving, while only adding the RevenueCat framework. The entire RevenueCat SDK is 1.7mb.

@cnordvik based on the output that you shared, I expect that you also had the RevenueCatUI framework in there. This is where the image that you pointed to lives.

We do have plans to further optimize the RevenueCatUI framework in itself, but the overall impact of Paywalls-related data in the main SDK should be very minimal, as you can see in the X-Ray (although definitely let me know if I'm missing something!)

@jesus-mg-ios
Copy link
Author

jesus-mg-ios commented Aug 15, 2024

Seems like it is spread along the SDK not only in the paywallData. PaywallEvents, Paywall Caches for prewarming, colors, texts (that maybe is the second greater part), customerCenterConfig, PaywallViewMode, and so on .. https://www.emergetools.com/app/example/ios/examp_7TSGKjCkcgfF?search=paywall https://www.emergetools.com/app/example/ios/examp_7TSGKjCkcgfF?search=color So yes, @aboedo I can see the hard work to do.

In that way in my case I would prefer an effort on creating a lightweight sdk for extensions #3985 Not because the size of the SDK that would be nice to reduce it, but the objects created and managed on execution time that are useless for this kinda extensions and we have a very tight memory to use in runtime.

aboedo added a commit that referenced this issue Aug 15, 2024
We don't currently export RevenueCatUI as an xcframework, which can be a
source of confusion and a dealbreaker for customers who need a pre-built
framework.
This adds RevenueCatUI's xcframework into the `export_xcframeworks`
lane, and includes the output in the `github_release` lane.


Should help address: 
- #4168
- #4146
- #4056
nyeu pushed a commit that referenced this issue Oct 2, 2024
We don't currently export RevenueCatUI as an xcframework, which can be a
source of confusion and a dealbreaker for customers who need a pre-built
framework.
This adds RevenueCatUI's xcframework into the `export_xcframeworks`
lane, and includes the output in the `github_release` lane.


Should help address: 
- #4168
- #4146
- #4056
@aboedo
Copy link
Member

aboedo commented Oct 9, 2024

hey folks, update here:

  • we're considering having some mode or something that's specific to app extensions, maybe that could also be a lighter weight in dimensions but we're mostly optimizing for functionality (meaning that it does as little as possible when running in extensions). I don't have an ETA for this yet, though.
  • As for the size, the combined size of RevenueCat + RevenueCatUI should be around 4mb, here's an example: https://www.emergetools.com/app/example/ios/examp_FbwC4JPkNYci. @cnordvik the larger size you're seeing is likely related to linking the framework dynamically. There are a few more things that you could do to ensure that the size is kept small, like linking statically, stripping binary symbols. There are a few more tips and tricks in Emerge's website for keeping the overall size small.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants