Skip to content

Commit e997483

Browse files
fix: ensure FlutterResult and invokeMethod are always called on main thread (#98)
* fix: ensure FlutterResult and invokeMethod are always called on main thread iOS 16 requires FlutterResult to be invoked on the main thread. When OptimizelyClient.start() or decideAsync closures call result() from a background thread under multi-SDK startup contention, iOS 16 silently drops the response causing the Dart Future to never resolve or return null, which previously crashed the app with an unhandled TypeError or PlatformException. Two layers fixed: 1. iOS native (SwiftOptimizelyFlutterSdkPlugin.swift) - Added mainThreadResult() private helper that wraps FlutterResult to always dispatch on the main thread (no-op if already on main) - Applied once in handle() so every current and future method handler is protected automatically — no per-handler changes needed 2. Dart layer (OptimizelyClientWrapper + OptimizelyUserContext) - Added _invoke() helper that wraps every MethodChannel.invokeMethod call with a null guard and PlatformException catch - Null response returns {success:false} instead of crashing via Map.from(null) → TypeError - PlatformException is caught and returned as {success:false} - All 18 call sites in OptimizelyClientWrapper and 14 in OptimizelyUserContext now route through _invoke Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update CLAUDE.md with expanded guidance and add IDE/env gitignore entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add unit tests for _invoke() null and PlatformException safety branches Covers the two previously-uncovered error branches in the iOS 16 fix: - Native returns null → success:false (no TypeError) - Native throws PlatformException → success:false (no unhandled exception) Tests both OptimizelyClientWrapper and OptimizelyUserContext. Overall unit test coverage: 85.9% → 87.3% Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: ensure MethodChannel.Result is always called on main thread (Android) Mirrors the iOS mainThreadResult() fix. Added safeResult() wrapper in onMethodCall() that dispatches success/error/notImplemented to the Android main thread via Handler(Looper.getMainLooper()) when called from a background thread. Applied once so all current and future handlers are automatically protected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use nullable-safe TestDefaultBinaryMessengerBinding access in tests Flutter 3.0.5 (used in CI) exposes instance as nullable — use ?. pattern consistent with existing test suite to fix compilation failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 89bbc6d commit e997483

7 files changed

Lines changed: 600 additions & 195 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,8 @@ app.*.symbols
120120
!**/ios/**/default.perspectivev3
121121
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
122122
!/dev/ci/**/Gemfile.lock
123+
124+
# Environment variables
125+
.env
126+
.mcp.json
127+
.claude/settings.local.json

CLAUDE.md

Lines changed: 131 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
Optimizely Flutter SDK - Cross-platform plugin wrapping native Optimizely SDKs (iOS, Android) for A/B testing, feature flags, CMAB, and ODP integration and others.
7+
Optimizely Flutter SDK - Cross-platform plugin wrapping native Optimizely SDKs (iOS, Android) for A/B testing, feature flags, CMAB, and ODP integration.
88

99
**Main Branch:** master
1010

11-
## Project Structure
12-
13-
```
14-
lib/ # Dart: Public API, data models, user context, platform bridge
15-
android/src/main/java/ # Java: OptimizelyFlutterClient.java, Plugin, helpers
16-
ios/Classes/ # Swift: Plugin, logger bridge, helpers
17-
test/ # Unit tests (SDK, CMAB, logger, nested objects)
18-
example/ # Example app
19-
```
20-
2111
## Essential Commands
2212

2313
```bash
2414
# Setup
2515
flutter pub get
16+
cd ios && pod install # iOS dependencies
2617

2718
# Testing
28-
flutter test # All tests
29-
flutter test test/cmab_test.dart # Specific test
30-
flutter test --coverage # With coverage
19+
flutter test # All tests
20+
flutter test test/cmab_test.dart # Specific test
21+
flutter test --coverage # With coverage
3122

3223
# Linting
3324
flutter analyze
3425

35-
# iOS setup
36-
cd ios && pod install
37-
38-
# Run example
26+
# Run example app
3927
cd example && flutter run
4028
```
4129

@@ -47,9 +35,9 @@ Dart API (OptimizelyFlutterSdk)
4735
4836
Wrapper (OptimizelyClientWrapper) + MethodChannel
4937
50-
Native (Swift/Java plugin implementations)
38+
Native Plugins (Swift/Java)
5139
52-
Native Optimizely SDKs
40+
Native Optimizely SDKs (5.2.1 iOS / 5.1.1 Android)
5341
```
5442

5543
### Critical Patterns
@@ -58,68 +46,151 @@ Native Optimizely SDKs
5846
- ALL methods return `BaseResponse` derivatives (never throw exceptions)
5947
- Check `success` boolean and `reason` string for errors
6048

61-
**2. Multi-Instance State**
49+
**2. Multi-Instance State Management**
6250
- SDK instances tracked by `sdkKey`
6351
- User contexts: `sdkKey → userContextId → context`
6452
- Notification listeners: `sdkKey → listenerId → callback`
6553
- Call `close()` for cleanup
6654

67-
**3. Platform-Specific Type Encoding**
55+
**3. Platform-Specific Type Encoding (CRITICAL)**
6856
- **iOS**: Attributes need type metadata: `{"value": 123, "type": "int"}`
6957
- **Android**: Direct primitives: `{"attribute": 123}`
70-
- Conversion in `convertToTypedMap()` (`optimizely_client_wrapper.dart`)
71-
72-
**4. Dual Channels**
73-
- `optimizely_flutter_sdk` - Main API
74-
- `optimizely_flutter_logger` - Native log forwarding
75-
76-
## Key Files
77-
78-
**Dart:**
79-
- `lib/optimizely_flutter_sdk.dart` - Public API entry point
80-
- `lib/src/optimizely_client_wrapper.dart` - Platform channel bridge
81-
- `lib/src/user_context/optimizely_user_context.dart` - User context API
82-
- `lib/src/data_objects/` - 21 response/request models
83-
84-
**Android:**
85-
- `android/src/.../OptimizelyFlutterSdkPlugin.java` - MethodChannel handler
86-
- `android/src/.../OptimizelyFlutterClient.java` - Core client wrapper
87-
- `android/build.gradle` - Dependencies & build config
58+
- Conversion in `Utils.convertToTypedMap()` (lib/src/utils/utils.dart)
59+
- Test override: `forceIOSFormat` parameter
8860

89-
**iOS:**
90-
- `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` - MethodChannel handler
91-
- `ios/optimizely_flutter_sdk.podspec` - Pod dependencies
61+
**4. Dual MethodChannel Architecture**
62+
- `optimizely_flutter_sdk` - Main API operations
63+
- `optimizely_flutter_sdk_logger` - Native → Dart log forwarding
9264

9365
## Adding Cross-Platform Features
9466

9567
1. Add data models in `lib/src/data_objects/` if needed
96-
2. Update `optimizely_client_wrapper.dart` with method channel call
68+
2. Update `lib/src/optimizely_client_wrapper.dart` with MethodChannel call
9769
3. **Android**: Add case in `OptimizelyFlutterClient.java`, parse args, call native SDK
9870
4. **iOS**: Add case in `SwiftOptimizelyFlutterSdkPlugin.swift`, parse args, call native SDK
99-
5. Handle type conversions (iOS requires metadata)
100-
6. Add tests
101-
7. Update public API in `optimizely_flutter_sdk.dart`
71+
5. Handle type conversions (iOS requires metadata wrapping)
72+
6. Write tests in `test/`
73+
7. Update public API in `lib/optimizely_flutter_sdk.dart`
74+
75+
## Version Management
76+
77+
**Three locations must stay synchronized:**
78+
1. `pubspec.yaml``version: X.Y.Z`
79+
2. `lib/package_info.dart``version = 'X.Y.Z'`
80+
3. `README.md` → Installation example `^X.Y.Z`
81+
82+
## Release Workflow
83+
84+
### With Claude Code (Recommended)
85+
86+
Simply ask Claude to create a release:
87+
```
88+
"Create release 3.4.2 with ticket FSSDK-12345"
89+
"Bump patch version with ticket FSSDK-12345"
90+
```
91+
92+
Claude will execute the full workflow:
93+
- ✅ Pre-flight checks (master branch, clean working tree)
94+
- ✅ Create release branch (prepare-X.Y.Z)
95+
- ✅ Update all 3 version files (pubspec.yaml, package_info.dart, README.md)
96+
- ✅ Generate CHANGELOG.md template
97+
- ✅ Commit and push
98+
- ✅ Create PR
99+
100+
### Manual Release
102101

102+
```bash
103+
# 1. Create release branch
104+
git checkout -b prepare-X.Y.Z
105+
106+
# 2. Update versions (pubspec.yaml, package_info.dart, README.md)
107+
# 3. Update CHANGELOG.md (add release notes at top)
108+
109+
# 4. Commit with standard format
110+
git commit -m "chore: prepare for release X.Y.Z
111+
112+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
113+
114+
# 5. Push and create PR
115+
git push -u origin prepare-X.Y.Z
116+
gh pr create --title "[FSSDK-XXXXX] prepare for release X.Y.Z"
117+
118+
# 6. After merge, create GitHub release
119+
gh release create vX.Y.Z --title "Release X.Y.Z" --draft --target master
120+
121+
# 7. Publish to pub.dev
122+
flutter pub publish
123+
```
124+
125+
### CHANGELOG Format
126+
```markdown
127+
## X.Y.Z
128+
Month Day, Year
129+
130+
### New Features / Enhancements / Bug Fixes
131+
* Description ([#PR](link))
132+
```
103133

104-
## Contributing
134+
## Contributing Guidelines
105135

106-
### Commit Format
107-
Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines): `feat:`, `fix:`, `docs:`, `refactor:`, `test:`
136+
### Commit Messages
137+
Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines):
138+
- `feat:` - New features
139+
- `fix:` - Bug fixes
140+
- `chore:` - Maintenance
141+
- `docs:` - Documentation
142+
- `refactor:` - Code restructuring
143+
- `test:` - Test additions
144+
145+
### Branch Strategy
146+
- **Never commit directly to `master`**
147+
- Feature branches: `feature/name`, `prepare-X.Y.Z`, `fix/name`
148+
- All PRs target `master` branch
108149

109150
### Requirements
110-
- **Never commit directly to `master` branch** - Always create a feature branch
111-
- Tests required for all changes
112-
- PR to `master` branch
113-
- All CI checks must pass (unit tests, build validation, integration tests)
151+
- Tests required for all code changes
152+
- All CI checks must pass (4 parallel workflows)
114153
- Apache 2.0 license header on new files
154+
- Sign CLA (Contributor License Agreement)
115155

116156
### CI Pipeline
117-
- `unit_test_coverage` (macOS) - Coverage to Coveralls
118-
- `build_test_android/ios` - Build validation
119-
- `integration_android/ios_tests` - External test app triggers
157+
- `unit_test_coverage` (macOS) - Dart tests + Coveralls upload
158+
- `build_test_android` (Ubuntu) - Android build validation
159+
- `build_test_ios` (macOS) - iOS build validation
160+
- `integration_android_tests` (Ubuntu) - Triggers `optimizely-flutter-testapp` repo
161+
- `integration_ios_tests` (Ubuntu) - Triggers `optimizely-flutter-testapp` repo
120162

121163
## Platform Requirements
122164

123-
- Dart: >=2.16.2 <4.0.0, Flutter: >=2.5.0
124-
- Android: minSdk 21, compileSdk 35
125-
- iOS: 10.0+, Swift 5.0
165+
**Flutter/Dart:**
166+
- Dart: >=2.16.2 <4.0.0
167+
- Flutter: >=2.5.0
168+
169+
**Android:**
170+
- minSdk: 21 (Android 5.0)
171+
- compileSdk: 35 (Android 15)
172+
- Kotlin: 2.1.0
173+
- Native SDK: android-sdk 5.1.1
174+
175+
**iOS:**
176+
- Minimum: iOS 10.0
177+
- Swift: 5.0
178+
- Native SDK: OptimizelySwiftSDK 5.2.1
179+
180+
## Key Implementation Files
181+
182+
**Dart Layer:**
183+
- `lib/optimizely_flutter_sdk.dart` - Public API (initialize, decide, track)
184+
- `lib/src/optimizely_client_wrapper.dart` - Platform bridge (545 LOC)
185+
- `lib/src/user_context/optimizely_user_context.dart` - User context operations
186+
- `lib/src/data_objects/` - 21 response/request models
187+
188+
**Android Layer:**
189+
- `android/src/.../OptimizelyFlutterSdkPlugin.java` - MethodChannel handler
190+
- `android/src/.../OptimizelyFlutterClient.java` - Core client wrapper (921 LOC)
191+
- `android/build.gradle` - Dependencies & SDK versions
192+
193+
**iOS Layer:**
194+
- `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` - Plugin implementation (786 LOC)
195+
- `ios/Classes/OptimizelyFlutterLogger.swift` - Logger bridge with task queue
196+
- `ios/optimizely_flutter_sdk.podspec` - CocoaPods dependencies

0 commit comments

Comments
 (0)