Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ android {
}

dependencies {
api 'com.instabug.library:instabug:15.0.2.7160278-SNAPSHOT'
api 'com.instabug.library:instabug:16.0.0.6893269-SNAPSHOT'
testImplementation 'junit:junit:4.13.2'
testImplementation "org.mockito:mockito-inline:3.12.1"
testImplementation "io.mockk:mockk:1.13.13"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;

import com.instabug.flutter.generated.InstabugPigeon;
import com.instabug.flutter.modules.ApmApi;
import com.instabug.flutter.modules.BugReportingApi;
import com.instabug.flutter.modules.CrashReportingApi;
Expand All @@ -27,19 +31,23 @@
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.plugin.common.BinaryMessenger;

public class InstabugFlutterPlugin implements FlutterPlugin, ActivityAware {
public class InstabugFlutterPlugin implements FlutterPlugin, ActivityAware, LifecycleEventObserver {
private static final String TAG = InstabugFlutterPlugin.class.getName();

@SuppressLint("StaticFieldLeak")
private static Activity activity;

private InstabugPigeon.InstabugFlutterApi instabugFlutterApi;
private Lifecycle lifecycle;

@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
register(binding.getApplicationContext(), binding.getBinaryMessenger(), (FlutterRenderer) binding.getTextureRegistry());
instabugFlutterApi = new InstabugPigeon.InstabugFlutterApi(binding.getBinaryMessenger());
}

@Override
Expand All @@ -50,23 +58,60 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
activity = binding.getActivity();

// Register lifecycle observer if available
if (binding.getLifecycle() instanceof HiddenLifecycleReference) {
lifecycle = ((HiddenLifecycleReference) binding.getLifecycle()).getLifecycle();
lifecycle.addObserver(this);
}
}

@Override
public void onDetachedFromActivityForConfigChanges() {
if (lifecycle != null) {
lifecycle.removeObserver(this);
}
activity = null;
}

@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
activity = binding.getActivity();

// Re-register lifecycle observer if available
if (binding.getLifecycle() instanceof HiddenLifecycleReference) {
lifecycle = ((HiddenLifecycleReference) binding.getLifecycle()).getLifecycle();
lifecycle.addObserver(this);
}
}

@Override
public void onDetachedFromActivity() {
if (lifecycle != null) {
lifecycle.removeObserver(this);
lifecycle = null;
}
activity = null;
}

@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_PAUSE) {
handleOnPause();
}
}

private void handleOnPause() {
if (instabugFlutterApi != null) {
instabugFlutterApi.dispose(new InstabugPigeon.InstabugFlutterApi.Reply<Void>() {
@Override
public void reply(Void reply) {
Log.d(TAG, "Screen render cleanup dispose called successfully");
}
});
}
}

private static void register(Context context, BinaryMessenger messenger, FlutterRenderer renderer) {
final Callable<Bitmap> screenshotProvider = new Callable<Bitmap>() {
@Override
Expand All @@ -77,7 +122,7 @@ public Bitmap call() {

Callable<Float> refreshRateProvider = new Callable<Float>() {
@Override
public Float call(){
public Float call() {
return getRefreshRate();
}
};
Expand Down
17 changes: 0 additions & 17 deletions android/src/main/java/com/instabug/flutter/modules/ApmApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,6 @@ public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) {
}
}

/**
* Starts an execution trace and handles the result
* using callbacks.
*
* @param id The `id` parameter is a non-null String that represents the identifier of the execution
* trace.
* @param name The `name` parameter in the `startExecutionTrace` method represents the name of the
* execution trace that will be started. It is used as a reference to identify the trace during
* execution monitoring.
* @param result The `result` parameter in the `startExecutionTrace` method is an instance of
* `ApmPigeon.Result<String>`. This parameter is used to provide the result of the execution trace
* operation back to the caller. The `success` method of the `result` object is called with the
*
* @deprecated see {@link #startFlow}
*/


/**
* Starts an AppFlow with the specified name.
* <br/>
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ target 'Runner' do

use_frameworks!
use_modular_headers!
pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.31/Instabug.podspec'
pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.32/Instabug.podspec'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

Expand Down
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
PODS:
- Flutter (1.0.0)
- Instabug (15.1.31)
- Instabug (15.1.32)
- instabug_flutter (14.3.0):
- Flutter
- Instabug (= 15.1.31)
- Instabug (= 15.1.32)
- OCMock (3.6)

DEPENDENCIES:
- Flutter (from `Flutter`)
- Instabug (from `https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.31/Instabug.podspec`)
- Instabug (from `https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.32/Instabug.podspec`)
- instabug_flutter (from `.symlinks/plugins/instabug_flutter/ios`)
- OCMock (= 3.6)

Expand All @@ -20,16 +20,16 @@ EXTERNAL SOURCES:
Flutter:
:path: Flutter
Instabug:
:podspec: https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.31/Instabug.podspec
:podspec: https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.32/Instabug.podspec
instabug_flutter:
:path: ".symlinks/plugins/instabug_flutter/ios"

SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Instabug: 447d3f5a9f1c83120235437e08c9a51aaa8f8605
instabug_flutter: 65aa2dee3036a3c7c8feff8e898c9547239a891d
Instabug: ee379b2694fa1dd3951526e5a34782bac886102e
instabug_flutter: 33230b1cc57be3b343b4d30f6dfdd03f9bf43599
OCMock: 5ea90566be239f179ba766fd9fbae5885040b992

PODFILE CHECKSUM: c5b98f57c27da87950775c360d20aaedd3216b8d
PODFILE CHECKSUM: 41b206566c390a4111f60619beb4e420eba98359

COCOAPODS: 1.15.2
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void main() {
appVariant: 'variant 1',
);
APM.setScreenRenderingEnabled(true);
APM.setAutoUITraceEnabled(false);
// APM.setAutoUITraceEnabled(false);
FlutterError.onError = (FlutterErrorDetails details) {
Zone.current.handleUncaughtError(details.exception, details.stack!);
};
Expand Down
2 changes: 1 addition & 1 deletion ios/instabug_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ Pod::Spec.new do |s|
s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-framework "Flutter" -framework "InstabugSDK"'}

s.dependency 'Flutter'
s.dependency 'Instabug', '15.1.31'
s.dependency 'Instabug', '15.1.32'
end

2 changes: 0 additions & 2 deletions lib/src/modules/apm.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// ignore_for_file: avoid_classes_with_only_static_members

import 'dart:async';
import 'dart:developer';

import 'package:flutter/widgets.dart' show WidgetBuilder;
import 'package:instabug_flutter/src/generated/apm.api.g.dart';
Expand Down Expand Up @@ -141,7 +140,6 @@ class APM {
static Future<void> startUITrace(String name) async {
final isScreenRenderingEnabled =
await FlagsConfig.screenRendering.isEnabled();
log("startUITrace: isScreenRenderEnabled: $isScreenRenderingEnabled");
await InstabugScreenRenderManager.I
.checkForScreenRenderInitialization(isScreenRenderingEnabled);

Expand Down
20 changes: 20 additions & 0 deletions lib/src/modules/instabug.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:instabug_flutter/src/utils/feature_flags_manager.dart';
import 'package:instabug_flutter/src/utils/ibg_build_info.dart';
import 'package:instabug_flutter/src/utils/instabug_logger.dart';
import 'package:instabug_flutter/src/utils/screen_name_masker.dart';
import 'package:instabug_flutter/src/utils/screen_rendering/instabug_widget_binding_observer.dart';
import 'package:meta/meta.dart';

enum InvocationEvent {
Expand Down Expand Up @@ -136,6 +137,23 @@ enum CustomTextPlaceHolderKey {

enum ReproStepsMode { enabled, disabled, enabledWithNoScreenshots }

/// Disposal manager for handling Android lifecycle events
class _InstabugDisposalManager implements InstabugFlutterApi {
_InstabugDisposalManager._();

static final _InstabugDisposalManager _instance =
_InstabugDisposalManager._();

static _InstabugDisposalManager get instance => _instance;

@override
void dispose() {
// Call the InstabugWidgetsBindingObserver dispose method when Android onPause is triggered
// to overcome calling onActivityDestroy() from android side before sending the data to it.
InstabugWidgetsBindingObserver.dispose();
}
}

class Instabug {
static var _host = InstabugHostApi();

Expand All @@ -154,6 +172,8 @@ class Instabug {
BugReporting.$setup();
Replies.$setup();
Surveys.$setup();
// Set up InstabugFlutterApi for Android onDestroy disposal
InstabugFlutterApi.setup(_InstabugDisposalManager.instance);
}

/// @nodoc
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:flutter/widgets.dart';
import 'package:instabug_flutter/src/utils/ibg_build_info.dart';
import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_manager.dart';
import 'package:instabug_flutter/src/utils/screen_name_masker.dart';
import 'package:instabug_flutter/src/utils/screen_rendering/instabug_screen_render_manager.dart';
import 'package:instabug_flutter/src/utils/ui_trace/flags_config.dart';
import 'package:meta/meta.dart';

class InstabugWidgetsBindingObserver extends WidgetsBindingObserver {
Expand All @@ -19,41 +21,58 @@ class InstabugWidgetsBindingObserver extends WidgetsBindingObserver {
/// Logging tag for debugging purposes.
static const tag = "InstabugWidgetsBindingObserver";

/// Disposes all screen render resources.
static void dispose() {
// Always call dispose to ensure proper cleanup with tracking flags
//Save the screen rendering data for the active traces Auto|Custom.
InstabugScreenRenderManager.I.stopScreenRenderCollector();

// The dispose method is safe to call multiple times due to state tracking
InstabugScreenRenderManager.I.dispose();
}

void _handleResumedState() {
final lastUiTrace = ScreenLoadingManager.I.currentUiTrace;
if (lastUiTrace == null) {
return;
}

if (lastUiTrace == null) return;

final maskedScreenName = ScreenNameMasker.I.mask(lastUiTrace.screenName);

ScreenLoadingManager.I
.startUiTrace(maskedScreenName, lastUiTrace.screenName)
.then((uiTraceId) {
if (uiTraceId != null &&
InstabugScreenRenderManager.I.screenRenderEnabled) {
//End any active ScreenRenderCollector before starting a new one (Safe garde condition).
InstabugScreenRenderManager.I.endScreenRenderCollector();

//Start new ScreenRenderCollector.
InstabugScreenRenderManager.I
.startScreenRenderCollectorForTraceId(uiTraceId);
}
.then((uiTraceId) async {
if (uiTraceId == null) return;

final isScreenRenderEnabled =
await FlagsConfig.screenRendering.isEnabled();

if (!isScreenRenderEnabled) return;

await InstabugScreenRenderManager.I
.checkForScreenRenderInitialization(isScreenRenderEnabled);

//End any active ScreenRenderCollector before starting a new one (Safe garde condition).
InstabugScreenRenderManager.I.endScreenRenderCollector();

//Start new ScreenRenderCollector.
InstabugScreenRenderManager.I
.startScreenRenderCollectorForTraceId(uiTraceId);
});
}

void _handlePausedState() {
if (InstabugScreenRenderManager.I.screenRenderEnabled) {
// Only handles iOS platform because in android we use pigeon @FlutterApi().
// To overcome the onActivityDestroy() before sending the data to the android side.
if (InstabugScreenRenderManager.I.screenRenderEnabled &&
IBGBuildInfo.I.isIOS) {
InstabugScreenRenderManager.I.stopScreenRenderCollector();
}
}

Future<void> _handleDetachedState() async {
if (InstabugScreenRenderManager.I.screenRenderEnabled) {
// Only handles iOS platform because in android we use pigeon @FlutterApi().
// To overcome the onActivityDestroy() before sending the data to the android side.
if (InstabugScreenRenderManager.I.screenRenderEnabled &&
IBGBuildInfo.I.isIOS) {
dispose();
}
}
Expand Down
5 changes: 5 additions & 0 deletions pigeons/instabug.api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ abstract class FeatureFlagsFlutterApi {
);
}

@FlutterApi()
abstract class InstabugFlutterApi {
void dispose();
}

@HostApi()
abstract class InstabugHostApi {
void setEnabled(bool isEnabled);
Expand Down
14 changes: 14 additions & 0 deletions test/apm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,20 @@ void main() {
verify(mHost.setScreenRenderEnabled(isEnabled)).called(1);
});

test("[setScreenRenderEnabled] should call host method when enabled",
() async {
const isEnabled = true;
await APM.setScreenRenderingEnabled(isEnabled);
verify(mHost.setScreenRenderEnabled(isEnabled)).called(1);
});

test("[setScreenRenderEnabled] should call host method when disabled",
() async {
const isEnabled = false;
await APM.setScreenRenderingEnabled(isEnabled);
verify(mHost.setScreenRenderEnabled(isEnabled)).called(1);
});

test(
"[startUITrace] should start screen render collector with right params, if screen render feature is enabled",
() async {
Expand Down
Loading