diff --git a/.circleci/config.yml b/.circleci/config.yml index 44d0c8c33..0b712a8fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,10 @@ version: 2.1 orbs: - android: circleci/android@2.0 + android: circleci/android@2.5.0 flutter: circleci/flutter@2.0.2 - node: circleci/node@5.1.0 + node: circleci/node@5.2.0 + advanced-checkout: vsco/advanced-checkout@1.1.0 commands: setup_flutter: @@ -46,7 +47,7 @@ commands: steps: - run: name: Install XCUITest Driver - command: appium driver install xcuitest@4.35.0 + command: appium driver install xcuitest@7.14.0 - when: condition: equal: @@ -55,7 +56,7 @@ commands: steps: - run: name: Install UIAutomator2 Driver - command: appium driver install uiautomator2@2.29.5 + command: appium driver install uiautomator2@3.1.0 - run: name: Launch Appium # Enable --relaxed-security for `mobile: shell` command that Captain uses internally. @@ -103,13 +104,12 @@ commands: name: Build Pigeons command: dart run build_runner build --delete-conflicting-outputs - jobs: danger: executor: name: node/default steps: - - checkout + - advanced-checkout/shallow-checkout - node/install-packages: pkg-manager: yarn override-ci-command: yarn install --frozen-lockfile --network-concurrency 1 @@ -126,7 +126,7 @@ jobs: docker: - image: cirrusci/flutter:<> steps: - - checkout + - advanced-checkout/shallow-checkout - install_flutter_and_dart_packages: generate_pigeons: true - run: flutter test --coverage @@ -142,16 +142,10 @@ jobs: executor: name: android/android-machine resource-class: xlarge - tag: 2022.04.1 + tag: default steps: - - checkout + - advanced-checkout/shallow-checkout - setup_flutter - - android/start-emulator-and-run-tests: - system-image: system-images;android-30;google_apis;x86 - additional-avd-args: -d "Nexus 5" - post-emulator-launch-assemble-command: cd example && flutter build apk - run-tests-working-directory: example/android - test-command: ./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../test_driver/example.dart - android/run-tests: working-directory: example/android test-command: ./gradlew test @@ -160,42 +154,46 @@ jobs: executor: name: android/android-machine resource-class: xlarge - tag: 2022.04.1 + tag: default steps: - - checkout + - advanced-checkout/shallow-checkout - setup_captain: platform: android - setup_flutter - android/start-emulator-and-run-tests: - system-image: system-images;android-30;google_apis;x86 - additional-avd-args: -d "pixel_4" - post-emulator-launch-assemble-command: cd example || true && flutter build apk --debug - test-command: cd e2e || true && dotnet test + run-tests-working-directory: e2e + additional-avd-args: --device 3 + system-image: system-images;android-33;default;x86_64 + post-emulator-launch-assemble-command: cd example && flutter build apk --debug + test-command: dotnet test test_ios: macos: - xcode: 13.4.1 + xcode: 15.2.0 resource_class: macos.m1.medium.gen1 + environment: + INSTABUG_SOURCEMAPS_UPLOAD_DISABLE: true steps: - - checkout + - advanced-checkout/shallow-checkout - setup_ios - run: name: Build and run tests - working_directory: example/ios + working_directory: ~/project/example/ios command: | xcodebuild -allowProvisioningUpdates \ - -workspace Runner.xcworkspace \ - -scheme Runner \ - -sdk iphonesimulator \ - -destination 'name=iPhone 12 Pro Max' \ - test | xcpretty + -workspace Runner.xcworkspace \ + -scheme Runner \ + -resultBundlePath coverage/result.xcresult \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.2' \ + test | xcpretty e2e_ios_captain: macos: - xcode: 13.4.1 + xcode: 15.2.0 resource_class: macos.m1.medium.gen1 steps: - - checkout + - advanced-checkout/shallow-checkout - setup_captain: platform: ios - setup_ios @@ -213,7 +211,7 @@ jobs: docker: - image: cirrusci/flutter steps: - - checkout + - advanced-checkout/shallow-checkout - install_flutter_and_dart_packages: generate_pigeons: false - run: @@ -224,7 +222,7 @@ jobs: docker: - image: cirrusci/flutter steps: - - checkout + - advanced-checkout/shallow-checkout - install_flutter_and_dart_packages: generate_pigeons: true - run: @@ -235,7 +233,7 @@ jobs: docker: - image: cirrusci/flutter steps: - - checkout + - advanced-checkout/shallow-checkout - install_flutter_and_dart_packages: generate_pigeons: true - run: @@ -245,11 +243,11 @@ jobs: release: macos: - xcode: 13.4.1 + xcode: 15.2.0 resource_class: macos.m1.medium.gen1 working_directory: "~" steps: - - checkout: + - advanced-checkout/shallow-checkout: path: ~/project # Flutter doesn't support Apple Silicon yet, so we need to install Rosetta use Flutter on M1 machines. - run: @@ -284,6 +282,7 @@ workflows: version: 2 build-test-and-approval-deploy: jobs: + - test_android - danger: requires: - test_flutter-stable @@ -293,7 +292,6 @@ workflows: - test_flutter: name: test_flutter-2.10.5 version: 2.10.5 - - test_android - e2e_android_captain - test_ios - e2e_ios_captain diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7d78ecd..cebed1910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [13.0.0](https://github.com/Instabug/Instabug-React-Native/compare/v12.7.0...dev) (April 29, 2024) + +### Added + +- Adds custom app rating api ([#453](https://github.com/Instabug/Instabug-Flutter/pull/453)) +- Add `SessionReplay.getSessionReplayLink` API which retrieves the current session's replay link ([#445](https://github.com/Instabug/Instabug-Flutter/pull/445)). +- Add support for App Flows APIs `APM.startFlow`, `APM.endFlow` and `APM.setFlowAttribute` ([#446](https://github.com/Instabug/Instabug-Flutter/pull/446)). + +### Deprecated + +- Deprecate execution traces APIs `APM.startExecutionTrace`, `APM.setExecutionTraceAttribute`, `APM.endExecutionTrace`, `Trace.setAttribute` and `Trace.end` in favor of the new app flow APIs ([#446](https://github.com/Instabug/Instabug-Flutter/pull/446)). + +### Changed +- Bump Instabug Android SDK to v13.0.0 ([#455](https://github.com/Instabug/Instabug-Flutter/pull/455)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v13.0.0). +- Bump Instabug iOS SDK to v13.0.0 ([#446](https://github.com/Instabug/Instabug-Flutter/pull/446)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/13.0.0). + ## [12.7.0](https://github.com/Instabug/Instabug-Flutter/compare/v12.5.0...v12.7.0) (February 15, 2024) ### Added diff --git a/android/build.gradle b/android/build.gradle index 52f1d9b50..9afbfed05 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group 'com.instabug.flutter' -version '12.7.0' +version '13.0.0' buildscript { repositories { @@ -41,7 +41,7 @@ android { } dependencies { - api 'com.instabug.library:instabug:12.7.1' + api 'com.instabug.library:instabug:13.0.0' testImplementation 'junit:junit:4.13.2' testImplementation "org.mockito:mockito-inline:3.12.1" diff --git a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java index c0a4a81e8..c0862acac 100644 --- a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java @@ -1,24 +1,21 @@ package com.instabug.flutter.modules; import android.util.Log; - import androidx.annotation.NonNull; - +import androidx.annotation.Nullable; import com.instabug.apm.APM; import com.instabug.apm.model.ExecutionTrace; import com.instabug.apm.networking.APMNetworkLogger; import com.instabug.flutter.generated.ApmPigeon; import com.instabug.flutter.util.Reflection; import com.instabug.flutter.util.ThreadManager; - +import io.flutter.plugin.common.BinaryMessenger; import org.json.JSONObject; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import io.flutter.plugin.common.BinaryMessenger; - public class ApmApi implements ApmPigeon.ApmHostApi { private final String TAG = ApmApi.class.getName(); private final HashMap traces = new HashMap<>(); @@ -95,6 +92,33 @@ public void run() { ); } + @Override + public void startFlow(@NonNull String name) { + try { + APM.startFlow(name); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void setFlowAttribute(@NonNull String name, @NonNull String key, @Nullable String value) { + try { + APM.setFlowAttribute(name, key, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void endFlow(@NonNull String name) { + try { + APM.endFlow(name); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Override public void setExecutionTraceAttribute(@NonNull String id, @NonNull String key, @NonNull String value) { try { diff --git a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java index 80e9cfa67..5cfc178e8 100644 --- a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java @@ -76,7 +76,7 @@ public void setCurrentPlatform() { @Override public void setEnabled(@NonNull Boolean isEnabled) { try { - if(isEnabled) + if (isEnabled) Instabug.enable(); else Instabug.disable(); @@ -171,13 +171,12 @@ public void setSessionProfilerEnabled(@NonNull Boolean enabled) { @Override public void setValueForStringWithKey(@NonNull String value, @NonNull String key) { - if(ArgsRegistry.placeholders.containsKey(key)) { + if (ArgsRegistry.placeholders.containsKey(key)) { InstabugCustomTextPlaceHolder.Key resolvedKey = ArgsRegistry.placeholders.get(key); placeHolder.set(resolvedKey, value); Instabug.setCustomTextPlaceHolders(placeHolder); - } - else { - Log.i(TAG, "Instabug: " + key + " is only relevant to iOS."); + } else { + Log.i(TAG, "Instabug: " + key + " is only relevant to iOS."); } } @@ -397,4 +396,9 @@ public void networkLog(@NonNull Map data) { Log.e(TAG, "Network logging failed"); } } + + @Override + public void willRedirectToStore() { + Instabug.willRedirectToStore(); + } } diff --git a/android/src/main/java/com/instabug/flutter/modules/SessionReplayApi.java b/android/src/main/java/com/instabug/flutter/modules/SessionReplayApi.java index cf142c0b4..2170f2c98 100644 --- a/android/src/main/java/com/instabug/flutter/modules/SessionReplayApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/SessionReplayApi.java @@ -1,8 +1,10 @@ package com.instabug.flutter.modules; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.instabug.flutter.generated.SessionReplayPigeon; +import com.instabug.library.OnSessionReplayLinkReady; import com.instabug.library.sessionreplay.SessionReplay; import io.flutter.plugin.common.BinaryMessenger; @@ -33,4 +35,11 @@ public void setInstabugLogsEnabled(@NonNull Boolean isEnabled) { public void setUserStepsEnabled(@NonNull Boolean isEnabled) { SessionReplay.setUserStepsEnabled(isEnabled); } + + @Override + public void getSessionReplayLink(@NonNull SessionReplayPigeon.Result result) { + SessionReplay.getSessionReplayLink(result::success); + } + + } diff --git a/android/src/test/java/com/instabug/flutter/ApmApiTest.java b/android/src/test/java/com/instabug/flutter/ApmApiTest.java index 45528217a..aefb3b62d 100644 --- a/android/src/test/java/com/instabug/flutter/ApmApiTest.java +++ b/android/src/test/java/com/instabug/flutter/ApmApiTest.java @@ -148,6 +148,39 @@ public void testEndExecutionTrace() { verify(mTrace).end(); } + @Test + public void testStartFlow() { + String appFlowName = "appFlowName"; + + api.startFlow(appFlowName); + + mAPM.verify(() -> APM.startFlow(appFlowName)); + mAPM.verifyNoMoreInteractions(); + } + + @Test + public void testEndFlow() { + String appFlowName = "appFlowName"; + + api.startFlow(appFlowName); + + mAPM.verify(() -> APM.startFlow(appFlowName)); + mAPM.verifyNoMoreInteractions(); + } + + @Test + public void testSetFlowAttribute() { + String appFlowName = "appFlowName"; + String flowAttributeKey = "attributeKey"; + String flowAttributeValue = "attributeValue"; + + + api.setFlowAttribute(appFlowName, flowAttributeKey, flowAttributeValue); + + mAPM.verify(() -> APM.setFlowAttribute(appFlowName, flowAttributeKey, flowAttributeValue)); + mAPM.verifyNoMoreInteractions(); + } + @Test public void testStartUITrace() { String name = "login"; diff --git a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java index 140daab65..b542259b6 100644 --- a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java +++ b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java @@ -493,7 +493,7 @@ public void testAddFileAttachmentWithURLWhenFileDoesNotExists() { @Test public void testAddFileAttachmentWithData() { - byte[] data = new byte[] {65, 100}; + byte[] data = new byte[]{65, 100}; String name = "Issue"; api.addFileAttachmentWithData(data, name); @@ -549,4 +549,10 @@ public void testNetworkLog() { mJSONObject.close(); } + + @Test + public void testWillRedirectToStore() { + api.willRedirectToStore(); + mInstabug.verify(Instabug::willRedirectToStore); + } } diff --git a/android/src/test/java/com/instabug/flutter/SessionReplayApiTest.java b/android/src/test/java/com/instabug/flutter/SessionReplayApiTest.java index ace140c92..223871844 100644 --- a/android/src/test/java/com/instabug/flutter/SessionReplayApiTest.java +++ b/android/src/test/java/com/instabug/flutter/SessionReplayApiTest.java @@ -4,10 +4,13 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import com.instabug.flutter.generated.SessionReplayPigeon; import com.instabug.flutter.modules.SessionReplayApi; import com.instabug.flutter.util.GlobalMocks; +import com.instabug.library.OnSessionReplayLinkReady; import com.instabug.library.sessionreplay.SessionReplay; import org.junit.After; @@ -81,6 +84,28 @@ public void testSetUserStepsEnabled() { mSessionReplay.verify(() -> SessionReplay.setUserStepsEnabled(true)); } + @Test + public void testGetSessionReplayLink() { + SessionReplayPigeon.Result result = mock(SessionReplayPigeon.Result.class); + String link="instabug link"; + + mSessionReplay.when(() -> SessionReplay.getSessionReplayLink(any())).thenAnswer( + invocation -> { + OnSessionReplayLinkReady callback = (OnSessionReplayLinkReady) invocation.getArguments()[0]; + callback.onSessionReplayLinkReady(link); + return callback; + }); + api.getSessionReplayLink(result); + + + mSessionReplay.verify(() -> SessionReplay.getSessionReplayLink(any())); + mSessionReplay.verifyNoMoreInteractions(); + + + verify(result, timeout(1000)).success(link); + + + } } diff --git a/e2e/BugReportingTests.cs b/e2e/BugReportingTests.cs index d951ae902..1e933c030 100644 --- a/e2e/BugReportingTests.cs +++ b/e2e/BugReportingTests.cs @@ -69,6 +69,7 @@ public void ShakeInvocationEvent() [Fact] public void TwoFingersSwipeLeftInvocationEvent() { + ScrollUp(); captain.FindByText("Two Fingers Swipe Left").Tap(); Thread.Sleep(500); @@ -112,6 +113,8 @@ public void ManualInvocation() [Fact] public void MultipleScreenshotsInReproSteps() { + ScrollDown(); + captain.FindByText("Enter screen name").Tap(); captain.Type("My Screen"); captain.HideKeyboard(); @@ -189,6 +192,8 @@ public void ChangeFloatingButtonEdge() [Fact] public void OnDismissCallbackIsCalled() { + ScrollUp(); + captain.FindByText("Set On Dismiss Callback").Tap(); captain.FindByText("Invoke").Tap(); diff --git a/e2e/FeatureRequestsTests.cs b/e2e/FeatureRequestsTests.cs index 8a3ba89a1..41c97f684 100644 --- a/e2e/FeatureRequestsTests.cs +++ b/e2e/FeatureRequestsTests.cs @@ -11,6 +11,8 @@ public class FeatureRequestsTests : CaptainTest public void ShowFeatureRequetsScreen() { ScrollDown(); + ScrollDown(); + captain.FindByText("Show Feature Requests").Tap(); var screenTitle = captain.FindById( diff --git a/e2e/Utils/CaptainTest.cs b/e2e/Utils/CaptainTest.cs index d73f37e3c..dbab7e912 100644 --- a/e2e/Utils/CaptainTest.cs +++ b/e2e/Utils/CaptainTest.cs @@ -9,11 +9,11 @@ public class CaptainTest : IDisposable { AndroidApp = Path.GetFullPath("../../../../example/build/app/outputs/flutter-apk/app-debug.apk"), AndroidAppId = "com.instabug.flutter.example", - AndroidVersion = "11", + AndroidVersion = "13", IosApp = Path.GetFullPath("../../../../example/build/ios/iphonesimulator/Runner.app"), IosAppId = "com.instabug.InstabugSample", - IosVersion = "15.5", - IosDevice = "iPhone 13 Pro Max" + IosVersion = "17.2", + IosDevice = "iPhone 15 Pro Max" }; protected static readonly Captain captain = new(_config); diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 9e8ff84e9..04f99d12a 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { applicationId "com.instabug.flutter.example" - minSdkVersion 18 + minSdkVersion flutter.minSdkVersion targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/build.gradle b/example/android/build.gradle index 58a8c74b1..713d7f6e6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/ios/InstabugTests/ApmApiTests.m b/example/ios/InstabugTests/ApmApiTests.m index 795c9e1a1..09e8bad46 100644 --- a/example/ios/InstabugTests/ApmApiTests.m +++ b/example/ios/InstabugTests/ApmApiTests.m @@ -114,6 +114,35 @@ - (void)testEndExecutionTrace { OCMVerify([mTrace end]); } +- (void) testStartFlow { + NSString* appFlowName = @"app-flow-name"; + FlutterError *error; + + [self.api startFlowName:appFlowName error:&error]; + + OCMVerify([self.mAPM startFlowWithName:appFlowName]); +} + +- (void) testEndFlow { + NSString* appFlowName = @"app-flow-name"; + FlutterError *error; + + [self.api endFlowName:appFlowName error:&error]; + + OCMVerify([self.mAPM endFlowWithName:appFlowName]); +} + +- (void) testSetFlowAttribute { + NSString* appFlowName = @"app-flow-name"; + NSString* attributeKey = @"attribute-key"; + NSString* attributeValue = @"attribute-value"; + FlutterError *error; + + [self.api setFlowAttributeName:appFlowName key:attributeKey value:attributeValue error:&error]; + + OCMVerify([self.mAPM setAttributeForFlowWithName:appFlowName key:attributeKey value:attributeValue]); +} + - (void)testStartUITrace { NSString *name = @"login"; FlutterError *error; diff --git a/example/ios/InstabugTests/InstabugApiTests.m b/example/ios/InstabugTests/InstabugApiTests.m index 3b55e0c3f..9f9684120 100644 --- a/example/ios/InstabugTests/InstabugApiTests.m +++ b/example/ios/InstabugTests/InstabugApiTests.m @@ -398,4 +398,11 @@ - (void)testNetworkLog { ]); } +- (void)testWillRedirectToAppStore { + FlutterError *error; + [self.api willRedirectToStoreWithError:&error]; + + OCMVerify([self.mInstabug willRedirectToAppStore]); +} + @end diff --git a/example/ios/InstabugTests/SessionReplayApiTests.m b/example/ios/InstabugTests/SessionReplayApiTests.m index 1eeca6ca9..d00ef0af2 100644 --- a/example/ios/InstabugTests/SessionReplayApiTests.m +++ b/example/ios/InstabugTests/SessionReplayApiTests.m @@ -1,4 +1,6 @@ #import +#import +#import #import "OCMock/OCMock.h" #import "SessionReplayApi.h" #import "Instabug/IBGSessionReplay.h" @@ -54,4 +56,17 @@ - (void)testSetUserStepsEnabled { OCMVerify([self.mSessionReplay setUserStepsEnabled:YES]); } +- (void)testGetSessionReplayLink { + NSString *link = @"link"; + id result = ^(NSString * result, FlutterError * error) { + XCTAssertEqualObjects(result, link); + }; + + OCMStub([self.mSessionReplay sessionReplayLink]).andReturn(link); + [self.api getSessionReplayLinkWithCompletion:result]; + OCMVerify([self.mSessionReplay sessionReplayLink]); + +} + + @end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e49ff8e53..4a9cac97e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Instabug (12.7.0) - - instabug_flutter (12.7.0): + - Instabug (13.0.0) + - instabug_flutter (13.0.0): - Flutter - - Instabug (= 12.7.0) + - Instabug (= 13.0.0) - OCMock (3.6) DEPENDENCIES: @@ -23,9 +23,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/instabug_flutter/ios" SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - Instabug: 59f0b0bc2c062b5cdbbf417cca365480a1fe55d8 - instabug_flutter: f6ea69a1629e5d7dbdd21b1a0d3199a09fe3e43c + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Instabug: fa52de4a6cac26cde0a60ec5e0540f2461a06fe2 + instabug_flutter: b80c4b8748d1da660a8f0cc0b2e5f4375898761c OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 PODFILE CHECKSUM: 637e800c0a0982493b68adb612d2dd60c15c8e5c diff --git a/example/lib/main.dart b/example/lib/main.dart index 31705db25..d5e90e1db 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -170,6 +170,23 @@ class _MyHomePageState extends State { Surveys.showSurvey('PMqUZXqarkOR2yGKiENB4w'); } + final _scaffoldKey = GlobalKey(); + + void getCurrentSessionReplaylink() async { + final result = await SessionReplay.getSessionReplayLink(); + if (result == null) { + const snackBar = SnackBar( + content: Text('No Link Found'), + ); + ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(snackBar); + } else { + var snackBar = SnackBar( + content: Text(result), + ); + ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(snackBar); + } + } + void showFeatureRequests() { FeatureRequests.show(); } @@ -204,10 +221,11 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( + key: _scaffoldKey, appBar: AppBar(title: Text(widget.title)), body: SingleChildScrollView( physics: ClampingScrollPhysics(), - padding: const EdgeInsets.only(top: 20.0), + padding: const EdgeInsets.only(top: 20.0, bottom: 16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -342,7 +360,7 @@ class _MyHomePageState extends State { ), SectionTitle('Color Theme'), ButtonBar( - mainAxisSize: MainAxisSize.max, + mainAxisSize: MainAxisSize.min, alignment: MainAxisAlignment.center, children: [ ElevatedButton( @@ -364,6 +382,11 @@ class _MyHomePageState extends State { ), ], ), + SectionTitle('Sessions Replay'), + InstabugButton( + onPressed: getCurrentSessionReplaylink, + text: 'Get current session replay link', + ), ], )), // This trailing comma makes auto-formatting nicer for build methods. ); diff --git a/example/pubspec.lock b/example/pubspec.lock index a22d74e64..055c11dd1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" espresso: dependency: "direct dev" description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -91,55 +91,79 @@ packages: path: ".." relative: true source: path - version: "12.7.0" + version: "13.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" process: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -157,18 +181,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -197,10 +221,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" vector_math: dependency: transitive description: @@ -213,26 +237,18 @@ packages: dependency: transitive description: name: vm_service - sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f - url: "https://pub.dev" - source: hosted - version: "11.7.1" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "13.0.0" webdriver: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.2.0-0 <4.0.0" flutter: ">=2.10.0" diff --git a/ios/Classes/Modules/ApmApi.m b/ios/Classes/Modules/ApmApi.m index a849df1ec..da7fa84be 100644 --- a/ios/Classes/Modules/ApmApi.m +++ b/ios/Classes/Modules/ApmApi.m @@ -56,6 +56,18 @@ - (void)endExecutionTraceId:(NSString *)id error:(FlutterError *_Nullable *_Nonn } } +- (void)startFlowName:(nonnull NSString *)name error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { + [IBGAPM startFlowWithName:name]; +} + +- (void)setFlowAttributeName:(nonnull NSString *)name key:(nonnull NSString *)key value:(nullable NSString *)value error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { + [IBGAPM setAttributeForFlowWithName:name key:key value:value]; +} + +- (void)endFlowName:(nonnull NSString *)name error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { + [IBGAPM endFlowWithName:name]; +} + - (void)startUITraceName:(NSString *)name error:(FlutterError *_Nullable *_Nonnull)error { [IBGAPM startUITraceWithName:name]; } diff --git a/ios/Classes/Modules/InstabugApi.m b/ios/Classes/Modules/InstabugApi.m index 7def622f0..493d1fc19 100644 --- a/ios/Classes/Modules/InstabugApi.m +++ b/ios/Classes/Modules/InstabugApi.m @@ -305,4 +305,8 @@ - (void)networkLogData:(NSDictionary *)data error:(FlutterError } } +- (void)willRedirectToStoreWithError:(FlutterError * _Nullable __autoreleasing *)error { + [Instabug willRedirectToAppStore]; +} + @end diff --git a/ios/Classes/Modules/SessionReplayApi.m b/ios/Classes/Modules/SessionReplayApi.m index 600fae725..8a4c8d1bf 100644 --- a/ios/Classes/Modules/SessionReplayApi.m +++ b/ios/Classes/Modules/SessionReplayApi.m @@ -27,4 +27,11 @@ - (void)setUserStepsEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(Flutte IBGSessionReplay.userStepsEnabled = [isEnabled boolValue]; } +- (void)getSessionReplayLinkWithCompletion:(void (^)(NSString *, FlutterError *))completion { + NSString * link= IBGSessionReplay.sessionReplayLink; + completion(link,nil); + +} + + @end diff --git a/ios/instabug_flutter.podspec b/ios/instabug_flutter.podspec index 73c4b23b1..2f54e05da 100644 --- a/ios/instabug_flutter.podspec +++ b/ios/instabug_flutter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'instabug_flutter' - s.version = '12.7.0' + s.version = '13.0.0' s.summary = 'Flutter plugin for integrating the Instabug SDK.' s.author = 'Instabug' s.homepage = 'https://www.instabug.com/platforms/flutter' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-framework "Flutter" -framework "Instabug"'} s.dependency 'Flutter' - s.dependency 'Instabug', '12.7.0' + s.dependency 'Instabug', '13.0.0' end diff --git a/lib/src/models/trace.dart b/lib/src/models/trace.dart index 9a6a3f458..bf267640b 100644 --- a/lib/src/models/trace.dart +++ b/lib/src/models/trace.dart @@ -10,11 +10,26 @@ class Trace { final String name; final Map attributes = {}; + /// Sets attribute of execution trace. + /// [String] id of the trace. + /// [String] key of attribute. + /// [String] value of attribute. + /// + /// Please migrate to the App Flows APIs: [APM.startFlow], [APM.setFlowAttribute], and [APM.endFlow]. + @Deprecated( + 'Please migrate to the App Flows APIs: APM.startAppFlow, APM.endFlow, and APM.setFlowAttribute. This feature was deprecated in v13.0.0', + ) void setAttribute(String key, String value) { APM.setExecutionTraceAttribute(id, key, value); attributes[key] = value; } + /// Ends Execution Trace + /// + /// Please migrate to the App Flows APIs: [APM.startFlow], [APM.setFlowAttribute], and [APM.endFlow]. + @Deprecated( + 'Please migrate to the App Flows APIs: APM.startAppFlow, APM.endFlow, and APM.setFlowAttribute. This feature was deprecated in v13.0.0', + ) void end() { APM.endExecutionTrace(id); } diff --git a/lib/src/modules/apm.dart b/lib/src/modules/apm.dart index 6f71a9db4..58b27c082 100644 --- a/lib/src/modules/apm.dart +++ b/lib/src/modules/apm.dart @@ -33,6 +33,11 @@ class APM { /// Starts an execution trace. /// [String] name of the trace. + /// + /// Please migrate to the App Flows APIs: [startFlow], [setFlowAttribute], and [endFlow]. + @Deprecated( + 'Please migrate to the App Flows APIs: APM.startAppFlow, APM.endFlow, and APM.setFlowAttribute. This feature was deprecated in v13.0.0', + ) static Future startExecutionTrace(String name) async { final id = IBGDateTime.instance.now(); final traceId = await _host.startExecutionTrace(id.toString(), name); @@ -54,6 +59,11 @@ class APM { /// [String] id of the trace. /// [String] key of attribute. /// [String] value of attribute. + /// + /// Please migrate to the App Flows APIs: [startFlow], [setFlowAttribute], and [endFlow]. + @Deprecated( + 'Please migrate to the App Flows APIs: APM.startAppFlow, APM.endFlow, and APM.setFlowAttribute. This feature was deprecated in v13.0.0', + ) static Future setExecutionTraceAttribute( String id, String key, @@ -64,10 +74,51 @@ class APM { /// Ends an execution trace. /// [String] id of the trace. + /// + /// Please migrate to the App Flows APIs: [startFlow], [setFlowAttribute], and [endFlow]. + @Deprecated( + 'Please migrate to the App Flows APIs: APM.startAppFlow, APM.endFlow, and APM.setFlowAttribute. This feature was deprecated in v13.0.0', + ) static Future endExecutionTrace(String id) async { return _host.endExecutionTrace(id); } + /// Starts an AppFlow with the given [name]. + /// + /// The [name] must not be an empty string. It should be unique and not exceed 150 characters, + /// ignoring leading and trailing spaces. + /// + /// Duplicate [name]s will terminate the older AppFlow with the termination reason recorded as + /// 'force abandon end reason'. + /// + /// The method will only execute if APM is enabled, the feature is + /// active, and the SDK has been initialized. + static Future startFlow(String name) async { + if (name.isNotEmpty) { + return _host.startFlow(name.trim()); + } + } + + /// Assigns a custom attribute to an AppFlow with the specified [name], [key], and [value]. + /// + /// The [name] must not be an empty string. The [key] should not exceed 30 characters, + /// and [value] should not exceed 60 characters, with both ignoring leading and trailing spaces. + /// + /// To remove an attribute, set its [value] to null. Attributes cannot be added or + /// modified after an AppFlow has concluded. + static Future setFlowAttribute( + String name, + String key, + String? value, + ) async { + return _host.setFlowAttribute(name, key, value); + } + + /// Ends the AppFlow with the given [name]. + static Future endFlow(String name) async { + return _host.endFlow(name); + } + /// Enables or disables auto UI tracing. /// [boolean] isEnabled static Future setAutoUITraceEnabled(bool isEnabled) async { diff --git a/lib/src/modules/instabug.dart b/lib/src/modules/instabug.dart index 54450c1e5..ac6c49698 100644 --- a/lib/src/modules/instabug.dart +++ b/lib/src/modules/instabug.dart @@ -412,4 +412,13 @@ class Instabug { final darkKey = await dark.obtainKey(configuration); return _host.setCustomBrandingImage(lightKey.name, darkKey.name); } + + /// Allows detection of app review sessions which are submitted through custom prompts. + /// + /// Use this when utilizing a custom app rating prompt. It should be called + /// once the user clicks on the Call to Action (CTA) that redirects them to the app store. + /// Helps track session data for insights on user interactions during review submission. + static Future willRedirectToStore() async { + return _host.willRedirectToStore(); + } } diff --git a/lib/src/modules/session_replay.dart b/lib/src/modules/session_replay.dart index e900f2fe5..1e5fa8e86 100644 --- a/lib/src/modules/session_replay.dart +++ b/lib/src/modules/session_replay.dart @@ -63,4 +63,15 @@ class SessionReplay { static Future setUserStepsEnabled(bool isEnabled) async { return _host.setUserStepsEnabled(isEnabled); } + + /// Retrieves current session's replay link. + /// + /// Example: + /// + /// ```dart + /// await SessionReplay.getSessionReplayLink(); + /// ``` + static Future getSessionReplayLink() async { + return _host.getSessionReplayLink(); + } } diff --git a/pigeons/apm.api.dart b/pigeons/apm.api.dart index 573aad192..dfb23366c 100644 --- a/pigeons/apm.api.dart +++ b/pigeons/apm.api.dart @@ -9,6 +9,9 @@ abstract class ApmHostApi { @async String? startExecutionTrace(String id, String name); + void startFlow(String name); + void setFlowAttribute(String name, String key, String? value); + void endFlow(String name); void setExecutionTraceAttribute( String id, String key, diff --git a/pigeons/instabug.api.dart b/pigeons/instabug.api.dart index 2d1308ce1..7113f164c 100644 --- a/pigeons/instabug.api.dart +++ b/pigeons/instabug.api.dart @@ -54,4 +54,6 @@ abstract class InstabugHostApi { void clearFileAttachments(); void networkLog(Map data); + + void willRedirectToStore(); } diff --git a/pigeons/session_replay.api.dart b/pigeons/session_replay.api.dart index 75983fa6f..d9253289d 100644 --- a/pigeons/session_replay.api.dart +++ b/pigeons/session_replay.api.dart @@ -6,4 +6,6 @@ abstract class SessionReplayHostApi { void setNetworkLogsEnabled(bool isEnabled); void setInstabugLogsEnabled(bool isEnabled); void setUserStepsEnabled(bool isEnabled); + @async + String getSessionReplayLink(); } diff --git a/pubspec.yaml b/pubspec.yaml index 6bdcc3e1b..db1b25f92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: instabug_flutter -version: 12.7.0 +version: 13.0.0 description: >- Instabug empowers mobile teams to monitor, prioritize, and debug performance and stability issues throughout the app development lifecycle. diff --git a/test/apm_test.dart b/test/apm_test.dart index 72d4bcf2a..8551c0c82 100644 --- a/test/apm_test.dart +++ b/test/apm_test.dart @@ -66,6 +66,7 @@ void main() { when(mHost.startExecutionTrace(id.toString(), name)) .thenAnswer((_) async => id.toString()); + // ignore: deprecated_member_use_from_same_package final trace = await APM.startExecutionTrace(name); expect(trace.id, id.toString()); @@ -80,6 +81,7 @@ void main() { const key = "attr-key"; const attribute = "Trace Attribute"; + // ignore: deprecated_member_use_from_same_package await APM.setExecutionTraceAttribute(id, key, attribute); verify( @@ -90,6 +92,7 @@ void main() { test('[endExecutionTrace] should call host method', () async { final id = DateTime.now().toString(); + // ignore: deprecated_member_use_from_same_package await APM.endExecutionTrace(id); verify( @@ -97,6 +100,40 @@ void main() { ).called(1); }); + test('[startFlow] should call host method', () async { + const flowName = "flow-name"; + await APM.startFlow(flowName); + + verify( + mHost.startFlow(flowName), + ).called(1); + verifyNoMoreInteractions(mHost); + }); + + test('[setFlowAttribute] should call host method', () async { + const flowName = "flow-name"; + const flowAttributeKey = 'attribute-key'; + const flowAttributeValue = 'attribute-value'; + + await APM.setFlowAttribute(flowName, flowAttributeKey, flowAttributeValue); + + verify( + mHost.setFlowAttribute(flowName, flowAttributeKey, flowAttributeValue), + ).called(1); + verifyNoMoreInteractions(mHost); + }); + + test('[endFlow] should call host method', () async { + const flowName = "flow-name"; + + await APM.endFlow(flowName); + + verify( + mHost.endFlow(flowName), + ).called(1); + verifyNoMoreInteractions(mHost); + }); + test('[startUITrace] should call host method', () async { const name = 'UI-trace'; diff --git a/test/instabug_test.dart b/test/instabug_test.dart index 8f623796e..78a370963 100644 --- a/test/instabug_test.dart +++ b/test/instabug_test.dart @@ -383,4 +383,13 @@ void main() { mHost.clearFileAttachments(), ).called(1); }); + + test('[willRedirectToStore] should call host method', () async { + await Instabug.willRedirectToStore(); + + //assert + verify( + mHost.willRedirectToStore(), + ).called(1); + }); } diff --git a/test/session_replay_test.dart b/test/session_replay_test.dart index ad334bb7b..8c717f72e 100644 --- a/test/session_replay_test.dart +++ b/test/session_replay_test.dart @@ -55,4 +55,15 @@ void main() { mHost.setUserStepsEnabled(isEnabled), ).called(1); }); + + test('[getSessionReplayLink] should call host method', () async { + const link = 'link'; + when(mHost.getSessionReplayLink()).thenAnswer((_) async => link); + + final result = await SessionReplay.getSessionReplayLink(); + expect(result, link); + verify( + mHost.getSessionReplayLink(), + ).called(1); + }); } diff --git a/test/trace_test.dart b/test/trace_test.dart index 3f06d1cbb..2415420be 100644 --- a/test/trace_test.dart +++ b/test/trace_test.dart @@ -25,6 +25,7 @@ void main() { }); test('[end] should call host method', () async { + // ignore: deprecated_member_use_from_same_package trace.end(); verify( @@ -35,6 +36,7 @@ void main() { test('[setAttribute] should call host method', () async { const key = "attr-key"; const attribute = "Trace Attribute"; + // ignore: deprecated_member_use_from_same_package trace.setAttribute(key, attribute); verify(