I've being studying Flutter for about 4 days, it's becoming tedius, now I want put something together. I want to show a different AppBar content at each screen. But it seems my Scaffold's AppBar is begin ignored.
Here's my code:
class Login extends StatelessWidget{
Widget build(BuildContext context){
return Scaffold(appBar: AppBar(title:Text( 'Identification') ), body: Stack( children: [RaisedButton( child: Text("logar"), onPressed: () => {
Navigator.pushNamed(context, '/other-screen')
})]));
}
}
The app bar above it seems invisible, doesn't show
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title placedholder',
initialRoute: '/',
routes: {
'/' : (BuildContext context) => Login(),
'/other-screen' : (BuildContext context) => Scaffold(body: Row(children: <Widget>[],))
},
I have copied your code into DartPad and the first AppBar seems to be Ok.
The problem is with your second page.
Every time you create a new Scaffold, you need to add an AppBar to it.
Try running flutter clean and then running it again.
I find this fixes most issues when the code is correct but the application doesn't reflect the intended behaviour.
Related
I have created a widget with a scaffold and called a widget in it as
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Everything Store"),
),
body: Register(),
);
}
}
the Register component doesn't have a scaffold in it, but when I try to navigate from the Register widget to another one that also doesn't have a scaffold too, so I used
onPressed: () => {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => login_page()))
},
I got an error as " No Material widget found. "
So is there a way to have one scaffold or should I make a scaffold for each widget?
The scaffold is one of the main widgets that helps you build up the UI screen on a device but a screen doesn't necessarily need it. The error that you are getting it might be because you are not passing the Widget class correctly.
Try to replace login_page() with the class name like so Register().
I am trying to perform basic widget testing in Flutter. Basically I would like to have a list with list of data, and display each of the items in a custom widget (BasicListItem) which also has a ListTile widget in it.
Root widget:
class MyApp extends StatelessWidget {
final List taskList = ['List-1', 'List-2', 'List-3'];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: ListView.builder(
itemCount: taskList.length, itemBuilder: _itemBuilder),
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
final String item = taskList[index];
return BasicListItem(key: Key(item), title: item);
}
}
The list item widget (BasicListItem) takes a title, and use it inside the ListTile widget.
class BasicListItem extends StatelessWidget {
final String title;
const BasicListItem({required Key key, required this.title})
: super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.map),
title: Text(title),
);
}
}
This is the test for it:
testWidgets('has title and Icons', (WidgetTester tester) async {
const testKey = Key('my-key-1');
const testTitle = 'Demo title';
await tester.pumpWidget(BasicListItem(key: testKey, title: testTitle));
expect(find.text(testTitle), findsOneWidget);
});
But the test throws an error:
No Material widget found. ListTile widgets require a Material widget
ancestor.
...
...
The following TestFailure object was thrown running a test:
Expected: exactly one matching node in the widget tree Actual:
_TextFinder:<zero widgets with text "Demo title" (ignoring offstage widgets)>
However, the test does pass if I wrap ListTile around a MaterialApp, inside the BasicListItem build method. Like so:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: title,
home: Scaffold(
body: ListTile(
leading: Icon(Icons.map),
title: Text(title),
),
)
);
}
But doing this I cannot use it inside the ListView widget. And also I would like to have modular/separate custom widgets so that I can use it on different places as well. I am new and maybe I am missing something. How can I build custom widget and test it out? Could you help me out please.
I didn't understand Darshan's answer at first, because I think the code he provided made me implement the MaterialApp and Material widget into BasicListItem widget class build method directly, instead of implementing it on just the test suit. But that gave me the clue to implement it.
So, this is the final test case. I did wrapped MaterialApp and Material widget with BasicListItem, but not in the build method, instead I wrapped them just on the test case:
testWidgets('has title and Icons', (WidgetTester tester) async {
const testKey = Key('my-key-1');
const testTitle = 'Demo title';
await await tester.pumpWidget(MaterialApp(
home: Material(
child: BasicListItem(key: testKey, title: testTitle),
),
));;
expect(find.text(testTitle), findsOneWidget);
});
I hope this will help others like me as well.
The ListTile component comes from the Material part of Flutter UI components & is not an independent widget, therefore it needs a MaterialApp as parent.
You can check that the ListTile is under material library here: https://api.flutter.dev/flutter/material/ListTile-class.html
Also, you can create as many custom Widgets to use in separate modules,
the only requirement would be to use MaterialApp at the very beginning of the app initialisation.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
/// Only this needs to be a MaterialApp
return MaterialApp(
title: 'Welcome to Flutter',
/// this point to different screen widget also, like MainScreen()
/// Or you can start using Scaffold from here as well.
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
It is not necessary to use MaterialApp as a parent on every custom widget you build. Just the root can be fine too.
But if you are using a single widget to simply test out, & it requires a Material ancestor, you can simply wrap the widget in a Material widget as well.
Okay, this is not specifically in Flutter Docs but is hinted about all over the place. On flutter test side we are pumping a root widget to render a frame as our palette used to test widgets.
Translates to you need to create a Root App Widget to wrap the widget under test. eBay's Golden Toolkit supplies the hooks to make this possible via pumpWidgetBuilder which is an extension of Widget Tester.
For more see my blog, https://fredgrott.medium.com
As the title says, I have a String parameter and when I load the Home Stateful Widget I would like to open this bottom sheet if the parameter is not null.
As I understood I can't call showModalBottomSheet() in the build function of the Home widget because it can't start building the bottom sheet while building the Home Widget, so, is there a way to call this immediately after the Home Widget is built?
One of the solutions might be using addPostFrameCallback function of the SchedulerBinding instance. This way you could call showModalBottomSheet after the Home widget is built.
import 'package:flutter/scheduler.dart';
...
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
//Your builder code
},
);
});
//Return widgets tree for Home
}
Here's one way:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
child: Text('heyooo'),
);
}
);
});
return Scaffold(
appBar: AppBar(),
body: Container(),
);
}
}
I created a route for navigating from one page to another, in the following way
class task extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Task',
home: new task(),
routes: <String, WidgetBuilder>{
"/Completed": (BuildContext context) => new Completed()
}
);
}
}
class taskScreen extends StatefulWidget{
#override
taskState createState() => new taskState();
}
class taskState extends State<taskScreen> {
#override
Widget build(BuildContext context) {
Column taskScreen = Column(
children: <Widget>[
FlatButton(
..,
onPressed: (){
Navigator.of(context).pushNamed("/Completed");
},
child: Text(
"Completed",
),
),
],
)
]);
return Scaffold(
appBar: AppBar(
title: Text('Task Screen')),
body: taskScreen,
);
}
}
However when i try navigating it gives the error :
Could not find a generator for route RouteSettings("/Completed", null) in the _WidgetsAppState.
How can I fix this error?
I have used implemented route before from my main.dart page to the second page which worked properly however its not working here.
try this one
Navigator.pushNamed(context, "/Completed");
I figured out my error. Instead of instantiating task(),i had created an instance of taskScreen(). Hence the new route was never set. It solved my issue but I'm still curious as to what difference it made as both of them gave the same output Screen.
The following phenomenon occurs.
Transition from FirstPage to NextPage by Navigation.push.
Select a TextField of the NextPage.
1 is called again.
Done with the keyboard.
1 is called again.
Below is the source.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("sample"),
),
body: RaisedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
print("Before call NextPage()");
return NextPage();
}),
);
},
child: Text("next page"),
),
);
}
}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("next page"),
),
body: TextField(),
);
}
}
Below is animated gif where the log is printed when taping TextField and DONE keyboard.
Why does this happen? (bug?)
How can I solve it?
As you can see, even though that log is called multiple times, you are only seeing a transition on the first one, or, in another words, you’re getting the desired effect. This happens for a reason. Let’s break it down:
With Flutter, builders can be called multiple times, for multiple reasons and should be idempotent (not affect logic business);
That being said, when you’re navigating to your second screen, you’re building a TextFormField that is a child of a Stateless Widget`, which should be immutable. That means that its final values (if needed) should be passed in the constructor and each time a state changes (like the default text of the field, for example) will then require changes and thus rebuild the view by triggering the builder;
So, if you, for example, use a Stateful Widget instead, then, your builder won’t probably be called and only the build method of its State.
You can find a discussion about this in a similar closed issue here
I had the same problem, solved by:
create a global variable, say gstrCurPage = 'First'
In your main widget, do not return firstpage() only, instead, add a 'switch' statement, return firstpage() or nextpage() according to gstrCurPage.
Inside the button click of the first page, set gstrCurPage = 'Next'
To allow your 'Many Pages' to be stored inside 'Many Dart Files', first of all, create a dart file called 'GlobalVariables.dart' and stores all 'Global Variables' inside this file.
class gv {
// Declare Global Variables Here to be accessed by all 'Pages'
// These variables must be static !!!
static String gstrCurPage = 'First';
}
The following is the update of your original program:
import 'package:flutter/material.dart';
import 'GlobalVariables.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
switch (gv.gstrCurPage) {
case 'First':
return MaterialApp(
home: FirstPage(),
);
break;
case 'Next':
return MaterialApp(
home: NextPage(),
);
break;
default:
return MaterialApp(
home: FirstPage(),
);
}
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("sample"),
),
body: RaisedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
print("Before call NextPage()");
gv.gstrCurPage = 'Next';
return NextPage();
}),
);
},
child: Text("next page"),
),
);
}
}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("next page"),
),
body: TextField(),
);
}
}
Now, you can put your 'First Page' and 'Next Page' (as well as other pages) in separate dart files, all of them should import 'GlobalVariables.dart'. Any changes in the variables defined in class gv, will be 'known' by all pages.