Is that possible to cache widget to resue? - flutter

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.

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
}

Is it possible to put data in widget before build?

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(){}).

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 Bloc , Bloc state , navigate?

what I’m facing now is after I implemented bloc following one of the tutorials, I'm stuck now in place where after I'm getting the response and the state is changed, I want to navigate to another widget
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(APP_TITLE),
),
body: buildBody(context));
}
}
BlocProvider<SignInBloc> buildBody(BuildContext context) {
return BlocProvider(
create: (_) => sl<SignInBloc>(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
if(state is Empty)
return MessageDisplay(message: 'Sign In please.',);
else if(state is Loaded)
return HomePage();
else
return MessageDisplay(message: 'Sign In please.',);
}
),
SignInControls(),
],
),
),
),
);
}
in state of loaded I want to navigate to another widget.
so how to achieve that, and what is the best way for it?
You can't use the navigator or change the state while the widget is being built (your case).
There're two ways
1. The old fashioned way
WidgetsBinding.instance.addPostFrameCallback((_){
// Your code goes here
});
2. Since you already implemented the BLOC library you have a more elegant way to achieve this by using BlocListener. you can learn more about it in the documentation
Hope i helped!
Navigation can be used like Inherited widgets:
Navigator nav = Navigator.of(this.context);
then you can use somthing like:
nav.push(MaterialPageRoute(builder: (context) => YourSecondPage()))
in flutter, you can't just move to some page directly. you should use a route.
I think the cleanest way to use named routes. this is an example:
// here you put a class of names to use later in all of your project.
class RouteNames{
static String homepage = "/";
static String otherPage= "/otherpage";
}
// in your main file , MyApp class
var routes = {
RouteNames.homepage: (context)=> new MyHomePage(),
RouteNames.otherPage: (context)=> new MyOtherPage()
};
// then use routes variable in your MaterialApp constructor
// and later on in your project you can use this syntax:
Navigator.of(context).pushNamed(RouteNames.otherPage);
I think this way is clean and it's centralized, it's good if you want to send arguments to routes.
To learn more about navigation: navigation official documentation is pretty good
A note about the Bloc builder & listener:
Since BlocBuilder is going to be called lots of times. it should only contain widgets and widgets only. if you put navigation code inside it, this code would be called multiple times.
As Ayham Orfali said You definitely should use BlocListener for that. Inside it you can listen to changes in state. here is an example
// some code
children: <Widget>[
BlocListener(
bloc: BlocProvider.of<SignInBloc>(context),
listener: (context, state) {
if(state is Loaded){
Navigator.of(context).pushNamed("some other page");
}
// else do nothing!
},
child:// just bloc builder which contains widgets only. ,
SignInControls(),
]
// some other code