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/Runner/InstabugExampleMethodCallHandler.m b/example/ios/Runner/InstabugExampleMethodCallHandler.m index 3e4ee70d6..0dbbc56a2 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,), + + ], + ), + ), ], ); }