Skip to content

Commit 6ce6dd4

Browse files
Add AI context files (#200)
* add ai context files * remove test_coverate from copilot setup * add flutter_lints dev dependency * no fatal infos/warnings in flutter analyze * no fatal infos
1 parent 360e096 commit 6ce6dd4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+7722
-6
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# Mixpanel Flutter SDK Architecture
2+
3+
## Overview
4+
5+
The Mixpanel Flutter SDK is a cross-platform analytics plugin that provides a unified Dart API for tracking events and managing user profiles across iOS, Android, and Web platforms. The SDK uses Flutter's platform channel mechanism to communicate between Dart code and native platform implementations.
6+
7+
## Architecture Layers
8+
9+
### 1. Dart API Layer (`lib/mixpanel_flutter.dart`)
10+
11+
The main entry point providing a unified interface across all platforms:
12+
13+
```dart
14+
class Mixpanel {
15+
static final MethodChannel _channel = kIsWeb
16+
? const MethodChannel('mixpanel_flutter')
17+
: const MethodChannel('mixpanel_flutter', StandardMethodCodec(MixpanelMessageCodec()));
18+
19+
// Core tracking method
20+
Future<void> track(String eventName, {Map<String, dynamic>? properties}) async {
21+
await _channel.invokeMethod<void>('track', {
22+
'eventName': eventName,
23+
'properties': _MixpanelHelper.ensureSerializableProperties(properties)
24+
});
25+
}
26+
}
27+
```
28+
29+
Key components:
30+
- **Mixpanel**: Main singleton class for event tracking
31+
- **People**: User profile management (accessed via `mixpanel.getPeople()`)
32+
- **MixpanelGroup**: Group analytics management (accessed via `mixpanel.getGroup()`)
33+
34+
### 2. Platform Channel & Serialization
35+
36+
#### Custom Message Codec (`lib/codec/mixpanel_message_codec.dart`)
37+
38+
Handles serialization of complex types between Dart and native platforms:
39+
40+
```dart
41+
class MixpanelMessageCodec extends StandardMessageCodec {
42+
static const int _kDateTime = 128;
43+
static const int _kUri = 129;
44+
45+
@override
46+
void writeValue(WriteBuffer buffer, dynamic value) {
47+
if (value is DateTime) {
48+
buffer.putUint8(_kDateTime);
49+
buffer.putInt64(value.millisecondsSinceEpoch);
50+
} else if (value is Uri) {
51+
buffer.putUint8(_kUri);
52+
final bytes = utf8.encoder.convert(value.toString());
53+
writeSize(buffer, bytes.length);
54+
buffer.putUint8List(bytes);
55+
} else {
56+
super.writeValue(buffer, value);
57+
}
58+
}
59+
}
60+
```
61+
62+
### 3. Platform Implementations
63+
64+
#### Android Implementation
65+
66+
**MixpanelFlutterPlugin.java**:
67+
```java
68+
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
69+
switch (call.method) {
70+
case "track":
71+
handleTrack(call, result);
72+
break;
73+
// ... other methods
74+
}
75+
}
76+
77+
private void handleTrack(MethodCall call, Result result) {
78+
String eventName = call.argument("eventName");
79+
Map<String, Object> mapProperties = call.<HashMap<String, Object>>argument("properties");
80+
JSONObject properties = new JSONObject(mapProperties == null ? EMPTY_HASHMAP : mapProperties);
81+
properties = MixpanelFlutterHelper.getMergedProperties(properties, mixpanelProperties);
82+
mixpanel.track(eventName, properties);
83+
result.success(null);
84+
}
85+
```
86+
87+
**MixpanelMessageCodec.java**: Mirrors Dart codec for Date/URI handling
88+
89+
#### iOS Implementation
90+
91+
**SwiftMixpanelFlutterPlugin.swift**:
92+
```swift
93+
private func handleTrack(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
94+
let arguments = call.arguments as? [String: Any] ?? [String: Any]()
95+
let event = arguments["eventName"] as! String
96+
let properties = arguments["properties"] as? [String: Any]
97+
let mpProperties = MixpanelTypeHandler.mixpanelProperties(properties: properties, mixpanelProperties: mixpanelProperties)
98+
instance?.track(event: event, properties: mpProperties)
99+
result(nil)
100+
}
101+
```
102+
103+
**Custom codec implementation**:
104+
```swift
105+
public class MixpanelReader : FlutterStandardReader {
106+
public override func readValue(ofType type: UInt8) -> Any? {
107+
switch type {
108+
case DATE_TIME:
109+
var value: Int64 = 0
110+
readBytes(&value, length: 8)
111+
return Date(timeIntervalSince1970: TimeInterval(value / 1000))
112+
case URI:
113+
let urlString = readUTF8()
114+
return URL(string: urlString)
115+
default:
116+
return super.readValue(ofType: type)
117+
}
118+
}
119+
}
120+
```
121+
122+
#### Web Implementation
123+
124+
**mixpanel_flutter_web.dart**:
125+
```dart
126+
void handleTrack(MethodCall call) {
127+
Map<Object?, Object?> args = call.arguments as Map<Object?, Object?>;
128+
String eventName = args['eventName'] as String;
129+
dynamic properties = args['properties'];
130+
Map<String, dynamic> props = {
131+
..._mixpanelProperties,
132+
...(properties ?? {})
133+
};
134+
track(eventName, safeJsify(props));
135+
}
136+
```
137+
138+
**Type conversion for web**:
139+
```dart
140+
JSAny? safeJsify(dynamic value) {
141+
if (value == null) {
142+
return null;
143+
} else if (value is Map) {
144+
return value.jsify();
145+
} else if (value is DateTime) {
146+
return value.jsify();
147+
} // ... other type conversions
148+
}
149+
```
150+
151+
## Event Flow: track() Method
152+
153+
### 1. Initialization Flow
154+
155+
```dart
156+
// Dart layer
157+
final mixpanel = await Mixpanel.init("YOUR_PROJECT_TOKEN",
158+
optOutTrackingDefault: false,
159+
trackAutomaticEvents: true);
160+
161+
// Platform channel invocation
162+
await _channel.invokeMethod<void>('initialize', {
163+
'token': token,
164+
'optOutTrackingDefault': optOutTrackingDefault,
165+
'trackAutomaticEvents': trackAutomaticEvents,
166+
'mixpanelProperties': _mixpanelProperties, // {$lib_version: '2.4.4', mp_lib: 'flutter'}
167+
'superProperties': superProperties,
168+
'config': config
169+
});
170+
```
171+
172+
### 2. Track Event Flow
173+
174+
```
175+
Dart Layer (mixpanel.track("Event Name", properties: {...}))
176+
177+
Platform Channel (invokeMethod('track', {eventName, properties}))
178+
179+
Native Platform Handler
180+
├── Android: MixpanelFlutterPlugin.handleTrack()
181+
├── iOS: SwiftMixpanelFlutterPlugin.handleTrack()
182+
└── Web: MixpanelFlutterPlugin.handleTrack()
183+
184+
Property Processing
185+
├── Merge with library properties ($lib_version, mp_lib)
186+
├── Type conversion (Date, URI, etc.)
187+
└── Platform-specific formatting
188+
189+
Native SDK Call
190+
├── Android: mixpanel.track(eventName, JSONObject)
191+
├── iOS: instance?.track(event:properties:)
192+
└── Web: track(eventName, jsProperties)
193+
194+
Mixpanel Servers
195+
```
196+
197+
### 3. Data Serialization Details
198+
199+
#### Native Platforms (Android/iOS)
200+
- Custom codec handles DateTime and Uri objects
201+
- DateTime: Serialized as milliseconds since epoch (int64)
202+
- Uri: Serialized as UTF-8 encoded string
203+
- Complex objects (Maps, Lists) are recursively converted
204+
205+
#### Web Platform
206+
- No custom codec needed - uses StandardMethodCodec
207+
- `safeJsify()` converts Dart types to JavaScript-compatible types
208+
- DateTime objects converted using `.jsify()`
209+
- Direct JS interop with Mixpanel JavaScript library
210+
211+
## Key Design Decisions
212+
213+
1. **Platform Channel Architecture**: Enables code reuse while allowing platform-specific optimizations
214+
215+
2. **Custom Message Codec**: Ensures DateTime and Uri objects are properly serialized across platform boundaries
216+
217+
3. **Library Properties**: Automatically injected metadata (`$lib_version`, `mp_lib`) helps with analytics segmentation
218+
219+
4. **Async API**: All methods return Futures for consistency, even if underlying native calls are synchronous
220+
221+
5. **Type Safety**: Platform-specific type handlers ensure proper conversion between Dart and native types
222+
223+
6. **Web Implementation**: Uses JS interop instead of platform channels for better performance and smaller bundle size
224+
225+
## Platform Dependencies
226+
227+
- **Android**: Mixpanel Android SDK v8.0.3
228+
- **iOS**: Mixpanel-swift v5.0.0
229+
- **Web**: Mixpanel JavaScript library (loaded from CDN)
230+
231+
## Example Usage
232+
233+
```dart
234+
// Initialize
235+
final mixpanel = await Mixpanel.init("YOUR_PROJECT_TOKEN",
236+
trackAutomaticEvents: true);
237+
238+
// Track simple event
239+
await mixpanel.track("Button Clicked");
240+
241+
// Track with properties
242+
await mixpanel.track("Purchase", properties: {
243+
"product": "Premium Subscription",
244+
"price": 9.99,
245+
"currency": "USD",
246+
"timestamp": DateTime.now(),
247+
"store_url": Uri.parse("https://store.example.com")
248+
});
249+
250+
// Identify user
251+
await mixpanel.identify("user123");
252+
253+
// Set user profile properties
254+
mixpanel.getPeople().set("name", "John Doe");
255+
mixpanel.getPeople().set("email", "[email protected]");
256+
257+
// Group analytics
258+
mixpanel.setGroup("company", "Acme Corp");
259+
final group = mixpanel.getGroup("company", "Acme Corp");
260+
group.set("plan", "Enterprise");
261+
```
262+
263+
## Architecture Benefits
264+
265+
1. **Unified API**: Developers write once, run everywhere
266+
2. **Type Safety**: Strong typing prevents runtime errors
267+
3. **Performance**: Native SDK usage ensures optimal performance per platform
268+
4. **Maintainability**: Clear separation of concerns between layers
269+
5. **Extensibility**: Easy to add new methods or platforms
270+
271+
## Future Considerations
272+
273+
1. **Null Safety**: The SDK fully supports Dart null safety
274+
2. **Platform Expansion**: Architecture supports adding new platforms (e.g., Windows, Linux)
275+
3. **Feature Parity**: Platform implementations should maintain feature parity where possible
276+
4. **Testing**: Platform-specific functionality should be tested through the example app

0 commit comments

Comments
 (0)