setState() clears the data in form element data flutter - user-interface

I'm new to flutter and experimenting with Sateful widget. Here is the thing, In my UI screen layout, I have two different widgets
DropdownButton widget
TextFormField which holds card number.
When I was trying to update dropdown selected value to the DropdownButton widget, it automatically clears the text in TextFormField. Does it require to store text in global variable to restore again every time we call setState() method to update the values?
Here is the code for widgets,
DropdownButton
new Padding(
padding: const EdgeInsets.all(15.0),
child: new Column(
children: <Widget>[
DropdownButton<String>(
value: _referPractice,
isDense: true,
hint: new Text(CONST_SELECT),
items: _stdCodesList.map((value) {
return new DropdownMenuItem<String>(
value: value.dialCode,
child: new Text("${value.code} ${value.dialCode}"),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
_referPractice = newValue; // here I`m trying to update selected value.
});
},
)
],
)),
TextFormField
TextFormField(
controller: _textController,
keyboardType: TextInputType.number,
style: new TextStyle(
fontWeight: FontWeight.w200,
color: Colors.black,
fontSize: 18.0,
),
decoration: new InputDecoration(
hintText: HING_ENTER_NUMBER,
suffixIcon: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
)),
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return ERROR_CARD_DETAILS;
}
},
),
I understand that Stateful widget rebuild the widget every time when ever it calls setState but how do I persist the data for form data which is not stored anywhere yet.
Suggestions please! Thanks in advance.

With the given code, one mistake I can think of is creating TextEditingController every time.
#override
Widget build(BuildContext context) {
var _textController = TextEditingController(); //problem
// build and return widgets
It should be in outside of build method. We can have it in constructor or initState.
If you have _textController outside build, can you add some more code?

Related

flutter - bad perf- advice

I'm developing my first app with flutter. At some point I was wondering :Am I developping the UX part correctly ? Meaning am I using the proper widget, is there any better way to do that etc.. I find out about Flutter Performance on Intellj Idea and I saw that most of the pages I developed are red...
FYI : The code I created for a simple page
Flutter inspector result => radio-btn-aligned
import 'package:flutter/material.dart';
import 'package:testapp/my_theme.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: MyAppTheme(ctx: context).defaultTheme,
home: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: AddDailyTaskPage()),
);
}
}
enum Options { goal, category }
class AddDailyTaskPage extends StatefulWidget {
#override
_AddDailyTaskPageState createState() => new _AddDailyTaskPageState();
}
class _AddDailyTaskPageState extends State<AddDailyTaskPage> {
Options _options = Options.goal;
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Container(
child: Text("Task Description"),
),
Container(child: Row(
children: [
Expanded(
child: Row(
children: [
Radio<Options>(
value: Options.goal,
groupValue: _options,
onChanged: (Options value) {
setState(() {
_options = value;
});
},
),
Text(
'Goal',
style: new TextStyle(fontSize: 16.0),
)
]
)
),
Expanded(
child: Row(
children: [
Container(
child: Radio(
value: Options.category,
groupValue: _options,
onChanged: (Options value) {
setState(() {
_options = value;
});
},
),
),
Container(
child: Text(
'Category',
style: new TextStyle(
fontSize: 16.0,
),
),
)
],
)
)
],
),)
// Container(
// child: TextField(
// maxLines: 10,
// decoration: InputDecoration(
// // suffixIcon:
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.grey, width: 5.0),
// ),
// enabledBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.black, width: 5.0),
// ),
// hintText: 'Description task',
// ),
// ),
// )
],
),
);
}
}
To see the difference, I checked the sample code provided while creating a flutter Project
FYI :
flutter performance result : auto-increment
As we can see on the previous pic, it doesn't seems optimised.. :/
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Few questions :
1/ Does anyone has / knows a good flutter repo with ton of examples based on performance ?
2/ What is your standard in term of perf ? I mean on my virtual device, the UX seems fluid but if the Flutter Perf is "red", then I'm guessing I'm doing something wrong and there is a better way to do ?
3/ Do you guys knows a website / forum / someone who's willing to do a core review once a week to help me implementing good pattern in my flutter application ?
4/ What is wrong with my current design ? What is wrong with the default design ? Why the performance are doesn't seems good ? I started to read the official documentation for the perf, but how could I know if the UX itself have good perf or not ? Actually by testing some stuff, I find out that putting everything in Container / Row or Column widget, improved a lot the performance but even with this widgets, it's not enough :/
Any advice ?
Thanks for your help
You use a StatefulWidget for the whole page. That means that when you call setState() the whole page gets rebuild.
One example is:
Radio<Options>(
value: Options.goal,
groupValue: _options,
onChanged: (Options value) {
setState(() {
_options = value;
});
},
),
There's no need to rebuild your whole page when you set an option. If you extract that code out into it's own StatefulWidget, only this portion gets rebuild.
You can click on "Track Widget rebuilds" to see what Widgets do rebuild in your app and then think about whether those Widget actually should rebuild.
Once you do smaller Widgets you get the problem that events in one Widget are supposed to influence other Widgets. That's when state management solutions come into play. The weather example of the Bloc package shows a good structure of an app that you can copy.

How to rebuild a particular widget property instead of the whole widget?

I am making an application and for showing circular progress indicator i had used library of modal_progress_hud and when i set the state of inAsyncCall true or false, it rebuild all of its child widgets inside modal_progress_hud. And i do not want to rebuild all the UI again and again. It decreases the efficency of application by increasing usage of GPU. Is there any other way to make it better, that we just change the property and rebuild that property of widget only instead of whole screen and widgets inside it.
bool circularindicator = false;
Color circularColor;
double circularOpacity;
child: ModalProgressHUD(
inAsyncCall: circularindicator,
child: body,
color: circularColor,
opacity: circularOpacity,
),
In body, we have all of the widgets in listview.
And after pressing the login button we called the showProgress() method and set the state and rebuild the widget.
#override
showProgress() {
setState(() {
circularindicator = true;
circularOpacity = 0.5;
circularColor = Colors.grey;
});
}
Make your class StatelessWidget and use Provider Package for state management. Inside the widget build function keep the Provider listener false and wrap the changeable widget with Consumer Widget.
Widget build(BuildContext context) {
final cart = Provider.of<Cart>(context, listen: false);
return GridTileBar(
leading: Consumer<Product>(
builder: (ctx, product, child) => IconButton(
onPressed: () {
product.toggleFavoriteStatus();
},
icon: Icon(product.isFavorite ? Icons.favorite : Icons.favorite_border),
color: Theme.of(context).accentColor,
),
),
trailing: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
cart.addItem(product.id, product.title, product.price);
},
color: Theme.of(context).accentColor,
),
backgroundColor: Colors.black87,
title: Text(product.title),
);
}
For more details on provider package: State Management Using Provider

Why Checkbox animation doesn't show in Flutter?

I used Checkbox to update some data. The sharing data method is Consumer<T>.
The problem is that Checkbox can work ok on changing state, but the checking animation is missing.
I have located the problem, if I used notifyListeners()to notify the Data changed, then the animation of Checkbox is missing.
The Widget code just like below:
bool _value = false;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Row(
children: <Widget>[
Checkbox(
activeColor: Colors.green,
value: _value,
onChanged: (value) {
setState(() {
_value = value;
widget.updateEnergy(); //The problem is Here!!!!!
});
},
),
Text("foodName",
style: TextStyle(color: Colors.blue,
fontSize: 13, fontWeight: FontWeight.w300),
),
Spacer(),
],
);
}
If I used the widget.updateEnergy(); to update the data in onChanged(), the animation is missing.
The updateEnergy() as below:
void updateCurrentEnergy(){
_currentEnergyCount.setValue(_getCurrentEnergyCount(), _dailyTargetEnergy.unit);
notifyListeners();
}
The key is "notifyListeners()", if removed the invoke, the animation is return.
And my data class is "with ChangeNotifier", so notifyListeners() is from ChangeNotifier.
And I used the ChangeNotifierProvider.value(value: dailyRecord), to share the data in the father node. And user the Consumer<DailyRecord>(...using data.
I hope your help, how can get the animation of checkbox.
Thank you!

Flutter ListView.builder - How to Jump to Certain Index Programmatically

i have a screen that build using MaterialApp, DefaultTabController, Scaffold and TabBarView.
in this screen, i have body content that retreive a list of element from sqllite using StreamBuilder. i get exact 100 elements ("finite list") to be shown using ListView.
my question, using ListView.builder, How we can jump to certain index when this screen opened ?
my main screen:
...
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner : false,
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: Pigment.fromString(UIData.primaryColor),
elevation: 0,
centerTitle: true,
title: Text(translations.text("quran").toUpperCase()),
bottom: TabBar(
tabs: [
Text("Tab1"),
Text("Tab2"),
Text("Tab3")
],
),
leading: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: InkWell(
child: SizedBox(child: Image.asset("assets/images/home.png"), height: 10, width: 1,),
onTap: () => Navigator.of(context).pop(),
)
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Testing Index Jump',
child: Text("GO"),
),
body:
TabBarView(
children: [
Stack(
children: <Widget>[
MyDraggableScrollBar.create(
scrollController: controller,
context: context,
heightScrollThumb: 25,
child: ListView(
controller: controller,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(30, 15, 30, 8),
child: Container(
alignment: Alignment.center,
height: 30,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: TextField(
style: TextStyle(color: Colors.green),
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(5),
border: InputBorder.none,
filled: true,
hintStyle: new TextStyle(color: Colors.green, fontSize: 14),
prefixIcon: Icon(FontAwesomeIcons.search,color: Colors.green,size: 17,),
hintText: translations.text("search-quran"),
fillColor: Colors.grey[300],
prefixStyle: TextStyle(color: Colors.green)
),
onChanged: (val) => quranBloc.searchSurah(val),
),
)
)
),
//surah list
streamBuilderQuranSurah(context)
],
)
) // MyDraggableScrollBar
],
),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
)
)));
}
Widget streamBuilderQuranSurah(BuildContext ctx){
return StreamBuilder(
stream: quranBloc.chapterStream ,
builder: (BuildContext context, AsyncSnapshot<ChaptersModel> snapshot){
if(snapshot.hasData){
return ListView.builder(
controller: controller,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount:(snapshot.data.chapters?.length ?? 0),
itemBuilder: (BuildContext context, int index) {
var chapter =
snapshot.data.chapters?.elementAt(index);
return chapterDataCell(chapter);
},
);
}
else{
return SurahItemShimmer();
}
},
);
}
...
class MyDraggableScrollBar.dart :
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
class MyDraggableScrollBar {
static Widget create({
#required BuildContext context,
#required ScrollController scrollController,
#required double heightScrollThumb,
#required Widget child,
}) {
return DraggableScrollbar(
alwaysVisibleScrollThumb: true,
scrollbarTimeToFade: Duration(seconds: 3),
controller: scrollController,
heightScrollThumb: heightScrollThumb,
backgroundColor: Colors.green,
scrollThumbBuilder: (
Color backgroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
Text labelText,
BoxConstraints labelConstraints,
}) {
return InkWell(
onTap: () {},
child: Container(
height: height,
width: 7,
color: backgroundColor,
),
);
},
child: child,
);
}
}
i have tried find other solutions but seems not working, for example indexed_list_view that only support infinite list
and it seems flutter still not have feature for this, see this issue
Any Idea ?
You can use https://pub.dev/packages/scrollable_positioned_list. You can pass the initial index to the widget.
ScrollablePositionedList.builder(
initialScrollIndex: 12, //you can pass the desired index here//
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
General Solution:
To store anything which can be represented as a number/string/list of strings, Flutter provides a powerful easy-to-use plugin which stores the values needed to be stored along with a key. So the next time you need you'll need to retrieve or even update that value all that you'll need is that key.
To get started, add the shared_preferences plugin to the pubspec.yaml file,
dependencies:
flutter:
sdk: flutter
shared_preferences: "<newest version>"
Run flutter pub get from the terminal or if your using IntelliJ just click on Packages get(You'll find it somewhere around the top-right corner of your screen while viewing the pubspec.yaml file)
Once the above command is successfully executed, import the below file in your main.dart or concerned file.
import 'package:shared_preferences/shared_preferences.dart';
Now just attach a ScrollController to your ListView.builder() widget and make sure that the final/last offset is stored along with a specific key using shared_preferences whenever the user leaves the app in any way and is set when the initState of your concerned widget is called.
In order to know to detect changes in the state of our app and to act with accordance to it, we'll be inheriting WidgetsBindingObserver to our class.
Steps to follow:
Extend the WidgetsBindingObserver class along with the State class of your StatefulWidget.
Define a async function resumeController() as a function member of the above class.
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
Declare two variables one to store and pass the scrollcontroller and the other to store and use the instance of SharedPreferences.
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Call resumeController() and pass your class to the addObserver method of the instance object in WidgetsBinding class.
resumeController();
WidgetsBinding.instance.addObserver(this);
Simply paste this code in the class definition (outside other member functions)
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
Pass the ScrollController() to the concerned Scrollable.
Working Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{
//[...]
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
}
#override
void initState() {
resumeController();
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Smart Scroll View"),
),
body: ListView.builder(
itemCount: 50,
controller: _scrollController,
itemBuilder: (c,i)=>
Padding(
padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),
child: Text((i+1).toString()),
),
),
),
);
}
}
Solution without knowing the size of your widgets
the Solution I found without knowing the size of your widget is displaying a reverse 'sublist' from the index to the end, then scroll to the top of your 'sublist' and reset the entire list. As it is a reverse list the item will be add at the top of the list and you will stay at your position (the index).
the problem is that you can't use a listView.builder because you will need to change the size of the list
example
class _ListViewIndexState extends State<ListViewIndex> {
ScrollController _scrollController;
List<Widget> _displayedList;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_displayedList = widget.items.sublist(0, widget.items.length - widget.index);
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
//here the sublist is already build
completeList();
});
}
}
completeList() {
//to go to the last item(in first position)
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
//reset the list to the full list
setState(() {
_displayedList = widget.items;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _scrollController,
reverse: true,
children: _displayedList,
),
]
);
}
}
The https://pub.dev/packages/indexed_list_view package could maybe help you out for this. Use something like this:
IndexedListView.builder(
controller: indexScrollController,
itemBuilder: itemBuilder
);
indexScrollController.jumpToIndex(10000);
I'll present another approach, which supports list lazy loading unlike #Shinbly 's method, and also support tiles in list to resize without recalculating the correct offset of the ListView nor saving any persistent information like "#Nephew of Stackoverflow" does.
The essential key to this approach is to utilize CustomScrollView, the CustomScrollView.center property.
Here's an example based on the example code from Flutter document (widgets.CustomScrollView.2):
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> top = [];
List<int> bottom = [0];
List<int> test = List.generate(10, (i) => -5 + i);
bool positionSwitcher = true;
#override
Widget build(BuildContext context) {
positionSwitcher = !positionSwitcher;
final jumpIndex = positionSwitcher ? 1 : 9;
Key centerKey = ValueKey('bottom-sliver-list');
return Scaffold(
appBar: AppBar(
title: const Text('Press Jump!! to jump between'),
leading: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
top.add(-top.length - 1);
bottom.add(bottom.length);
});
},
),
),
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RaisedButton(
child: Text('Jump!!'),
onPressed: () => setState(() {}),
),
Text(positionSwitcher ? 'At top' : 'At bottom'),
],
),
Expanded(
child: CustomScrollView(
center: centerKey,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = jumpIndex - 1 - i;
return Container(
alignment: Alignment.center,
color: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: jumpIndex,
),
),
SliverList(
key: centerKey,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = i + jumpIndex;
return Container(
alignment: Alignment.center,
color: i == 0
? Colors.red
: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: test.length - jumpIndex,
),
),
],
),
)
],
),
);
}
}
Explanation:
We use single list as data source for both SliverList
During each rebuild, we use center key to reposition the second SliverList inside ViewPort
Carefully manage the conversion from SliverList index to data source list index
Notice how the scroll view build the first SliverList by passing an index starting from bottom of this SliverList (i.e. index 0 suggests last item in the first list sliver)
Give the CustomeScrollView a proper key to decide whether to "re-position" or not
Working Example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ScrollToIndexDemo extends StatefulWidget {
const ScrollToIndexDemo({Key? key}) : super(key: key);
#override
_ScrollToIndexDemoState createState() => _ScrollToIndexDemoState();
}
class _ScrollToIndexDemoState extends State<ScrollToIndexDemo> {
late AutoScrollController controller = AutoScrollController();
var rng = Random();
ValueNotifier<int> scrollIndex = ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ValueListenableBuilder(
valueListenable: scrollIndex,
builder: (context, index, child) {
return Text('Scroll Demo - $index');
},
),
),
body: ListView.builder(
itemCount: 100,
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(8),
child: AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
highlightColor: Colors.black.withOpacity(0.1),
child: Container(
padding: EdgeInsets.all(10),
alignment: Alignment.center,
color: Colors.grey[300],
height: 100,
child: Text(
'index: $index',
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
scrollIndex.value = rng.nextInt(100);
await controller.scrollToIndex(scrollIndex.value, preferPosition: AutoScrollPosition.begin);
},
tooltip: 'Increment',
child: Center(
child: Text(
'Next',
textAlign: TextAlign.center,
),
),
),
);
}
}
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.builder(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);

How should I customize DropdownButtons and DropdownMenuItems in Flutter?

The default DropdownButton with DropdownMenuItems returns a light-grey dropdown. How should I customize the dropdown (e.g. background color, dropdown width)? I can change the style property in both DropdownButton and DropdownMenuItem, like this:
return new DropdownButton(
value: ...,
items: ...,
onChanged: ...,
style: new TextStyle(
color: Colors.white,
),
);
but this doesn't change the dropdown's background color.
Should I copy DropdownMenu and extend it? Does Flutter plan to add customization for this widget in the near future?
You can accomplish this by wrapping the DropdownButton in a Theme widget and overriding the canvasColor.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
int _value = 42;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.blue.shade200,
),
child: new DropdownButton(
value: _value,
items: <DropdownMenuItem<int>>[
new DropdownMenuItem(
child: new Text('Foo'),
value: 0,
),
new DropdownMenuItem(
child: new Text('Bar'),
value: 42,
),
],
onChanged: (int value) {
setState(() {
_value = value;
});
},
),
),
),
);
}
}
I was able to change the background for the Dropdown by wrapping it in a Container with the color property set.
Before:
After:
Here's the code:
Define these values in the widget state:
final items = ['One', 'Two', 'Three', 'Four'];
String selectedValue = 'Four';
then use this code
Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
// dropdown below..
child: DropdownButton<String>(
value: selectedValue,
onChanged: (String newValue) =>
setState(() => selectedValue = newValue),
items: items
.map<DropdownMenuItem<String>>(
(String value) => DropdownMenuItem<String>(
value: value,
child: Text(value),
))
.toList(),
// add extra sugar..
icon: Icon(Icons.arrow_drop_down),
iconSize: 42,
underline: SizedBox(),
),
);
As Collin said, your DropdownMenuItem will follow your ThemeData class. Not only its backgroundColor will match the canvasColor in your ThemeData class, but also it will follow the same TextStyle.
So, for a quick example:
new ThemeData(
fontFamily: "Encode Sans", //my custom font
canvasColor: _turquoise, //my custom color
//other theme data)
Furthermore, if you want to control the width of the menu, you can feed its child property a new Container and add the desired width, check the following GIF, I started with width: 100.0 then hot reloaded after changing it to 200.0, notice how the width was manipulated, just make sure you use a suitable width so that you do not get overflow problems later on when you use the menu within a more complex layout.
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title:new Text ("Test"),
),
body: new Center(
child: new DropdownButton(items: new List.generate(20, (int index){
return new DropdownMenuItem(child: new Container(
child: new Text ("Item#$index"),
width: 200.0, //200.0 to 100.0
));
})
, onChanged: null)
),
);
}
}
If you want the DropdownButton to fill the space that it is in, use the property isExpanded and set it to true
DropdownButton<String>(
isExpanded: true,
)
You can do something very simple in the latest version of Flutter.
The DropdownButton class has an inbuilt variable called 'dropdownColor' which can be assigned any color you need directly, without changing any 'ThemeData'. Automatically changes the color of the dropdown menu items as well.
Use this for color
DropdownButtonFormField(
items: null,
onChanged: null,
dropdownColor: Colors.red,
),
https://api.flutter.dev/flutter/material/DropdownButton/style.html will help you to figure out some stylings.
DropdownButton(
dropdownColor: Colors.grey,
value: this.repeatType,
onChanged: (String? value) {
print(value);
setState(() {
this.repeatType = value!;
});
},
selectedItemBuilder: (BuildContext context) {
return this.repeatTypes.map((String value) {
return Text(
this.repeatType,
style: const TextStyle(color: Colors.white),
);
}).toList();
},
items: this
.repeatTypes
.map((item) => DropdownMenuItem(
child: Text(
item,
style: TextStyle(color: Colors.green),
),
value: item,
))
.toList())
You can wrap it with container like this:
Container(
margin: const EdgeInsets.all(15.0),
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.white)
),
child: DropdownButton(
dropdownColor: Colors.white,
style: TextStyle(
color: Colors.black,
backgroundColor: Colors.white,
),
value: 'ar',
items: [
DropdownMenuItem(child: Text('English'), value: 'en'),
DropdownMenuItem(child: Text('العربية'), value: 'ar'),
],
),
)
The output:
It's so simple now,
Just use the dropdownColor property inside the DropdownButton widget like this:
DropdownButton(
dropdownColor: Colors.red, // here you change the background color
value: 'Your value',
items: [ ],
)
you can use the dropdown_button2 package.
You can use the dropdownDecoration property to customize the dropdown menu appearance. This is the best package I found to fully customize a DropdownButton

Resources