Flutter snackbar in Hero widget - flutter

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?

Related

Idiomatic way to change aspects of the active scaffold from within Flutter TabView child

Essentially, I'm trying to devise a way for a child of a TabView to customise aspects of the active Scaffold instance, such as the floating action button, application bar and so on.
Using the code snippet below, the idea is for_ATabState to set the floatingActionButton of the Scaffold instance to a custom widget that it controls.
class MasterWidget extends StatefulWidget {
#override
_MasterWidgetState createState() => _MasterWidgetState();
Widget? setFloatingActionButton() {
// how to access state and invoke `setState`?
}
}
class _MasterWidgetState extends State<MasterWidget> {
#override
Widget build(BuildContext context) {
final tabs = [ATab()];
return Scaffold(
appBar: AppBar(),
floatingActionButton: activeTab.buildFloatingActionButton(),
body: DefaultTabController(
length: tabs.length,
child: TabBarView(
children: tabs,
controller: tabController,
),
),
);
}
}
class ATab extends StatefulWidget {
#override
_ATabState createState() => _ATabState();
}
class _ATabState extends State<ATab> {
#override
Widget build(BuildContext context) {
// Doesn't work:
// context.findAncestorWidgetOfExactType<Scaffold>()?.floatingActionButton = AFloatingActionButton();
// context.findAncestorWidgetOfExactType<MasterWidget>()?.setFloatingActionButton(AFloatingActionButton());
return SomeWidget();
}
}
Here's what I tried:
Try to context.findAncestorWidgetOfExactType<Scaffold>() in _ATabState and somehow set Scaffold's floatingActionButton attribute; unfortunately there does not seem to be a setter available.
Try to context.findAncestorWidgetOfExactType<MasterWidget>() in _ATabState but then I'm not able to access the state where the rendering takes place.
What's the approach applicable here?

can you navigate between widgets with only one scaffold

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().

Custom widget testing needs Material widget to test

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

AppBar not showing in scaffold

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.

How to implement Scroll Controller in Flutter (Scroll Widget)?

I am trying to implement a Scroll Widget in Flutter but not able to implement it.
I saw many tutorials but they have implemented scroll widget with ListView or Grid View.
In my case, I have Register page with multiple Text Inputs and Buttons. In this case, my page is not scrolling down please help someone.
div{
Scroll Widget on the Register page.
}
like this
class ExampleScroll extends StatefulWidget {
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Container(),
),
);
}
}
Please wrap with a SingleChildScrollView Widget.
Like this...
class AppDashBoard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: Text('It Scrollable'),
),
);
}
}
You can use SingleChildScrollView for scrolling like this.
SingleChildScrollView(
child:Column(...)
)
You can use SingleChildScrollView and provide scrolling contents as its child
for more you can find on the documentation
https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
Eg:
SingleChildScrollView(
// scrollable contents as child
child:Column(...)
)