diff --git a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt index 17a7d35c6..67e7d5bab 100644 --- a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt +++ b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt @@ -24,12 +24,12 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler { } SEND_NATIVE_FATAL_HANG -> { Log.d(TAG, "Sending native fatal hang for 3000 ms") - sendANR() + sendFatalHang() result.success(null) } SEND_ANR -> { Log.d(TAG, "Sending android not responding 'ANR' hanging for 20000 ms") - sendFatalHang() + sendANR() result.success(null) } SEND_OOM -> { @@ -71,58 +71,41 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler { } private fun sendANR() { + android.os.Handler(Looper.getMainLooper()).post { try { Thread.sleep(20000) } catch (e: InterruptedException) { throw RuntimeException(e) } + } } private fun sendFatalHang() { - try { - Thread.sleep(3000) - } catch (e: InterruptedException) { - throw RuntimeException(e) + android.os.Handler(Looper.getMainLooper()).post { + + try { + Thread.sleep(3000) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } } } private fun sendOOM() { + android.os.Handler(Looper.getMainLooper()).post { + + oomCrash() + } } private fun oomCrash() { - Thread { - val stringList: MutableList = ArrayList() - for (i in 0 until 1000000) { - stringList.add(getRandomString(10000)) - } - }.start() - } - - private fun getRandomString(length: Int): String { - val charset: MutableList = ArrayList() - var ch = 'a' - while (ch <= 'z') { - charset.add(ch) - ch++ + val list = ArrayList() + while (true) { + list.add(ByteArray(10 * 1024 * 1024)) // Allocate 10MB chunks } - ch = 'A' - while (ch <= 'Z') { - charset.add(ch) - ch++ - } - ch = '0' - while (ch <= '9') { - charset.add(ch) - ch++ - } - val randomString = StringBuilder() - val random = java.util.Random() - for (i in 0 until length) { - val randomChar = charset[random.nextInt(charset.size)] - randomString.append(randomChar) - } - return randomString.toString() } + + } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 484d0ae99..fb6db9601 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -25,7 +25,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Instabug: 97a4e694731f46bbc02dbe49ab29cc552c5e2f41 - instabug_flutter: 4e4a9b162d77d5624d08ccdf81745d923745e062 + instabug_flutter: ef9cb6bf3c9dd29b1e4bf3129a16dcbcb2d332e6 OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 PODFILE CHECKSUM: 8f7552fd115ace1988c3db54a69e4a123c448f84 diff --git a/example/ios/Runner/InstabugExampleMethodCallHandler.m b/example/ios/Runner/InstabugExampleMethodCallHandler.m index 6b1331587..7e62025cb 100644 --- a/example/ios/Runner/InstabugExampleMethodCallHandler.m +++ b/example/ios/Runner/InstabugExampleMethodCallHandler.m @@ -55,19 +55,21 @@ + (BOOL)requiresMainQueueSetup } - (void)oomCrash { - dispatch_async(self.serialQueue, ^{ - self.oomBelly = [NSMutableArray array]; - [UIApplication.sharedApplication beginBackgroundTaskWithName:@"OOM Crash" expirationHandler:nil]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSMutableArray *belly = [NSMutableArray array]; while (true) { - unsigned long dinnerLength = 1024 * 1024 * 10; - char *dinner = malloc(sizeof(char) * dinnerLength); - for (int i=0; i < dinnerLength; i++) - { - //write to each byte ensure that the memory pages are actually allocated - dinner[i] = '0'; + // 20 MB chunks to speed up OOM + void *buffer = malloc(20 * 1024 * 1024); + if (buffer == NULL) { + NSLog(@"OOM: malloc failed"); + break; } - NSData *plate = [NSData dataWithBytesNoCopy:dinner length:dinnerLength freeWhenDone:YES]; - [self.oomBelly addObject:plate]; + memset(buffer, 1, 20 * 1024 * 1024); + NSData *data = [NSData dataWithBytesNoCopy:buffer length:20 * 1024 * 1024 freeWhenDone:YES]; + [belly addObject:data]; + + // Optional: slight delay to avoid CPU spike + [NSThread sleepForTimeInterval:0.01]; } }); } @@ -108,7 +110,9 @@ - (void)sendNativeFatalCrash { } - (void)sendFatalHang { - [NSThread sleepForTimeInterval:3.0f]; + dispatch_async(dispatch_get_main_queue(), ^{ + sleep(20); // Block main thread for 20 seconds + }); } - (void)sendOOM { diff --git a/example/lib/main.dart b/example/lib/main.dart index 91b0a67e7..257bdde5b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -20,6 +20,8 @@ import 'src/widget/section_title.dart'; part 'src/screens/crashes_page.dart'; +part 'src/screens/bug_reporting.dart'; + part 'src/screens/complex_page.dart'; part 'src/screens/apm_page.dart'; diff --git a/example/lib/src/app_routes.dart b/example/lib/src/app_routes.dart index 9175d5405..f52636cde 100644 --- a/example/lib/src/app_routes.dart +++ b/example/lib/src/app_routes.dart @@ -9,6 +9,8 @@ final appRoutes = { "/": (BuildContext context) => const MyHomePage(title: 'Flutter Demo Home Pag'), CrashesPage.screenName: (BuildContext context) => const CrashesPage(), + BugReportingPage.screenName: (BuildContext context) => + const BugReportingPage(), ComplexPage.screenName: (BuildContext context) => const ComplexPage(), ApmPage.screenName: (BuildContext context) => const ApmPage(), ScreenLoadingPage.screenName: (BuildContext context) => diff --git a/example/lib/src/components/fatal_crashes_content.dart b/example/lib/src/components/fatal_crashes_content.dart index c262b63a6..909f2fde3 100644 --- a/example/lib/src/components/fatal_crashes_content.dart +++ b/example/lib/src/components/fatal_crashes_content.dart @@ -20,31 +20,37 @@ class FatalCrashesContent extends StatelessWidget { children: [ InstabugButton( text: 'Throw Exception', + key: const Key('fatal_crash_exception'), onPressed: () => throwUnhandledException( Exception('This is a generic exception.')), ), InstabugButton( text: 'Throw StateError', + key: const Key('fatal_crash_state_exception'), onPressed: () => throwUnhandledException(StateError('This is a StateError.')), ), InstabugButton( text: 'Throw ArgumentError', + key: const Key('fatal_crash_argument_exception'), onPressed: () => throwUnhandledException( ArgumentError('This is an ArgumentError.')), ), InstabugButton( text: 'Throw RangeError', + key: const Key('fatal_crash_range_exception'), onPressed: () => throwUnhandledException( RangeError.range(5, 0, 3, 'Index out of range')), ), InstabugButton( text: 'Throw FormatException', + key: const Key('fatal_crash_format_exception'), onPressed: () => throwUnhandledException(UnsupportedError('Invalid format.')), ), InstabugButton( text: 'Throw NoSuchMethodError', + key: const Key('fatal_crash_no_such_method_error_exception'), onPressed: () { // This intentionally triggers a NoSuchMethodError dynamic obj; @@ -53,20 +59,24 @@ class FatalCrashesContent extends StatelessWidget { ), const InstabugButton( text: 'Throw Native Fatal Crash', + key: const Key('fatal_crash_native_exception'), onPressed: InstabugFlutterExampleMethodChannel.sendNativeFatalCrash, ), const InstabugButton( text: 'Send Native Fatal Hang', + key: const Key('fatal_crash_native_hang'), onPressed: InstabugFlutterExampleMethodChannel.sendNativeFatalHang, ), Platform.isAndroid ? const InstabugButton( text: 'Send Native ANR', + key: const Key('fatal_crash_anr'), onPressed: InstabugFlutterExampleMethodChannel.sendAnr, ) : const SizedBox.shrink(), const InstabugButton( text: 'Throw Unhandled Native OOM Exception', + key: const Key('fatal_crash_oom'), onPressed: InstabugFlutterExampleMethodChannel.sendOom, ), ], diff --git a/example/lib/src/components/non_fatal_crashes_content.dart b/example/lib/src/components/non_fatal_crashes_content.dart index c3d331187..8d3bb7fb3 100644 --- a/example/lib/src/components/non_fatal_crashes_content.dart +++ b/example/lib/src/components/non_fatal_crashes_content.dart @@ -42,31 +42,41 @@ class _NonFatalCrashesContentState extends State { children: [ InstabugButton( text: 'Throw Exception', + key: const Key('non_fatal_exception'), onPressed: () => throwHandledException(Exception('This is a generic exception.')), ), InstabugButton( text: 'Throw StateError', + key: const Key('non_fatal_state_exception'), onPressed: () => throwHandledException(StateError('This is a StateError.')), ), InstabugButton( text: 'Throw ArgumentError', + key: const Key('non_fatal_argument_exception'), + onPressed: () => throwHandledException(ArgumentError('This is an ArgumentError.')), ), InstabugButton( text: 'Throw RangeError', + key: const Key('non_fatal_range_exception'), + onPressed: () => throwHandledException( RangeError.range(5, 0, 3, 'Index out of range')), ), InstabugButton( text: 'Throw FormatException', + key: const Key('non_fatal_format_exception'), + onPressed: () => throwHandledException(UnsupportedError('Invalid format.')), ), InstabugButton( text: 'Throw NoSuchMethodError', + key: const Key('non_fatal_no_such_method_exception'), + onPressed: () { dynamic obj; throwHandledException(obj.methodThatDoesNotExist()); @@ -74,6 +84,8 @@ class _NonFatalCrashesContentState extends State { ), const InstabugButton( text: 'Throw Handled Native Exception', + key: Key('non_fatal_native_exception'), + onPressed: InstabugFlutterExampleMethodChannel.sendNativeNonFatalCrash, ), @@ -86,6 +98,7 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "Crash title", + key: const Key("non_fatal_crash_title_textfield"), controller: crashNameController, validator: (value) { if (value?.trim().isNotEmpty == true) return null; @@ -100,7 +113,9 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "User Attribute key", - controller: crashUserAttributeKeyController, + key: const Key("non_fatal_user_attribute_key_textfield"), + + controller: crashUserAttributeKeyController, validator: (value) { if (crashUserAttributeValueController.text.isNotEmpty) { if (value?.trim().isNotEmpty == true) return null; @@ -113,7 +128,9 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "User Attribute Value", - controller: crashUserAttributeValueController, + key: const Key("non_fatal_user_attribute_value_textfield"), + + controller: crashUserAttributeValueController, validator: (value) { if (crashUserAttributeKeyController.text.isNotEmpty) { if (value?.trim().isNotEmpty == true) return null; @@ -130,7 +147,9 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "Fingerprint", - controller: crashfingerPrintController, + key: const Key("non_fatal_user_attribute_fingerprint_textfield"), + + controller: crashfingerPrintController, )), ], ), @@ -141,6 +160,8 @@ class _NonFatalCrashesContentState extends State { Expanded( flex: 5, child: DropdownButtonHideUnderline( + key: const Key("non_fatal_crash_level_dropdown"), + child: DropdownButtonFormField( value: crashType, @@ -161,7 +182,7 @@ class _NonFatalCrashesContentState extends State { ], ), ), - SizedBox( + const SizedBox( height: 8, ), InstabugButton( @@ -190,7 +211,7 @@ class _NonFatalCrashesContentState extends State { fingerprint: crashfingerPrintController.text, level: crashType); ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text("Crash sent"))); + .showSnackBar(const SnackBar(content: Text("Crash sent"))); crashNameController.text = ''; crashfingerPrintController.text = ''; crashUserAttributeValueController.text = ''; diff --git a/example/lib/src/screens/bug_reporting.dart b/example/lib/src/screens/bug_reporting.dart new file mode 100644 index 000000000..121e24292 --- /dev/null +++ b/example/lib/src/screens/bug_reporting.dart @@ -0,0 +1,437 @@ +part of '../../main.dart'; + +class BugReportingPage extends StatefulWidget { + static const screenName = 'bugReporting'; + + const BugReportingPage({Key? key}) : super(key: key); + + @override + _BugReportingPageState createState() => _BugReportingPageState(); +} + +class _BugReportingPageState extends State { + List reportTypes = [ReportType.bug,ReportType.feedback,ReportType.question]; + List invocationOptions = []; + + final disclaimerTextController = TextEditingController(); + + bool attachmentsOptionsScreenshot = true; + bool attachmentsOptionsExtraScreenshot = true; + bool attachmentsOptionsGalleryImage = true; + bool attachmentsOptionsScreenRecording = true; + + void restartInstabug() { + Instabug.setEnabled(false); + Instabug.setEnabled(true); + BugReporting.setInvocationEvents([InvocationEvent.floatingButton]); + } + + void setInvocationEvent(InvocationEvent invocationEvent) { + BugReporting.setInvocationEvents([invocationEvent]); + } + + void setUserConsent( + String key, + String description, + bool mandatory, + bool checked, + UserConsentActionType? actionType, + ) { + BugReporting.addUserConsents( + key: key, + description: description, + mandatory: mandatory, + checked: true, + actionType: actionType); + } + + void show() { + Instabug.show(); + } + + void addAttachmentOptions() { + BugReporting.setEnabledAttachmentTypes( + attachmentsOptionsScreenshot, + attachmentsOptionsExtraScreenshot, + attachmentsOptionsGalleryImage, + attachmentsOptionsScreenRecording); + } + + void toggleReportType(ReportType reportType) { + if (reportTypes.contains(reportType)) { + reportTypes.remove(reportType); + } else { + reportTypes.add(reportType); + } + setState(() { + + }); + BugReporting.setReportTypes(reportTypes); + } + + void addInvocationOption(InvocationOption invocationOption) { + if (invocationOptions.contains(invocationOption)) { + invocationOptions.remove(invocationOption); + } else { + invocationOptions.add(invocationOption); + } + BugReporting.setInvocationOptions(invocationOptions); + // BugReporting.setInvocationOptions([invocationOption]); + } + + void showDialogOnInvoke(BuildContext context) { + BugReporting.setOnDismissCallback((dismissType, reportType) { + if (dismissType == DismissType.submit) { + showDialog( + context: context, + builder: (_) => const AlertDialog( + title: Text('Bug Reporting sent'), + )); + } + }); + } + + void setOnDismissCallback() { + BugReporting.setOnDismissCallback((dismissType, reportType) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('On Dismiss'), + content: Text( + 'onDismiss callback called with $dismissType and $reportType', + ), + ); + }, + ); + }); + } + + void setDisclaimerText() { + BugReporting.setDisclaimerText(disclaimerTextController.text); + } + + @override + void dispose() { + disclaimerTextController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Page( + title: 'Bug Reporting', + children: [ + const SectionTitle('Enabling Bug Reporting'), + InstabugButton( + key: const Key('instabug_restart'), + onPressed: restartInstabug, + text: 'Restart Instabug', + ), + InstabugButton( + key: const Key('instabug_disable'), + onPressed: () => Instabug.setEnabled(false), + text: "Disable Instabug", + ), + InstabugButton( + key: const Key('instabug_enable'), + onPressed: () => Instabug.setEnabled(true), + text: "Enable Instabug", + ), + InstabugButton( + key: const Key('instabug_post_sending_dialog'), + onPressed: () => {showDialogOnInvoke(context)}, + text: "Set the post sending dialog", + ), + const SectionTitle('Invocation events'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_event_none'), + onPressed: () => setInvocationEvent(InvocationEvent.none), + child: const Text('None'), + ), + ElevatedButton( + key: const Key('invocation_event_shake'), + onPressed: () => setInvocationEvent(InvocationEvent.shake), + child: const Text('Shake'), + ), + ElevatedButton( + key: const Key('invocation_event_screenshot'), + onPressed: () => setInvocationEvent(InvocationEvent.screenshot), + child: const Text('Screenshot'), + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_event_floating'), + onPressed: () => + setInvocationEvent(InvocationEvent.floatingButton), + child: const Text('Floating Button'), + ), + ElevatedButton( + key: const Key('invocation_event_two_fingers'), + onPressed: () => + setInvocationEvent(InvocationEvent.twoFingersSwipeLeft), + child: const Text('Two Fingers Swipe Left'), + ), + ], + ), + const SectionTitle('User Consent'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + layoutBehavior: ButtonBarLayoutBehavior.padded, + children: [ + ElevatedButton( + key: const Key('user_consent_media_manadatory'), + onPressed: () => setUserConsent( + 'media_mandatory', + "Mandatory for Media", + true, + true, + UserConsentActionType.dropAutoCapturedMedia), + child: const Text('Drop Media Mandatory'), + ), + ElevatedButton( + key: const Key('user_consent_no_chat_manadatory'), + onPressed: () => setUserConsent( + 'noChat_mandatory', + "Mandatory for No Chat", + true, + true, + UserConsentActionType.noChat), + child: const Text('No Chat Mandatory'), + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + layoutBehavior: ButtonBarLayoutBehavior.padded, + children: [ + ElevatedButton( + key: const Key('user_consent_drop_logs_manadatory'), + onPressed: () => setUserConsent( + 'dropLogs_mandatory', + "Mandatory for Drop logs", + true, + true, + UserConsentActionType.dropLogs), + child: const Text('Drop logs Mandatory'), + ), + ElevatedButton( + key: const Key('user_consent_no_chat_optional'), + onPressed: () => setUserConsent( + 'noChat_mandatory', + "Optional for No Chat", + false, + true, + UserConsentActionType.noChat), + child: const Text('No Chat optional'), + ), + ], + ), + const SectionTitle('Invocation Options'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_option_disable_post_sending_dialog'), + onPressed: () => addInvocationOption( + InvocationOption.disablePostSendingDialog), + child: const Text('disablePostSendingDialog'), + ), + ElevatedButton( + key: const Key('invocation_option_email_hidden'), + onPressed: () => + addInvocationOption(InvocationOption.emailFieldHidden), + child: const Text('emailFieldHidden'), + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_option_comment_required'), + onPressed: () => + addInvocationOption(InvocationOption.commentFieldRequired), + child: const Text('commentFieldRequired'), + ), + ElevatedButton( + onPressed: () => + addInvocationOption(InvocationOption.emailFieldOptional), + child: const Text('emailFieldOptional'), + ), + ], + ), + InstabugButton( + key: const Key('instabug_show'), + onPressed: show, + text: 'Invoke', + ), + const SectionTitle('Attachment Options'), + Wrap( + children: [ + CheckboxListTile( + isThreeLine: false, + tristate: false, + value: attachmentsOptionsScreenshot, + onChanged: (value) { + setState(() { + attachmentsOptionsScreenshot = value ?? false; + }); + addAttachmentOptions(); + }, + title: const Text("Screenshot"), + subtitle: const Text('Enable attachment for screenShot'), + key: const Key('attachment_option_screenshot'), + + ), + CheckboxListTile( + value: attachmentsOptionsExtraScreenshot, + onChanged: (value) { + setState(() { + attachmentsOptionsExtraScreenshot = value ?? false; + }); + addAttachmentOptions(); + + }, + title: const Text("Extra Screenshot"), + subtitle: const Text('Enable attachment for extra screenShot'), + key: const Key('attachment_option_extra_screenshot'), + + ), + CheckboxListTile( + value: attachmentsOptionsGalleryImage, + onChanged: (value) { + setState(() { + attachmentsOptionsGalleryImage = value ?? false; + }); + addAttachmentOptions(); + + }, + title: const Text("Gallery"), + subtitle: const Text('Enable attachment for gallery'), + key: const Key('attachment_option_gallery'), + + ), + CheckboxListTile( + value: attachmentsOptionsScreenRecording, + onChanged: (value) { + setState(() { + attachmentsOptionsScreenRecording = value ?? false; + }); + addAttachmentOptions(); + + }, + title: const Text("Screen Recording"), + subtitle: const Text('Enable attachment for screen Recording'), + key: const Key('attachment_option_screen_recording'), + + ), + ], + ), + const SectionTitle('Bug reporting type'), + + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('bug_report_type_bug'), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.bug)?Colors.grey.shade400:null + ), + onPressed: () => toggleReportType(ReportType.bug), + child: const Text('Bug'), + ), + ElevatedButton( + key: const Key('bug_report_type_feedback'), + onPressed: () => toggleReportType(ReportType.feedback), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.feedback)?Colors.grey.shade400:null + ), + child: const Text('Feedback'), + ), + ElevatedButton( + key: const Key('bug_report_type_question'), + onPressed: () => toggleReportType(ReportType.question), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.question)?Colors.grey.shade400:null + ), + child: const Text('Question'), + ), + ], + ), + InstabugButton( + onPressed: () => { + BugReporting.show( + ReportType.bug, [InvocationOption.emailFieldOptional]) + }, + text: 'Send Bug Report', + ), + const SectionTitle('Disclaimer Text'), + InstabugTextField( + key: const Key('disclaimer_text'), + controller: disclaimerTextController, + label: 'Enter disclaimer Text', + ), + ElevatedButton( + key: const Key('set_disclaimer_text'), + onPressed: () => setDisclaimerText, + child: const Text('set disclaimer text'), + ), + + const SectionTitle('Extended Bug Reporting'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('extended_bug_report_mode_disabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.disabled), + child: const Text('disabled'), + ), + ElevatedButton( + key: + const Key('extended_bug_report_mode_required_fields_enabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithRequiredFields), + child: const Text('enabledWithRequiredFields'), + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: + const Key('extended_bug_report_mode_optional_fields_enabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithOptionalFields), + child: const Text('enabledWithOptionalFields'), + ), + ], + ), + const SectionTitle('Set Callback After Discarding'), + InstabugButton( + onPressed: setOnDismissCallback, + text: 'Set On Dismiss Callback', + ), + ], // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/example/lib/src/screens/my_home_page.dart b/example/lib/src/screens/my_home_page.dart index 404d79cdd..293e2fa18 100644 --- a/example/lib/src/screens/my_home_page.dart +++ b/example/lib/src/screens/my_home_page.dart @@ -20,6 +20,9 @@ class _MyHomePageState extends State { final primaryColorController = TextEditingController(); final screenNameController = TextEditingController(); final featureFlagsController = TextEditingController(); + final userAttributeKeyController = TextEditingController(); + + final userAttributeValueController = TextEditingController(); @override void dispose() { @@ -124,6 +127,11 @@ class _MyHomePageState extends State { Instabug.setColorTheme(colorTheme); } + void _navigateToBugs() { + ///This way of navigation utilize screenLoading automatic approach [Navigator 1] + Navigator.pushNamed(context, BugReportingPage.screenName); + } + void _navigateToCrashes() { ///This way of navigation utilize screenLoading automatic approach [Navigator 1] Navigator.pushNamed(context, CrashesPage.screenName); @@ -161,6 +169,8 @@ class _MyHomePageState extends State { ); } + final _formUserAttributeKey = GlobalKey(); + @override Widget build(BuildContext context) { return Page( @@ -178,6 +188,10 @@ class _MyHomePageState extends State { onPressed: restartInstabug, text: 'Restart Instabug', ), + InstabugButton( + onPressed: _navigateToBugs, + text: 'Bug Reporting', + ), const SectionTitle('Primary Color'), InstabugTextField( controller: primaryColorController, @@ -334,7 +348,7 @@ class _MyHomePageState extends State { ), ], ), - SectionTitle('FeatureFlags'), + const SectionTitle('FeatureFlags'), InstabugTextField( controller: featureFlagsController, label: 'Feature Flag name', @@ -351,6 +365,63 @@ class _MyHomePageState extends State { onPressed: () => removeAllFeatureFlags(), text: 'RemoveAllFeatureFlags', ), + + const SectionTitle('Set User Attribute'), + + Form( + key: _formUserAttributeKey, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: InstabugTextField( + label: "User Attribute key", + key: const Key("user_attribute_key_textfield"), + controller: userAttributeKeyController, + validator: (value) { + if (value?.trim().isNotEmpty == true) return null; + return 'this field is required'; + }, + )), + Expanded( + child: InstabugTextField( + label: "User Attribute Value", + key: const Key("user_attribute_value_textfield"), + controller: userAttributeValueController, + validator: (value) { + if (value?.trim().isNotEmpty == true) return null; + + return 'this field is required'; + }, + )), + ], + ), + SizedBox(height: 8,), + InstabugButton( + text: 'Set User attribute', + key: const Key('set_user_data_btn'), + onPressed: () { + if (_formUserAttributeKey.currentState?.validate() == true) { + Instabug.setUserAttribute(userAttributeKeyController.text, + userAttributeValueController.text); + } + }, + ), + InstabugButton( + text: 'remove User attribute', + key: const Key('remove_user_data_btn'), + onPressed: () { + if (_formUserAttributeKey.currentState?.validate() == true) { + Instabug.removeUserAttribute(userAttributeKeyController.text); + } + }, + ), + SizedBox(height: 10,), + + ], + ), + ), ], ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 93971dc8b..c6b9dd525 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,58 +5,58 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -120,18 +120,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -152,10 +152,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -168,71 +168,71 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" process: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -245,18 +245,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" typed_data: dependency: transitive description: @@ -277,18 +277,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54"