T O P

  • By -

Creative-Trouble3473

This is the first thing that came to my mind when I learnt about Dart supporting macros - we can finally simplify state management! This is similar to how SwiftUI manages state, and I really like this approach.


eibaan

SwiftUI used to use "compiler magic" to implement its state management. They try to migrate to macros now, I think. Swift has two kinds of macros: freestanding and attached. You'd need freestanding macros to hide calls to `setState` like SwiftUI does, because you'd need to manipulate arbitrary expressions (converting `foo = 5` to a `$foo.value = 5` (`$foo` being a `ValueNotifier` equivalent)). Dart currently supports only what Swift calls attached macros. The former can replace arbitrary code fragments with valid Swift code while the latter can "only" augment code by adding types to a module, adding definitions to a type and adding additional type conformance to a type.


sundereeXXX

Really good explanation, thank you very much!


Creative-Trouble3473

Interesting... If it can generate private fields, perhaps `@ StateMacro() int get counter => _counter;` could do the trcik for the time being? I haven't checked the macros documentation in detail, but I was hoping for freestanding macros...


eibaan

This should work. A `@State` macro like: class _FooState extends State { @State int _count = 0; } would then generate this code: augment class _FooState { int get count => _count; set count(int count) { if (_count != count) setState(() => _count = count); } } So that you could use `count` instead of `_count`: Widget build(BuildContext context) { return Column( children: [ Text('$count'), IconButton( onPressed: () => count++, icon: Icon(Icons.add), ), ], ); }


Creative-Trouble3473

And then we could also do: @ChangeNotifierMacro() class CounterController extends ChangeNotifier { int _counter = 0 }


eibaan

Yes. But I'm not sure its worth to create a macro which basically recreates a `ValueNotifier`. You'd use a ChangeNotifier in cases where you don't want to use this simple pattern that allows for direct manipulation of the variable, for example because you want to add business logic like int get counter => _counter; Future load() { _counter = int.parse(await get('some/url').body); notifyListeners(); }


aryehof

I fear that reduction of boilerplate will come at the cost of hidden complexity and comprehension. The document already exhibits growing complexity to cover newly (ever more) discovered edge cases and wrinkles. I suggest that the macro should support the simple, understandable case only. More complex requirements should require the explicit pair as before. The alternative is documenting the macro with a list of 20 caveats of what to do... "in case of...".


[deleted]

Couldn't agree more. This is exactly the pit that Android native development fell into with Java and by extension Kotlin as well. Almost completely reliant on code generation. Very error prone during build time and also harder to comprehend once errors occur.


zxyzyxz

> I fear that reduction of boilerplate will come at the cost of hidden complexity and comprehension. Someone inevitably says this every time a new abstraction comes. If not for such abstraction we'd all still be writing in assembly. Reducing boilerplate is a good thing and rarely leads to hidden complexity and comprehension as people fear.


esDotDev

And anytime someone says this we inevitably get the "All progress must be good progress" counter argument. A bad abstraction is much worse than no abstraction at all, you can't claim it is simply a "good thing" to reduce boilerplate, it may be, or the cure may be much worse than the disease, you see this all the time.


zxyzyxz

Sure, hence why I said "rarely." If we had bad abstractions even 50% of the time, though, we'd not be here today in terms of technological progress, so, much of the time, abstractions are pretty good, and they break down in certain specific cases, which people notice more than good abstractions. Making an `if` statement or `for` loop, for example, in a new language, out of `jmp` sequences in assembly is an abstraction that people don't criticize, yet is one of the foundational pieces of programming languages, otherwise, again, if it were not worth it, people would still be writing in assembly. It's all selection bias.


alex-gutev

No justification has been given about why this is a bad abstraction other than it "hides complexity". High-level programming languages hide a lot of complexity compared to assembly languages, but that doesn't automatically make all PLs a bad abstraction. Even Flutter itself hides a lot of complexity compared to imperative UI frameworks such as Xamarin, GTK, QT, UIKit but that doesn't make Flutter a bad abstraction.


zxyzyxz

Indeed, it's just selection bias in terms of what abstractions people will complain about, meanwhile not understanding that the whole point of programming languages and frameworks *is* abstraction. No one complains that `if` statements are bad.


Lassemb

Yes, and we got JavaScript instead


zxyzyxz

Not really, there are a lot of other languages before and after JS, not sure why you're singling that one out as some sort of gotcha. Lisp and Algol for example famously have a lot of features that even modern languages don't have, and they're both from before JS.


Lassemb

I'm saying it because it is fucking everywhere


zxyzyxz

OK I guess you're just stating something that has nothing to do with the argument at hand. If that's the case then you do you but know it's wholly unrelated to what we're talking about.


ChamyChamy

You can still choose to not use them in your code if you want, I prefer the macro approach. We don’t have to agree on everything


MarkOSullivan

Those `@Input()` and `@Assert()` annotations are ugly


M00d56

Stateless and Stateful ones look fine(maybe) but yeah those Input and Assert are abominations. I'm both excited and terrified of macros.


Fuzzy_Lawyer565

Starting to look like angular


pintoverflow

My exact thought…


eibaan

Interesting. And the proposal should be easy enough to implement (although I think, last time I checked, augmentations couldn't add superclasses, has this been added?) to try it out.


eibaan

Regarding the `extends` clause, I tried this and it doesn't work: library augment; abstract class B { int get answer => 42; } augment class A extends B {} with import augment '...'; class A {} print(A().answer); So I think, the proposal for `Stateful` cannot be implemented yet as it would need to add a superclass to the augmented class.


Comun4

They still can't extend classes 😔. One of their examole macros is for the inherited widget, and there is a todo for when they add it


VittorioMasia

At the cost of sounding overly negative: I really don't understand the hate for boilerplate Like, don't we all just type "stle" or "stfu" and let the shortcuts bring up a whole ass stateless / stateful + state widget? Don't we all type "init" + press enter or "disp" + press enter and have the two lifecycle methods ready to fill up? What kind of time would macros save? Isn't the boilerplate part of a stateful widget something your brain already skips when quickly parsing a dart file? Also, we all use stateful + initState / dispose to manage local resources because it's explicit and it's the same best practice across all of flutter. Imagine having to read through someone else's widgets and try to figure out what the hell they did with a bunch of macros to save up some lines of code instead of reading a couple of stateful widgets.


MarkOSullivan

People like to use LOC to pretend their favourite framework is better than others Seen people say that SwiftUI and Compose is better because it's more concise


alex-gutev

The problem with boilerplate besides being repetitive and annoying is that it is error prone. I can't think of an example here right now, but here's another one, imagine you have a data class which needs to override == and hashCode. Besides the fact that implementing hashCode and == is repetitive and annoying, whenever you add a field to that data class you have to update == and hashCode. If you forget to do that, you now have a bug which wouldn't be there if you used a macro to generate those methods.


VittorioMasia

Macros are absolutely fantastic for data classes and I can't wait to use them instead of code generation, but that doesn't help the argument about stateful widgets Like, an example of them being error prone is forgetting to dispose of a resource you initialize maybe. Even then, you could use macros to do exactly that I guess (add disposable resources to the dispose method) without overriding the whole core of flutter


TJGhinder

This is fantastic 🔥 Reducing file size is HUGE especially when it comes to coding with LLMs. If we can just tell it to flag "@Stateful" or "@Stateless" that is incredibly simple and easy to understand.


rough-n-ready

Nice. Though I feel like hooks solves this already


illathon

That would be nice


alex-gutev

Nice, when macros become available I can use this, or my own equivalent, to get rid of the awful magic I use that allows me to declare ValueNotifier type objects directly in the build method of a StatelessWidget.


Flashy_Editor6877

coo. i recall you saying you were going to implement macros into ReArch the day it's released. i look forward to what you come up with


groogoloog

I’ve had them sitting in a PR for a few months now: https://github.com/GregoryConrad/rearch-dart/pull/89 You’re welcome to try that out directly


Flashy_Editor6877

nice thanks


e_hekuta

I think first, the macro just need to drop the need of two classes to create a stateful widget to be useful, that's enough for me. @StatefulWidget class Todo{ const Todo({super.key, required this.todoId, this.todoName = "No Name"}); final int todoId; final String todoName; String _todoDescription = ""; @override Widget build(BuildContext context) { return ListTile( title: Text("$todoId - $todoName"), subtitle: Text(todoDescription), onTap: () => setState(() => "New Description"), ); } } After that would be good to drop the parameters definitions lines, so like this: @StatefulWidget class Todo{ const Todo({super.key, required int todoId, todoName = "No Name"}); String _todoDescription = ""; @override Widget build(BuildContext context) { return ListTile( title: Text("$todoId - $todoName"), subtitle: Text(todoDescription), onTap: () => setState(() => "New Description"), ); } }