I am currently working on a simple application to learn more about Flutter. I can now play around with the UI elements, and have reached the point where I need to communicate around between parents and children and save away the state. But I’m unsure whether there is a ‘right answer here’…
Question is, what is the right way to implement this functionality?
The body of the page looks like:
Widget _buildBody() {
return GestureDetector(
onTapUp: (details) {
setState(() {
// Use the transformation controller to translate the zoomed/panned
// coordinates to the real image coordinates
Offset pos = _controller.toScene(details.localPosition);
_circles.add(Circle(Position: pos, size: 20, colour: Colors.Red));
});
},
child: InteractiveViewer(
minScale: 0.1,
maxScale: 1.6,
transformationController: _controller,
child: Container(
constraints: const BoxConstraints.expand(),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/image.jpg'),
fit: BoxFit.cover,
),
),
child: Stack(children: _circles))));
}
The idea is you have the assets/image.jpg displayed on the screen, you can zoom in/out and pan around the image, then when you tap on the image it will create a circle where you tap with a default size and colour.
That all works fine…
My next job is to have some kind of method of changing the size of the circle. Now, one thing I can do is to show a modal dialog when you tap on a circle, this would then be implemented inside the Circle class (much like the Draggable inside the class is used to change the position of the circle). Or I could create (at the parent level) a bottom navigation bar to have a + and – button to change the size and push this change downwards, such that when a child is tapped the parent is notified (with a callback) that it is the currently selected child such that an onTap() handler (of the plus button) can call into the selected child to increment or decrement the size.
// Inside the scaffold
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline),
label: 'Size up',
),
BottomNavigationBarItem(
icon: Icon(Icons.remolve_circle_outline),
label: 'Size down',
),
onTap: (index) {
if(index == 0) {
_circles[_currentlySelectedCircle].incrementSize();
}
if(index == 1) {
_circles[_currentlySelectedCircle].decrementSize();
}
},
// Inside the parent class, this is called by the circle to tell the parent it is the selected one
_notifySelected(int selectedCircle) {
_currentlySelectedCircle = selectedCircle;
}
// When creating the circle, pass in the notification callback plus the array offset of the child
_circles.add(Circle(
Position: pos,
size: 20,
colour: Colors.Red,
which: _circles.length,
notify_select: _notifySelected);
I feel like the right answer to this is probably something of a UI question (how should the user change the size of the circle?) which will then lend to one of two solutions (handling it entirely inside the circle class or having this concept of a notification callback to select a circle then calling into the circle to adjust its size).
Is there a ‘right’ way of doing something like this?
Finally, if I want to save all this information away (number of circles, and for each circle the position, size and colour) so that the next time I start my app it always remembers and recreates the same state. Is there a ‘correct’ way of doing this? Should my statefull circle class be able to handle this, or do I need to be able to extract the state of the circle from the parent and save it away? I’ve seen many references to many different packages to handle this kind of stuff, but unsure what would be the best place to start? Maybe Bloc?
2
Answers
I think you approach is not wrong with an example, but in a real project i recommend to storage these state inside ViewModel.
I would suggest it as follows:
Of course a circle class can handle resizing on its own, but I think in this scenario if you’re building a kind of a custom canvas, the information where and which kind of circles exists belong to the canvas. So we have different variables that (can) change at runtime: Where does a circle exist? Which size? In which mode I am? (adding new circles vs. resizing them). I would store all these info just in a stateful widget. (without using BLoC or what’soever…) When creating a new circle (in the code above I’ve used boxes) I would just pass a handler to the circle object. The object will invoke the handler on being selected and the handler is implemented in the parent stateful widget.
Regarding persistency: I would implement the procedure for storing the circles’ state persistently outside the canvas component. It should be possible to create a canvas component with given circles. Maybe the parent of the canvas component, in my example MyPainterComponent, just wraps this class + functionality how to store the state info to a local database/Firebase etc. Again, I would just pass an "on storing" handler to MyPainterComponent. Using BLoC or any other state management lib is IMHO also absolutely dispensable in this case.
Maybe using BLoC is helpful when building a really complex app, when there’s is really complex "business logic" needed for processing data from the backend. Like a complex news app that receives push notifications. But in my opinion, even for just a simple chat functionality BLoC and other state management "solutions" are absolutely dispensable.
Maybe you like to watch this vid: After 4 YEARS as a Flutter instructor, here are my 5 tips for newcomers [for 2022]. His tip #2: "stop learning what the best "State Management" package is."