Skip to content

Commit 7eca732

Browse files
committed
claude mds instrumentation
1 parent ed207f5 commit 7eca732

File tree

7 files changed

+2659
-0
lines changed

7 files changed

+2659
-0
lines changed

CLAUDE.md

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
# CLAUDE.md - Snowplow Flutter Tracker Documentation
2+
3+
## Project Overview
4+
5+
The Snowplow Flutter Tracker is a cross-platform analytics SDK that enables Flutter applications to send events to Snowplow collectors. It wraps native iOS, Android, and JavaScript trackers to provide a unified Flutter API for comprehensive event tracking and analytics.
6+
7+
**Core Technologies:**
8+
- Flutter/Dart for cross-platform API
9+
- Platform channels for native communication
10+
- Kotlin for Android implementation
11+
- Swift for iOS implementation
12+
- JavaScript interop for Web support
13+
14+
## Development Commands
15+
16+
```bash
17+
# Build and run
18+
flutter pub get # Install dependencies
19+
flutter analyze # Run static analysis
20+
flutter test # Run unit tests
21+
flutter run --dart-define=ENDPOINT=http://localhost:9090 # Run example app
22+
23+
# Integration testing
24+
cd example && flutter test integration_test --dart-define=ENDPOINT=http://192.168.0.20:9090
25+
26+
# Format code
27+
dart format lib test example/lib
28+
29+
# Check package score
30+
flutter pub publish --dry-run
31+
```
32+
33+
## Architecture
34+
35+
### System Design
36+
37+
The tracker follows a **Plugin Architecture** with platform-specific implementations:
38+
39+
```
40+
┌─────────────────────────────────────────┐
41+
│ Flutter/Dart API Layer │
42+
│ (lib/snowplow.dart, tracker.dart) │
43+
└─────────────┬───────────────────────────┘
44+
│ MethodChannel
45+
┌─────────┴──────────┬──────────────┐
46+
▼ ▼ ▼
47+
┌──────────┐ ┌──────────┐ ┌──────────┐
48+
│ Android │ │ iOS │ │ Web │
49+
│ (Kotlin)│ │ (Swift) │ │ (JS) │
50+
└──────────┘ └──────────┘ └──────────┘
51+
```
52+
53+
### Module Organization
54+
55+
- **`lib/`**: Core Flutter/Dart API
56+
- **`configurations/`**: Configuration classes for tracker setup
57+
- **`events/`**: Event type definitions
58+
- **`entities/`**: Data entities (media tracking)
59+
- **`src/web/`**: Web-specific implementations
60+
- **`android/`**: Android platform implementation
61+
- **`ios/`**: iOS platform implementation
62+
- **`example/`**: Demo application and integration tests
63+
- **`test/`**: Unit tests
64+
65+
## Core Architectural Principles
66+
67+
### 1. Immutable Event Pattern
68+
All events and configurations are immutable with `@immutable` annotation:
69+
```dart
70+
// ✅ Correct: Immutable event
71+
@immutable
72+
class ScreenView implements Event {
73+
final String name;
74+
const ScreenView({required this.name});
75+
}
76+
77+
// ❌ Wrong: Mutable event
78+
class ScreenView implements Event {
79+
String name; // Mutable field
80+
}
81+
```
82+
83+
### 2. Platform Channel Communication
84+
Use consistent message passing through MethodChannel:
85+
```dart
86+
// ✅ Correct: Clean method channel usage
87+
await _channel.invokeMethod('trackScreenView', event.toMap());
88+
89+
// ❌ Wrong: Direct platform access
90+
// Attempting to bypass the channel abstraction
91+
```
92+
93+
### 3. Null-Safe Map Serialization
94+
Always remove null values from serialization maps:
95+
```dart
96+
// ✅ Correct: Remove null values
97+
Map<String, Object?> toMap() {
98+
final map = {'field': value};
99+
map.removeWhere((key, value) => value == null);
100+
return map;
101+
}
102+
103+
// ❌ Wrong: Including null values
104+
Map<String, Object?> toMap() => {'field': value}; // May include nulls
105+
```
106+
107+
### 4. Factory Constructor Pattern for Deserialization
108+
Use named factory constructors for map deserialization:
109+
```dart
110+
// ✅ Correct: Factory constructor
111+
ScreenView.fromMap(Map<String, Object?> map)
112+
: name = map['name'] as String;
113+
114+
// ❌ Wrong: Static method
115+
static ScreenView fromMap(Map map) { } // Inconsistent pattern
116+
```
117+
118+
## Layer Organization & Responsibilities
119+
120+
### API Layer (lib/)
121+
- **Responsibility**: Public Flutter API, event definitions, configurations
122+
- **Key Classes**: `Snowplow`, `SnowplowTracker`, `Event` implementations
123+
- **Pattern**: Immutable data classes with `toMap()` serialization
124+
125+
### Platform Layer (android/, ios/, web/)
126+
- **Responsibility**: Native implementation and platform-specific features
127+
- **Key Classes**: `SnowplowTrackerPlugin`, platform readers
128+
- **Pattern**: Message readers for deserialization, controller for business logic
129+
130+
### Configuration Layer
131+
- **Responsibility**: Tracker initialization and feature configuration
132+
- **Key Classes**: `Configuration`, `TrackerConfiguration`, `NetworkConfiguration`
133+
- **Pattern**: Builder-like immutable configuration objects
134+
135+
## Critical Import Patterns
136+
137+
### Event Imports
138+
```dart
139+
// ✅ Correct: Import from snowplow_tracker package
140+
import 'package:snowplow_tracker/snowplow_tracker.dart';
141+
import 'package:snowplow_tracker/events/screen_view.dart';
142+
143+
// ❌ Wrong: Direct file imports
144+
import '../events/screen_view.dart'; // Use package imports
145+
```
146+
147+
### Platform-Specific Code
148+
```dart
149+
// ✅ Correct: Use kIsWeb for platform checks
150+
import 'package:flutter/foundation.dart';
151+
if (kIsWeb) { /* web specific */ }
152+
153+
// ❌ Wrong: Using Platform.isAndroid on web
154+
import 'dart:io';
155+
if (Platform.isAndroid) { } // Crashes on web
156+
```
157+
158+
## Essential Library Patterns
159+
160+
### Tracker Initialization
161+
```dart
162+
// ✅ Correct: Comprehensive tracker setup
163+
final tracker = await Snowplow.createTracker(
164+
namespace: 'ns1',
165+
endpoint: 'https://collector.example.com',
166+
trackerConfig: TrackerConfiguration(appId: 'app'),
167+
);
168+
169+
// ❌ Wrong: Missing required configuration
170+
final tracker = await Snowplow.createTracker(); // Missing params
171+
```
172+
173+
### Event Tracking
174+
```dart
175+
// ✅ Correct: Track with contexts
176+
await tracker.track(
177+
ScreenView(name: 'home'),
178+
contexts: [SelfDescribing(schema: 'iglu:...', data: {})],
179+
);
180+
181+
// ❌ Wrong: Incorrect event structure
182+
await tracker.track({'type': 'screen'}); // Not an Event object
183+
```
184+
185+
### Media Tracking
186+
```dart
187+
// ✅ Correct: Start media tracking with configuration
188+
final media = await tracker.startMediaTracking(
189+
MediaTrackingConfiguration(id: 'video-1'),
190+
);
191+
192+
// ❌ Wrong: Missing required ID
193+
final media = await tracker.startMediaTracking(
194+
MediaTrackingConfiguration(), // Missing id
195+
);
196+
```
197+
198+
## Model Organization Pattern
199+
200+
### Event Hierarchy
201+
```dart
202+
abstract class Event {
203+
String endpoint();
204+
Map<String, Object?> toMap();
205+
}
206+
207+
// Concrete implementations
208+
class ScreenView implements Event { }
209+
class Structured implements Event { }
210+
class SelfDescribing implements Event { }
211+
```
212+
213+
### Configuration Pattern
214+
```dart
215+
@immutable
216+
class Configuration {
217+
final String namespace;
218+
final NetworkConfiguration networkConfig;
219+
// Optional configs
220+
final TrackerConfiguration? trackerConfig;
221+
222+
Map<String, Object?> toMap() {
223+
// Serialize and remove nulls
224+
}
225+
}
226+
```
227+
228+
## Common Pitfalls & Solutions
229+
230+
### 1. WebView Integration
231+
```dart
232+
// ❌ Wrong: Forgetting to register JavaScript channel
233+
webView.loadUrl('https://example.com');
234+
235+
// ✅ Correct: Register channel before loading
236+
tracker.registerWebViewJavaScriptChannel(
237+
webViewController: controller,
238+
);
239+
webView.loadUrl('https://example.com');
240+
```
241+
242+
### 2. Navigator Observer
243+
```dart
244+
// ❌ Wrong: Creating observer without tracker
245+
MaterialApp(navigatorObservers: [SnowplowObserver()]);
246+
247+
// ✅ Correct: Use tracker's observer
248+
MaterialApp(
249+
navigatorObservers: [tracker.getObserver()],
250+
);
251+
```
252+
253+
### 3. Platform Context Properties
254+
```dart
255+
// ❌ Wrong: Setting platform properties on Web
256+
TrackerConfiguration(
257+
platformContextProperties: properties, // Not supported on Web
258+
);
259+
260+
// ✅ Correct: Check platform first
261+
TrackerConfiguration(
262+
platformContextProperties: kIsWeb ? null : properties,
263+
);
264+
```
265+
266+
### 4. Async Initialization
267+
```dart
268+
// ❌ Wrong: Not awaiting tracker creation
269+
final tracker = Snowplow.createTracker(...); // Returns Future
270+
271+
// ✅ Correct: Await initialization
272+
final tracker = await Snowplow.createTracker(...);
273+
```
274+
275+
## File Structure Template
276+
277+
```
278+
lib/
279+
├── snowplow_tracker.dart # Package exports
280+
├── snowplow.dart # Main API class
281+
├── tracker.dart # Tracker instance
282+
├── configurations/
283+
│ ├── configuration.dart # Base configuration
284+
│ ├── tracker_configuration.dart
285+
│ └── network_configuration.dart
286+
├── events/
287+
│ ├── event.dart # Event interface
288+
│ ├── screen_view.dart
289+
│ └── self_describing.dart
290+
└── entities/
291+
└── media_player_entity.dart
292+
```
293+
294+
## Testing Patterns
295+
296+
### Unit Test Structure
297+
```dart
298+
void main() {
299+
setUp(() async {
300+
// Mock method channel
301+
TestDefaultBinaryMessengerBinding.instance
302+
.defaultBinaryMessenger
303+
.setMockMethodCallHandler(channel, handler);
304+
});
305+
306+
test('tracks event', () async {
307+
await tracker.track(ScreenView(name: 'test'));
308+
expect(capturedMethod, 'trackScreenView');
309+
});
310+
}
311+
```
312+
313+
### Integration Test Pattern
314+
```dart
315+
testWidgets('end-to-end tracking', (tester) async {
316+
final events = await getMicroEvents(endpoint);
317+
expect(events.any((e) => e['eventType'] == 'struct'), true);
318+
});
319+
```
320+
321+
## Quick Reference
322+
323+
### Event Type Checklist
324+
- [ ] Implements `Event` interface
325+
- [ ] Has `@immutable` annotation
326+
- [ ] Implements `endpoint()` method
327+
- [ ] Implements `toMap()` with null removal
328+
- [ ] Has factory constructor `.fromMap()` for deserialization
329+
- [ ] All fields are `final`
330+
331+
### Configuration Checklist
332+
- [ ] All fields are `final` and nullable (optional)
333+
- [ ] Has `toMap()` method with null removal
334+
- [ ] Uses `@immutable` annotation
335+
- [ ] Documents platform-specific features
336+
337+
### Platform Implementation Checklist
338+
- [ ] Has reader class for deserialization
339+
- [ ] Handles null values appropriately
340+
- [ ] Maps to native tracker methods
341+
- [ ] Consistent error handling
342+
343+
## Contributing to CLAUDE.md
344+
345+
When adding or updating content in this document, please follow these guidelines:
346+
347+
### File Size Limit
348+
- **CLAUDE.md must not exceed 40KB** (currently ~19KB)
349+
- Check file size after updates: `wc -c CLAUDE.md`
350+
- Remove outdated content if approaching the limit
351+
352+
### Code Examples
353+
- Keep all code examples **4 lines or fewer**
354+
- Focus on the essential pattern, not complete implementations
355+
- Use `// ❌` and `// ✅` to clearly show wrong vs right approaches
356+
357+
### Content Organization
358+
- Add new patterns to existing sections when possible
359+
- Create new sections sparingly to maintain structure
360+
- Update the architectural principles section for major changes
361+
- Ensure examples follow current codebase conventions
362+
363+
### Quality Standards
364+
- Test any new patterns in actual code before documenting
365+
- Verify imports and syntax are correct for the codebase
366+
- Keep language concise and actionable
367+
- Focus on "what" and "how", minimize "why" explanations
368+
369+
### Multiple CLAUDE.md Files
370+
- **Directory-specific CLAUDE.md files** can be created for specialized modules
371+
- Follow the same structure and guidelines as this root CLAUDE.md
372+
- Keep them focused on directory-specific patterns and conventions
373+
- Maximum 20KB per directory-specific CLAUDE.md file
374+
375+
### Instructions for LLMs
376+
When editing files in this repository, **always check for CLAUDE.md guidance**:
377+
378+
1. **Look for CLAUDE.md in the same directory** as the file being edited
379+
2. **If not found, check parent directories** recursively up to project root
380+
3. **Follow the patterns and conventions** described in the applicable CLAUDE.md
381+
4. **Prioritize directory-specific guidance** over root-level guidance when conflicts exist

0 commit comments

Comments
 (0)