Skip to content
Open
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
13 changes: 13 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"WebFetch(domain:superwall.com)",
"Bash(gh pr edit:*)",
"Bash(flutter pub run pigeon:*)",
"Bash(flutter analyze:*)",
"Bash(flutter build:*)",
"Bash(dart run pigeon:*)"
],
"deny": []
}
}
134 changes: 134 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is the Superwall Flutter SDK - a Flutter plugin that wraps native iOS and Android SDKs for paywall management and subscription handling. The project uses **Pigeon** for type-safe communication between Flutter and native platforms.

## Key Architecture

### Pigeon-Based Communication
- **Single Source of Truth**: `pigeons/configure.dart` defines all data models and API interfaces
- **Code Generation**: Pigeon generates type-safe interfaces for Dart, Kotlin (Android), and Swift (iOS)
- **Communication Flow**: Flutter ↔ Generated Interface ↔ Method Channel ↔ Native Implementation ↔ Native SDK

### Core Components
- **Flutter Layer**: `lib/src/public/Superwall.dart` - Main SDK class, singleton pattern
- **Generated Code**: `lib/src/generated/superwallhost.g.dart` - Auto-generated from Pigeon
- **Native Hosts**:
- Android: `android/src/main/kotlin/.../SuperwallHost.kt`
- iOS: `ios/Classes/SuperwallHost.swift`
- **Proxy Classes**: `lib/src/private/*Proxy.dart` - Handle callback routing between Flutter and native

### Data Flow Patterns
- **Host API**: Native-to-Flutter calls (e.g., `PSuperwallHostApi`)
- **Flutter API**: Flutter-to-Native calls (e.g., `PSuperwallDelegateGenerated`)
- **Event Streams**: For continuous updates (e.g., `SubscriptionStatusStream`)

## Development Commands

### Code Generation
Generate Pigeon interfaces after modifying `pigeons/configure.dart`:
```bash
flutter pub run pigeon --input pigeons/configure.dart
```

### Testing
Run unit tests:
```bash
flutter test
```

Run integration tests (non-UI methods):
```bash
cd test_app
flutter test integration_test/sdk_methods_test.dart
```

Run UI tests with Maestro:
```bash
# iOS
./run_tests.sh ios

# Android
./run_tests.sh android
```

Individual Maestro test:
```bash
# iOS
maestro test -e PLATFORM_ID=com.superwall.Advanced test_app/maestro/handler/flow.yaml

# Android
maestro test -e PLATFORM_ID=com.superwall.superapp test_app/maestro/handler/flow.yaml
```

### Build Commands
Build example app:
```bash
cd example
flutter build ios --no-codesign # iOS
flutter build apk # Android
```

### Analysis and Linting
```bash
flutter analyze
dart format .
```

## Adding New Methods

1. **Define in Pigeon**: Add method and any new data classes to `pigeons/configure.dart`
- Prefix Pigeon classes with `P` (e.g., `PPaywallInfo`)
- Use sealed classes for polymorphic types
2. **Generate Code**: Run `flutter pub run pigeon --input pigeons/configure.dart`
3. **Implement Native**: Add implementations in `SuperwallHost.kt` and `SuperwallHost.swift`
4. **Flutter Integration**: Add public method in `lib/src/public/Superwall.dart`
5. **Mapping**: Create mapping functions between public classes and Pigeon classes

## Code Conventions

### File Organization
- **Public API**: `lib/src/public/` - Classes exposed to SDK users
- **Private API**: `lib/src/private/` - Internal implementation details
- **Generated**: `lib/src/generated/` - Auto-generated Pigeon code (do not edit)

### Naming Patterns
- **Pigeon Classes**: Prefixed with `P` (e.g., `PSuperwallOptions`)
- **Public Classes**: No prefix (e.g., `SuperwallOptions`)
- **Proxy Classes**: Suffixed with `Proxy` (e.g., `PurchaseControllerProxy`)

### Data Mapping
- Always map between public SDK classes and Pigeon classes
- Use helper methods like `_convertToGeneratedOptions()` in `Superwall.dart`
- Handle sealed class conversions with switch statements

## Testing Structure

### Integration Tests
- Location: `test_app/integration_test/sdk_methods_test.dart`
- Purpose: Test SDK methods without UI dependencies
- Run with: `flutter test integration_test/sdk_methods_test.dart`

### UI Tests (Maestro)
- Location: `test_app/maestro/`
- Test flows: handler, delegate, purchase controller
- Platform-specific app IDs:
- iOS: `com.superwall.Advanced`
- Android: `com.superwall.superapp`

## Project Structure Notes

- **Multiple Example Apps**: Both `example/` and `test_app/` serve different testing purposes
- **Platform-Specific Code**: Native implementations in `android/` and `ios/` directories
- **Shared Configuration**: `analysis_options.yaml` enables inline-class experiment
- **Asset Management**: `pubspec.yaml` included as asset for SDK functionality

## Important Files

- `pigeons/configure.dart` - API definition and data models
- `lib/src/public/Superwall.dart` - Main SDK entry point
- `CONTRIBUTING.md` - Detailed architecture documentation
- `run_tests.sh` - Automated testing script for both platforms
4 changes: 2 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class _MyAppState extends State<MyApp> {
const useRevenueCat = true;

super.initState();
_handleIncomingLinks(); // Set up deep link handling
configureSuperwall(useRevenueCat);
}

Expand All @@ -40,7 +41,7 @@ class _MyAppState extends State<MyApp> {

void _handleIncomingLinks() {
appLinks.uriLinkStream.listen((Uri uri) {
debugPrint('uri: $uri');
debugPrint('Incoming deep link: $uri');
Superwall.shared.handleDeepLink(uri);
}, onError: (Object err) {
print('Error receiving incoming link: $err');
Expand Down Expand Up @@ -84,7 +85,6 @@ class _MyAppState extends State<MyApp> {
print('Executing Superwall configure completion block');
listenForPurchases();
});
_handleIncomingLinks();
Superwall.shared.setDelegate(delegate);
// MARK: Step 3 – Configure RevenueCat and Sync Subscription Status
/// Always configure RevenueCat after Superwall and keep Superwall's
Expand Down
57 changes: 22 additions & 35 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
url: "https://pub.dev"
source: hosted
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
version: "85.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
sha256: f648ac7103c7128c367c99c6684869bf47de4261afcef31f9c70c0f793b21f59
url: "https://pub.dev"
source: hosted
version: "6.7.0"
version: "7.5.3"
app_links:
dependency: "direct main"
description:
Expand Down Expand Up @@ -122,10 +117,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
convert:
dependency: transitive
description:
Expand Down Expand Up @@ -272,18 +267,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
Expand All @@ -300,14 +295,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher:
dependency: transitive
description:
Expand Down Expand Up @@ -400,7 +387,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
Expand All @@ -421,10 +408,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
Expand All @@ -437,17 +424,17 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
superwallkit_flutter:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "2.2.0-beta.2"
version: "2.3.4"
sync_http:
dependency: transitive
description:
Expand All @@ -468,10 +455,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
typed_data:
dependency: transitive
description:
Expand Down Expand Up @@ -500,10 +487,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.3.0"
watcher:
dependency: transitive
description:
Expand All @@ -524,10 +511,10 @@ packages:
dependency: transitive
description:
name: webdriver
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
yaml:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions ios/Classes/SuperwallHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ final class SuperwallHost : NSObject, PSuperwallHostApi {

func handleDeepLink(url: String) -> Bool {
guard let url = URL(string: url) else { return false }
// Use static method that safely queues deep links before configuration
return Superwall.handleDeepLink(url)
}

Expand Down
3 changes: 3 additions & 0 deletions lib/src/public/Superwall.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Superwall {
static final generated.PSuperwallHostApi hostApi =
generated.PSuperwallHostApi();


// Private constructor
Superwall._();

Expand Down Expand Up @@ -350,6 +351,8 @@ class Superwall {
}

// Handles deep links for paywall previews
// iOS: Uses static Superwall.handleDeepLink() that safely queues links before configuration
// Android: Uses instance method, works after configuration
Future<bool> handleDeepLink(Uri url) async {
return await hostApi.handleDeepLink(url.toString());
}
Expand Down