Is it possible to put data in widget before build? - flutter

I have a system that sends 2 things in order:
sends info on what widget to build (Label, text input, button, etc)
sends data to be inserted into the widget (label text, text input hint text, etc)
the issue is from what I know about flutter, I couldn't add data in after I made the widget, before building (I think globalkey for the state only works AFTER the widget is built)
does anyone know if it's possible to put the data into the widget that's already defined before build?
I can send some code if anyone requests it but this is a general question.
Example:
(I build the widget using widgetListData (the individual data isnt known yet))
#override
Widget build(BuildContext context) {
List<Map<String, dynamic>> widgetListData = [
{
"WidgetType": "TextField",
"ButtonText": "Account Receivable",
"Position": "New",
"ID": "text_textfield"
}
];
List<Widget> widgetList = createWidgetList(widgetListData, passData);
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text("Menu"),
),
body: SingleChildScrollView(
child: Column(
children: widgetList,
))),
);
}
in the actual app, the widgetListData will come from a List of actions, the first one being this create widget action, and the second one being display/ put data
the code i showed will be done first as it is the first element of the action List like so:
List<Map<String, dynmaic>> actionList = [
{"Type": "CreateWidget", "Content": ...},
{"Type": "PutData", "Content": ...}
]
the put data action will be processed AFTER i finished creating the widgets, but before I build them.

Create a class which will take 2 parameters (1. Widget type, 2. Widget data). Based on the widget type create widgets and add data into that widget. For example you get widget type text then return a widget Text('$content') or if you get widget type button then return MaterialButton(child:Text(content), onPressed(){}).

Related

Generic filter Flutter

Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...

passing data to a new scree nusing builder (flutter)

I'm tryng to pass data from grid view builder to a materialpageroute component to a new screen, I already pass movies index, but can't seem to give it to the new screen
In the first screenshot, you are trying to assign title as a parameter in onTap function. You cannot do that in onTap.
I think you want to pass the title to DetailPage, you can get the title in movies[index], so you don't have to declare a title.
In the second screenshot, if you want to show the title of your movie in the AppBar, you must do it using title parameter.
AppBar(
title: Text(movies[index].original_title),
),
And you cannot give just one widget to Column's children parameter. It must be a list.
Column(
children: [
_yourWidget(),
],
), // put your widget inside list
If you have one widget, you don't need to use Column.
Lastly, you get just one movie, so that you don't have to use ListView. You can use a single widget like Container. And if you want your view can be scrollable, you can wrap the Column with SingleChildScrollView, like this:
SingleChildScrollView(
child: Column(
children: [
_yourWidget(),
],
),
),
Edit: You didn't declare movies at the top:
class DetailPage extends StatelessWidget {
final Movie? movies; // declare this first
const DetailPage {...} // your constructor
}

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.

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.

Flutter - Keep page static throughout lifecycle of app?

I have created an AppDrawer widget to wrap my primary drawer navigation and reference it in a single place, like so:
class AppDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text("Page1"),
trailing: new Icon(Icons.arrow_right),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => Page1.singleInstance));
}
),
new ListTile(
title: new Text("Page2"),
trailing: new Icon(Icons.arrow_right),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new Page2("Page 2")));
}
),
]
),
);
}
}
I have also created a custom AppScaffold widget, which simply returns a consistent AppBar, my custom AppDrawer, and body:
class AppScaffold extends StatelessWidget {
final Widget body;
final String pageTitle;
AppScaffold({this.body, this.pageTitle});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(title: new Text(pageTitle), backgroundColor: jet),
drawer: AppDrawer(),
body: body
);
}
}
I have created two pages: Page1, and Page2. They are simple right now, and look something like this:
class Page1 extends StatelessWidget {
final String pageText;
Page1(this.pageText);
static Page1 get singleInstance => Page1("Page1");
Widget build(BuildContext context) {
return AppScaffold(
pageTitle: this.pageText,
body: SafeArea(
child: Stack(
children: <Widget>[
Center(child: SomeCustomWidget())
],
)
),
);
}
}
class Page2 extends StatelessWidget {
final String pageText;
Page2(this.pageText);
#override
Widget build(BuildContext context) {
return AppScaffold(
pageTitle: this.pageText,
body: SafeArea(
child: Stack(
children: <Widget>[
Center(child: SomeOtherCustomWidget())
],
)
),
);
}
}
When I run my app, I can see the navbar and drawer correctly. I can click on the links in the drawer to navigate between my pages. However, each time I navigate to a page, all of the widgets on that page get reset to their initial state. I want to ensure that the widgets do not get reset. Another way to think of this is: I only want one instance of each page throughout the lifecycle of the app, instead of creating them new whenever a user navigates to them.
I tried creating a static instance of Page1 that the Drawer uses when the onTap event is fired, but this does not work. Am I thinking about this incorrectly? Do I need to convert to a Stateful widget?
Oh, you're in for a treat... This will be kinda long (sorry) but please read all of it before making decisions and taking action - I promise I am saving you time.
There are many different solutions to this problem, but in general what you're asking about is state management (which is really software engineering, more info here - Understanding state management, and why you never will).
I'll try my best to explain what is happening in your specific case...
Problem:
Think of Navigator as a List of application states, which you can manipulate via its various methods (i.e. pop(), push(), etc.), with this in mind it is clear what is happening - on a button press you're actually removing the current state (page) and right after that you're pushing a new instance of your state (page).
Solution(s):
As I said, there are many solutions to this problem, for example, you may be tempted to store the state (the changes you made to a particular "page") somewhere in a var and inject that var when navigating between "pages", when creating a new instance of that page, but you'll soon run into other problems. This is why I don't think anyone can provide a simple solution to this problem...
First, may I suggest you some useful reads on the matter:
Flutter official docs on state management - When you get to the "Options" section of this, the fun part begins and can quickly get overwhelming, but fear not :P
Be sure to read the medium article mentioned in the start of my answer too, I found it really helpful.
These reads will be more than enough to help you make a decision, plus there are a ton of articles on Medium and YouTube videos touching on the matter of state management with Flutter (even some from the authors of the framework) - just search for "State management with Flutter".
Now my own personal opinion:
If it's a really simple use case and you don't plan to grow (which is almost never the case, trust me), you can just use StatefulWidgets in combination with setState() and maybe InheritedWidget (for dependency injection down the tree, or like React guys call it "lifting state up"). Or instead of the above, maybe have a look at scoped_model, which kinda abstracts all of this for you (tho, I haven't played with it).
What I use right now for a real world project is bloc and flutter_bloc (BLoC = Business Logic Component), I will not get into the details of it, but basically it takes the idea of scoped_model one step further, without over-complicating abstractions. bloc is responsible for abstracting away the "business logic" of your application and flutter_bloc to "inject" the state in your UI and react to state changes (official Flutter position on the matter is that UI = f(State)).
A BLoC has an input and an output, it takes in events as an input (can be user input, or other, any type of event really) and produces a state. In summary that's it about bloc.
A great way to get started is BLoC's official documentation. I highly recommend it. Just go through everything.
(p.s. This may be my personal opinion, but in the end state management in Flutter is all based on some form of using InheritedWidget and setState() in response to user input or other external factors that should change the application state, so I think the BLoC pattern is really on point with abstracting those :P)