Flutter requires inline event handlers because BuildContext is inline? - flutter

Is it true that Flutter encourages inline event handlers because it is the only way to obtain the BuildContext?
e.g.,
class X extends StatelessWidget {
handler() {
//cannot use this if I need the BuildContext
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () { //must be inline, cannot reference "handler" because need "ctx"
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
)
)
}
}
Most languages encourage simplifying the code by moving event handling code away from the UI but with Flutter, if BuildContext object is needed, there is no "pretty" way to do it except to put the handler inline.
Have I mistaken?

Most languages encourage simplifying the code by moving event handling code away from the UI
Actually, it seems like the way the industry is moving is towards this declarative "component" model; we have SwiftUI, React, Jetpack Compose etc.
Part of the attraction of declarative UI is the fact that the hierarchy of the code matches the hierarchy of the created widgets in the UI. The syntax of Dart makes this quite nice, for example you can rewrite your build method to (using Flutter 2.5.2):
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!"),
)),
),
);
}
and the indentation provides a good visual representation of how the elements are nested in the final UI.
Now, you aren't wrong for being concerned about excessive in-lining, but the declarative way of dealing with this seems to be splitting a component up into sub-components. For example, with your X widget, the TextButton could be broken out into a specialised component:
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: SnackButton(),
);
}
class SnackButton extends StatelessWidget {
Widget build(BuildContext context) => TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!")
)),
);
}
This still keeps the onPressed handler near the item it is acting on, so readers of the code don't need to jump around looking for the definition of the handler.
But why did Flutter design it that way in the first place? Why didn't they make all event handlers (onPress, onTap) pass in the context by default?
I can see a through-line from the choice of declarative UI to expecting that everything that needs a BuildContext will be contained in-line in the build method, as this way it's all declared as it will be laid out, without having to look elsewhere. It is definitely a trade-off (as are all things), but I think if you are sensible about the implementation, and look for places to split off components or groups of components, this won't be as annoying as you are finding it now.

It should be something like that. Pass ctx to functions to have access.
class X extends StatelessWidget {
handler(BuildContext ctx) {
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () => handler(ctx)
)
)
}
}
Edit, just like #msbit explained

Related

Is it possible to share and update one screen's reactive value in another screen without Provider?

So I have this block of code in a widget that navigates to another screen:
screen_one.dart
class ScreenOne extends StatefulWidget {
const ScreenOne({ super.key });
#override
State<ScreenOne> createState() => _ScreenOneState();
}
class _ScreenOneState extends State<ScreenOne> {
List<String> state = [''];
#override
Widget build(BuildContext context) {
return Column(
MaterialButton(
onPressed: () => Navigator.pushNamed(context, '/screen-two'),
child: Text('Click here.')
),
Text(state[0]),
);
}
}
screen_two.dart
class ScreenTwo extends StatelessWidget {
const ScreenTwo({ super.key });
#override
Widget build(BuildContext context) {
return Container();
}
}
Basically I need to pass the state variable from ScreenOne to ScreenTwo and then update it there (in ScreenTwo)
ScreenTwo needs to display the same thing as ScreenOne and add() a new item to the state list when some button is clicked which should show on both the screens.
Its just one simple List so I am trying to avoid using provider.
Is it possible to do though?
I'm currently just passing it through the Navigator:
screen_one.dart
Navigator.pushNamed(
context,
'/post-info',
arguments: state,
),
screen_two.dart
Widget build(BuildContext context) {
final List<String> post = ModalRoute.of(context)!.settings.arguments as List<String>;
// ...
}
first I want to recommend you when things go bigger and more complex, it's better to use a state management approach, However since you did say that you have only one List you can simply use a ValueNotifier, with ValueListenableBuilder:
// this should be outside widget classes, maybe in a custom-made class or just in a global scope.
ValueNotifier stateNotifier = ValueNotifier([""]);
now in the places you want to use that state, you can use ValueListenableWidget like this:
ValueListenableBuilder(
valueListenable: stateNotifier,
builder: (context, value, child) {
return Column(
children: [
Text('${state[0]}'),
MaterialButton(
onPressed: () {
Navigator.pushNamed(context, '/screen-two'),
},
child: Text('click'),
),
],
);
},
);
}
}
and any other place where you want to see that state get updates, you need to use ValueListenableWidget.
Now, for executing a method like add() on the List and notify the widgets, you need to assign a new value for it like this:
void addInTheList(String elem) {
List current = stateNotifier.value;
current.add(elem);
// this exactly what will be responsible for updating.
stateNotifier.value = current;
}
now, you can just call addInTheList and expect it to update in all of them:
addInTheList("Example");

Dynamically create instance from type name

There's the question – could I create class instance if I have string variable which contains its name?
For example, I have
var className = 'DocumentsList';
could I do something like this
var docListWidget = createInstance(className[, params]);
Flutter (and Dart) do not have the facilities to do that. On purpose. They implement tree shaking, a mechanism to remove unused code to make your app smaller and faster. However, to do that, the compiler has to know what code is used and what is not. And it cannot possibly know what code gets used if you can do stuff like you describe.
So no, this is not possible. Not with that degree of freedom. You can have a big switch statement to create your classes based on strings, if you know in advance which string it will be. That is static, the compiler can work with that.
What you want is called "reflection" and you can add some capabilities using packges like reflectable or mirror but they cannot change the compilation process, they too work through the fact that you specify beforehand which classes need reflection. A totally dynamic usage is just not possible (on purpose).
Since you mentioned the routing table: You cannot create a class from a string, you can however create the string form the class:
import 'package:flutter/material.dart';
typedef Builder<T> = T Function(BuildContext context);
String routeName<T extends Widget>() {
return T.toString().toLowerCase();
}
MapEntry<String, Builder<Widget>> createRouteWithName<T extends Widget>(Builder<T> builder) {
return new MapEntry(routeName<T>(), (context) => builder(context));
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: routeName<ScreenPicker>(),
routes: Map.fromEntries([
createRouteWithName((context) => ScreenPicker()),
createRouteWithName((context) => ScreenOne()),
createRouteWithName((context) => ScreenTwo()),
]),
);
}
}
class ScreenPicker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Navigate a route")),
body: Column(children: [
RaisedButton(
child: Text('One'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenOne>())),
RaisedButton(
child: Text('Two'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenTwo>())),
]));
}
}
class ScreenTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Second Screen")), body: Center(child: Text("Two")));
}
}
class ScreenOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("First Screen")), body: Center(child: Text("One")));
}
}
This way you have no strings with route names in your project that can be changed or mistyped or forgotten when renaming something.
The best way to do that is using a named constructor like:
class MyClass {
const MyClass.create();
}
So the way you do this is passing the Type calling the method within. Remember you can pass the function without calling it, like:
Get.lazyPut(MyClass.create);
If you really need to use String for identifying the class type I suggest you create some mapping for it.
There's some functions from rootBundle that can be useful in this situation, like rootBundle.loadString() that can be done to load code from files.
Hope this was helpful.

Flutter: localization not working due to context being null. how to correctly pass it through stateless to stateful?

I have this stateless widget called myPage.dart. which contains a Stack of Texts and Stateful List View Builder.
here is the code (I commented out the 2nd group of Text and Stateful List View Builder for now:
Widget content(BuildContext context) =>
Container(
child: Stack(
children: <Widget>[
sameDayText(context),
SameDayWorkManagement(context),
// nextDayText(),
// nextDay.NextDayWorkManagement(),
],
),
);
The sameDayText is no problem. probably because the class for that is inside the myPage.dart but I can't seem to pass the context to sameDayWorkManagement.dart which is a stateful widget that contains a listview builder. keep in mind that everything worked in the past. its just that when I tried to add localization now, It seems that the context is null for some reason in the sameDayWorkManagement. Localization requires context. and I keep getting error on snippet of codes in the sameDayWorkManagement that localizes text. and again because of the context being null:
here is the sample code of the context being null in the sameDayWorkManagement.dart
Localization.of(widget.buildContext).getTranslatedValue('wakeup')
and here is the script for the sameDayWorkManagement.dart
class SameDayWorkManagement extends StatefulWidget {
BuildContext buildContext;
SameDayWorkManagement(buildContext);
#override
_SameDayWorkManagementState createState() => _SameDayWorkManagementState();
}
class _SameDayWorkManagementState extends State<SameDayWorkManagement>
with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Container(
// backgroundColor: Color(app_background_color_blue),
child: LayoutBuilder(
builder: (context, constraints) => SafeArea(
child: Container(
child: new StoreConnector<AppState, MainPageViewModel>(
converter: (store) => MainPageViewModel.fromStore(store),
builder: ( _, viewModel) => content(viewModel, constraints),
),
),
),
),
);
}
#override
void initState () {
super.initState();
if(widget.buildContext != null) {
print("is true");
} else {
print("is not true");
}
}
In initState the result is is not true
to be more precise. here is the image of myPage that does not have Localization and instead uses static Japanese Text
The first dot and Japanese Text with a telephone icon in the right is the sameDayText widget. the card below it is the sameDayWorkManagement its a list view and its scrollable.
and then the rest bellow are those that I commented out ( for now) called next day
I created a really ugly work around, so I'm still hoping this would be fixed. my work around is I created a map of all the necessary translated text in myPage using the localization which again is working in there. and pass that map to the sameDayWorkManagement as a parameter. and use that map to populate my needed text. yes it is very ugly. but for now it is working.

How to provide a BLoC (with flutter_bloc) to `showSearch`

I am using the package flutter_bloc for state management. I want to create a search screen, and found the showSearch Flutter function, and have been having issues providing a BLoC instance to the ListView my SearchDelegate implementation creates. I finally made it work, but would like to ask what the best way of doing this is. Here is the code (excerpts, starting from a button that is placed in an AppBar within a Scaffold):
class ItemSearchButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.search),
onPressed: () {
final itemListBloc = context.bloc<ItemListBloc>();
showSearch(
context: context,
delegate: _ItemSearchDelegate(itemListBloc),
);
},
);
}
}
class _ItemSearchDelegate extends SearchDelegate<String> {
final ItemListBloc itemListBloc;
_ItemSearchDelegate(this.itemListBloc);
// other overridden methods
#override
Widget buildSuggestions(BuildContext context) {
return BlocProvider.value(
value: itemListBloc,
child: ItemListWidget(),
);
}
}
Basically, the context that invokes the showSearch method has the correct BLoC instance, but it is not available within my SearchDelegate implementation, unless I re-provide it again explicitly in buildSuggestions.
Why is the BLoC not available by default? The showSearch function internally pushes a new Navigator Route, is this the issue?
What is the canonical way of dealing with things like this?
Yes, when the route changes, buildContextchanges too. So you have to provide that bloc to the new context. Just wrap your page where you want to navigate with BlocProvider:
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) =>
BlocProvider(create: Mybloc(),child:MyPage());
In the end it works as intended - the pushed route has a new context that is not a child of a context that has my BLoC, it is a child of the Navigator. The solution is to either do what I did initially - pass the BLoC explicitly as constructor argument - or make sure the Navigator context has the BLoCs, which is what I eventually did; to do this, make sure the Navigator is a child of the (Multi)BlocProvider.

Flutter: can I use setState() with a BLoC architecture?

My understanding of state management is that calling setState() alone opens up a variety of messy issues, code files become huge and difficult to debug, and it prevents holding a sensible structure to a project. In cases where the widget's appearance changes slightly, it makes little sense to have a complex architecture like BLoC or ScopedModel just to show/hide a widget (for example). However, the way I have understood it is that you can't mix setState() and an architecture together, otherwise what's the point of the architecture?
Let's use BLoC for this question (simply because I happen to be using it), specifically this package. Let's say I have this super simple example code:
class MyWidget extends StatefulWidget {
#override
void createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget>() {
bool _isShowing = false;
MyBloc bloc;
#override
void initState() {
super.init();
bloc = MyBloc();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: bloc,
builder: (context, state) {
return Column(
children: <Widget>[
Text(state.myText),
if (_isShowing)
Text("Button has been pressed!"),
RaisedButton(
child: Text("Show label"),
onTap: () => setState(() => _isShowing = true),
),
RaisedButton(
child: Text("Run event"),
onTap: () => bloc.add(NewEvent()),
),
],
);
},
);
}
}
In the crude example above, is it right/acceptable to mix the BLoC pattern with setState()? Why would I not use BLoC to handle showing the Text widget? Where do I draw the line? What are the pros/cons? Is there a performance difference?
Note: I'm not looking for "just merge the two Text widgets together" answers. I'm looking for purely architectural perspectives.
You can.
Architecture like scoped_model/bloc/etc aren't about removing calls to setState.
They are about separating concerns and simplifying the implementation
You can and should use setState when it makes sense to use it, such as with animations.
To begin with, even these architectures use setState. You just don't see it, but it's there