can i return the GetBuilder in StatelessWidget build function directly? - flutter

some days ago, i raise an issue which link https://github.com/jonataslaw/getx/issues/2038#issue-1075353819
now i think there are something wrong in use getx ?
in my project i always return GetBuilder in build function, because my Scaffold has too many logic and state to use.
like this
class AppLogsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<AppLogsLogic>(builder: (l) => Scaffold(
appBar: AppBar(
title: Text("app logs"),
),
// ... many logic and state use
),
);
}
}
but i saw the official document many times, it show me place the GetBuilder in the area where has some logic or state should be inject.
so i want to know it will make me face some problem? or not the best practice in Getx

Related

Can I use BlocBuilder directly in BlocProvider in Flutter?

Can I use BlocBuilder directly in BlocProvider and have an access to a state in whole tree or should I use BlocBuilder on every widget separately if I plan to change it via state ?
Right now I have this construction on a top level and inject state to lower parts of the tree:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NavigationCubit>(create: (context) => NavigationCubit())
],
child: BlocBuilder<NavigationCubit, NavigationState>(
builder: (context, state) {
return WillPopScope(
onWillPop: () async => false,
child: const MaterialApp(
home: RootContainer(state),
),
);
},
),
);
}
}
well yes You can but its not recommended to do it in certain way
for small project and prototype ? sure
for large project ? big no
normally You want to separate things for as much as you can because like the name of it Blocbuilder it will rebuild widgets 'INSIDE' of it
so If you wraps with that many widgets
example is like for post widget
if you tap like Iconbutton, widget that need rebuild is just iconButton right but if you wrap a entire a post it will re render in User screen in 60 fps(standard)
so if you doing that way dnt do it again, because user experience will be awful when project goes large
tldr: its like setstate with extra steps if u doing that way

Triggering Widget Rebuilds with Provider's context.read<T>() Method

According to Flutter's documentation and this example, as I'm understanding it, a key difference between the Provider package's context.read<T> and context.watch<T> methods relate to triggering widget rebuilds. You can call context.watch<T>() in a build method of any widget to access current state, and to ask Flutter to rebuild your widget anytime the state changes. You can't use context.watch<T>() outside build methods, because that often leads to subtle bugs. Instead, they say, use context.read<T>(), which gets the current state but doesn't ask Flutter for future rebuilds.
I tried making this simple app:
class MyDataNotifier extends ChangeNotifier {
String _testString = 'test';
// getter
String get testString => _testString;
// update
void updateString(String aString) {
_testString = aString;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => MyDataNotifier(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(context.read<MyDataNotifier>().testString),
),
body: Container(
child: Level1(),
),
),
);
}
}
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (val) {
context.read<MyDataNotifier>().updateString(val);
},
),
Text(context.read<MyDataNotifier>().testString),
],
);
}
}
All the calls are to counter.read<T>(). The app's state changes, but the UI is not rebuilt with the new value. I have to change one of the calls to counter.watch<T>() to get the state to rebuild.
On the other hand, in DZone's simple example, the UI rebuilds, and all the calls are to context.read().
What's different between their code and mine? Why can't I rebuild with counter.read() calls?
TLDR: after a quick glance, the DZone article looks like it has a bug.
Longer answer
context.watch<Foo>() does 2 things:
return the instance of the state from the tree
mark context as dependent on Foo
context.read<Foo>() only does 1).
Whenever your UI depends on Foo, you should use context.watch, since this appropriately informs Flutter about that dependency, and it will be rebuilt properly.
In general, it boils down to this rule of thumb:
Use context.watch in build() methods, or any other method that returns a Widget
Use context.read in onPressed handlers (and other related functions)
The main reason people seem to use context.read inappropriately is for performance reasons. In general, preferring context.read over context.watch for performance is an anti-pattern. Instead, you should use context.select if you want to limit how often a widget rebuilds. This is most useful whenever you have a value that changes often.
Imagine you have the following state:
class FooState extends ChangeNotifier {
// imagine this us updated very often
int millisecondsSinceLastTap;
// updated less often
bool someOtherProperty = false;
}
If you had a widget that displays someOtherProperty, context.watch could cause many unnecessary rebuilds. Instead, you can use context.select only depend on a processed part of the state:
// read the property, rebuild only when someOtherProperty changes
final property = context.select((FooState foo) => foo.someOtherProperty);
return Text('someOtherProperty: $property');
Even with a frequently updating value, if the output of the function provided to select doesn't change, the widget won't rebuild:
// even though millisecondsSinceLastTap may be updating often,
// this will only rebuild when millisecondsSinceLastTap > 1000 changes
final value = context.select((FooState state) => state.millisecondsSinceLastTap > 1000);
return Text('${value ? "more" : "less"} than 1 second...');

How to write widget tree test to verify tree hierarchy

I am learning Flutter and I want to write one test for my simple MyAppBarWidget. Below is my widget
class MyAppBarWidget extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("My first widget"),
),
));
}
}
I want to test widget tree hierarchy as
MaterialApp has Scaffold property
Scaffold has AppBar property
AppBar has title property as Text
title is My first widget
Any suggestion what kind of test I should write
I tried below test
void main() {
testWidgets("verify app bar", (WidgetTester tester) async {
await tester.pumpWidget(MyAppBarWidget());
var byWidget = find.byType(MaterialApp);
var text = find.text("My first widget");
expect(byWidget, findsOneWidget);
expect(text, findsOneWidget);
});
}
But this test does not say that my text field is inside AppBar widget
Can someone help me how should I write test to verify this ?
Thanks
I suggest not to test widget hierarchy, you will change it often and always have to adjust the test without actually knowing anything when the test fails or succeeds. It is better to test functionality, the presence of something or the absence, tap events and interaction.
You can also look into golden (screenshot) tests to ensure that screens or pages don't change.
That being said, if you really want to do this you can use
find.ancestor(find.byType(AppBar), find.text("My first widget"));
EDIT
Or with newer versions of the test library, thanks Fred Grott:
find.ancestor(of: find.byType(AppBar), matching: find.text("My first widget"));

Flutter - Keep page static throughout lifecycle of app?

I have created an AppDrawer widget to wrap my primary drawer navigation and reference it in a single place, like so:
class AppDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text("Page1"),
trailing: new Icon(Icons.arrow_right),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => Page1.singleInstance));
}
),
new ListTile(
title: new Text("Page2"),
trailing: new Icon(Icons.arrow_right),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new Page2("Page 2")));
}
),
]
),
);
}
}
I have also created a custom AppScaffold widget, which simply returns a consistent AppBar, my custom AppDrawer, and body:
class AppScaffold extends StatelessWidget {
final Widget body;
final String pageTitle;
AppScaffold({this.body, this.pageTitle});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(title: new Text(pageTitle), backgroundColor: jet),
drawer: AppDrawer(),
body: body
);
}
}
I have created two pages: Page1, and Page2. They are simple right now, and look something like this:
class Page1 extends StatelessWidget {
final String pageText;
Page1(this.pageText);
static Page1 get singleInstance => Page1("Page1");
Widget build(BuildContext context) {
return AppScaffold(
pageTitle: this.pageText,
body: SafeArea(
child: Stack(
children: <Widget>[
Center(child: SomeCustomWidget())
],
)
),
);
}
}
class Page2 extends StatelessWidget {
final String pageText;
Page2(this.pageText);
#override
Widget build(BuildContext context) {
return AppScaffold(
pageTitle: this.pageText,
body: SafeArea(
child: Stack(
children: <Widget>[
Center(child: SomeOtherCustomWidget())
],
)
),
);
}
}
When I run my app, I can see the navbar and drawer correctly. I can click on the links in the drawer to navigate between my pages. However, each time I navigate to a page, all of the widgets on that page get reset to their initial state. I want to ensure that the widgets do not get reset. Another way to think of this is: I only want one instance of each page throughout the lifecycle of the app, instead of creating them new whenever a user navigates to them.
I tried creating a static instance of Page1 that the Drawer uses when the onTap event is fired, but this does not work. Am I thinking about this incorrectly? Do I need to convert to a Stateful widget?
Oh, you're in for a treat... This will be kinda long (sorry) but please read all of it before making decisions and taking action - I promise I am saving you time.
There are many different solutions to this problem, but in general what you're asking about is state management (which is really software engineering, more info here - Understanding state management, and why you never will).
I'll try my best to explain what is happening in your specific case...
Problem:
Think of Navigator as a List of application states, which you can manipulate via its various methods (i.e. pop(), push(), etc.), with this in mind it is clear what is happening - on a button press you're actually removing the current state (page) and right after that you're pushing a new instance of your state (page).
Solution(s):
As I said, there are many solutions to this problem, for example, you may be tempted to store the state (the changes you made to a particular "page") somewhere in a var and inject that var when navigating between "pages", when creating a new instance of that page, but you'll soon run into other problems. This is why I don't think anyone can provide a simple solution to this problem...
First, may I suggest you some useful reads on the matter:
Flutter official docs on state management - When you get to the "Options" section of this, the fun part begins and can quickly get overwhelming, but fear not :P
Be sure to read the medium article mentioned in the start of my answer too, I found it really helpful.
These reads will be more than enough to help you make a decision, plus there are a ton of articles on Medium and YouTube videos touching on the matter of state management with Flutter (even some from the authors of the framework) - just search for "State management with Flutter".
Now my own personal opinion:
If it's a really simple use case and you don't plan to grow (which is almost never the case, trust me), you can just use StatefulWidgets in combination with setState() and maybe InheritedWidget (for dependency injection down the tree, or like React guys call it "lifting state up"). Or instead of the above, maybe have a look at scoped_model, which kinda abstracts all of this for you (tho, I haven't played with it).
What I use right now for a real world project is bloc and flutter_bloc (BLoC = Business Logic Component), I will not get into the details of it, but basically it takes the idea of scoped_model one step further, without over-complicating abstractions. bloc is responsible for abstracting away the "business logic" of your application and flutter_bloc to "inject" the state in your UI and react to state changes (official Flutter position on the matter is that UI = f(State)).
A BLoC has an input and an output, it takes in events as an input (can be user input, or other, any type of event really) and produces a state. In summary that's it about bloc.
A great way to get started is BLoC's official documentation. I highly recommend it. Just go through everything.
(p.s. This may be my personal opinion, but in the end state management in Flutter is all based on some form of using InheritedWidget and setState() in response to user input or other external factors that should change the application state, so I think the BLoC pattern is really on point with abstracting those :P)

InheritedWidget with Scaffold as child doesn't seem to be working

I was hoping to use InheritedWidget at the root level of my Flutter application to ensure that an authenticated user's details are available to all child widgets. Essentially making the Scaffold the child of the IW like this:
#override
Widget build(BuildContext context) {
return new AuthenticatedWidget(
user: _user,
child: new Scaffold(
appBar: new AppBar(
title: 'My App',
),
body: new MyHome(),
drawer: new MyDrawer(),
));
}
This works as expected on app start so on the surface it seems that I have implemented the InheritedWidget pattern correctly in my AuthenticatedWidget, but when I return back to the home page (MyHome) from elsewhere like this:
Navigator.popAndPushNamed(context, '/home');
This call-in the build method of MyHome (which worked previously) then results in authWidget being null:
final authWidget = AuthenticatedWidget.of(context);
Entirely possible I'm missing some nuances of how to properly implement an IW but again, it does work initially and I also see others raising the same question (i.e. here under the 'Inherited Widgets' heading).
Is it therefore not possible to use a Scaffold or a MaterialApp as the child of an InheritedWidget? Or is this maybe a bug to be raised? Thanks in advance!
MyInherited.of(context) will basically look into the parent of the current context to see if there's a MyInherited instantiated.
The problem is : Your inherited widget is instantiated within the current context.
=> No MyInherited as parent
=> crash
The trick is to use a different context.
There are many solutions there. You could instantiate MyInherited in another widget, so that the context of your build method will have a MyInherited as parent.
Or you could potentially use a Builder to introduce a fake widget that will pass you it's context.
Example of builder :
return new MyInheritedWidget(
child: new Builder(
builder: (context) => new Scaffold(),
),
);
Another problem, for the same reasons, is that if you insert an inheritedWidget inside a route, it will not be available outside of this route.
The solution is simple here !
Put your MyInheritedWidget above MaterialApp.
above material :
new MyInherited(
child: new MaterialApp(
// ...
),
)
Is it therefore not possible to use a Scaffold or a MaterialApp as the
child of an InheritedWidget?
It is very possible to do this. I was struggling with this earlier and posted some details and sample code here.
You might want to make your App-level InheritedWidget the parent of the MaterialApp rather than the Scaffold widget.
I think this has more to do with how you are setting up your MaterialWidget, but I can't quite tell from the code snippets you have provided.
If you can add some more context, I will see if I can provide more.