How to use CustomMultiChildLayout & CustomSingleChildLayout in Flutter - flutter

Can someone with experience in using CustomSingleChildLayout and CustomMultiChildLayout classes be able to explain in detail (with examples) in how to use them.
I am new to Flutter and am trying to understand how to use these. However, the documentation is horrible and is not clear. I tried to scour the internet for examples, but there aren't any other documentation.
I would be eternally grateful if you could help.
Thank you!

First of all, I want to say that I am glad to help you with this as I can understand your struggles - there are benefits to figuring it out by yourself as well (the documentation is amazing).
What CustomSingleChildLayout does will be obvious after I explained CustomMultiChildLayout to you.
CustomMultiChildLayout
The point of this widget is allowing you to layout the children you pass to this widget in a single function, i.e. their positions and sizes can depend on each other, which is something you cannot achieve using e.g. the prebuilt Stack widget.
CustomMultiChildLayout(
children: [
// Widgets you want to layout in a customized manner
],
)
Now, there are two more steps you need to take before you can start laying out your children:
Every child you pass to children needs to be a LayoutId and you pass the widget you actually want to show as a child to that LayoutId. The id will uniquely identify your widgets, making them accessible when laying them out:
CustomMultiChildLayout(
children: [
LayoutId(
id: 1, // The id can be anything, i.e. any Object, also an enum value.
child: Text('Widget one'), // This is the widget you actually want to show.
),
LayoutId(
id: 2, // You will need to refer to that id when laying out your children.
child: Text('Widget two'),
),
],
)
You need to create a MultiChildLayoutDelegate subclass that handles the layout part. The documentation here seems to be very elaborate.
class YourLayoutDelegate extends MultiChildLayoutDelegate {
// You can pass any parameters to this class because you will instantiate your delegate
// in the build function where you place your CustomMultiChildLayout.
// I will use an Offset for this simple example.
YourLayoutDelegate({this.position});
final Offset position;
}
Now, all the setup is done and you can start implementing the actual layout. There are three methods you can use for that:
hasChild, which lets you check whether a particular id (remember LayoutId?) was passed to the children, i.e. if a child of that id is present.
layoutChild, which you need to call for every id, every child, provided exactly once and it will give you the Size of that child.
positionChild, which allows you to change the position from Offset(0, 0) to any offset you specify.
I feel like the concept should be pretty clear now, which is why I will illustrate how to implement a delegate for the example CustomMultiChildLayout:
class YourLayoutDelegate extends MultiChildLayoutDelegate {
YourLayoutDelegate({this.position});
final Offset position;
#override
void performLayout(Size size) {
// `size` is the size of the `CustomMultiChildLayout` itself.
Size leadingSize = Size.zero; // If there is no widget with id `1`, the size will remain at zero.
// Remember that `1` here can be any **id** - you specify them using LayoutId.
if (hasChild(1)) {
leadingSize = layoutChild(
1, // The id once again.
BoxConstraints.loose(size), // This just says that the child cannot be bigger than the whole layout.
);
// No need to position this child if we want to have it at Offset(0, 0).
}
if (hasChild(2)) {
final secondSize = layoutChild(
2,
BoxConstraints(
// This is exactly the same as above, but this can be anything you specify.
// BoxConstraints.loose is a shortcut to this.
maxWidth: size.width,
maxHeight: size.height,
),
);
positionChild(
2,
Offset(
leadingSize.width, // This will place child 2 to the right of child 1.
size.height / 2 - secondSize.height / 2, // Centers the second child vertically.
),
);
}
}
}
Two other examples are the one from the documentation (check preparation step 2) and a real world example I wrote some time back for the feature_discovery package: MultiChildLayoutDelegate implementation and CustomMultiChildLayout in the build method.
The last step is overriding the shouldRelayout method, which simple controls whether performLayout should be called again at any given point in time by comparing to an old delegate, (optionally you can also override getSize) and adding the delegate to your CustomMultiChildLayout:
class YourLayoutDelegate extends MultiChildLayoutDelegate {
YourLayoutDelegate({this.position});
final Offset position;
#override
void performLayout(Size size) {
// ... (layout code from above)
}
#override
bool shouldRelayout(YourLayoutDelegate oldDelegate) {
return oldDelegate.position != position;
}
}
CustomMultiChildLayout(
delegate: YourLayoutDelegate(position: Offset.zero),
children: [
// ... (your children wrapped in LayoutId's)
],
)
Considerations
I used 1 and 2 as the ids in this example, but using an enum is probably the best way to handle the ids if you have specific ids.
You can pass a Listenable to super (e.g. super(relayout: animation)) if you want to animate the layout process or trigger it based on a listenable in general.
CustomSingleChildLayout
The documentation explains what I described above really well and here you will also see why I said that CustomSingleChildLayout will be very obvious after understanding how CustomMultiChildLayout works:
CustomMultiChildLayout is appropriate when there are complex relationships between the size and positioning of a multiple widgets. To control the layout of a single child, CustomSingleChildLayout is more appropriate.
This also means that using CustomSingleChildLayout follows the same principles I described above, but without any ids because there is only a single child.
You need to use a SingleChildLayoutDelegate instead, which has different methods for implementing the layout (they all have default behavior, so they are technically all optional to override):
getConstraintsForChild, which is equivalent to the constraints I passed to layoutChild above.
getPositionForChild, which is equivalent to positionChild above.
Everything else is exactly the same (remember that you do not need LayoutId and only have a single child instead of children).
MultiChildRenderObjectWidget
This is what CustomMultiChildLayout is built on.
Using this requires even deeper knowledge about Flutter and is again a bit more complicated, but it is the better option if you want more customization because it is even lower level. This has one major advantage over CustomMultiChildLayout (generally, there is more control):
CustomMultiChildLayout cannot size itself based on its children (see issue regarding better documentation for the reasoning).
I will not explain how to use MultiChildRenderObjectWidget here for obvious reasons, but if you are interested, you can check out my submission to the Flutter Clock challenge after January 20, 2020, in which I use MultiChildRenderObjectWidget extensively - you can also read an article about this, which should explain a bit of how all of it works.
For now you can remember that MultiChildRenderObjectWidget is what makes CustomMultiChildLayout possible and using it directly will give you some nice benefits like not having to use LayoutId and instead being able to access the RenderObject's parent data directly.
Fun fact
I wrote all the code in plain text (in the StackOverflow text field), so if there are errors, please point them out to me and I will fix them.

Related

Custom Event listeners in flutter

I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list.  All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?  
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}

Detect user created widgets in flutter widget tree

I've been working on a problem today to detect certain widgets in the widget tree so I've been playing around with context.visitChildElements and element. visitChildren. I can see all the widgets in the tree, but there's just too many.
I looked at the way the flutter widget inspector does it and they have some internal expectations that won't exist within other users code bases. The example, I have a scaffold with a body Center and a child Material button. Passing the context to my function below prints out about 200+ widgets with those three scattered in between. I would like to only print out those three, or at least elliminate all widgets created by Flutter automatically and not created by the code the user supplied.
List<WidgetInfo> getElements(BuildContext context) {
var widgetsOfInterest = <WidgetInfo>[];
widgetsOfInterest.clear();
int indentation = 0;
void visitor(Element element) {
indentation++;
Key? key = element.widget.key;
String className = element.widget.runtimeType.toString();
while (element.findRenderObject() is! RenderBox) {}
RenderBox box = element.findRenderObject() as RenderBox;
var offset = box.getTransformTo(null).getTranslation();
final indent = ' ' * indentation;
// Here I want to check if this is a widget we created and print its name and offset
if (debugIsLocalCreationLocation(element)) print('$className $offset');
if ((MaterialButton).toString() == className) {
widgetsOfInterest.add(WidgetInfo(
indentation: indentation,
size: box.size,
paintBounds: box.paintBounds.shift(
Offset(offset.x, offset.y),
),
key: key,
className: className,
));
}
element.visitChildren(visitor);
}
context.visitChildElements(visitor);
return widgetsOfInterest;
}
If anyone have any insights or experience with the Flutter widget tree that could point me in the right direction I would appreciate that.
it's obviously seems not the best solution here(and will increase unnecessary code) but this might work.
you can create a custom widget key that have some prefix inside of it and use it in every component you want it to be detected
for example
//1
Appbar(key: FSKey())
//2
Center(key:FSKey("awesome_widget"))
internally if you have access to those key while you iterate through elements you can detect those widgets using the prefix you set.
actuall key values
//1
"fskey_1273zj72ek628"
//2
"fskey_awesome_widget"
again this might not be a very optimal solution but iit gives you some control of what parts of the tree you want it to be detected and eventually if there is no other way.. this will work.

Most elegant/efficient way to refactor widgets in Flutter and Dart

Searching online on "how to refactor Flutter widgets" I found that there exist two possible ways that are both functioning as per my testing, still very different from a structural standpoint. The second method, indeed includes and additional building instruction, which should bring a further burden on the app performances right?
This is the code I want to refactor:
Container(
child: Column(
children: <Widget> [
[long code to create a button with many properties],
[long code to create a button with many properties],
[long code to create a button with many properties],
[long code to create a button with many properties],
],),);
These are the main ways I found:
1):
Widget MyButton(Color color, String text) {
return [long code to create a button with many properties];
}
2):
class MyButton extends StatelessWidget {
MyButton(this.color, this.text);
final Color color;
final String text;
#override
Widget build(BuildContext context) {
return [long code to create a button with many properties];
}
}
Which is the best method?
Please take a look and consider this other question:
What is the difference between functions and classes to create reusable widgets?
Short answer: It' better the second method (both efficient and elegant).
In the first method (extract to a function), you are just creating a function that return the encapsulated widget.
In the second method (extract to a class), you are extracting the widget to a new class that extends from StatelessWidget. This difference gives to the Flutter framework a way to make optimizations.

How to force ScrollController to recalculate position.maxExtents?

I'm trying to create a desktop-style scrollbar, that changes it's size based on the size of the content. My scrollbar shares a ScrollController with a list, and relies on the position.maxExtents to know how large the content area is.
The issue is that when I change the number of rows, the maxExtents will not update, until a scrollEvent is initiated.
I've worked around it with code like this, moving 1px up, and 1px down over 100ms:
widget.controller.jumpTo(controller.position.pixels + 1);
Future.microtask(() => widget.controller.animateTo(controller.position.pixels - 1, duration: 100.milliseconds, curve: Curves.linear));
Which works pretty quite well when the list can scroll. However, when the list is < the height of the view, it can't scroll, and these calls have no effect, and I'm stuck with a stale maxExtents.
How can I just tell the list: "Hey, list, recalculate your children!"?
You can delay your code to when the controller has been updated using the following.
WidgetsBinding.instance.addPostFrameCallback((_) {
...your code which requires controller's max extents...
});
Aside from the issues you've mentioned, animating the scroll position will cause Flutter to draw frames unnecessarily.
Borrowing from #ltk and #Rakaton, I think there's a simpler and more efficient way to do this:
// Run this code whenever the layout changes (i.e. any time you setState
// to update the list content)
WidgetsBinding.instance?.addPostFrameCallback((_) {
widget.scrollController.position.notifyListeners();
});
You may also want to wrap your list component in a SizeChangedLayoutNotifier to detect size changes from things like window resize:
NotificationListener<SizeChangedLayoutNotification>(
onNotification: (notification) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
scrollController.position.notifyListeners();
});
return true;
},
child: SizeChangedLayoutNotifier(
child: ListView(
// ...
),
),
);
I think you should consider to use force pixels, but it's a protected method so it's gonna give you a warning. And I don't know about the performance or another stuffs about it.
widget.controller.position.forcePixels(controller.position.pixels + 1);
or combination of correct pixels and notifylistener.
widget.controller.position.correctPixels(controller.position.pixels + 1);
widget.controller.position.notifyListeners();

How to dynamically add Children to Scaffold Widget

Let's say, I have a chat screen that looks like this.
Now, when the user clicks the "Press when ready" button, the method fetchNewQuestion() is called.
My intention is that this will make a HTTP request, and display the result using
_buildUsersReply(httpResponse);
But, the problem is that this return must be made inside the current scaffold's widget as a child under the existing children, so that it is built at the bottom with the previous ones still there. The result would be like this:
You can find my complete code here.
Is this possible to be done pro-grammatically? Or do I have to change the concept of how I do this?
[Update, I now understand that my approach above is wrong and I have to use a listview builder. CurrentStatus below shows my progress towards achieving that goal.]
Current status:
I have built a list of Widgets:
List<Widget> chatScreenWidgets = [];
And on setState, I am updating that with a new Widget using this:
setState(() { chatScreenWidgets.add(_buildUsersReply("I think there were 35 humans and one horse.")); });
Now at this point, I am not sure how to pass the widget inside the scaffold. I have written some code that does not work. For instance, I tried this:
Code in the image below and in the gist here:
Just for future reference, here is what I really needed to do:
1. Create a list of widgets
List<Widget> chatScreenWidgets = [];
2. Inside my method, I needed to use a setState in order to add elements to that list. Every widget I add to this will be displayed on ths Scaffold.
`setState(() {
chatScreenWidgets.add(_buildUsersReply("Some Text"));
});`
3. And then, load that inside my Scaffold, I used an itemBuilder in order to return a list of widgets to my ListView. I already had that ListView (where I was manually adding children). Now this just returns them through the setState method inside my business logic method (in this case, fetchNewQuestion()).
body: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 0),
child: new ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 25),
itemCount: chatScreenWidgets.length,
itemBuilder: (BuildContext context, int itemCount) {
return chatScreenWidgets[itemCount];
}
),
),
],
),
);`
I hope this helps future flutter engineers!
forget about the scaffold the idea is about what you really want to change, lets say it is
a list and your getting the data from an array if you update the array, then the list will update,if it is another type widgets then you can handle it in a different way i will edit this answer if you clarify what each part does in your widget as i cant read your full code.
first you have to create an object with two attributes one is the type of the row(if it is a user replay or other messages) and the second attribute is the string or the text.
now create a global list in the listview class from the above object, so you get the data from the user or even as a news and you create a new object from the above class and add your data to it and add it to the list.
item builder returns a widget so according to the the widget that you return the row will be set , so according to the data in the object call one of your functions that return the views like _buildUsersReply(string text).
if you have any other question you can ask :) if this what you need please mark it as the answer.