I tried to display snackBar at the bottom when an error occurs by using the try ... catch syntax as shown below, but for some reason, it is not displayed. Does anyone have a solution for this?
Widget _confirmButton() {
return SizedBox(
height: 54,
width: double.infinity,
child: ElevatedButton(
onPressed: () {
try {
_tryValidation();
resetPassword(userEmail).then(
(value) => Get.to(
() => CompletePasswordReset(),
),
);
} catch (e) {
const snackBar = SnackBar(
content: Text('아이디 또는 비밀번호가 맞지 않습니다.'),
backgroundColor: errorColor40,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.all(30),
duration: Duration(seconds: 1),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
},
child: Text(
'가입여부 확인',
style: TextStyle(
color: baseColor10,
fontFamily: 'semi-bold',
fontSize: titleMedium,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor50,
elevation: 0,
shadowColor: Colors.transparent,
side: BorderSide(
color: Colors.transparent,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
),
);
}
You need to throw an error on your _tryValidation & resetPassword method/function
Right now implementation has never reach the catch statement even the snackbar is working well.
You can see here that by replacing the statement on the try block statement with
throw 500;
the snackbar is working well.
try {
throw 500;
} catch (e) {
const snackBar = SnackBar(
content: Text('아이디 또는 비밀번호가 맞지 않습니다.'),
backgroundColor: errorColor40,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.all(30),
duration: Duration(seconds: 1),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
This snack bar need's context you have to pass context in method instead of using this you can use Get-X Snack bar you can use it anywhere.
Example;-
Get.snackbar('아이디 또는 비밀번호가 맞지 않습니다', '',
margin: EdgeInsets.all(15),
padding: EdgeInsets.all(0),
duration: Duration(milliseconds: 1000),
snackPosition: SnackPosition.BOTTOM,
);
I have showModalBottomSheet with list of folder name. If user want to add file into folder, click add and ModalBottomSheet will show up and user tap the folder name and call the bloc event to add file into folder. If success, I got CreateFavouriteSuccess state and if success, close the Modal and show the snack bar as follow. So far ok.
The issue is that if user click snackbar action, i got the following error. Snackbar will auto hide in 3 second and Ok action to close immediately.
I understand that Navigator.of(context).pop(); close the ancestor, shwoModalBottomSheet. May I know how to solve this issue.
Error
FlutterError (Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)
Code
Widget build(BuildContext context) {
return BlocListener<FavouriteBloc, FavouriteState>(
listener: (context, state) {
if (state is FavouriteError) {
isSubmitButtonActive = false;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: AutoSizeText(state.error),
duration: const Duration(seconds: 3),
action: SnackBarAction(
label: kYes,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
} else if (state is Processing) {
setState(() {
isSubmitButtonActive = false;
});
} else if (state is CreateFavouriteSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: AutoSizeText(state.successMessage),
duration: const Duration(seconds: 3),
action: SnackBarAction(
label: kYes,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
Navigator.of(context).pop();
} else if (state is CreateDownloadSuccess) {
Navigator.of(context).pop();
}
},
child: _favouriteForm(widget.socialMode),
);
}
Widget _favouriteForm(SocialMode socialMode) {
return FormBuilder(
key: formKey,
autovalidateMode: AutovalidateMode.disabled,
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 20,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: FormBuilderTextField(
textInputAction: TextInputAction.next,
name: "name",
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8),
prefixIcon: const Icon(Icons.favorite_outline),
hintText: "Enter playlist name",
hintStyle: kHintStyle,
fillColor: Colors.grey[200],
filled: true,
enabledBorder: kOutlineBorder,
focusedBorder: kOutlineBorder,
errorBorder: kOutlineErrorBorder,
focusedErrorBorder: kOutlineErrorBorder,
),
),
),
const SizedBox(
height: 20,
),
CustomButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
create(socialMode, widget.currentMediaItem);
}
},
child: const Text("Create"),
)
],
),
),
),
);
}
Actually, I should listen the success state at main page and show the Snackbar since I am using bloc state.
I want to know how to move between pages in flutter introduction screen. To clarify the problem, I want to go back a page when skip button is pressed.
So far I have done this:
class _IntroPageState extends State<IntroPage> {
int currentIndex = 0;
void _onIntroEnd(context) {
getIt<IntroLocalDataSource>().setIntroSeenState(true);
Navigator.of(context).pushReplacementNamed(SignInPage.id);
}
#override
Widget build(BuildContext context) {
final strings = Languages.of(context);
final bodyStyle = Theme.of(context)
.textTheme
.subtitle2
?.copyWith(fontSize: k16TextFontSize);
final titleStyle = Theme.of(context)
.textTheme
.headline1
?.copyWith(fontSize: k20TextFontSize);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Container(
child: IntroductionScreen(
onChange: (index) {
setState(() {
currentIndex = index;
});
},
rtl: true,
globalBackgroundColor: Colors.transparent,
showNextButton: false,
rawPages: strings.introScreenTitles.asMap().entries.map((entry) {
int idx = entry.key;
String val = entry.value;
return Center(
child: IntroCardWidget(
index: idx,
title: val,
bodyStyle: bodyStyle,
titleStyle: titleStyle,
image: Assets.introImages[idx],
description: strings.introScreenDescriptions[idx],
));
}).toList(),
showDoneButton: false,
curve: Curves.fastLinearToSlowEaseIn,
showSkipButton: currentIndex != 0,
skip: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
primary: kLightTextColor,
shape: CircleBorder(),
),
onPressed: () {
setState(() {
currentIndex--; // <<--------- This does not work
});
},
child: Icon(
Icons.keyboard_arrow_left_rounded,
color: Theme.of(context).primaryColor,
)),
dotsDecorator: DotsDecorator(
color: kLightTextColor.withOpacity(.15),
activeSize: Size(20, 10),
activeShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0)),
activeColor: kLightTextColor),
controlsPadding: kIsWeb
? const EdgeInsets.all(12.0)
: const EdgeInsets.symmetric(
vertical: Spacings.paddingSm,
horizontal: Spacings.paddingXs),
),
),
),
Container(
constraints: BoxConstraints(
maxHeight: Spacings.margin5Xl, minHeight: Spacings.margin5Xl),
margin: EdgeInsets.only(bottom: Spacings.margin5Xl),
child: currentIndex != 3
? Container()
: ElevatedButton(
onPressed: () => _onIntroEnd(context),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Spacings.padding5Xl,
vertical: Spacings.paddingSm),
child: Text(
strings.goToVerificationPageBtnText,
style: TextStyle(
fontSize: k16TextFontSize, color: kDarkTextColor),
),
),
style: ElevatedButton.styleFrom(
elevation: 1,
primary: kLightTextColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Spacings.radiusLg),
),
),
),
),
],
);
}
But the page does not change. It seems to make sense, as the index is not passed to the IntroductionScreen widget. So I was wondering how to navigate in these pages as will.
I found a way to do what I wanted.
It could be done with using keys & calling IntroductionScreenState's methods.
After going through the source code of library, it seems it is changing pages using this public method:
Future<void> animateScroll(int page) async {
setState(() => _isScrolling = true);
await _pageController.animateToPage(
max(min(page, getPagesLength() - 1), 0),
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.curve,
);
if (mounted) {
setState(() => _isScrolling = false);
}
}
and also has these methods for going forward & backward:
void next() => animateScroll(_currentPage.round() + 1);
void previous() => animateScroll(_currentPage.round() - 1);
From this point, it was just a matter of calling the methods of this state when needed. This could be done using keys:
GlobalKey<IntroductionScreenState> _introScreenKey =
GlobalKey<IntroductionScreenState>();
IntroductionScreen(
key: _introScreenKey,
skip: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
primary: kLightTextColor,
shape: CircleBorder(),
),
onPressed: () {
_introScreenKey.currentState?.previous(); // <<----- This here
},
child: Icon(
Icons.keyboard_arrow_left_rounded,
color: Theme.of(context).primaryColor,
)),
)
And ofcourse it is pretty easy to navigate directly to any page using the animateScroll().
Since you're using navigator, you should give a try to Navigator.pop()
onPressed: () { Navigator.pop(context); }
here you can find some examples
I have a flutter project with a FloatingSearchBar from this library (pub.dev). I have four buttons, one of which is a GestureDetector (which is not really important but explains some behind scenes info).
This all looks like this:
or this:
or one other variation.
These buttons all work fine, although I've spent a lot of time getting them that way. They are toggleable, and their Icons are decided by a variable that can be updated using setState.
There's also, however, this layout:
which should be able to be toggled on and off by this switch/button inside the menu:
It too just updates the offlineMode variable inside a setState. But this setState doesn't update the search bar until I force the bar to update by clicking any of the buttons.
Why is setState not working in this particular circumstance?
Code for search bar:
Widget buildFloatingSearchBar(AsyncSnapshot snapshot) {
const bool isPortrait = true;
return DescribedFeatureOverlay( //Don't worry about these I don't think
featureId: 'search',
tapTarget: Icon(Icons.search),
title: Text('Search'),
description: Text(
'Tap the bar at the top to open the search interface, where you can search for GeoTags by name, approximate location or by author.'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: FloatingSearchBar(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
progress: loadingSearch,
title: offlineMode //Checking offlineMode variable Should update with setState
? Row(
children: [
Text(
'Geotagger',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
Text(
' - Offline Mode',
style: TextStyle(
//fontWeight: FontWeight.bold,
fontSize: 15,
),
),
],
)
: null,
backgroundColor: Theme.of(context).backgroundColor,
hint: 'Find a GeoTag...', //Shown if offlineMode is off (title is null)
transitionDuration: const Duration(milliseconds: 800),
transitionCurve: Curves.easeInOut,
physics: const BouncingScrollPhysics(),
axisAlignment: isPortrait ? 0.0 : -1.0,
openAxisAlignment: 0.0,
maxWidth: isPortrait ? 600 : 500,
height: compassExpanded ? 62 : 48,
debounceDelay: const Duration(milliseconds: 1500),
onQueryChanged: (inQuery) {
String query = inQuery.trim();
if (offlineMode || query == null || query == '' || query == '#') {
return;
}
if (query.startsWith('#')) {}
setState(() {
loadingSearch = true;
});
// Call your model, bloc, controller here.
Future.delayed(const Duration(seconds: 3), () {
setState(() {
loadingSearch = false;
});
});
},
// Specify a custom transition to be used for
// animating between opened and closed stated.
transition: ExpandingFloatingSearchBarTransition(),
accentColor: Theme.of(context).accentColor,
actions: [
FloatingSearchBarAction.searchToClear(
showIfClosed: false,
),
FloatingSearchBarAction( //This one is to check current GPS state
showIfOpened: false,
child: StreamBuilder<Object>(
stream: Stream.periodic(Duration(seconds: 5), (x) => x),
builder: (context, _) {
return FutureBuilder(
future: Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.medium,
timeLimit: Duration(seconds: 5),
),
builder: (context, compassDir) {
if (compassDir.hasError) {
//print('failure');
return Padding(
padding: const EdgeInsets.only(
right: 7,
bottom: 2,
),
child: Icon(
Icons.wrong_location,
color: Colors.red,
),
);
} else {
return Container(width: 0, height: 0);
}
});
}),
),
FloatingSearchBarAction(
showIfOpened: false,
child: DescribedFeatureOverlay(
featureId: 'toggleLocationSnapping',
tapTarget: Icon(Icons.control_camera),
title: Text('Location Snapping'),
description: Text(
'This button toggles the map mode between snapping (default) and free. When the icon shown is a circle with a line across it, tap it to switch to free mode, and you\'ll be able to move, zoom and rotate the map freely. In snapping mode, you will be snapped to your current location and orientation, and you\'ll also be unable to pan, rotate or zoom (usually).'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: Icon(moveMapToUser
? Icons.location_disabled
: Icons.my_location),
onPressed: () {
setState(() {
moveMapToUser = !moveMapToUser;
rotateMapToUser = false;
});
},
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: moveMapToUser,
child: DescribedFeatureOverlay(
featureId: 'rotationAndNorth',
tapTarget: Icon(Icons.screen_rotation),
title: Text('Rotation & Panning Mode'),
description: Text(
'This button has three states. When showing an upward facing arrow in free mode, tap it to orientate the map north, and remain in free mode. When showing a lock symbol in snapping mode, tap it to renable automatic rotation and prevent zooming. When showing an unlock symbol in snapping mode, tap it to allow rotation and zooming, but still prevent panning away from your current location.'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: Icon(rotateMapToUser ? Icons.lock_open : Icons.lock),
onPressed: () {
setState(() {
rotateMapToUser = !rotateMapToUser;
if (!rotateMapToUser) {
controller.rotate(0);
}
});
},
),
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: !moveMapToUser,
child: CircularButton(
icon: Icon(Icons.north),
onPressed: () {
setState(() {
controller.rotate(0);
});
},
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: !offlineMode,
child: DescribedFeatureOverlay(
featureId: 'refresh',
tapTarget: Icon(Icons.refresh),
title: Text('Refresh'),
description: Text(
'Tap this button to search the area visible on your device for GeoTags after panning the map.'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: const Icon(Icons.refresh),
onPressed: () {
//setState(() {
paintGeoTags(
true,
() => setState(() {
loadingSearch = true;
}),
() => setState(() {
loadingSearch = false;
}));
//});
}),
),
),
),
FloatingSearchBarAction( //Profile Pic icon
showIfOpened: !compassExpanded, //Don't worry about compassExpanded
child: Visibility(
visible: !offlineMode, //Should update on setState
child: DescribedFeatureOverlay(
featureId: 'profile',
tapTarget: Icon(Icons.account_circle),
title: Text('Public Profile'),
description: Text(
'Tap this button to view your public profile, and view information such as rank, total points and various other information. You can manage your profile by tapping the settings cog icon in that screen, or by choosing Manage My Profile from the menu. The colored border represents the color of your current rank, and the point will always face north. You can hold down on this icon to toggle the visibility of the compass point and to expand or reduce the height of the search bar. \n\nAfter you\'ve tapped the icon above to move to the next button introduction, tap the menu button in the top left, and your introduction will continue there!'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: Hero(
tag: 'profileImage',
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
ProfileScreen.routeName,
arguments: ScreenArguments(
globals.userData.displayName,
//'test user',
FirebaseFirestore.instance
.doc('users/' + globals.userData.uid),
true,
),
),
onLongPressStart: (e) {
setState(() {
compassExpanding = true;
});
},
onLongPressEnd: (e) => {
setState(() {
compassExpanded = !compassExpanded;
compassExpanding = false;
}),
//showLogoutAlert(context)
},
child: StreamBuilder<Object>(
stream: FlutterCompass.events,
builder: (context, compassDir) {
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'));
} else {
if (snapshot.data == null) {
return Container();
}
}
return Padding(
padding: const EdgeInsets.only(
left: 4,
),
child: Container(
width: 41,
height: 41,
transform: Matrix4Transform()
.rotateDegrees(
double.parse(((compassDir.data) ?? 0)
.toString()) *
-1 +
45,
origin: Offset(41 / 2, 41 / 2))
.matrix4,
decoration: ShapeDecoration(
//shape: BoxShape.circle,
shape: CustomRoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(21),
bottomRight: Radius.circular(21),
topRight: Radius.circular(21),
topLeft: Radius.circular(
compassExpanded ? 0 : 21),
),
leftSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomLeftCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
rightSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomRightCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
topSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
topRightCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
),
color: //!compassExpanding
/*?*/ changeMedalColor(
snapshot.data.points)
//: Theme.of(context).accentColor,
/*border: Border.all(
width: 2,
color: changeMedalColor(snapshot.data.points),
),*/
),
//duration: Duration(milliseconds: 250),
child: SizedBox(
child: !compassExpanding
? Transform.rotate(
angle: vmath.radians(double.parse(
((compassDir.data) ?? 0)
.toString()) -
45),
child: CircleAvatar(
minRadius: 1000,
maxRadius: 5000,
backgroundImage: NetworkImage(
globals.userData.photoURL,
),
backgroundColor: changeMedalColor(
snapshot.data.points),
),
)
: Transform.rotate(
angle: vmath.radians(double.parse(
((compassDir.data) ?? 0)
.toString()) -
45),
child: Icon(Icons.expand,
color: Colors.white),
),
),
),
);
}),
),
),
),
),
),
],
builder: (context, transition) {
return ClipRRect(
//borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
//elevation: 4.0,
child: !offlineMode
? Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Search for a GeoTag by name, approximate location. To search by author, use \'#\' in front of the search query.',
textAlign: TextAlign.center,
),
),
SizedBox(height: 15),
Column(
mainAxisSize: MainAxisSize.min,
children: Colors.accents.map((color) {
return Container(height: 112, color: color);
}).toList(),
),
],
)
: Column(
children: [
Center(
child: Icon(
Icons.cloud_off,
size: 40,
),
),
SizedBox(height: 10),
Text(
'You\'re in Offline Mode',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'Whilst in Offline Mode, the search feature is disabled.'),
],
),
),
);
},
),
);
}
Code inside drawer to update variable:
DescribedFeatureOverlay(
featureId: 'offlineMode',
tapTarget: Icon(Icons.file_download),
title: Text('Offline Mode'),
description: Text(
'One of the most important and complex features of this app. Allows for fully offline functioning, including downloading large areas of maps. You can see more detail about it\'s features and drawbacks by tapping this menu item after the introduction is complete.'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(widget.mainContext).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: ListTile(
title: Text(offlineMode
? 'Offline Manager'
: (internetConnected
? 'Enable Offline Mode'
: 'Offline Mode Disabled')),
onTap: offlineMode
? () {
Navigator.pushNamed(
widget.mainContext, OfflineManagerScreen.routeName);
}
: (internetConnected //Don't worry about internetConnected
? () {
showAlert(
context,
widget.mainContext,
'Offline Mode',
<Widget>[
//Some text widgets
],
'Agree & Enable Offline Mode',
'Cancel', () {
StorageManager.saveData('offlineMode', 'true');
setState(() {
offlineMode = true; //Should Update
});
if (!Directory(globals.saveDir.path + '/tiles')
.existsSync()) {
Directory(globals.saveDir.path + '/tiles')
.createSync(recursive: true);
}
});
}
: null),
leading: Icon(offlineMode
? Icons.offline_pin
: (internetConnected
? Icons.file_download
: Icons.cloud_off)),
trailing: Switch(
value: offlineMode,
onChanged: offlineMode && internetConnected
? (bool newVal) {
showAlert(
context,
widget.mainContext,
'Disable Offline Mode',
<Widget>[
const Text(
'Disabling Offline Mode will delete all saved tiles, and resync information with the server. Any GeoTags tagged will be uploaded, and the appropriate point value will be added to your account. Are you sure you want to disable Offline Mode?',
textAlign: TextAlign.justify,
),
],
'Disable',
'Stay Offline', () {
setState(() {
offlineMode = false; //Should update
});
StorageManager.saveData('offlineMode', 'false');
Directory(globals.saveDir.path + '/tiles')
.deleteSync(recursive: true);
setState(() {
totalSize = 0;
filesList = [];
sizeList = [];
});
});
}
: null,
activeColor: Theme.of(widget.mainContext).primaryColorLight,
),
),
),
If you haven't already realised, this is my first big Flutter project, so tips about other things are also appreciated!
Widget is updating on inner widget. but all state is not being changed.
you should pass state to all your inner widgets like this (in showalert in your case)
StatefulBuilder(builder: (context, newState) {
return
showAlert(
context,
widget.mainContext,
'Offline Mode',
<Widget>[
//Some text widgets
],
'Agree & Enable Offline Mode',
'Cancel', () {
StorageManager.saveData('offlineMode', 'true');
newState(() {
offlineMode = true; //Should Update
});
if (!Directory(globals.saveDir.path + '/tiles')
.existsSync()) {
Directory(globals.saveDir.path + '/tiles')
.createSync(recursive: true);
}
});
});
Please try this! I didnt test, but the logic is you should pass the state.
Maybe you should wrap listtile with new state..