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().
Related
I used to had two widgets where the first displays a list and the second is the detail page of a specific item in the list.
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body ListView.builder(...)
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body ....
}
}
such that WidgetA navigates to WidgetB.
Navigator.push(context,MaterialPageRoute(builder: (context) => WidgetB()));
But since I want a nice animation for the transition, I now use a Hero widget in both Widgets.
// Widget B
#override
Widget build(BuildContext context) {
return Hero(
tag: someObject,
);
}
But now the problem is that whenever WidgetB wants to display a snackbar, it is not visible there. If you would however navigate back to WidgetA before the snackbar would have disappeard, you can see it there.
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: const Duration(seconds: 3), content: Text('some text'),
));
I looked at the Scaffold documentation which states
It is typically not necessary to nest Scaffolds. For example, in a tabbed UI, where the bottomNavigationBar is a TabBar and the body is a TabBarView, you might be tempted to make each tab bar view a scaffold with a differently titled AppBar. Rather, it would be better to add a listener to the TabController that updates the AppBar
So I also tried returning a Simple Column in WidgetB which is wrapped inside a ScaffoldMessenger but none of these solutions works... I did some lookup online and people talk about it that this is intended behaviour. But no official sources say so. How could I nonetheless display a snackbar in the detail page while still using nice Hero transitions?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Text('This is my default text'),
),
);
}
}
I have recently started learning the Flutter framework , and it's hell lot on confusing for me , can u please explain the sections of this code above like , what is that BuildContext
, the MaterialApp (is it a contructor or a class, ? ),
and it's parameters , I tried learning from their docs , but their is just too much its confusing to me , all i know from this code written in this class is that , build method returns a widget , and scaffold is some sort of predefined code which helps with the layout , something like bootstrap helps with CSS
Let's look at it one line at a time.
class MyApp extends StatelessWidget {
In this line you are declaring a class MyApp which extends the Stateless widget class. That means that MyApp will have the functionality of a Stateless widget and the bits of code that you are adding to it.
Widget build(BuildContext context) {
Here you are defining the build method. A Widget needs a build method, that's how flutter knows how to build the widget and show it. The build method receives a BuildContext parameter which is a handle to the location of a widget in the widget tree.
return MaterialApp(
The build method returns a MaterialApp widget. In fact, MaterialApp() is the constructor of the MaterialApp widget.
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Text('This is my default text'),
The constructor of MaterialApp has a home and a body parameter. Here you are passing a Scaffold widget to the Home parameter. The Scaffold widget constructor (Scaffold()) has an appBar parameter, that receives a Widget (in this case, the Widget is AppBar). And the AppBar constructor has a title parameter, which receives a Text widget. And the body parameter also receives a Text widget.
),
);
}
}
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
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.
I have a BottomNavigationBar in my widget, when I want to show a new widget inside one of the items with Navigator, It shows a new screen on my last widget.
How can I replace my new widget with items of BottomNavigationBar?
Main Widget State:
class _MainWidgetState extends State<MainWidget> {
int _currentIndex = 0;
final List<Widget> _children = [
HomeWidget(title: AppConstants.appName),
ExerciseWidget(title: AppConstants.exercise),
ProfileWidget(title: AppConstants.profile),
];
.
.
Profile Widget State:
class _ProfileWidgetState extends State<ProfileWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.settings),
onPressed: () {
ViewUtils.openWidget(context, SettingWidget());
},
),
),
);
}
openWidget in ViewUtils:
static void openWidget(BuildContext context, Widget widget) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => widget
),
);
}
I need to Navigator shows my SettingWidget like SupportFragmentManager in Android: getSupportFragmentManager().beginTransaction().replace(..).
How do I do this?
More Info
I have Splash Widget that use pushAndRemoveUntil with ModalRoute.withName('/main'); that Main Widget creates BottomNavigationBar with some items.
In one of them I click settings button which shows a new widget, this showing occurs with Navigator.of(context).push or Navigator.of(context).pushReplacement.
Both of them shows my new widget on top of my main widget not replacing to one of my items in BottomNavigationBar.