barrierDismissible in showGeneralDialog is not working with Scaffold - flutter

I am still new with Flutter. I try to make my dialog to be able to dismiss when click outside of the dialog. However if i use Scaffold, the barrierDismissible:true is not working. I tried to use Wrap but an error : No Material widget found will be displayed. Is there any idea on how to dismiss the dialog?
This is my code:
showGeneralDialog(
barrierDismissible: true,
pageBuilder: (context, anim1, anim2) {
context1 = context;
return StatefulBuilder(
builder: (context, setState) {
return Scaffold(
backgroundColor: Colors.black .withOpacity(0.0),
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
child: InkWell()
)
)
}
}
)

Scaffold is not required to display showGeneralDialog. The Material widget was required in your code because the InkWell widget needs a Material ancestor. You could use any widget that provides material such as Card or Material widget itself. Also barrierLabel cannot be null.
Please see the working code below or you can directly run the code on Dartpad https://dartpad.dev/6c047a6cabec9bbd00a048c972098671
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text("showGeneralDialog Demo"),
),
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black54,
pageBuilder: (context, anim1, anim2) {
return Center(
child: Container(
width: 200,
height: 100,
child: StatefulBuilder(
builder: (context, snapshot) {
return const Card(
color: Colors.white,
child: Center(
child: InkWell(
child: Text(
"Press outside to close",
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
),
);
},
);
},
child: const Text("Show Dialog"));
}
}

For anyone who needs to use a Scaffold in their AlertDialogs (perhaps to use ScaffoldMessenger), here is the simple work around:
Wrap the Scaffold with an IgnorePointer. The "barrierDismissible" value will now work.
#override
Widget build(BuildContext context) {
return IgnorePointer(
child: Scaffold(
backgroundColor: Colors.transparent,
body: AlertDialog(
title: title,
content: SizedBox(
width: MediaQuery.of(context).size.width,
child: SingleChildScrollView(
child: ListBody(
children: content
),
),
),
actions: actions,
insetPadding: const EdgeInsets.all(24.0),
shape: Theme.of(context).dialogTheme.shape,
backgroundColor: Theme.of(context).dialogTheme.backgroundColor,
)
),
);
}

Add this in showGeneralDialog
barrierLabel: ""
Code will look like this
showGeneralDialog(
barrierDismissible: true,
barrierLabel: "",
pageBuilder: (context, anim1, anim2) {
context1 = context;
return StatefulBuilder(
builder: (context, setState) {
return Scaffold(
backgroundColor: Colors.black .withOpacity(0.0),
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
child: InkWell()
)
)
}
}
)

I was encountering this issue when using showGeneralDialog on top of a map view. The root cause of my issue was that I had wrapped the dialog in a PointerInterceptor.
To fix it, I had to set intercepting to false.
PointerInterceptor(
intercepting: onMap, // false when not an map
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
)
)

Related

How can I achieve a draggable header in bottomsheet modal?

In my flutter app, I’m using a pinned header and a listview within a bottomsheet modal.
The problem usually is that when I overscroll, I have to scroll all the way back to be able to dismiss the modal. I would like to drag the header instead to collapse the bottom sheet modal even when the list is overscrolled.
Does anyone know how I can make the header draggable to achieve this effect?
You can find similar behavior in for example "places" section on Snapchat or the "comments" section of YouTube’s mobile app.
Here is a preview: https://imgur.com/a/fxww2IW
Here is a link to my code snippet: https://dartpad.dev/?id=960bf30a2c288ec1a0a48374b6cdbfd3
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ElevatedButton(
child: const Text('Show modal'),
onPressed: () => {
showModalBottomSheet(
backgroundColor: Colors.transparent,
clipBehavior: Clip.antiAlias,
context: context,
enableDrag: true,
isDismissible: true,
isScrollControlled: true,
builder: (modalContext) => MyWidget())
},
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.5,
minChildSize: 0.5,
maxChildSize: 0.9,
expand: false,
builder: (BuildContext context, ScrollController scrollController) =>
Container(
color: Colors.white,
child: CustomScrollView(
controller: scrollController,
slivers: [
const SliverAppBar(
title: Text("Header Title"),
pinned: true,
),
SliverFillRemaining(
child: ListView.builder(
itemCount: 500,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
}),
),
],
),
),
);
}
}
I have tried using CustomScrollview with Slivers. I expect that dragging the pinned header downwards will also move the bottomsheet downwards as in the image I have shared in the description
Look The solution for your problem is easy make a late ScrollController _scrollController; and put it in your listviewthen put this code in initState
super.initState();
_controller = ScrollController()..addListener(_scrollListener);
void _scrollListener() {`
if (_controller.position.userScrollDirection == ScrollDirection.forward &&
_controller.position.extentAfter >= _controller.position.maxScrollExtent) {
Navigator.pop(context);
}
`}
this mean when my ScrollController go up a little pop out that will close your showModalBottomSheet

how to implement animation textField on two pages in Flutter

How do I implement this kind of animation textField? and also this should be on two pages. (same as a gif). When user click back button/system back button should be back to the previous page.
I got from Facebook app, please check
I found my own answer, I used Hero and PageRouteBuilder
If anyone know a better way, please let me know
class TextScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
Hero(
tag: 'text',
transitionOnUserGestures: true,
child: Material(
type: MaterialType.transparency,
child: IconButton(
onPressed: () {
Navigator.of(context).push(
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return NewPage();
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return Align(
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
),
);
},
icon: Icon(
Icons.search,
color: Colors.white,
)),
),
),
],
),
);
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Hero(
tag: 'text',
child: Container(
height: 50,
decoration: BoxDecoration(color: Colors.white70, borderRadius: BorderRadius.all(Radius.circular(30))),
child: Material(type: MaterialType.transparency, child: TextField()),
)),
),
);
}
}

Navigate between screens with transition in Dart

I have 2 screens and I want to navigate between them with a custom transition (using a library named flutter_spinkit).
How can I go from Page1 to Page2 showing my custom loading screen for 2-3 seconds ?
Here is my code:
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
main() {
runApp(MaterialApp(
home: Page1(),
));
}
class Page1 extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Go!'),
onPressed: () {
Navigator.of(context).push(_createRoute());
},
),
),
);
}
}
Route _createRoute() {
return PageRouteBuilder(
// transitionDuration: Duration(seconds: 1),
transitionsBuilder: (context, animation, animationTime, child) {
child = Scaffold(
backgroundColor: Colors.purple[700],
body: Center(
child: SpinKitFadingCube(
color: Colors.white,
size: 100.0,
),
),
);
return ScaleTransition(
scale: animation,
child: child,
alignment: Alignment.center,
);
},
pageBuilder: (context, animation, animationTime) => Page2(),
);
}
class Page2 extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('Page 2'),
),
);
}
}
Animate when going from One Screen to Another Screen
Your onPressed or onTap method:
InkWell(
onTap: () {
Navigator.of(context).push(_createRoute());
},
)
and then inside your _createRoute method
Route _createRoute() {
return PageRouteBuilder(
transitionDuration: Duration(seconds: 2), //You can change the time here
pageBuilder: (context, animation, secondaryAnimation) => SecondScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var begin = Offset(1.0, 0.0);
var end = Offset.zero;
var curve = Curves.easeInCirc;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
with Duration(seconds: 2) you can change the time from microsecond, seconds, minutes, hours
There are many other Curves animations like Curves.easeInCirc, which can be found Here
OR
If you want to use Flutter_Spinkit for showing a type of Animated Loading Indicator than this is how it is done!
Add Dependency:
dependencies:
flutter_spinkit: "^4.1.2"
Then import it in your screen:
import 'package:flutter_spinkit/flutter_spinkit.dart';
and then you can use the Flutter Spinkit Loader where it is required like:
final spinkit = SpinKitFadingCircle(
itemBuilder: (BuildContext context, int index) {
return DecoratedBox(
decoration: BoxDecoration(
color: index.isEven ? Colors.red : Colors.green,
),
);
},
);
I finally got your point. Maybe you just need to change the transitionsBuilder to check the animation is complete or not. I also change the name of SpinKitFadingCube because it should not replace the original child Widget.
transitionsBuilder: (context, animation, animationTime, child) {
final loading= Scaffold(
backgroundColor: Colors.purple[700],
body: Center(
child: SpinKitFadingCube(
color: Colors.white,
size: 100.0,
),
),
);
if(animation.isCompleted){
return child;
}else{
return loading;
}
},

Problem wiht multiple listviews inside column / Horizontal viewport was given unbounded height

I am still new to flutter and trying to achieve a following layout of two listviews, one is displaying the top news articles, another one is showing further articles. The first one is supposed to be horizontal, the second one vertical, but both are element of one single scrollview.
Something like this here:
I am using cubit to have different states, while it loads the articles, but in combination with the SingleChildScrollView, I can never get the second, vertical listview to display, I always get
"Horizontal viewport was given unbounded height."
How can i fix this?
Also, if two widgets share the same list fetched by a bloc, is there a good way to reuse that list, instead of having two BlocBuilders?
Here is my code:
body.dart
class Body extends StatelessWidget {
Widget buildArticleWidgets(List<Article> articles) {
return ListView.builder(
scrollDirection: Axis.horizontal,
primary: false,
itemBuilder: (BuildContext context, int index) {
return ArticleWidget(articles[index]);
},
itemCount: 5,
);
}
Widget _buildSmallArticleWidgets(List<Article> articles) {
return ListView.builder(
scrollDirection: Axis.horizontal,
primary: false,
itemBuilder: (BuildContext context, int index) {
return SmallArticle(articles[index]);
},
itemCount: 5,
);
}
#override
Widget build(BuildContext context) {
final articleCubit = context.bloc<ArticlesCubit>();
articleCubit.getArticles();
return Column(
children: [
CategoriesTab(),
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.centerLeft,
child: Text('Popular News',
style: TextStyle(
color: Colors.black,
fontSize: 18,
)),
),
SizedBox(
height: 200,
child: BlocBuilder<ArticlesCubit, ArticlesState>(
builder: (context, state) {
if (state is ArticlesInitial) {
return Container();
} else if (state is ArticlesLoading) {
return Container();
} else if (state is ArticlesLoaded) {
return buildArticleWidgets(state.articles);
}
return Container();
},
)),
Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.centerLeft,
child: Text('More News',
style: TextStyle(
color: Colors.black,
fontSize: 18,
)),
),
BlocBuilder<ArticlesCubit, ArticlesState>(
builder: (context, state) {
if (state is ArticlesInitial) {
return Container();
} else if (state is ArticlesLoading) {
return Container();
} else if (state is ArticlesLoaded) {
return _buildSmallArticleWidgets(state.articles);
}
return Container();
},
)
],
)))
],
);
}
}
home_screen.dart
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: SvgPicture.asset("assets/icons/menu.svg"),
onPressed: () {
Scaffold.of(context).openDrawer();
},
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
},
),
title: Text(
'NewsLab',
),
centerTitle: true,
),
drawer: Drawer(),
backgroundColor: Colors.white,
body: Body(),
);
}
}
and
main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "NewsLab",
theme: ThemeData(
scaffoldBackgroundColor: Colors.transparent,
primaryColor: Colors.blue,
textTheme:
Theme.of(context).textTheme.apply(bodyColor: Colors.transparent),
visualDensity: VisualDensity.adaptivePlatformDensity),
home: BlocProvider(
create: (context) => ArticlesCubit(ArticlesRepository()),
child: HomeScreen(),
),
);
}
}
In the second Column widget in your body, use Expanded widget on the children widgets.

Could not find the correct Provider<X> above this ModalBottomSheet Widget

I'm new to flutter and I'm trying to understand How to use provider state management in an application which users Moor to save some data into sqlite table. My application is a task recording application. I'm getting the above error in my widget tree when I open my bottom sheet add a task. I'm using provider: ^4.3.1
class TaskView extends StatelessWidget {
DateTime selectedDate = DateTime.now();
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
child: Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context, scrollController) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)
)
);
}
Widget bottomSheet(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: 16.0,
top: 16.0,
right: 16.0,
bottom: 16.0 + MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Task',
),
),
SizedBox(height: 8),
TextField(
readOnly: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(Icons.date_range, color: Colors.grey),
onPressed: () => _selectDate(context),
),
labelText: 'Date',
),
),
SizedBox(height: 8),
Align(
alignment: Alignment.topRight,
child: context.watch<TaskViewModel>().state == ViewState.IDLE
? FlatButton(
child: Text("Save"),
color: Colors.blueAccent,
textColor: Colors.white,
onPressed: () => _onClickInsertTask(context))
: _loadingButtonChild(context))
],
),
);
}
Widget _loadingButtonChild(BuildContext context) {
return Container(
height: 20,
width: 20,
margin: EdgeInsets.all(5),
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
);
}
/// This function is responsible for displaying the date picker when user click
/// on task due date inputFiled
Future<Null> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate) {
print("Date selected ${selectedDate.toString()}");
}
}
/// This function is responsible for triggering insert task block event
void _onClickInsertTask(BuildContext context) {
var insertTask = TaskData(task: "task", dueDate: selectedDate);
context.read<TaskViewModel>().insertTask(insertTask);
}
}
The error suggested checking.
- The provider you are trying to read is in a different route.
I have not given the provider to s route but as the direct parent view.
- You used a BuildContext that is an ancestor of the provider you are trying to read.
I didn't understand what it means but I made the suggested fix in the error. like below
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
builder: (context, child) => Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context, scrollController) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)));
}
Still get the same error. Another thing to note here is error suggested the below.
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
But I could not find a builder: (context) so I used the builder: (context, child). Please let me know what I should change to get this working. Thanks.
Edit:
BaseModel
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.IDLE;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
TaskViewModel
class TaskViewModel extends BaseViewModel{
final TaskRepository _repository = TaskRepository();
Resource<int> insertTaskStatus;
Future<void> insertTask(TaskData task) async {
setState(ViewState.PROCESSING);
var tasksCompanion = TasksCompanion(task: Value(task.task),dueDate: Value(task.dueDate));
insertTaskStatus = await _repository.insertTask(tasksCompanion);
setState(ViewState.COMPLETED);
}
}
Although you call showMaterialModalBottomSheet in the Scaffold wrapped by the provider, the provider is not above both TaskView's Scaffold and the modalBottomSheet. Why?
The provider you are trying to read is in a different route.
So, it seems that the modalBottomSheet is on a different route that doesn't have a provider above. If you take a look at the implementation of showModalBottomSheet you'll see:
return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(....);
Clearly, it's a new route. So, to access the provider it should be above both routes. Since, the modalBottomSheet route is managed by the MaterialApp, you have to place the provider above the MaterialApp.
Provider uses lazy loading by default. So, objects are created when they are required and not on app start. However, if you don't want this behavior you can set lazy: false individually. For more info check the offical docs.
Another Easier option, for example if you are creating a package, that is supposed to inherit to be used in Another materialApp and you wish to use showModalBottomSheet, to read context of the parent provider, you have to disable the context of the showModalBottomSheet. This will force it to use the tree that has context for the provider in the parent widget.
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
builder: (context, child) => Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (_) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)));
}