-
-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Problem with sharing Hook values between HookWidget class's methods #120
Comments
InlineHookWidget == HookBuilder Consider taking a look at functional_widget And I don't quite understand what's the question/proposal |
@rrousselGit sorry I wasn't clear |
I had similar thoughts as @sahandevs, since I like the idea of splitting up the build function (which is seemingly very common). I suppose that React essentially uses local functions, so perhaps that's the approach that should be used/recommended. My main concern with that would be the performance impact since all functions would get re-instantiated on every build. Not an expert on Dart so not sure what the consensus on that stuff is? The InlineHookWidget/HookBuilder stuff seems to solve a different kind of problem so not too sure about that. Nonetheless, I do agree that a paragraph and an example should be added somewhere (e.g. readme) regarding how flutter_hooks can be used in a larger app (where you need/want to split your code up a bit more). |
Will do. I am finishing a project and then I will be working on giving hooks some attention (after fixing the build of functional_widget) |
Sounds good, appreciate the quick answer! Here's some examples of how code could be written (depending on whether you prefer local functions or not): class MyWidget extends HookWidget {
@override
Widget build(BuildContext context) {
final text = useState('My Text');
void modifyStringLocal() {
s.value += ' but it might rain later.';
}
text.value = makeString();
modifyString(text);
modifyStringLocal();
return Container(
child: buildText(text.value),
);
}
Widget buildText(String text) {
return Text(text);
}
String makeString() {
return 'Hello world';
}
void modifyString(ValueNotifier<String> s) {
s.value += ' it is sunny outside';
}
} |
This is definitely one of the main downsides with Hooks in Flutter, you lose that really nice pattern you can do with a class-based widget which allows you to split your tree into multiple widgets, with virtually no boilerplate. Essentially a quick and dirty ViewModel pattern that can handle multiple views, which is quite nice actually, especially for responsive designs where we might want to swap out views but keep the same controller in place.
|
Passing the This goes against separation of concern where widgets should be independent, and the only purpose is reducing boilerplate. Instead have your subviews receive callbacks and parameters: _Subview1(
controller1: controller1,
_handleAction1: () {}
) |
No, that's much too dogmatic of a way of looking at it imo, things aren't black and white like this. If that widget has a reason to be known to the outside app, ie, it is re-used, it is violation separation of concerns I agree. If the widget is used only internally, and that implementation is opaque to the rest of the application, then it really is not. If you need to use something in multiple views, then of course it should follow the classic pattern of callbacks and params. It's an extremely common architectural pattern, to have a view that is layout, tied to a controller with some API. It leads to much less repetition and cleaner code overall. I don't see any harm using this internally in some way, it allows 'hot-swapping' of views extremely quickly. This is really no different than a view looking up to a model for some business logic, here we have multiple views looking up to a shared state, that they receive as a direct injection. Lets say I have some view, like ClockPage(). It's stateful, and it has 5 or 6 controller methods, and then also various internal views _PhoneView _WatchView, _DesktopView etc. If I pass state directly, all the views can use it as a shared controller, boilerplate is minimal, nothing is repeated. The views are free to make the calls they need to make. The state knows nothing about who is triggering the ui calls, it just decides which views to show. You can end up with a very nice layout here, where you have 5 classes in 1 file, 150 lines, and a super clean seperation of business logic and layout code. Where's the problem? We can also easily change layouts completely, without losing state between them, that's a really nice freebie. If I do it as you are mentioning, each time I change the API I have 3, 4, 5 changes to make. I gain as many as 5-10 lines per view. And it's really accomplishing nothing because I simply never use these views in a standalone way. This is simply an argument about coupled vs de-coupling, and the position that de-coupled is always better is not fully thought through. De-coupling has downsides as well. If for some reason in the future, it turns out WatchView is useful elsewhere, it's a 5m job to de-couple from the hard dependencies on state, since it's used as a pure controller anyways. I don't use this a ton, but it can be very useful when you are only deconstructing a tree for readability, and the goal is to actually not separate concerns, but rather just to organize the code a little better. The idea that you can only go from one big giant function, to classes with a ton of boilerplate interfaces, and nothing in the middle , doesn't ring true for me. Somehow it's ok to share state over 400 lines of code, with tons of nested build-methods, but passing around a clean pointer, to some parent state, to multiple stateless views (which are basically just build() calls) is "very bad practice"? For example, we we arguing there is a large difference here in "good practice" between:
And:
This seems like a stylistic difference to me? |
This is the same sort of thinking that leads C# developers to thing every single object needs an interface. If you have a reason to de-couple, de-couple by all means. It's a tool to solve a problem, a means to an end, not an end in itself. Thinking that every single thing you ever build needs to plan for future use-cases that never happen, is just pointless and leads to bugs and rigidity, |
The end result here is the same, Not trying to be argumentative, I've just heard this repeated multiple times, and trying to get to the bottom of it. This seems to be something people repeat, but if you dig into the rationale, there's nothing there and they don't know why they are repeating it. |
@esDotDev is 100% correct. The "dogmatic" way is actually a bad practice. Personally, I'd even go so far as to suggest that it should be considered an anti-pattern. Multiple stateless widgets creates a much smaller API surface for your widget, much more easy to test - testing multiple single responsibility widgets is way easier than testing a single god widget. The React community suffered from this extracting rendering things into functions as well (it arguably still does). Instead of breaking down your build method into class functions, break it down into smaller widgets. The side-effect that I've seen is that eventually, devs realize they're creating the same (or similar) small widgets used by different larger ones, so you can create a single widget that can be shared. And when you do that, you've won composable interfaces, because that's how they're supposed to work. |
Any update on the recommended way to utilize |
hey @rrousselGit is there a recommended way to deal with this problem? |
one common action in flutter is to extract widget into functions when it gets very large:
from:
to:
if we do this in a Hook Widget we must add parameters for the generated functions because values are inside build function and are not properties so they are inaccessible. It gets annoying quickly. (specially for something like useTheme).
we can use hooks inside extracted methods too but it may cause bugs later because we made that part of widget conditional.
one solution is to create a wrapper widget :
doing so will fix the problem above but:
_MyWidget
another solution is to use local functions with closure inside HookWidget:
It solves the above problem and makes it really easy to use (like ReactJS's hooks). and I don't think performance is a problem here.
problems (challenges) with this approach is:
useCallBack
we can also create a widget like
InlineHookWidget
which takes a builder and acts like above:with this we can use hooks in loops or very small widgets that needs state but creating whole new separate HookWidget is not reasonable (or for fast prototyping)
The text was updated successfully, but these errors were encountered: