I need to change the ScrollPhysics for almost every scrollable widget in an app to BouncingScrollPhysics(). I have tried to find a way to do this without adding the physics property everywhere, but I haven't found good a way yet. One solution is to use flutter_platform_widgets and set initialPlatform to iOS, but that will change a lot of other things as well.
Does anyone know if this is possible, and in that case how?
You can copy paste run full code below
You can extend ScrollBehavior and put in builder of MaterialApp
In demo code, iOS, macOS, android will use BouncingScrollPhysics
code snippet
class ScrollBehaviorModified extends ScrollBehavior {
const ScrollBehaviorModified();
#override
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.android:
return const BouncingScrollPhysics();
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return const ClampingScrollPhysics();
}
return null;
}
}
...
builder: (context, widget) {
return ScrollConfiguration(
behavior: ScrollBehaviorModified(), child: widget);
},
working demo
full code
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class ScrollBehaviorModified extends ScrollBehavior {
const ScrollBehaviorModified();
#override
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.android:
return const BouncingScrollPhysics();
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return const ClampingScrollPhysics();
}
return null;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (context, widget) {
return ScrollConfiguration(
behavior: ScrollBehaviorModified(), child: widget);
},
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Text('Item$index');
},
separatorBuilder: (BuildContext context, int index) {
return Divider();
},
itemCount: 50,
),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
simple solution for your particular question is =>
inside ThemeData
platform: TargetPlatform.iOS,
Snapshot of the Theme code
Related
I'm trying to use Markdown (from package flutter_markdown: ^0.6.6) in a dialog box. Following example returns error:
RenderShrinkWrappingViewport does not support returning intrinsic
dimensions.
This is my code:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Package flutter_markdown'),
),
body: Center(
child: HelpButtonWithDialog(
title: 'markdown test',
content: '# Help Text\n\n* Text line 1\n*Text Line 2',
),
),
);
}
}
class HelpButtonWithDialog extends StatelessWidget {
final String title;
final String content;
HelpButtonWithDialog({
required this.title,
required this.content,
});
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.help_outline),
onPressed: () => showDialog(
barrierDismissible: true,
context: context,
builder: (BuildContext ctx) => AlertDialog(
title: Text(title),
content: Markdown(
data: content,
shrinkWrap: true,
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
)
],
),
),
);
}
}
I found a workaround by using Dialog and copying parts from AlertDialog. I also filed an enhancement request https://github.com/flutter/flutter/issues/89464 to the Flutter team to get this proper.
Here is the workaround code:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Package flutter_markdown'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'flutter_markdown: Please add example how to use in showDialog'),
Text('https://github.com/flutter/flutter/issues/89464'),
HelpButtonWithDialog(
title: 'markdown test',
content: '# Help Text\n\n* Text line 1\n* Text Line 2',
),
],
),
),
);
}
}
class HelpButtonWithDialog extends StatelessWidget {
final String title;
final String content;
HelpButtonWithDialog({
required this.title,
required this.content,
});
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final DialogTheme dialogTheme = DialogTheme.of(context);
return IconButton(
icon: Icon(Icons.help_outline),
onPressed: () => showDialog(
barrierDismissible: true,
context: context,
builder: (BuildContext ctx) => Dialog(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 24.0),
child: DefaultTextStyle(
style:
dialogTheme.titleTextStyle ?? theme.textTheme.headline6!,
child: Text(title),
),
),
MarkdownBody(
data: content,
shrinkWrap: true,
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
),
),
),
);
}
}
I have scoped model lib/scoped_models/main.dart:
import 'package:scoped_model/scoped_model.dart';
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And very simple app lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: MainModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
)
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MainModel _model = MainModel();
void initState() {
super.initState();
// _model.incrementCount(); // <-- doesn't work !!!
}
void _incrementCounter() {
setState(() {
// _model.incrementCount(); // <-- doesn't work !!!
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
model.incrementCount(); // <-- only this works !!!
// _incrementCounter(); // <-- doesn't work !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
The problem that I can't access MainModel outside of ScopedModelDescendant widget.
How to call MainModel methods at the beginning of _MyHomePageState class?
I believe it is possible because I don't want to keep all logic just in MainModel class and call every method in ScopedModelDescendant widget because it would be very inconvenient if there were many nested widgets.
So, how to get access to scoped model in StatefulWidget?
Use Scoped Model as provider
add ScopedModel just before the widget which use it (MyHomePage)
use ScopedModel.of<MainModel>(context) to control the model
use ScopedModelDescendant<MainModel> to listen the model
The advantage of using this:
You can access the same model in the descendants and share data easily
rebuild widget as small as possible (only ScopedModelDescendant part will be rebuilt)
code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ScopedModel<MainModel>(
model: MainModel(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
ScopedModel.of<MainModel>(context).incrementCount();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ScopedModelDescendant<MainModel>(
builder: (context,child, model){
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Put MainModel as a Singleton
As your solution, you create MainModel once and make it final. This can be more simple like below:
MainModel
final MainModel mainModel = MainModel();
class MainModel{
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
}
void setCount(int value) {
_count = value;
}
}
MyHomePage
MainModel even no need to extend Model or use notifyListeners becaue the widget use setState to rebuild
code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${mainModel.count}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
After watching into my code for a while I realized how stupid simple it was to fix.
So, obviously there should be just one instance of MainModel() for all widgets and files of the project and for convenience it should be placed in scoped model file lib/scoped_models/main.dart like this:
import 'package:scoped_model/scoped_model.dart';
final MainModel mainModel = MainModel(); // <-- create instance once for all files which require scoped model import
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And then you can use mainModel instance anywhere you import the model import 'package:<app_name>/scoped_models/main.dart';
So that, this code will be valid lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: mainModel, // <-- instance of model from 'lib/<app_name>/scoped_models/main.dart'
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
)
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount(); // <-- now it works !!!
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
// model.incrementCount(); // <-- works !!!
_incrementCounter(); // <-- now it's working too !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
Despite that fact that is seems reasonable, it can be overwhelming as well for the first time due to lack of examples.
I create a new flutter demo and modify it to use the provider package. But it doesn't work. And here is my code.
class MyState {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Provider<MyState>(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, state, _) {
return Text(
"${state.cnt}",
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
When press the button, the UI is not rebuilt. And as the printed messages show, the cnt field of Mystate had been changed. Why? May provider can not be used in statefulwidget?
Provider: You can use Provider to provide a value anywhere in the widget tree. It will not rebuild the widget tree whenever the value changes. It simply passes the model to its descendant's widget in the widget tree.
ChangeNotifierProvider: ChangeNotifierProvider listens for changes in the model object. It rebuilds the dependents widgets whenever ChangeNotifier.notifyListeners is called.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyState with ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyState>(
create: (context) => MyState(),
child: Scaffold(
appBar: AppBar(
title: Text("Page Title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, counter, child) => Text(
'${counter.cnt}',
style: Theme.of(context).textTheme.display1,
),
),
],
),
),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
);
}),
),
);
}
}
You can copy paste run full code below
Step 1: MyState extends ChangeNotifier and use notifyListeners()
Step 2: Use ChangeNotifierProvider
code snippet
class MyState extends ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
home: ChangeNotifierProvider(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
working demo
full code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyState extends ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ChangeNotifierProvider(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, state, _) {
return Text(
"${state.cnt}",
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I want to keep the widget's state in Scaffold.drawer. The Scaffold.drawer is a custom widget, which has a RaiseButton in it.
When click the button, the text in the button changed.
But when the drawer is closed, and reopen the drawer, the changed text is reseted.
I have use " with AutomaticKeepAliveClientMixin<> " in my custom Drawer, but it does't work.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
drawer: Drawer(child: CustomDrawer(),),
body: Center(
child: Text("Flutter Demo"),
),
);
}
}
class CustomDrawer extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _CustomDrawerState();
}
}
class _CustomDrawerState extends State<CustomDrawer> with AutomaticKeepAliveClientMixin<CustomDrawer> {
String btnText = "Click!";
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: RaisedButton(onPressed: () {
setState(() {
btnText = "Clicked!!";
});
}, child: Text(btnText),),
);
}
}
I expect the widget's state can keep, even if the Drawer is closed.
Create a separate widget for the drawer and just use in anywhere you need to.
Manage the Drawer State with a Provider
class DrawerStateInfo with ChangeNotifier {
int _currentDrawer = 0;
int get getCurrentDrawer => _currentDrawer;
void setCurrentDrawer(int drawer) {
_currentDrawer = drawer;
notifyListeners();
}
void increment() {
notifyListeners();
}
}
Adding State Management to the Widget tree
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider<DrawerStateInfo>(
builder: (_) => DrawerStateInfo()),
],
);
}
}
Creating The Drawer Widget for reuse in application
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text(
"Home",
style: currentDrawer == 0
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "Home") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) =>
MyHomePage(title: "Home")));
},
),
ListTile(
title: Text(
"About",
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "About") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => MyAboutPage()));
},
),
],
),
);
}
}
Use of Drawer in one of your pages
class MyAboutPage extends StatefulWidget {
#override
_MyAboutPageState createState() => _MyAboutPageState();
}
class _MyAboutPageState extends State<MyAboutPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About Page'),
),
drawer: MyDrawer("About"),
);
}
}
In your case, you have 2 choices:
You should keep your state in your Top level widget. in your case _MyHomePageState;
Use state managers like Redux, Bloc, ScopedModel. I think ScopedModel is great for you in this case.
otherwise, you can't control the state of Drawer. cause it re-creates every moment you call the Drawer by the action button in Appbar;
I am trying to navigate to a page called contactView. I have made a list of contacts and I wait to navogate to a contact when I click on there name. This is what I have so far. I am stuck trying to get the navigation to work. Any help would be great.
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return new ListView.builder(
padding: new EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return new _ContactListItem(_contacts[index]);
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact())
);
},
itemCount: _contacts.length,
);
}
}
Here are few things that I can immediately point out (Problems):
onPressed is not available on ListView.builder() , you may check
here:
https://docs.flutter.io/flutter/widgets/ListView/ListView.builder.html
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact()) this won't execute because it is after return
Suggestions:
You might need to wrap your _ContactListItem() inside a
GestureDetector and implement an onTap callback
Sample Code:
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
//TODO: Insert your navigation logic here
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
ContactView(_contacts[index])));
},
child: _ContactListItem(_contacts[index]),
);
},
itemCount: _contacts.length,
);
}
}
Another option could be to change the implementation of
_ContactListItem() and may be use a ListTile and implement an onTap in ListTile, you can find it here: https://docs.flutter.io/flutter/material/ListTile-class.html
You may also try to implement named routes, here is a tutorial for
that https://flutter.io/cookbook/networking/named-routes/
I hope this was helpful in someway, let me know if I misinterpreted the question.
See if the below is what you're looking for.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Contact Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Contact Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _contacts = [
Contact(name: 'John'),
Contact(name: 'Mary'),
Contact(name: 'Suzy')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
title: const Text(
'Contact Demo',
style: const TextStyle(color: Colors.white),
),
),
body: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Contact #$index'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) =>
ContactView(contact: _contacts[index]),
));
},
);
},
),
);
}
}
class Contact {
Contact({this.name});
final String name;
}
class ContactView extends StatelessWidget {
ContactView({this.contact});
final Contact contact;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(contact.name),
),
body: Center(
child: Text(contact.name),
),
);
}
}