So I know there are some similar questions about this issue but none of them worked for me. I have a ListView with different CheckboxListTiles and when I scroll down and choose an item, the ListView automatically jumps to the top. Is there a way to prevent this from happening? Thank you very much!
I've added a screenshot, so you can better understand.
This is my code:
class CheckboxWidget extends StatefulWidget {
const CheckboxWidget({
Key key,
this.item,
this.type,
this.state,
}) : super(key: key);
final Map<String, bool> item;
final String type;
final Map<String, dynamic> state;
#override
State<CheckboxWidget> createState() => _CheckboxWidgetState();
}
class _CheckboxWidgetState extends State<CheckboxWidget> {
#override
void initState() {
super.initState();
if (widget.state[widget.type].isEmpty) {
widget.item.updateAll((key, value) => value = false);
}
}
bool isChecked = false;
#override
Widget build(BuildContext context) {
final FilterProvider filterProvider = Provider.of<FilterProvider>(context);
return Expanded(
child: ListView(
key: UniqueKey(),
children: widget.item.keys.map(
(key) {
return CheckboxListTile(
contentPadding: EdgeInsets.only(left: 2, right: 2),
title: Text(
key,
style: TextStyle(fontSize: 19, fontWeight: FontWeight.w400),
),
value: widget.item[key],
activeColor: Color(0xffF6BE03),
checkColor: Color(0xff232323),
shape: CircleBorder(),
//contentPadding: EdgeInsets.all(0),
onChanged: (value) {
value
? filterProvider.multifiltervalue = [widget.type, key]
: filterProvider.multifiltervalue = [
widget.type,
key,
false
];
setState(
() {
widget.item[key] = value;
},
);
},
);
},
).toList(),
),
);
}
}
Probably because this line key: UniqueKey(), when you call setState the build function builds its widgets again, and the ListView will have a new UniqueKey so it will rebuild the list cause it thinks its a different widget
remove this line key: UniqueKey(), and it should work fine
CheckboxListTile is a stateless Widget... setState is redrawing the whole list.
You could wrap the CheckboxListTile into a Statefull Widget or into a StatefulBuilder ... if you call setState inside the StatefulBuilder only this part should be redrawed..
another way could be to save the scroll position... but i think redrawing only the part on screen you haved changed is smarter :-)
Related
I would like to get the value of the dropdown from the other widget in the real estate app. Say I have two widgets. First one is the dropdown widget, and the second one is Add New Property widget (or a page).. I would like to access the value of the dropdown from the Add New Property.
I could achieve this with final Function onChanged; but Im wondering if there is another way to achieve with the Provider package or the ValueNotifier
the code below is my Dropdown button widget
class PropertyType extends StatefulWidget {
final Function onChanged;
const PropertyType({
super.key,
required this.onChanged,
});
#override
State<PropertyType> createState() => _PropertyTypeState();
}
class _PropertyTypeState extends State<PropertyType> {
final List<String> _propertyTypeList = propertyType;
String? _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return ANPFormContainer(
fieldTitle: 'Property Type',
subTitle: 'အိမ်ခြံမြေအမျိုးအစား',
child: FormBuilderDropdown<String>(
name: 'a2-propertyType',
initialValue: _propertyType,
items: _propertyTypeList
.map(
(itemValue) => DropdownMenuItem(
value: itemValue,
child: Text(itemValue),
),
)
.toList(),
onChanged: (val) {
setState(() {
_propertyType = val;
widget.onChanged(val);
});
},
),
);
}
}
And this is the "Add New Property" form page
class ANPTest extends StatefulWidget {
const ANPTest({super.key});
#override
State<ANPTest> createState() => _ANPTestState();
}
class _ANPTestState extends State<ANPTest> {
final TextEditingController _propertyid = TextEditingController();
String _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ZayyanColorTheme.zayyanGrey,
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
PropertyID(propertyID: _propertyid),
PropertyType(onChanged: (String value) {
_propertyType = value;
}),
addVerticalSpacer(25),
ANPNextButton(onPressed: _onpressed),
],
),
),
);
}
_onpressed() {
final anp = MdlFirestoreData(
propertyid: _propertyid.text, propertyType: _propertyType)
.toFirestore();
FirebaseFirestore.instance.collection('Selling Posts').add(anp);
}
}
Thank you for helping me out.
Best
yes, you could use Getx or provider package by creating a controller(function) and the package helps you to have access to variables in
your controller to use them everywhere in your program,
you may need to learn about Getx
it can help you manage your navigation and state
I'm new to RiverPod and generally it's been working for me, but I've run into a problem updating a DropdownButton. It updates the actual value appropriately, but then will not display it in the UI. If I refresh and get it to rebuild, then the correct value is displayed, but it's not rebuilding on its own. I'm sure there's an issue in the way I've set it up, I'm just not sure what it is. This is my DropdownButton:
Container editState(
String Function() state, Function update, bool Function() editable) =>
Container(
height: 35.0,
decoration:
BoxDecoration(border: Border.all(color: Colors.grey, width: 2.0)),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: state(),
icon: const Icon(Icons.arrow_downward),
onChanged: (String? newValue) {
if (newValue != null &&
newValue != '' &&
state() != newValue &&
editable()) {
update(newValue);
}
},
items: usStateList.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
This is all down the widget tree but still contained under a ConsumerWidget. That Widget starts...
class SinglePatientView extends ConsumerWidget {
const SinglePatientView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
const gap4 = Gap(4);
const gap32 = Gap(32);
final readNotifier = ref.read(singlePatientViewProvider.notifier);
final watchNotifier = ref.watch(singlePatientViewProvider.notifier);
bool editable() => ref.watch(boolProvider.notifier).state;
It then calls the following Widgets...
editAddress(
context,
street,
city,
watchNotifier.patientState,
zip,
readNotifier.updatePatientAddress,
editable,
)
List<Widget> editAddress(
BuildContext context,
TextEditingController streetAddressController,
TextEditingController cityController,
String Function() state,
TextEditingController zipCodeController,
Function update,
bool Function() editable,
) =>
[
editStateZip(
context,
state,
zipCodeController,
update,
editable,
),
];
Widget editStateZip(
BuildContext context,
String Function() state,
TextEditingController controller,
Function update,
bool Function() editable,
) =>
SizedBox(
child: Row(
children: [
editState(state, update, editable),
],
),
);
So as I said, it correctly updates the patient's State (geographical state, not app state), but doesn't redraw the Widget/UI automatically. I'm sure I'm missing something obvious, but any help would be appreciated.
More code (in case it's helpful):
StateNotifierProvider
final singlePatientViewProvider =
StateNotifierProvider<SinglePatientViewStateNotifier, Patient>(
(ref) => SinglePatientViewStateNotifier(ref));
The SinglePatientViewStateNotifier is constructed like this with the method being called shown:
class SinglePatientViewStateNotifier extends StateNotifier<Patient> {
SinglePatientViewStateNotifier(this.ref) : super(Patient());
final Ref ref;
void updatePatientAddress(String? geographicalState) {
state = state.copyWith(
address: updateAddressFromStrings(
address: state.address,
index: 0,
streetAddress: streetAddress,
city: city,
state: geographicalState,
postalCode: zipCode,
));
}
}
I've made a custom checkbox widget in dart and used a global key to save the state.
class CheckBox extends StatefulWidget {
final String label;
final void Function(dynamic) onChanged;
const CheckBox({required this.label, required this.onChanged, Key? key})
: super(key: key);
#override
CheckBoxState createState() => CheckBoxState();
}
class CheckBoxState extends State<CheckBox> {
final key = GlobalKey();
late bool isChecked;
#override
void initState() {
isChecked=false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(widget.label, style: Theme.of(context).textTheme.headline4),
KeyedSubtree(
key: key,
child: Checkbox(
activeColor: LightSeaGreen,
checkColor: White,
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = !isChecked;
});
widget.onChanged(value);
},
),
),
],
),
);
}
}
I've populated a list of checkboxes using the above widget as below,
when I select the checkboxes in one category (i.e. certificate provider ) and check the checkboxes in the course language category, the selected checkboxes of the certificate provider remains unchecked (the state is not saved).What can I do to save the state when I go from from one category to another? Any kind of help would be highly appreciated.
You can't use Global key to preserve state of all your responces. you should use a class (factory) to save your responces and use a state management library to handle your responces maybe, provider will work well. Or, you can use a local data base like hive to store your responces and you can fetch these responces whenever you want that will be best pratice.
Thank you
I have a widget who need to select a single item using Radio as dynamically. I already created that widget like below:
int number;
return Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Radio(
value: index,
groupValue: number,
activeColor: Color(0xFFE91E63),
onChanged: (int val) {
setState(() {
number = val;
print('Show the Resumes $number');
});
},
),
Text(
'Show',
),
],
),
);
I looping the above widget inside a ListView.builder. And the index in the value is from index from itemBuilder on ListView.builder. And when I run the code, it looks like this.
So how to make my Radio is only select a single item?
Maybe you can write like this, declare value and groupValue outside the loop (builder in ListView). And create value, groupValue, and onChanged in the constructor. And the result like this.
...
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<int> _numbers = List<int>.generate(5, (index) => index);
int _groupNumber;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return HomeContent(
value: _numbers[index],
groupValue: _groupNumber,
onChanged: (int value) {
setState(() {
_groupNumber = value;
});
},
);
},
itemCount: _numbers.length,
),
);
}
}
class HomeContent extends StatelessWidget {
final int value;
final int groupValue;
final ValueChanged<int> onChanged;
const HomeContent({
Key key,
this.value,
this.groupValue,
this.onChanged,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return RadioListTile<int>(
value: this.value,
groupValue: this.groupValue,
onChanged: this.onChanged,
title: Text('Value $value On Group $groupValue'),
);
}
}
Change 'groupValue: number' to
final var _groupValue = -1;
return Container(
...
groupValue: _groupValue,
...
);
and show Trouble with flutter radio button
I'm learning Flutter (and coding in general) and I can't seem to find the issue with my latest project. When I run in simulator the slider forms just fine, click the thumb and the label shows, but the thumb won't move on the track at all, and thus never calls the onChanged event.
import 'resources.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class ItemDetail extends StatefulWidget {
final Item item;
ItemDetail({Key key, #required this.item}) : super(key: key);
#override
_ItemDetailState createState() => new _ItemDetailState(item: item);
}
class _ItemDetailState extends State<ItemDetail> {
Item item;
_ItemDetailState({Key key, #required this.item});
#override
Widget build(BuildContext context) {
double margin = ((item.listPrice - item.stdUnitCost)/item.listPrice)*100;
return new Scaffold(
appBar: AppBar(
title: new Text('Item Detail'),
),
body: new Column(
children: <Widget>[
new Padding(padding: EdgeInsets.symmetric(vertical: 20.0)),
new Text(item.itemCode),
new Text(item.itemDescription),
new Text(item.itemExtendedDescription),
new Divider(height: 40.0,),
new Text('List Price: \$${item.listPrice}'),
new Text('Cost: \$${item.stdUnitCost}'),
item.itemType=='N'
? new Text('Non-Stock (${item.itemType})')
: new Text('Stock Item (${item.itemType})'),
new Text('Available: ${item.stockAvailable}'),
new Padding(padding: EdgeInsets.symmetric(vertical: 10.0)),
new Slider(
value: margin,
divisions: 20,
min: 0.0,
max: 100.0,
label: margin.round().toString(),
onChanged: (double value) {
setState(() {
margin = value;
});
},
)
],
),
);
}
}
Problem: In the above example, margin is not a state variable. It is a local variable inside a build method.
Fix: Move this as an instance variable.
Reason: Widgets will get rebuild only when there is a change in its state.
Code:
class _ItemDetailState extends State<ItemDetail> {
Item item;
var margin;
_ItemDetailState({Key key, #required this.item}) {
this.margin = ((item.listPrice - item.stdUnitCost)/item.listPrice)*100;
}
#override
Widget build(BuildContext context) {
//same as now
}
}
For others coming here, I had a slightly different reason that the slider wasn't updating. I had the value set to a constant. Make sure that value is a variable.
This:
Slider(
value: _myValue,
...
onChanged: (newValue) => {
setState(() => _myValue = newValue)
},
),
Not this:
Slider(
value: 0, // <-- problem
...
onChanged: (newValue) => {
setState(() => _myValue = newValue)
},
),
I got this issue when I put the variable inside the build function 😩
#override
Widget build(BuildContext context) {
int brightness = 85;
return Scaffold(
...
Moved the variable outside the function and got it solved 😎
int brightness = 85;
#override
Widget build(BuildContext context) {
return Scaffold(
...
because each time when state is changed, this build method is called and sets the variable back to the assigned value 😝
This might help! (I USED STATEFULBUILDER TO UPDATE THE VALUE)
double _value = 20;
StatefulBuilder(
builder: (context, state) => Center(
child: CupertinoSlider(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (val) {
state(() {
_value = val;
});
},
),
),
)
I think the variable margin does not have scope from where the UI is building. When you debug, you can see changes in variable but it is not rendering. I tried it in following way and able to update the UI value.
class _MyHomePageState extends State<MyHomePage> {
double margin=0;
Widget getSlider(BuildContext context)
{
return Slider(
value:margin.toDouble(),
onChanged: (newRating){
setState(() {
margin = newRating;
});
},
min: 0,
max: 100,
divisions: 5,
);
} // getslider method closed
// #0verride Widget build method
// under children widgets , simply call the getSlider method to place the slider
} //class
I was having an issue when giving the Slider(value: tasksDetails.taskImportance.toDouble(), etc)
I used Slider((_currentImportance ?? tasksDetails.taskImportance).toDouble(), etc)
and could then move the slider. I have no idea why this now works. Can only guess something to do with Denish's reason above 'Reason: Widgets will get rebuild only when there is a change in its state'. The selected slider value was in both cases was save correctly.
My first problem was answered by Dinesh. Then the slider wasn't smooth. I was only able to tap to get it to move. I am on iOS, so I changed the Slider widget to Slider.adaptive.
This changes the slider to a CupertinoSlider.
Copy and paste class for your testing convenience:
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
double _currentSliderValue = 20;
#override
Widget build(BuildContext context) {
return Slider.adaptive(
value: _currentSliderValue,
max: 100,
divisions: 5,
label: _currentSliderValue.round().toString(),
onChanged: (double value) {
setState(() {
_currentSliderValue = value;
});
},
);
}
}