Flutter Expanding List Optimisation, Performance and Best Practices - list

What is the best practice for displaying a list of Widgets in flutter, where the list can be extended or reduced in length via State.
Let's say we have a Provider State that determines the length and we render widgets accordingly.
List _state = Provider.of<Somesuch>(context).routes;
for(int i = 0; i < _state.length; i ++)
Container(child: Text('Item $I'),
The problem on testing this is that if the state length increases, all child widgets seem to be rebuilt. Due to the number of animations contained in each actual child, this does not seem to be an optimal approach.
We have experimenting with nesting the children:
class NestContainer extends StatelessWidget {
NestContainer({
this.child = const SizedBox(),
this.nextChild = const SizedBox(),
});
final Widget child, nextChild;
}
Widget build(BuildContext context) {
return Stack(children:[
child,
nextChild,
]);
}
then as the child of some Widget:
Builder(builder: (context) {
List _state = Provider.of<Somesuch>(context).routes;
buildContainer(int index){
return NestContainer(
child: Container(child: Text('Item $index')),
nextChild
: _state.asMap().containsKey(index + 1) ?
buildContainer(index + 1) : const SizedBox()
}
return buildContainer(0);
});
This seems to not cause the rebuilds but I am concerned about the use of functions as this is discouraged in the docs.
Also it makes passing actual content to these children for more involved as technically all potential children (that are actually page routes contained in a Map). Each NestContainer would need to receive the whole Map so that it can render the correct item from it which means passing a lot of unused data to every single NestContainer.
What is the best approach for dealing with this matter?

Keys are important. They allow flutter to look at a stack of widgets as a whole and determine whether anything inside that stack has changed.
List _state = Provider.of<Somesuch>(context).routes;
for(int i = 0; i < _state.length; i ++)
Container(key: ValueKey('Container $I'), child: Text('Item $I'),
The addition of that key means that if that container does not change and it's place in the list does not change none of it will be checked for a change.
However, this is not the full answer. Memory usage is based around a number of rendered widgets on screen among other things. Getting rid of anything not on screen is important.
Many will say that the best way is to remove unused widgets from the tree. ie
Stack(children:[
someStatefield ? SomeWidget() : SizedBox()
])
This is true but it is not always possible particularly in a complex navigator. The idea is instead to replace widgets that can't be seen on screen with the minus amount of content possible until they are needed to be seen again.
The easiest way is to use the Visibility class. With Visibility(visible: false), the child of this class will be replaced with a SizedBox and it's content restored when set to true. Adding maintainState: true will allow it to maintain responses to any other state changes without rendering any content or performing any anumations until it's visible again.
Visibility(
visible: (true or false based on your state),
maintainState: true // ensures the child still responds to changes in state despite rendering no content or animations
child: // Your Widgets and content that will be replaced by a SizedBox when not visible
)

Related

DART/FLUTTER: How to get access to member variable / values of Widgets

I have an list of widgets. The widgets are background images for a website. The last widget(image) in this list is displayed onscreen. As nav functions push/pop, the last element is removed/added.
One of the fields in the Widget(BackdropImage) is a bool called 'isForeground'. I need access to it.
Why? Some background images are foreground and are to be rendered above the site's semi-transparent background texture, some are to be rendered behind it.
If I have a List(BackDropImage) and the BackDropImage contains:
Path, BoxFit, Opacity, isForeground(bool) etc etc. How can I get access to the last BackDropImage in BackDropImage list and access its isForground field?
//PSUDO WIDGET TREE:
if(backdropImageList.last's 'isForground' field is FALSE) BackDropImage.last,//render here below the texture
BackgroundTextureWidget,
if(backdropImageList.last's isForground field is TRUE) BackDropImage.last //render here above the texture
HeadingWidget,
OtherWidgets
I'd appreciate help if possible (or alternative approaches). The alterative is to flip a bool programmatically every time a button is pressed/popped and keeping track of what images are displayed where. Knowing which images are fore/background in the first place and controlling from the object itself is much neater IMO.
Thanks folks.
list.firstWhere((x) => x['isForground'] == true);
Widget is an abstract class. When working with Widgets like Container etc that have member variables you need to access programmatically, it can be done like this:
Widget testContainer = Container (width:100)
print(testContainer.width) // WON'T WORK
print((testContainer as Container).width) // WILL WORK
To access the member variables of individual Widgets in a list of Widgets:
List<Widget> listOfWidgets = [Container(color:Colors.blue), Container(color:Colors.red), Container(color:Colors.green)]
void printColorsOfAllContainers () {
for (var element in listOfWidgets) {
if (element is Container) {
print(element.color);
}
}
}
Alternatively, you can also do things like this:
void printColorsOfAllContainers() {
final List<Color> listOfContainerColours = [];
for (var element in listOfWidgets) {
element as Container;
listOfContainerColours.add(element.color!);
}
}

Implementing an active item function in QAbstractListModel subclass for QML

I have created a ListView in QML, and I want to be able to implement something like an active item, using an QAbstractListModel as the model that the QML uses. To be more specific, I am using a generic object model, as described in the answer to this question. However, in my QML delegate I have something like this:
Component
{
id: itemDlgt
Rectangle
{
id: rec
width: 50
height: 50
color: "#645357"
property bool itemActive: false
AbstractItem //AbstractItem is the class that my object model uses. Its only property is a boolean value
{
id: s
}
MouseArea
{
anchors.fill: parent
onClicked:
{
s.status = !s.status
itemActive= s.status // using this to trigger onItemActiveChanged
console.log(s.status)
console.log(index)
}
}
onItemActiveChanged:
{
if (itemActive == true)
rec.color = "#823234"
else
rec.color = "#645357"
}
}
}
What I want to do, is have only one item in the ListView to hold a true value at a time. As soon as another item is clicked, I want to set the AbstractItem of the previously selected item to false, then set the AbstractItem of the new item to true.
Of course, I could use something like this:
ListView
{
id: view
anchors.fill: parent
clip: true
model: myAbstractModel
delegate: itemDlgt
spacing: 5
focus: true //using focus could allow to highlight currently selected item,
//by using ListView.isCurrentItem ? "blue": "red"
}
But this doesn't seem to work with a QAbstractListModel, since neither arrow keys, nor clicking on an item seems to highlight the current item.
In addition, I want that item to be highlighted again, in the event that the ListView is forced to reset itself from the c++ side, when I use beginResetModel() and endResetModel(). If I were using a QAbstractListModel as described in Qt Documentation, I would be able to do that easily, by saving the index of the selected item, and storing it until a new item was selected. In other words, something like this:
//somewhere in QAbstractListModel's subclass .h file
int x; // temporary storage for keeping currently selected item
//QAbstractListModel's subclass .cpp file
changeCurrentItem(int index) // function that gets called when user selects an item
{
//...
//Checking if x has a value, If not, I simply set the
//needed item's value to true, and then set x to the value of index.
//Else do the following...
m_ItemList.at(x).setToFalse();
m_ItemList.at(index).setToTrue();
x = index;
}
But I was facing several issues when I was using that, which is the reason why I decided to use a generic object model, which seems to be more flexible.
Finally, I want to be able to send a signal to the c++ side of the code whenever the currently selected item changes, which is trivial with a MouseArea, but I know not of a method to do that using the ListView's focus property, should that be an option.
To make long things short, this is my question in a few words:
Am I missing something in regards to QML code, that would allow me to highlight the currently selected item, while also being able to keep it active after reseting ListView, and being able to send a signal to c++ when it changes?
If not, is there a way to implement a function inside my generic object model, that keeps track of the currently selected item, so that I can highlight it?
If using the ListView's currentIndex property is the goto approach.
Simply set the color in the delegate via:
color: index == view.currentIndex ? "blue": "red"
And don't forget that in order for this to work, you must set the active index, it doesn't work by magic, and by default it will be -1, so no item will be highlighted:
//in the delegate mouse area
onClicked: view.currentIndex = index
The same applies to keyboard events too, you will have to tell the view what to do with the events, so you can have the up and down keys increment and decrements the current index.
There is another approach, which is more applicable if you want to detach the active item functionality from any view, and have it at item level:
property ItemType activeItem: null
function setActiveItem(item) {
if (activeItem) activeItem.active = false
activeItem = item
activeItem.active = true
}
The focus property only specifies whether an item has keyboard event focus or not.
If you want to signal on the the index change, use onCurrentIndexChanged

Sitecore get all parent (ancestor) with little processing (performance)

I'm trying to come up with solution on getting all the ancestors for the context item. One option is to store _path in index and other option is to do similar to one below:
http://www.glass.lu/Blog/GettingAncestors
I'm having no luck with getting the solution work for above (glass mapper) solution.
I got the index solution working but would like to avoid using index just to get the _path (collection of ancestors) as we don't have any other requirements to use index e.g. search etc.
Appreciate if someone could share the snippet for working solution or even better if Glassmapper has already included the above blog solution.
The most efficient way to check if one item is a descendant of another is to simply check that the current item property Paths.LongID starts with the LongID of the parent item:
Item currentItem = Sitecore.Context.Item;
IList<Item> menuItems = GetMenuItems();
foreach (var menuItem in menuItems)
{
bool isActive = currentItem.Paths.LongID.StartsWith(menuItem.Paths.LongID);
// do code
}
This will work since the path GUIDs are unique for each item.
Alternatively, if you want to use Glass models only then you can use the SitecoreInfoType.FullPath attribute:
[SitecoreInfo(SitecoreInfoType.FullPath)]
public virtual string FullPath { get; private set; }
And then from your code you can simply check:
Item currentItem = Sitecore.Context.Item; //or use SitecoreContext() to get a strongly types model
IEnumerable<MenuItem> menuItems = GetMenuItems();
foreach (var menuItem in menuItems)
{
bool isActive = currentItem.Paths.FullPath.StartsWith(menuItem.FullPath);
// do code
}
Just a word of warning, since each menu item now need code to run in order to determine state, this will make your menu component difficult to cache, resulting caching too many variations or caching per page. You would be better to move this logic into Javascript to set the menu state using the current page URL, this will allow your component to be cached once for all pages.

Pass parameters to rendering using ItemRendering in Sitecore

I have a rendering that calls its datasources children. Each child item has a rendering attached in the renderings field.
I am calling
#Html.Sitecore().ItemRendering(item)
Which works.
However I want to pass some parameters to the child's rendering, so I tried the following code;
#Html.Sitecore().ItemRendering(item, new { Parameters = "active=1" })
But the parameters do not get passed to the child rendering when I call #Html.Sitecore().CurrentRendering.Parameters["active"]
So I tried #Html.Sitecore().ItemRendering(item, new { Active = 1 }). I called it again in the child rendering and still no luck.
Is there a way to pass parameters to the child using #Html.Sitecore().ItemRendering()
The ItemRendering method does not seem to handle properties correctly (or as one would expect!).
A work-around is to use #Html.Sitecore().Rendering() instead. You can use this in the same way as the ItemRendering method with the work-around below. Note that you should be setting the "Renderers" field of the datasource item (or its template standard values) rather than the "Renderings" field as you mentioned:
#Html.Sitecore().Rendering(item["__Renderers"], new {Datasource = item.ID, Message = "Hello World"})
In the child rendering, use the Properties property, not Parameters:
#Html.Sitecore().CurrentRendering.Properties["Message"]
var viewData = new ViewDataDictionary();
viewData["active"] = "1";
Html.Sitecore().ItemRendering(ItemToRender, viewData);
In the rendering for the Item, you can access the viewdata like this:
(ViewData["active"] != null && int.Parse(ViewData["active"].ToString()) == 1)

Qt 5.4/Qml: Prevent binding loop

I have a global singleton "Settings" which holds application settings. When I try to run the following code I get a QML CheckBox: Binding loop detected for property "checked":
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
Settings.someSetting = checked;
}
}
It is obvious why this error occurs, but how can I correctly implement this functionality without a binding loop? E.g. I want to save the current checked state of the checkbox in the settings singleton.
I am using Qt 5.4 and Qml Quick 2.
Regards,
Don't bind it. Because the check box does not fully depend on Setting.someSetting.
When a user clicked the checkbox, the CheckBox.checked is changed by itself. At the same time, the property binding is no longer valid. Settings.someSetting cannot modify the CheckBox after it is clicked by user. Therefore, the checked: Settings.someSetting binding is wrong.
If you want to assign an initial value to the check box when the component is ready, use Component.onCompleted to assign it:
CheckBox {
id: someSettingCheckBox
Component.onCompleted: checked = Settings.someSetting
onCheckedChanged: Settings.someSetting = checked;
}
If you are working on a more complex scenario, the Setting.someSetting may be changed by some other things during runtime and the state of the check box is required to be changed simultaneously. Catch onSomeSettingChanged signal and explicitly changed the check box. Submit the value of someSettingCheckBox to Settings only when the program/widget/dialog/xxx finished.
CheckBox { id: someSettingCheckBox }
//within the Settings, or Connection, or somewhere that can get the signal.
onSomeSettingChanged: someSettingCheckBox.checked = someSetting
I prefer this solution
// Within the model
Q_PROPERTY(bool someSetting READ getSomeSetting WRITE setSomeSetting NOTIFY someSettingChanged)
void SettingsModel::setSomeSetting(bool checkValue) {
if (m_checkValue != checkValue) {
m_checkValue = checkValue;
emit someSettingChanged();
}
}
// QML
CheckBox {
checked: Settings.someSetting
onCheckedChanged: Settings.someSetting = checked
}
The trick is you protect the emit with an if check in the model. This means you still get a binding loop but only a single one, not an infinite one. It stops when that if check returns false thereby not emitting to continue the loop. This solution is very clean, you do not get the warning, and yet you still get all the benefits of the binding.
I want to talk about the limitations of the other solutions presented
CheckBox {
Component.onCompleted: checked = Settings.someSetting
onCheckedChanged: Settings.someSetting = checked;
}
In this solution you lose your binding. It can only have a default setting on creation and be changed by the user. If you expand your program such that other things change the values in your model, this particular view will not have a way to reflect those changes.
Settings {
id: mySettings
onSomeSettingChanged: checkBox.checked = someSetting
}
CheckBox {
id: checkBox
onCheckedChanged: mySettings.someSetting = checked
}
This solution was mentioned to address these problems but never written out. It is functionally complete. Model changes are reflected, the user can change the data, and there are no binding loops because there are no bindings; only two discrete assignments. (x: y is a binding, x = y is an assignment)
There are a couple problems with this. The first is that I think its ugly and inelegant, but that is arguably subjective. It seems fine here but if you have a model representing 10 things in this view, this turns into signal spaghetti. The bigger problem is that it does not work well with delegates because they only exist on demand.
Example:
MyModel {
id: myModel
// How are you going to set the check box of a specific delegate when
// the model is changed from here?
}
ListView {
id: listView
model: myModel.namesAndChecks
delegate: CheckDelegate {
id: checkDelegate
text: modelData.name
onCheckStateChanged: modelData.checkStatus = checked
}
}
You can actually do it. I've made up custom QML signals and connections to do it, but the code complexity makes me want to hurl, and even worse you could possibly be forcing creation of a delegate when it is not necessary.
If you don't want to make a binding loop - don't make a binding, use a proxy variable, for example. Other simple solution can be to check the value:
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
if (checked !== Settings.someSetting) {
Settings.someSetting = checked;
}
}
}
You can also make two-way binding to resolve this issue:
CheckBox {
id: checkBox
Binding { target: checkBox; property: "checked"; value: Settings.someSetting }
Binding { target: Settings; property: "someSetting"; value: checkBox.checked }
}
Sometimes it is useful to separate input and output values in control. In this case control always displays real value and it can also show a delay to the user.
CheckBox {
checked: Settings.someSetting
onClicked: Settings.someSetting = !checked
}