Skip to content

Commit a095f31

Browse files
abdelhamid-f-nasserHeshamMegid
authored andcommitted
feat: add new session replay apis (#395)
Jira ID: MOB-13047, IBGCRASH-20007
1 parent 4030ab5 commit a095f31

File tree

13 files changed

+361
-2
lines changed

13 files changed

+361
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased](https://github.com/Instabug/Instabug-Flutter/compare/v11.14.0...dev)
44

5+
### Added
6+
7+
- Add support for Session Replay, which includes capturing session details, visual reproduction of sessions as well as support for user steps, network and Instabug logs. ([#395](https://github.com/Instabug/Instabug-Flutter/pull/395)).
8+
59
### Changed
610

711
- **BREAKING** Remove deprecated APIs ([#385](https://github.com/Instabug/Instabug-Flutter/pull/385)). See migration guide for more details.

android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.instabug.flutter.modules.InstabugApi;
1818
import com.instabug.flutter.modules.InstabugLogApi;
1919
import com.instabug.flutter.modules.RepliesApi;
20+
import com.instabug.flutter.modules.SessionReplayApi;
2021
import com.instabug.flutter.modules.SurveysApi;
2122

2223
import java.util.concurrent.Callable;
@@ -88,6 +89,7 @@ public Bitmap call() {
8889
InstabugApi.init(messenger, context, screenshotProvider);
8990
InstabugLogApi.init(messenger);
9091
RepliesApi.init(messenger);
92+
SessionReplayApi.init(messenger);
9193
SurveysApi.init(messenger);
9294
}
9395

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.instabug.flutter.modules;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import com.instabug.flutter.generated.SessionReplayPigeon;
6+
import com.instabug.library.sessionreplay.SessionReplay;
7+
8+
import io.flutter.plugin.common.BinaryMessenger;
9+
10+
public class SessionReplayApi implements SessionReplayPigeon.SessionReplayHostApi {
11+
12+
public static void init(BinaryMessenger messenger) {
13+
final SessionReplayApi api = new SessionReplayApi();
14+
SessionReplayPigeon.SessionReplayHostApi.setup(messenger, api);
15+
}
16+
17+
@Override
18+
public void setEnabled(@NonNull Boolean isEnabled) {
19+
SessionReplay.setEnabled(isEnabled);
20+
}
21+
22+
@Override
23+
public void setNetworkLogsEnabled(@NonNull Boolean isEnabled) {
24+
SessionReplay.setNetworkLogsEnabled(isEnabled);
25+
}
26+
27+
@Override
28+
public void setInstabugLogsEnabled(@NonNull Boolean isEnabled) {
29+
SessionReplay.setIBGLogsEnabled(isEnabled);
30+
}
31+
32+
@Override
33+
public void setUserStepsEnabled(@NonNull Boolean isEnabled) {
34+
SessionReplay.setUserStepsEnabled(isEnabled);
35+
}
36+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.instabug.flutter;
2+
3+
import static org.mockito.ArgumentMatchers.any;
4+
import static org.mockito.ArgumentMatchers.eq;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.mockStatic;
7+
8+
import com.instabug.flutter.generated.SessionReplayPigeon;
9+
import com.instabug.flutter.modules.SessionReplayApi;
10+
import com.instabug.flutter.util.GlobalMocks;
11+
import com.instabug.library.sessionreplay.SessionReplay;
12+
13+
import org.junit.After;
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
import org.mockito.MockedStatic;
17+
18+
import io.flutter.plugin.common.BinaryMessenger;
19+
20+
21+
public class SessionReplayApiTest {
22+
private final SessionReplayApi api = new SessionReplayApi();
23+
private MockedStatic<SessionReplay> mSessionReplay;
24+
private MockedStatic<SessionReplayPigeon.SessionReplayHostApi> mHostApi;
25+
26+
@Before
27+
public void setUp() throws NoSuchMethodException {
28+
mSessionReplay = mockStatic(SessionReplay.class);
29+
mHostApi = mockStatic(SessionReplayPigeon.SessionReplayHostApi.class);
30+
GlobalMocks.setUp();
31+
}
32+
33+
@After
34+
public void cleanUp() {
35+
mSessionReplay.close();
36+
mHostApi.close();
37+
GlobalMocks.close();
38+
}
39+
40+
@Test
41+
public void testInit() {
42+
BinaryMessenger messenger = mock(BinaryMessenger.class);
43+
44+
SessionReplayApi.init(messenger);
45+
46+
mHostApi.verify(() -> SessionReplayPigeon.SessionReplayHostApi.setup(eq(messenger), any(SessionReplayApi.class)));
47+
}
48+
49+
@Test
50+
public void testSetEnabled() {
51+
boolean isEnabled = true;
52+
53+
api.setEnabled(isEnabled);
54+
55+
mSessionReplay.verify(() -> SessionReplay.setEnabled(true));
56+
}
57+
58+
@Test
59+
public void testSetNetworkLogsEnabled() {
60+
boolean isEnabled = true;
61+
62+
api.setNetworkLogsEnabled(isEnabled);
63+
64+
mSessionReplay.verify(() -> SessionReplay.setNetworkLogsEnabled(true));
65+
}
66+
67+
@Test
68+
public void testSetInstabugLogsEnabled() {
69+
boolean isEnabled = true;
70+
71+
api.setInstabugLogsEnabled(isEnabled);
72+
73+
mSessionReplay.verify(() -> SessionReplay.setIBGLogsEnabled(true));
74+
}
75+
76+
@Test
77+
public void testSetUserStepsEnabled() {
78+
boolean isEnabled = true;
79+
80+
api.setUserStepsEnabled(isEnabled);
81+
82+
mSessionReplay.verify(() -> SessionReplay.setUserStepsEnabled(true));
83+
}
84+
85+
}
86+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#import <XCTest/XCTest.h>
2+
#import "OCMock/OCMock.h"
3+
#import "SessionReplayApi.h"
4+
#import "Instabug/IBGSessionReplay.h"
5+
6+
@interface SessionReplayApiTests : XCTestCase
7+
8+
@property (nonatomic, strong) id mSessionReplay;
9+
@property (nonatomic, strong) SessionReplayApi *api;
10+
11+
@end
12+
13+
@implementation SessionReplayApiTests
14+
15+
- (void)setUp {
16+
self.mSessionReplay = OCMClassMock([IBGSessionReplay class]);
17+
self.api = [[SessionReplayApi alloc] init];
18+
}
19+
20+
21+
- (void)testSetEnabled {
22+
NSNumber *isEnabled = @1;
23+
FlutterError *error;
24+
25+
[self.api setEnabledIsEnabled:isEnabled error:&error];
26+
27+
OCMVerify([self.mSessionReplay setEnabled:YES]);
28+
}
29+
30+
- (void)testSetInstabugLogsEnabled {
31+
NSNumber *isEnabled = @1;
32+
FlutterError *error;
33+
34+
[self.api setInstabugLogsEnabledIsEnabled:isEnabled error:&error];
35+
36+
OCMVerify([self.mSessionReplay setIBGLogsEnabled:YES]);
37+
}
38+
39+
- (void)testSetNetworkLogsEnabled {
40+
NSNumber *isEnabled = @1;
41+
FlutterError *error;
42+
43+
[self.api setNetworkLogsEnabledIsEnabled:isEnabled error:&error];
44+
45+
OCMVerify([self.mSessionReplay setNetworkLogsEnabled:YES]);
46+
}
47+
48+
- (void)testSetUserStepsEnabled {
49+
NSNumber *isEnabled = @1;
50+
FlutterError *error;
51+
52+
[self.api setUserStepsEnabledIsEnabled:isEnabled error:&error];
53+
54+
OCMVerify([self.mSessionReplay setUserStepsEnabled:YES]);
55+
}
56+
57+
@end

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11+
206286ED2ABD0A1F00925509 /* SessionReplayApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 206286EC2ABD0A1F00925509 /* SessionReplayApiTests.m */; };
1112
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
1213
3EA1F5233E85A5C4F9EF3957 /* Pods_InstabugUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5446C0D3B2623D9BCC7CCE3 /* Pods_InstabugUITests.framework */; };
1314
65C88E6E8EAE049E32FF2F52 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 853739F5879F6E4272829F47 /* Pods_Runner.framework */; };
@@ -66,6 +67,7 @@
6667
/* Begin PBXFileReference section */
6768
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
6869
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
70+
206286EC2ABD0A1F00925509 /* SessionReplayApiTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SessionReplayApiTests.m; sourceTree = "<group>"; };
6971
243EF14638ECA64074771B11 /* Pods-InstabugTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugTests.release.xcconfig"; path = "Target Support Files/Pods-InstabugTests/Pods-InstabugTests.release.xcconfig"; sourceTree = "<group>"; };
7072
354EA318B622513FE3FD25E4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
7173
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
@@ -231,6 +233,7 @@
231233
CC9925D4293DF534001FD3EE /* FeatureRequestsApiTests.m */,
232234
CC9925D6293DFB03001FD3EE /* InstabugLogApiTests.m */,
233235
CC9925D8293DFD7F001FD3EE /* RepliesApiTests.m */,
236+
206286EC2ABD0A1F00925509 /* SessionReplayApiTests.m */,
234237
CC3D69E6293F47FC000DCE54 /* ArgsRegistryTests.m */,
235238
);
236239
path = InstabugTests;
@@ -575,6 +578,7 @@
575578
CC198C61293E1A21007077C8 /* SurveysApiTests.m in Sources */,
576579
CCADBDD8293CFED300AE5EB8 /* BugReportingApiTests.m in Sources */,
577580
CC9925D9293DFD7F001FD3EE /* RepliesApiTests.m in Sources */,
581+
206286ED2ABD0A1F00925509 /* SessionReplayApiTests.m in Sources */,
578582
CC9925D2293DEB0B001FD3EE /* CrashReportingApiTests.m in Sources */,
579583
CC9925D7293DFB03001FD3EE /* InstabugLogApiTests.m in Sources */,
580584
CC9925D5293DF534001FD3EE /* FeatureRequestsApiTests.m in Sources */,

ios/Classes/InstabugFlutterPlugin.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#import "InstabugApi.h"
88
#import "InstabugLogApi.h"
99
#import "RepliesApi.h"
10+
#import "SessionReplayApi.h"
1011
#import "SurveysApi.h"
1112

1213
@implementation InstabugFlutterPlugin
@@ -19,6 +20,7 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
1920
InitInstabugApi([registrar messenger]);
2021
InitInstabugLogApi([registrar messenger]);
2122
InitRepliesApi([registrar messenger]);
23+
InitSessionReplayApi([registrar messenger]);
2224
InitSurveysApi([registrar messenger]);
2325
}
2426

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#import "SessionReplayPigeon.h"
2+
3+
extern void InitSessionReplayApi(id<FlutterBinaryMessenger> messenger);
4+
5+
@interface SessionReplayApi : NSObject <SessionReplayHostApi>
6+
@end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#import "Instabug.h"
2+
#import "IBGSessionReplay.h"
3+
#import "SessionReplayApi.h"
4+
#import "ArgsRegistry.h"
5+
6+
extern void InitSessionReplayApi(id<FlutterBinaryMessenger> messenger) {
7+
SessionReplayApi *api = [[SessionReplayApi alloc] init];
8+
SessionReplayHostApiSetup(messenger, api);
9+
}
10+
11+
@implementation SessionReplayApi
12+
13+
14+
- (void)setEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
15+
IBGSessionReplay.enabled = [isEnabled boolValue];
16+
}
17+
18+
- (void)setInstabugLogsEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
19+
IBGSessionReplay.IBGLogsEnabled = [isEnabled boolValue];
20+
}
21+
22+
- (void)setNetworkLogsEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
23+
IBGSessionReplay.networkLogsEnabled = [isEnabled boolValue];
24+
}
25+
26+
- (void)setUserStepsEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
27+
IBGSessionReplay.userStepsEnabled = [isEnabled boolValue];
28+
}
29+
30+
@end

lib/instabug_flutter.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export 'src/models/crash_data.dart';
33
export 'src/models/exception_data.dart';
44
export 'src/models/network_data.dart';
55
export 'src/models/trace.dart';
6-
76
// Modules
87
export 'src/modules/apm.dart';
98
export 'src/modules/bug_reporting.dart';
@@ -13,7 +12,7 @@ export 'src/modules/instabug.dart';
1312
export 'src/modules/instabug_log.dart';
1413
export 'src/modules/network_logger.dart';
1514
export 'src/modules/replies.dart';
15+
export 'src/modules/session_replay.dart';
1616
export 'src/modules/surveys.dart';
17-
1817
// Utils
1918
export 'src/utils/instabug_navigator_observer.dart';

lib/src/modules/session_replay.dart

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// ignore_for_file: avoid_classes_with_only_static_members
2+
3+
import 'dart:async';
4+
5+
import 'package:flutter/foundation.dart';
6+
import 'package:instabug_flutter/src/generated/session_replay.api.g.dart';
7+
8+
class SessionReplay {
9+
static var _host = SessionReplayHostApi();
10+
11+
/// @nodoc
12+
@visibleForTesting
13+
// ignore: use_setters_to_change_properties
14+
static void $setHostApi(SessionReplayHostApi host) {
15+
_host = host;
16+
}
17+
18+
/// Enables or disables Session Replay for your Instabug integration.
19+
///
20+
/// By default, Session Replay is enabled if it is available in your current plan.
21+
///
22+
/// Example:
23+
///
24+
/// ```dart
25+
/// await SessionReplay.setEnabled(true);
26+
/// ```
27+
static Future<void> setEnabled(bool isEnabled) async {
28+
return _host.setEnabled(isEnabled);
29+
}
30+
31+
/// Enables or disables network logs for Session Replay.
32+
/// By default, network logs are enabled.
33+
///
34+
/// Example:
35+
///
36+
/// ```dart
37+
/// await SessionReplay.setNetworkLogsEnabled(true);
38+
/// ```
39+
static Future<void> setNetworkLogsEnabled(bool isEnabled) async {
40+
return _host.setNetworkLogsEnabled(isEnabled);
41+
}
42+
43+
/// Enables or disables Instabug logs for Session Replay.
44+
/// By default, Instabug logs are enabled.
45+
///
46+
/// Example:
47+
///
48+
/// ```dart
49+
/// await SessionReplay.setInstabugLogsEnabled(true);
50+
/// ```
51+
static Future<void> setInstabugLogsEnabled(bool isEnabled) async {
52+
return _host.setInstabugLogsEnabled(isEnabled);
53+
}
54+
55+
/// Enables or disables capturing of user steps for Session Replay.
56+
/// By default, user steps are enabled.
57+
///
58+
/// Example:
59+
///
60+
/// ```dart
61+
/// await SessionReplay.setUserStepsEnabled(true);
62+
/// ```
63+
static Future<void> setUserStepsEnabled(bool isEnabled) async {
64+
return _host.setUserStepsEnabled(isEnabled);
65+
}
66+
}

pigeons/session_replay.api.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:pigeon/pigeon.dart';
2+
3+
@HostApi()
4+
abstract class SessionReplayHostApi {
5+
void setEnabled(bool isEnabled);
6+
void setNetworkLogsEnabled(bool isEnabled);
7+
void setInstabugLogsEnabled(bool isEnabled);
8+
void setUserStepsEnabled(bool isEnabled);
9+
}

0 commit comments

Comments
 (0)