How to update ListView after Flutter beta 2 update - dart

The code below is a simplified version of the original, where my goal is to have a list of list tiles: List where I can keep track of newly created ListTile Widgets and which I can use as an argument to pass to a ListView.
class Notes extends StatefulWidget{
_NotesState createState() => new _NotesState();
}
class _NotesState extends State<Notes>{
List<ListTile> notes =[];
void addNewNote(){
setState((){
notes.add(new ListTile(title: new Text("Broccolli")));
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(title: "NOTES"),
body: new Stack(
children: <Widget>[
// The notes
new Container(
child: new ListView(
children: new List.from(notes, growable: false),
)
),
// The add notes button
new FloatingActionButton(
tooltip: 'Add a note',
onPressed: addNewNote,
)
],
),
);
}
This used to work just fine before the last update to flutter where Dart 2 was introduced, but now I receive the following message:
type 'List<dynamic>' is not a subtype of type 'List<Widget>' where
List is from dart:core
List is from dart:core
Widget is from package:flutter/src/widgets/framework.dart
The issue stems from: new List.from(notes, growable: false)
My reason for doing this is because when I just pass the list as an argument to ListView.children flutter does not register a change and the new elements of the list are not displayed, so I just create a new List and my issue was resolved. However this is no longer viable

You have to specify the type of your List created by new List.from or else it defaults to dynamic.
The fact is that with strong-mode, List<dynamic> is not assignable to List<whatever> anymore. So you must make sure your List has the correct type.
In your case a simple new List<Widget>.from(notes) will do the trick

Related

Flutter Trying to fire a showModalBottomSheet mystery

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.

bottomNavigationBar keeps rebuilding multiple times when calling Navigator.pushNamed

I have a problem with the structure of Flutter project.
At the moment structure looks like this:
Homepage with bottomNavigationBar with multiple tabs, each tab is StatefulWidget and contains some heavy processing (remote API calls and data display).
In case I call Navigator.pushNamed from inside of any tab, following happens:
All tabs are being rebuild in the background (making API calls, etc).
New page opens normally.
When I press back button, page closes and all tabs are rebuild again.
So in total everything (each tab) is rebuilt 2 times just to open external navigator page.
Is this some sort of bug? Completely not understandable why it's fully rebuilding bottomNavigationBar just before pushing new route.
How it should work:
When I call Navigator.pushNamed from inside the tab, new page should be open and all bottomNavigationBar tabs should not be rebuild and stay in unchanged state.
When I press back, page should close and user return to the same state of bottomNavigationBar and it's tabs, no rebuilding at all.
Is this possible to achieve?
Here is the code:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
int index = 0;
final _tab1 = new tab1(); //StatefulWidget, api calls, heavy data processing
final _tab2 = new tab2(); //StatefulWidget, api calls, heavy data processing
#override
Widget build(BuildContext context) {
debugPrint('homepage loaded:'+index.toString());
return new Scaffold(
body: new Stack(
children: <Widget>[
new Offstage(
offstage: index != 0,
child: new TickerMode(
enabled: index == 0,
child: _tab1,
),
),
new Offstage(
offstage: index != 1,
child: new TickerMode(
enabled: index == 1,
child: _tab2,
),
),
],
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: index,
type: BottomNavigationBarType.fixed,
onTap: (int index) { setState((){ this.index = index; }); },
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.live_help),
title: new Text("Tab1"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.favorite_border),
title: new Text("Tab 2"),
),
],
),
);
}
}
Here is single tab code:
class tab1 extends StatefulWidget {
#override
tab1State createState() => new tab1State();
}
class tab1State extends State<tab1> {
#override
Widget build(BuildContext cntx) {
debugPrint('tab loaded'); //this gets called when Navigator.pushNamed called and when back button pressed
//some heave processing with http.get here...
//...
return new Center(
child: new RaisedButton(
onPressed: () {
Navigator.pushNamed(context, '/some_other_page');
},
child: new Text('Open new page'),
));
}
}
In build you should simply be building the Widget tree, not doing any heavy processing. Your tab1 is a StatefulWidget, so its state should be holding onto the current state (including results of your heavy processing). Its build should simply be rendering the current version of that state.
In tab1state, override initState to set initial values, and possibly start some async functions to start doing the fetching - calling setState once the results are available. In build, render whatever the current state is, bearing in mind that it may only be partially available as the heavy work continues in the background. So, for example, test for values being null and maybe replace them with progress indicators or empty Containers.
You can get more sophisticated by using StreamBuilder and FutureBuilder which make the fetch/setState/(possibly partial)render more elegant.
Since you are building BottomNavigationBar inside build function, it will be rebuilt every time state changes.
To avoid this you can build BottomNavigationBar inside initState() method as given below,
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
BottomNavigationBar _bottomNavigationBar;
#override
void initState() {
super.initState();
_bottomNavigationBar = _buildBottomNavigationBar();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(child: new Text('Hello', style: new TextStyle(decoration: TextDecoration.underline),),),
bottomNavigationBar: _bottomNavigationBar, // Use already created BottomNavigationBar rather than creating a new one
);
}
// Create BottomNavigationBar
BottomNavigationBar _buildBottomNavigationBar() {
return new BottomNavigationBar(
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.add),
title: new Text("trends")
),
new BottomNavigationBarItem(
icon: new Icon(Icons.location_on),
title: new Text("feed")
),
new BottomNavigationBarItem(
icon: new Icon(Icons.people),
title: new Text("community")
)
],
onTap: (index) {},
);
}
}
You can use any one of these to save the states and precvent rebuilding:
IndexedStack https://api.flutter.dev/flutter/widgets/IndexedStack-class.html
PageStorageKey https://api.flutter.dev/flutter/widgets/PageStorageKey-class.html
AutomaticKeepAliveClientMixin https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html

Widget state updated by scaffold floatingActionButton

I have a scaffold which includes a floatingActionButton as demonstrated below:
return new Scaffold(
appBar: new AppBar(
title : new Text('Test Flutter'),
),
body: new Center(
child: new ListSection(),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: null
),
);
List section:
class ListSection extends StatelessWidget
{
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new WeekSection(),
// new MonthSection(),
// new YearSection()
]
)
);
}
}
Lastly WeekSection is defined as follow:
class WeekSection extends StatefulWidget
{
#override
_WeekSectionState createState() => new _WeekSectionState();
}
How would I go about using the floatingActionButton.onPressed to updated the sate in _WeekSectionState
Basic way would be to have a private var in your MyApp where scaffold, and to pass the reference to that field all the way down to the WeekSection,
So you pass this variable trough constructor to the ListSection and pass it down again trough constructor to WeekSection.
inside WeekSectionState you can access it as widget.someField from WeekSection;
Later, when you call setState inside MyApp directly in the listener of the FloatingActionButton it will rebuild WeekSection because the Widget that holds information for the state has changed. The flutter checks all the children that need rebuilding.

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