Flutter Trying to fire a showModalBottomSheet mystery - dart

I question myself sometimes, whether I'm dumb, or Dart (Flutter) is weird.
How does this not work?
I'm using https://github.com/apptreesoftware/flutter_google_map_view
I show a map, and have added markers.
Since the package supports listeners, when a marker is tapped, I want to show a modal.
Does the listener work? Yep, because the print statement happens.
Does the modal work? I don't know. No error shows, nothing!
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
Please, what is the magic bullet?
Edit
Lemme thrown in more flesh. This is my Scaffold widget.
MapView mapView = new MapView();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text('Map View Example'),
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
),
),
);
And showMap(...) looks like this:
showMap(context) {
mapView.show(
new MapOptions(
mapViewType: MapViewType.normal,
showUserLocation: true,
showMyLocationButton: true,
showCompassButton: true,
initialCameraPosition:
new CameraPosition(new Location(5.6404963, -0.2285315), 15.0),
hideToolbar: false,
title: "Dashboard"),
// toolbarActions: [new ToolbarAction("Close", 1)],
);
mapView.onMapReady.listen((_) {
mapView.setMarkers(_markers);
});
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
}

The reason you've having issues is that your context doesn't contain a scaffold. If you look what you're doing in your code, your context actually comes from the widget enclosing your scaffold.
YourWidget <------------ context
MaterialApp
Scaffold
AppBar
Column
showMap....
There are a couple of ways to get around this. You can use a Builder widget something like this:
body: new WidgetBuilder(
builder: (context) => new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
)
),
in which case the context is actually rooted below the scaffold.
YourWidget
MaterialApp
Scaffold
AppBar
Builder <------------ context
Column
showMap....
However, what I would actually recommend is breaking your class into multiple classes. If your build function gets large enough you have to separate it out into another function (that's only used once), there's a good chance you need a new widget!
You could either make a body widget (probably Stateless), or a widget just for showing the map (Stateless or Stateful depending on your needs)... or more likely both!
Now as to why you're not seeing any errors... are you running in debug or release mode (if you're in debug mode there should be a little banner in the top right of the screen)? If you're in release mode it might ignore the fact that there is no scaffold in the context and fail silently, whereas in debug mode it should throw an assertion error. Running from the IDE or with flutter run generally runs in debug mode, but you may have changed it.

Related

Proper way of changing scaffold content depending on orientation

I have a screen with GoogleMap widget and couple Text widgets. What I'm trying to do is to let map utilize whole screen in landscape orientation without AppBar, while being restricted to Container size in portrait orientaion.
Now I just have 2 scaffold widgets which beign redrawn on each orientaion change, and after couple rotations whole device freezes and I have to reboot it.
Widget build(BuildContext context) {
final mediaQueryData = MediaQuery.of(context);
if (mediaQueryData.orientation == Orientation.landscape) {
return Scaffold(
body:GoogleMap(
...
),
);
}
else{
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: ListView(
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
Text('Text'),
Text('Text'),
Container(
height: MediaQuery.of(context).size.height/3,
child: GoogleMap(
...
),
),
],
),
);
}
So I would really appreciate if someone could guide me to more efficient way of doing something like this.
Also if there's not, I would also like to know if it's possible to have working gestures (scroll, pan, etc.) in GoogleMap widget nested in scrollable ListView.
If anyone intrested someone suggested this method, but comment got deleted:
Widget build(BuildContext context) {
final isLandScape = MediaQuery.of(context).orientation == Orientation.landscape;
return Scaffold(
appBar: isLandScape ? null : AppBar(
title: Text('Title'),
),
I used same method also for Visibility widget that includes other widgets besides map:
visible: isLandScape ? false : true,
And same for Container with map:
Container(
height: isLandScape ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height/3,
child: GoogleMap(
///
),
),
Seems to be working stable now.

Is it possible to loop over flutter cards?

Here is what I am trying to do
child: FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: <Widget>[
Card(
child: Text('I just want to loop over this card :)'),
),
],
)
);
}
),
I always find people looping over listView.builder. Anyone can help, please. Thank you.
I think you are making a bit of confusion about the use of these widgets.
Indeed both ListView and Column can be used to display a list of widgets, BUT, ListView.builder(...) provides a way to reuse the widgets thus is more memory efficient when you have to create a large number of widgets.
For example, when you want to display a list of electronics for an e-commerce app. For each electronic item you have a photo, title & price, in this case you would want to use a ListView.builder, because the list can be huge and you don't want to run out of memory.
Now on the other hand, the Column should be used when you have a small number of widgets that should be displayed in a list-like way (or one beneath the other).
For your case, if you want to transform the list of objects that you have, into a list of cards you can do something like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: res.data.map((item) {
return Card(child: Text(item.name));
}).toList());
);
}
),
I've assumed that res.data is a list of elements and each element has a property called name. Also in the return Card(...) line you can do extra processing of the item if you need to do so.
Hope that this can help you :).
If you have to do more processing
You can extract the processing in a method or a chain of methods something like this:
List<Widget>prepareCardWidgets(List<SomeObject> theObjects){
//here you can do any processing you need as long as you return a list of ```Widget```.
List<Widget> widgets = [];
theObjects.forEach((item) {
widgets.add(Card(child: Text(item.name),));
});
return widgets;
}
Then you can use it like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: prepareCardWidgets(res.data)
);
}
),

ListView does not refresh whereas attached list does (Flutter)

I'm trying to get familiar with flutter and I'm facing some weird case. I want to build a dynamic ListView where a + button allows to add elements. I wrote the following State code:
class MyWidgetListState extends State<MyWidgetList> {
List<Widget> _objectList = <Widget>[
new Text('test'),
new Text('test')
];
void _addOne() {
setState(() {
_objectList.add(new Text('test'));
});
}
void _removeOne() {
setState(() {
_objectList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new ListView(
shrinkWrap: true,
children: _objectList
),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.remove_circle),
iconSize: 36.0,
tooltip: 'Remove',
onPressed: _objectList.length > 2 ? _removeOne : null,
),
new IconButton(
icon: new Icon(Icons.add_circle),
iconSize: 36.0,
tooltip: 'Add',
onPressed: _addOne,
)
],
),
new Text(_objectList.length.toString())
],
);
}
}
My problem here is that the ListView is visually stuck with the 2 elements I initialized it with.
Internally the _objectList is well managed. For testing purpose I added a simple Text widget at the bottom that shows the size of the list. This one works fine when I click the Add/Remove buttons and it gets properly refreshed. Am I missing something?
Flutter is based around immutable data. Meaning that if the reference to an object didn't change, the content didn't either.
The problem is, in your case you always send to ListView the same array, and instead mutate its content. But this leads to ListView assuming the list didn't change and therefore prevent useless render.
You can change your setState to keep that in mind :
setState(() {
_objectList = List.from(_objectList)
..add(Text("foo"));
});
Another Solution!!
Replace ListView with ListView.builder
Code:
ListView.builder(
itemBuilder: (ctx, item) {
return _objectList[item];
},
shrinkWrap: true,
itemCount: _objectList.length,
),
Output:

How to use multiple navigators

I currently have a MaterialApp in my flutter application which makes the use of the Navigator extremely easy, which is great. But, I'm now trying to figure out how to create more navigators for particular views/widgets. For example I've got a custom tab bar with another widget/view in. I'd now like that widget/view to have it's own navigation stack. So the goal is to keep the tab bar at the top while I navigate to other pages from within my widget/view.
This question is almost exactly this: Permanent view with navigation bar in Flutter but in that code, there is no MaterialApp yet. Nesting MaterialApps give funky results and I don't believe that that would be a solution.
Any ideas?
You can create new Navigator for each page. As a reference check CupertinoTabView
Or simple example:
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
Navigator _getNavigator(BuildContext context) {
return new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(builder: (context) {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text(settings.name),
new FlatButton(
onPressed: () =>
Navigator.pushNamed(context, "${settings.name}/next"),
child: new Text('Next'),
),
new FlatButton(
onPressed: () =>
Navigator.pop(context),
child: new Text('Back'),
),
],
),
);
});
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: _getNavigator(context),
),
new Expanded(
child: _getNavigator(context),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
home: new Home(),
));
}
you could give first material app's context to secondary material app;
and in secondary material app ,when you need to go back first material app,
you could check whether the secondary app can pop, with this line:
Navigator.of(secondaryContext).canPop()
if true, then you could keep using,else use first material app's context,
like this:
Navigator.of(parentContext).pop();
otherwise

Replace initial Route in MaterialApp without animation?

Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),

Resources