Moving a dynamicaly generated Positioned Widget in Flutter - flutter

How can I move an Positioned Widget in Flutter that was created at runtime?
Szenario:
Lets say I have a Stack Widget. The children are a variable of type List<Widget>[].
I receive the command to create a new Positioned Widget.
A second stream sends updated x and y positions, those I want to apply on left and top of the dynamically created Positioned Widget.
To do this I added two variables to my class and set them to left and top of the Positioned Widget.
While this works as along as the Positioned Widget is created during build time, it does not work when I dynamically add the Widget to the List<Widget> at runtime. (the Positioned Widget appears, but i can not change left/top through the variables of _MyClass)
Some pseudo code that shows the bits.
class _MyClass extends State<StatefulWidget> {
double x = 0.0;
double y = 0.0;
List<Widget> myList = [];
[...]
void _updateLocation(dx, dy) { // called by a stream...
setState(() {
x = dx;
y = dy;
});
}
[...]
void addHeart() {
myList.clear();
myList.add (
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
);
}
[...]
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: Center(
child: Container (
child: Stack(
children: myList, //this does not update the top/left of the object
/* //THIS WORKS
children: <Widget>[
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
]
*/
)
)
),
);
}
I'm new to Flutter, and I propably don't get the mechanics right here, though, I tried to get this working with setState and Keys, but I feel like I'm missing a important bit of information here.
A possible hack I found is to provide the List<Widget> through a function to the Stack Widget and rebuilding the List<Widget> everytime I get an update on x and y. Which essentially deletes and adds the Widget from the stack everytime I get a new x and y... This seems like the worst way to achieve what I want.
How can I do this in a better way, what I'm missing here, why can I update top/left through x and y when creating the widget in build(..) but not when adding it to the list at runtime?

Ofcourse it would not work when you have a predefined list as children of stack. For your widget to move and change its location, flutter needs to actually rebuild the changed widgets, and note that it does call build method each time it re-renders.
So it is not a bad solution to call a function which creates the list each time (as it is exactly what flutter does for all widgets).
You do not need to worry about adding and deleting the items of stack each time the function is called, that is because flutter does not actually re-render anything on the page if it has not changed compared to current content of the page.

Related

Flutter: scrolling a list of DropdownButtons each containing 1k selections is too slow

I have a list of items (5-6 items) that are displayed using a ListView.builder. Each item contains a DropdownButton widget for the user to pick a number from 1 - 1000, thus containing 1000 DropdownMenuItems.
I implemented it as shown below, but the problem is that scrolling down the ListView is too slow and stutters. Even if the listView has 5 or 6 items, but note that each of them has an embedded DropdownButton containing 1000 DropdownMenuItems.
Is there a fix? Or another way to achieve my requirement?
N.B: Even if I reduce the number of DropdownMenuItems to 100, it still stutters when scrolling down the ListView.
class List extends StatelessWidget {
final List<Item> // Contains 5 items.
final List<int> quantityList = List<int>.generate(1000, (int i) => i);
//--
child: ListView.builder(
itemBuilder: (buildContext, i) {
return MyItem(
quantityList,
);
},
itemCount: items.length(),
)
class MyItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: DropdownButton<int>(
items: quantityList
.map(
(int e) =>
DropdownMenuItem<int>(
value: e,
child: Text(e.toString()),
),
)
.toList(),
),
),
);
}
Edit
I changed MyItem class to be as below, but still, the same problem exists.
Tried using ListView and ListView.custom instead of ListView.builder, to build the whole list in advance instead of lazily according to this, but still same issue.
I also tried running the app using --profile configuration to simulate a release version. The performance is better but still suffers from terrible stuttering and lag. Tested on emulator and physical device.
class MyItem extends StatelessWidget {
List<DropDownMenuItem> quantityList; // contains 1k
#override
Widget build(BuildContext context) {
return Container(
width:300,
height:300,
child: DropdownButton<int>(
items: quantityList,
),
),
);
}
ListView will create and destroy widgets as they come in and out of view. Your MyItem widget is a very heavy widget (lots of stuff to create and destroy).
You probably don't want each MyItem to be a Scaffold(). Normally you only have 1 Scaffold() visible as it's a fancy root view for an app. It has an app bar, snack bar, drawer, etc. Try having just your Container(...) that's currently under body: as the return from your MyItem.build().
In the items: of your DropdownButton, you build and destroy the list of items when the DropdownButton scrolls in and out of view. If this list is the same for every widget in your list like in your test code above, create one List<Widget>[] that contains your DropdownMenuItem widgets and pass it in to your MyItem() widgets. Something like:
//In your widget with the ListView
List<Widget> myItems;
//In the initState() of your widget with the ListView
...
myItems = quantitySelection.map(
(int e) => DropdownMenuItem<int>(
value: e,
child: Text(e.toString()),
),
).toList(),
...
//In your ListView.builder()
return MyItem(
...
items: myItems,
...
);
//In your MyItem.build() -> DropdownButton()
...
DropDownButton(
items: items
),
...
FWIW - we have a ListView with complex children that we test with 10,000 items. There's a significant difference in performance between the debug and release builds in Flutter. It stutters a little in the debug builds but is very smooth in the release builds.
I was able to solve the issue by only using the cacheExtent property of ListView.builder, setting it to list.length*200. This is kind of a workaround knowing that in my case the list length will always be small.
Pre-building the DropDownMenuItems had no sensed performance enhancement by a user, but it is a good recommended practice regardless, as instead of building the same DropDownMenuItems over and over again for every list item.
Although according to the docs: ListView and ListView.separated does not lazy load items rather build them all up at the beginning, I kept experiencing the same stuttering and lag during scrolling as with ListView.builder.

Flutter responsive layout by enclosing everything in a root widget / How to change all double values of descendant widgets using one root widget

I was writing my own responsive layouts like this:
Instead of writing Normal code
Container(padding: EdgeInsets.all(10)),
Text("abc", style: TextStyle(fontSize: 20)),
I used a function responsiveSize()
Container(padding: EdgeInsets.all(responsiveSize(10, context))),
Text("abc", style: TextStyle(fontSize: responsiveSize(20, context))),
and define the function responsiveSize() as
double responsiveSize(double number, BuildContext context){
if (MediaQuery.of(context).size.width < 450) {
return number;
} else {
return number * 1.5;
}
}
or something like this which I can easily change.
But the problem with this is that I have to wrap every double value in my code with the function responsiveSize(), which is very tedious. I am looking for a way to wrap all my widgets in one root widget, like wrapping my MaterialApp widget inside a ResponsiveSize widget:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ResponsiveSize(
child: MaterialApp(
home: HomePage(),
),
);
}
}
and writing Normal code (as mentioned above, use double values as it is without wrapping them in a responsiveSize() function) and define ResponsiveSize widget in some way which I don't know. This way I can use responsive layout by just one widget and can easily remove the root ResponsiveSize widget if I don't want it later.
However this involves changing all double values in all descendant widgets of the ResponsiveSize widget, and this is something which I don't know how to do.
So, I would like to know how I can change all double values of all descendant widgets of a root widget? How should I define the root widget?
The LayoutBuilder widget might be what you're looking for.

How do I make a child widget expand to fill a parent container inside of a stack when the child has no parameters to alter its layout?

I'm building a card game and using flame to pull the cards from a sprite sheet. The problem is that I set the width and height of the Container that holds the SpriteWidget, but the SpriteWidget expands to either the width or the height of the container, but not both. I want it to expand/stretch to be the same size as the parent container. Unfortunately, the SpriteWidget really has no parameters that could be used to change its size.
I've spent several hours scouring the internet for a solution and tried a number of widgets including FittedBox, Flex, Positioned.fill, etc., but I'm unable to achieve the desired effect. How can I make the SpriteWidget stretch to fill its parent when it has no parameters to do so?
class _PlayerHandPortraitLayout
extends WidgetView<PlayerHand, _PlayerHandController> {
#override
final state;
const _PlayerHandPortraitLayout(this.state) : super(state);
#override
Widget build(BuildContext build) {
return Stack(
children: state.displayHand().asMap().entries.map((cardItem) {
var index = cardItem.key;
var card = cardItem.value;
return Positioned(
left: index * CARD_OVERLAP_OFFSET,
child: Draggable<Container>(
childWhenDragging: Container(),
child: Container(
color: Colors.purple,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
feedback: Container(
color: Colors.yellow,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
),
);
}).toList(),
);
}
}
actually this will be not possible, SpriteWidget is designed to expand as long as it fits on the smallest dimension available on its parent, you can check on it source code here.
This is done so the Sprite will not get distorted when its parent has a different aspect ratio than the ratio of the Sprite.
If you have an use case where you would want the Sprite to get intentionally distorted, please open an issue on the Flame repository explaining the case, and we can try to take a look on it.

Finds Element without key

i'm still new in using flutter driver in testing, but as far as i know there are few identifiers that we can use to locate / identify elements, like By Text, By Type, etc
But the problem is, the app that i want to test doesn't have the identifier that i can use to locate them (please correct me if i'm wrong).. the widget code of the app looks like this
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => _controller.nextPage(),
),
),
);
}
where that widget is on a class that extends StatefulWidget.
How can i locate that icon in my test script and click it? can i use something like this? And what type of finder should i use? (byValueKey? bySemanticLabel? byType? or what?)
static final arrowKey = find.byValueKey(LoginKey.nextButton);
TestDriverUtil.tap(driver, arrowKey);
We have text and value checks here in Flutter Driver but if you don't have that you can always go the the hierarchy of app.
what I mean by hierarchy is so button has fix or specific parent right?
Let's take your example here, We have Align > Container > IconButton > Icon widget hierarchy which will not be true for others like there might be IconButton but not with the Container parent.
or StreamBuilder or anything that we can think of.
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => print("clicked button"),
),
),
);
}
This hierarchy should be atleast ideal for top bottom or bottom top approach.
Now what I mean by Top to bottom approach is Align must have IconButton and for bottom to up approach we are saying IconButton must have Align widget as parent.
Here i have taken top down approach so what I'm checking from below code is finding IconButton who is decendent of Align Widget.
also i added firstMatchOnly true as I was checking what happens if same hierarchy appears for both so
test('IconButton find and tap test', () async {
var findIconButton = find.descendant(of: find.byType("Align"), matching: find.byType("IconButton"), firstMatchOnly: true);
await driver.waitFor(findIconButton);
await driver.tap(findIconButton);
await Future.delayed(Duration(seconds: 3));
});
to check for multiple IconButtons with same Align as parent, we need to have some difference like parent should be having Text view or other widget.
find.descendant(of: find.ancestor(
of: find.byValue("somevalue"),
matching: find.byType("CustomWidgetClass")), matching: find.byType("IconButton"), firstMatchOnly: true)
usually I go something like above where I have split the code in seperate file and check for that widget.
But ultimately find something unique about that widget and you can work on that.
**In Lib directory dart class for connecting that widget**
class Testing extends StatelessWidget {
Testing();
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: YourClass(), // Next button containing class that need to test
);
}
}
**In Test directory**
testWidgets('Next widget field test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(Testing());
// find Widget
var buttonFind = find.byIcon(Icons.arrow_forward);
expect(buttonFind, findsOneWidget);
IconButton iconButton = tester.firstWidget(buttonFind);
expect(iconButton.color, Colors.blue);
});

Is that possible to cache widget to resue?

I am using getbody() method to get app body and the getbody() return a Widget If i change the variable ct count the the getbody() will return different widget and each widget is stroied in Listqueue<MyPage()> if i set variable ct value 1 it return widget from my list at position 1 and if i set variable ct 2 then it will return corresponding widget .
But the problem is in each widget i am doing an api call but when i reuse that widget it is again calling the API call and why this is calling after first time nd how to stop calling api when i am reusing the widget
ListQueue<Page> page1, page2, page3, page4;
_AppFramePageState() {
page1 = ListQueue();
page1.add(Page(widget: AppHomePage(), title: "Home Page"));
page2 = ListQueue();
page2.add(
Page(widget: TemplesListing(), title: "Temple listing"));
page3 = ListQueue();
page3.add(Page(widget: AppCommunities(), title: "Communities"));
page4 = ListQueue();
page4.add(Page(widget: AppVideos(), title: "Media"));
}
If user click on cart button from toolbar then i will add more value to page1 list and if user click on back button then i will remove last item from page1 list
Widget getBody() {
//This code is solved my 50% issue but renaming 50% is there. How to solve this issue?
return IndexedStack(
index: _selectedIndex,
children: <Widget>[
page1.last.widget,
page2.last.widget,
page3.last.widget,
page4.last.widget
],);<br>
//This is the code i used first time
switch (_selectedIndex) {
case 0:
return page1.last.widget;
case 1:
return page2.last.widget;
case 2:
return page3.last.widget;
case 3:
return page4.last.widget;
}
}
class Page{
Widget widget;
String title;
Page({this.title,this.widget});
}
Note: All widgets are StatefulWidget
In general, it's not possible to reuse the widgets.
You can manually reuse the cache widget by comparing old and new state of the widgets. Which is very lengthy and i don't think you should follow it.
There are many State management architecture patterns like Provider, BLOC, MOBX etc. to manage your app in a great way. They are used to improve your app performance and decrease widget re-renders, manage data flow across the whole app etc.
One more thing you can do to make your Stateful widgets more impactive by using the const keyword whenever possible.
like for following widget ,
Column(
children: <Widget>[
// Widget 1
Center(child: Text(dynamic_value),),
// Widget 2
Container(
child: const Center(
child: const Padding(
padding: const EdgeInsets.all(10),
child: Text("Hello"),
),
),
),
],
)
In above example for Widget 1, you can't use "const keyword" as it depends on "dynamic_value".
While for Widget 2, you can use the "const keyword" which will be useful if your build method gets called again then "Center", "Padding" & "Text" widgets will be not called again as they are declared as constant widgets.