Skip to content

Commit b580927

Browse files
unix14eyalYaakobi
authored andcommitted
Added Funnels Manager
1 parent c391a0a commit b580927

File tree

9 files changed

+257
-4
lines changed

9 files changed

+257
-4
lines changed

lib/analitix/extensions/map_extenions.dart

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
import 'package:analitix/analitix/abstract/analytix_manager.dart';
3+
import 'package:flutter/foundation.dart';
4+
5+
import 'funnel.dart';
6+
7+
class AnalytixFunnel extends Funnel {
8+
9+
// Constructor
10+
AnalytixFunnel(String funnelName, {bool shouldCountTime = false})
11+
: super(funnelName, shouldCountTime: shouldCountTime);
12+
13+
@override
14+
void track(String eventName) {
15+
super.track(eventName);
16+
var shouldReport = !kDebugMode || true;
17+
if(shouldReport) {
18+
var shouldReportFunnelDuration = shouldCountTime && endTime != null && startTime != null;
19+
var durationTime = shouldReportFunnelDuration ? endTime!.difference(startTime!).inMilliseconds : 0;
20+
var duration = shouldReportFunnelDuration ? (durationTime.toString()) : "";
21+
AnalytixManager().logEvent(funnelName, eventName,
22+
params: shouldReportFunnelDuration ? {
23+
'duration': duration
24+
} : {}
25+
);
26+
}
27+
}
28+
}

lib/funnels_manager/funnel.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
3+
import 'package:flutter/cupertino.dart';
4+
5+
/// Funnel class to track user journey
6+
/// it defines the structure of a funnel and its methods
7+
abstract class Funnel {
8+
9+
String funnelName;
10+
11+
// Time management
12+
bool shouldCountTime;
13+
DateTime? startTime;
14+
DateTime? endTime;
15+
16+
Funnel(this.funnelName, {this.shouldCountTime = false});
17+
18+
void start() {
19+
if(shouldCountTime) {
20+
startTime = DateTime.now();
21+
}
22+
print("Funnel Tracking:: Start: $funnelName");
23+
}
24+
25+
@mustCallSuper
26+
void track(String eventName) {
27+
print("Funnel Tracking:: $funnelName: $eventName");
28+
}
29+
30+
void finish() {
31+
if (shouldCountTime) {
32+
endTime = DateTime.now();
33+
}
34+
print("Funnel Tracking:: End: $funnelName ${shouldCountTime ? ", Duration: ${endTime!.difference(startTime!)}" : ""}");
35+
}
36+
}

lib/funnels_manager/funnels.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
/// Funnels are used to track user journey within the app
3+
/// and measure the time it takes to complete a certain journey
4+
class Funnels {
5+
6+
/// Funnel 1 is used to track the time it takes the users
7+
/// who just entered the app to click on the Funnels button
8+
static String funnel_1 = 'f#1';
9+
10+
/// Funnel 2 is used to track the time it takes the users
11+
/// who are already in the funnels screen and then they click on the exit button
12+
static String funnel_2 = 'f#2';
13+
14+
/// Funnel 3 is used to track the time it takes the users
15+
/// who are already in the funnels screen and then they move between the steps
16+
/// each step starts with start and ends with finish events
17+
/// in between we will report the step number and in the end we will report
18+
/// the time it takes to move between the steps
19+
/// it includes the following events:
20+
/// - start
21+
/// - step_1
22+
/// - step_2
23+
/// - step_3
24+
/// - finish
25+
static String funnel_3 = 'f#3';
26+
27+
/// Funnel Internet is used for users who are already registered and logged in to the app
28+
/// we want to measure the events related to internet connection
29+
/// it includes the following events:
30+
/// - Internet_down
31+
/// - Internet_up (will be sent only if internet_down was sent)
32+
static String funnel_Internet = 'Internet';
33+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
import 'funnel.dart';
3+
4+
class FunnelsManager {
5+
6+
static final FunnelsManager _instance = FunnelsManager._internal();
7+
8+
factory FunnelsManager() => _instance;
9+
FunnelsManager._internal();
10+
11+
final Map<String, Funnel> _funnels = {};
12+
13+
void start(Funnel funnel) {
14+
var funnelName = funnel.funnelName;
15+
if (_funnels[funnelName] == null) {
16+
print("FunnelsManager:: Adding funnel with name: $funnelName");
17+
_funnels[funnelName] = funnel;
18+
funnel.start();
19+
} else {
20+
print("FunnelsManager:: Funnel with name: $funnelName already exists");
21+
}
22+
}
23+
24+
void track(String funnelName, String data) {
25+
if (_funnels[funnelName] == null) {
26+
print("FunnelsManager:: No funnel found with name: $funnelName");
27+
return;
28+
}
29+
_funnels[funnelName]?.track(data);
30+
}
31+
32+
void finish(String funnelName, String data) {
33+
print("FunnelsManager:: Removing funnel with name: $funnelName");
34+
_funnels[funnelName]?.finish();
35+
// we are tracking the funnel only if it should count time
36+
if(_funnels[funnelName]?.shouldCountTime == true) {
37+
track(funnelName, data);
38+
}
39+
_funnels.remove(funnelName);
40+
}
41+
42+
void clear() {
43+
print("FunnelsManager:: Clearing all funnels");
44+
_funnels.clear();
45+
}
46+
}

lib/main.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import 'package:analitix/analitix/abstract/analytix_manager.dart';
2+
import 'package:analitix/funnels_manager/analytix_funnel.dart';
3+
import 'package:analitix/funnels_manager/funnels.dart';
4+
import 'package:analitix/funnels_manager/funnels_manager.dart';
25
import 'package:flutter/material.dart';
36
import 'custom_reporters/screen_logger_reporter.dart';
47
import 'ui/analytix_example_screen.dart';
@@ -26,4 +29,7 @@ _initAnalytics() {
2629
AnalytixManager().setUserProperty("amountOfConnectedSessions", 3);
2730
AnalytixManager().setUserProperty("lastLoginTime", DateTime.now());
2831
AnalytixManager().setUserProperty("refreshToken", "FNvof-Qq3llcnu8nvknerk87@#aaAxz-zZq3");
32+
33+
// Start f1 funnel
34+
FunnelsManager().start(AnalytixFunnel(Funnels.funnel_1, shouldCountTime: true));
2935
}

lib/ui/simple_event_example_screen.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
import 'package:analitix/analitix/abstract/analytix_manager.dart';
33
import 'package:analitix/custom_reporters/screen_logger_reporter.dart';
44
import 'package:analitix/ui/screen_view_example_screen.dart';
5+
import 'package:analitix/ui/simple_funnels_example_screen.dart';
56
import 'package:flutter/material.dart';
67

8+
import '../funnels_manager/funnels.dart';
9+
import '../funnels_manager/funnels_manager.dart';
10+
711
class SimpleEventExampleScreen extends StatefulWidget {
812
@override
913
State<SimpleEventExampleScreen> createState() => _SimpleEventExampleScreenState();
@@ -50,13 +54,22 @@ class _SimpleEventExampleScreenState extends State<SimpleEventExampleScreen> {
5054
});
5155
} : null,
5256
),
57+
IconButton(
58+
tooltip: "Funnels Manager",
59+
icon: const Icon(Icons.filter_alt_outlined),
60+
onPressed: () async {
61+
_showSnackBar("Open Funnels Manager");
62+
FunnelsManager().finish(Funnels.funnel_1, "finish");
63+
await _goToScreen(const SimpleFunnelExampleScreen());
64+
FunnelsManager().finish(Funnels.funnel_2, "finish");
65+
},
66+
),
5367
IconButton(
5468
tooltip: "Navigate to new Screen",
5569
icon: const Icon(Icons.screen_rotation_outlined),
5670
onPressed: () async {
5771
_showSnackBar("Open new Screen");
58-
await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScreenViewExampleScreen()));
59-
setState(() {});
72+
await _goToScreen(const ScreenViewExampleScreen());
6073
},
6174
),
6275
// disable / enable button for data collection
@@ -130,8 +143,13 @@ class _SimpleEventExampleScreenState extends State<SimpleEventExampleScreen> {
130143
_showSnackBar(String message) {
131144
SnackBar snackBar = SnackBar(
132145
content: Text(message),
133-
duration: const Duration(seconds: 2),
146+
duration: const Duration(seconds: 1),
134147
);
135148
ScaffoldMessenger.of(context).showSnackBar(snackBar);
136149
}
150+
151+
_goToScreen(StatefulWidget newScreen) async {
152+
await Navigator.push(context, MaterialPageRoute(builder: (context) => newScreen));
153+
setState(() {});
154+
}
137155
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
import 'package:flutter/material.dart';
3+
4+
import '../funnels_manager/analytix_funnel.dart';
5+
import '../funnels_manager/funnels.dart';
6+
import '../funnels_manager/funnels_manager.dart';
7+
8+
class SimpleFunnelExampleScreen extends StatefulWidget {
9+
const SimpleFunnelExampleScreen({super.key});
10+
11+
@override
12+
State<SimpleFunnelExampleScreen> createState() => _SimpleFunnelExampleScreenState();
13+
}
14+
15+
class _SimpleFunnelExampleScreenState extends State<SimpleFunnelExampleScreen> {
16+
17+
final PageController _pageController = PageController();
18+
19+
@override
20+
void initState() {
21+
// Start tracking Funnel 2
22+
FunnelsManager().start(AnalytixFunnel(Funnels.funnel_2, shouldCountTime: true));
23+
24+
// Start track Funnel 3
25+
FunnelsManager().start(AnalytixFunnel(Funnels.funnel_3, shouldCountTime: true));
26+
FunnelsManager().track(Funnels.funnel_3, "start");
27+
super.initState();
28+
}
29+
30+
@override
31+
void dispose() {
32+
// finish the funnel if a user decides to leave the screen
33+
// before finishing the whole steps. This won't affect if the user finishes all steps
34+
FunnelsManager().finish(Funnels.funnel_2, "finish");
35+
FunnelsManager().finish(Funnels.funnel_3, "finish");
36+
37+
super.dispose();
38+
}
39+
40+
@override
41+
Widget build(BuildContext context) {
42+
return Scaffold(
43+
appBar: AppBar(
44+
title: const Text('Simple Funnel Example'),
45+
),
46+
body: Center(
47+
child: PageView.builder(
48+
itemCount: 4,
49+
controller: _pageController,
50+
physics: const NeverScrollableScrollPhysics(),
51+
itemBuilder: (context, index) {
52+
var indexUserFriendly = index+1;
53+
return Column(
54+
children: [
55+
Center(
56+
child: ListTile(
57+
title: Text('Page #$indexUserFriendly'),
58+
onTap: () {
59+
// start a new funnel if possible - to track how much time we stay at each screen
60+
FunnelsManager().start(AnalytixFunnel(Funnels.funnel_3, shouldCountTime: true));
61+
// Track the event
62+
FunnelsManager().track(Funnels.funnel_3, "step_$indexUserFriendly");
63+
FunnelsManager().finish(Funnels.funnel_3, "finish");
64+
65+
66+
if(indexUserFriendly <4) {
67+
// start a new funnel for the next screen
68+
FunnelsManager().start(AnalytixFunnel(Funnels.funnel_3, shouldCountTime: true));
69+
_pageController.animateToPage(indexUserFriendly, duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
70+
} else {
71+
Navigator.pop(context);
72+
}
73+
},
74+
),
75+
),
76+
],
77+
);
78+
},
79+
)
80+
),
81+
);
82+
}
83+
}

test/widget_test.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
// gestures. You can also use WidgetTester to find child widgets in the widget
66
// tree, read text, and verify that the values of widget properties are correct.
77

8+
import 'package:analitix/ui/simple_event_example_screen.dart';
89
import 'package:flutter/material.dart';
910
import 'package:flutter_test/flutter_test.dart';
1011

1112
import 'package:analitix/main.dart';
1213

1314
void main() {
15+
/// todo write a test for the entire library
16+
return;
1417
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
1518
// Build our app and trigger a frame.
16-
await tester.pumpWidget(const SimpleEventExampleScreen());
19+
await tester.pumpWidget(SimpleEventExampleScreen());
1720

1821
// Verify that our counter starts at 0.
1922
expect(find.text('0'), findsOneWidget);

0 commit comments

Comments
 (0)