Best way to create a scrollable list that dynamically grows - flutter

I have a flutter web app (which may become a desktop app in the future) that contains a dialog box. The dialog allows the user to edit the properties of a model object that contains a list of elements. The user should be able to edit the list by adding or deleting elements to the list. So the dialog contents are of variable height -- it depends on the number of elements in the list.
I am having trouble creating a layout that dynamically resizes appropriately. What I want is for the dialog to grow as items are added to the list, up to the maximum size that would fit on the device's screen. If the contents grow larger than this, the elements in the list should be scrollable.
I've attached two screenshots of what I have working at the moment; the first has a list with only two items, which is easily displayed. The second is the same dialog with many items showing the overflow.
Here is the code for the Dialog:
Widget build(BuildContext context) {
return AlertDialog(
actions: [
FlatButton(
child: const Text('CANCEL'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: const Text('OK'),
onPressed: () =>
Navigator.of(context).pop<Modifier>(_createModifier()),
),
],
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Damage Editor'),
Divider(),
columnSpacer,
DiceSpinner(
onChanged: (value) => setState(() => _dice = value),
initialValue: _dice,
textFieldWidth: 90.0,
),
columnSpacer,
Container(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
border: Border.all(color: Colors.grey),
),
child: DropdownButton<DamageType>(
underline: Container(),
value: _type,
items: _damageTypeItems(),
onChanged: (value) => setState(() => _type = value),
),
),
columnSpacer,
SwitchListTile(
value: _direct,
onChanged: (state) => setState(() => _direct = state),
title: Text(_direct ? 'Internal (Direct)' : 'External (Indirect)'),
),
if (!_direct) ...<Widget>[
columnSpacer,
CheckboxListTile(
value: _explosive,
onChanged: (state) => setState(() => _explosive = state),
title: Text('Explosive'),
),
],
columnSpacer,
DynamicListHeader(
title: 'Enhancements/Limitations',
onPressed: () => setState(() =>
_modifiers.add(TraitModifier(name: 'Undefined', percent: 0))),
),
SingleChildScrollView(
child: ListBody(
children: _enhancementList(),
),
),
],
),
);
}
List<Widget> _enhancementList() {
var list = <Widget>[];
_modifiers.forEach(
(element) {
if (_modifiers.length > 0) list.add(columnSpacer);
list.add(_EnhancerEditor(element, index: _modifiers.indexOf(element),
onChanged: (index, enhancer) {
_modifiers[index] = enhancer;
}));
},
);
return list;
}
typedef TraitModifierCallback = void Function(int, TraitModifier);
class _EnhancerEditor extends StatefulWidget {
_EnhancerEditor(this.enhancer, {this.onChanged, this.index});
final TraitModifier enhancer;
final TraitModifierCallback onChanged;
final int index;
#override
__EnhancerEditorState createState() => __EnhancerEditorState();
}
class __EnhancerEditorState extends State<_EnhancerEditor> {
TextEditingController _nameController;
TextEditingController _percentController;
bool _validInput = true;
#override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.enhancer.name);
_nameController.addListener(_onChanged);
_percentController =
TextEditingController(text: widget.enhancer.percent.toString());
_percentController.addListener(_onChanged);
}
#override
void dispose() {
_nameController.removeListener(_onChanged);
_nameController.dispose();
_percentController.removeListener(_onChanged);
_percentController.dispose();
super.dispose();
}
void _onChanged() {
String text = _percentController.text.trim();
setState(() {
int value = int.tryParse(text);
_validInput = (value != null);
if (_validInput) {
widget.onChanged(widget.index,
TraitModifier(name: _nameController.text, percent: value));
}
});
}
#override
Widget build(BuildContext context) {
return IntrinsicHeight(
child: Row(
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
rowSmallSpacer,
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))
],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
}

It may sound counterintuitive but you should have ListView within Column. You need both.
In dialog Column will deal with dialog size, and ListView will deal with scrolling within.
Column(
mainAxisSize: MainAxisSize.min,
children:[
ListView(padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Ok, here you go ... Simple call of dialog ...
FlatButton(
onPressed: () {
showDialog(
useRootNavigator: false,
context: context,
builder: (BuildContext context) => Test(),
);
},
child: Text('Suppper'),
)
then code of dialog ... it is pretty straight forward ...
all controllers you can place in a map that you will deal with when you add new entries... both adding and removing is super easy ... let me know if further assitance is needed.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
void initState() {
super.initState();
}
Widget rowEntry(_nameController, _percentController) {
return IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
addEntry() async {
entries.add('new value');
setState(() {});
}
List entries = [];
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Container(
padding: EdgeInsets.all(16),
width: 300,
child: ListView(shrinkWrap: true, children: [
Text('here you will put all that is above (+)'),
Container(
margin: EdgeInsets.only(top: 16, bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Exhancements/Limitations'),
InkWell(
onTap: () {
addEntry();
},
child: Icon(Icons.add),
),
],
),
),
ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
scrollDirection: Axis.vertical,
itemCount: entries == null ? 0 : (entries.length),
itemBuilder: (BuildContext context, int index) {
return rowEntry(null, null);
}),
]),
),
);
}
}

Related

Flutter Search Bar with ListView with Checkboxes

I want to create a widget like this.
This contains the Textfield, Once type something in the field, It will be sorting the list and show the results below the field. The result list shown below can also have a checkbox field to select multiple items from the result.
Is there any built-in flutter widget that can be useful for this case?
Or any other package to achieve this.
Following is the screenshot for reference.
I tried with RawAutoComplete widget.
Here is the code.
class SearchBarDemo extends StatelessWidget {
const SearchBarDemo({super.key});
static List<String> suggestons = [
"USA",
"UK",
"Uganda",
"Uruguay",
"United Arab Emirates"
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: RawAutocomplete(
optionsBuilder: (textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
} else {
List<String> matches = <String>[];
matches.addAll(suggestons);
matches.retainWhere((s) {
return s
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
return matches;
}
},
fieldViewBuilder:
(context, textEditingController, focusNode, onFieldSubmitted) {
return TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(7),
),
hintText: 'Search',
contentPadding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 4), // EdgeInsets.only(top: 8, left: 5),
prefixIcon: Container(
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: Border(
right: BorderSide(
color: Colors.grey.shade400,
),
),
),
child: Icon(
Icons.search,
color: Colors.grey.shade400,
),
),
),
controller: textEditingController,
focusNode: focusNode,
onSubmitted: (String value) {},
);
},
onSelected: (selection) {},
optionsViewBuilder: (context, onSelected, options) {
return Material(
child: SingleChildScrollView(
child: Column(
children: options.map((opt) {
return InkWell(
onTap: () {
onSelected(opt);
},
child: Column(
children: [
Container(
height: 50,
width: 250,
alignment: Alignment.topLeft,
child: Card(
child: SizedBox(
child: ListTile(
title: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
opt,
style: TextStyle(fontSize: 12),
),
Transform.scale(
scale: 0.8,
child: Checkbox(
value: false,
onChanged: (val) {},
),
),
],
),
),
),
),
),
],
),
);
}).toList(),
),
),
);
},
),
);
}
}
Output of the above code is:
It covers the whole screen and show the result content in center.
You can customize the filter logic. Also you may like SearchDelegate-class
class SearchBarDemo extends StatefulWidget {
const SearchBarDemo({super.key});
#override
State<SearchBarDemo> createState() => _SearchBarDemoState();
}
class _SearchBarDemoState extends State<SearchBarDemo> {
static List<String> suggestons = [
"USA",
"UK",
"Uganda",
"Uruguay",
"United Arab Emirates"
];
List<String> filterItems = [];
List<String> checkedItems = [];
late final TextEditingController controller = TextEditingController()
..addListener(() {
/// filter logic will be here
final text = controller.text.trim();
filterItems = suggestons
.where(
(element) => element.toLowerCase().startsWith(text.toLowerCase()))
.toList();
setState(() {});
});
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
CupertinoTextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: filterItems.length,
itemBuilder: (context, index) {
final bool isChecked =
checkedItems.contains(filterItems[index]);
return CheckboxListTile(
value: isChecked,
title: Text("${filterItems[index]}"),
onChanged: (value) {
if (isChecked) {
checkedItems.remove(filterItems[index]);
} else {
checkedItems.add(filterItems[index]);
}
setState(() {});
},
);
}),
),
],
));
}
}

State of my Widget not changing in flutter when updating value

What I am aiming for is, when a user long presses on a particular date, they should be able to enter their weight value, and upon submitting, the modal bottom sheet should close, and the updated weight should be visible as soon as the modal sheet is out of view. But It does not happen.
Instead I have to go to other date and come back to see the changes. Please help me. I am new to flutter.
Here is the demo of the problem :
problem i am facing
Here is the code:
void _modalBottomSheetMenu(DateTime dt) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
context: context,
isScrollControlled: true,
builder: (builder) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("Add a weight"),
Container(
width: 200.0,
child: TextField(
controller: myController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
autofocus: true,
maxLength: 4,
onSubmitted: (value) {
weightValue = double.parse(value);
print("Weight entered is $weightValue");
print("Date passed is $dt");
setState(() {
_events[dt] = [weightValue].toList();
Navigator.pop(context);
});
print(_events[dt][0]);
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Weight',
hintText: 'Enter your weight'),
),
),
],
);
},
);
}
BuildEventList : This contains the weight to be printed
import 'package:flutter/material.dart';
class BuildEventList extends StatefulWidget {
String weight;
BuildEventList(this.weight);
#override
_BuildEventListState createState() => _BuildEventListState();
}
class _BuildEventListState extends State<BuildEventList> {
#override
void setState(fn) {
// TODO: implement setState
super.setState(fn);
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(width: 0.8),
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Container(
width: double.infinity,
child: Center(child: Text("${widget.weight}")),
),
);
}
}
This is where it is called :
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title), actions: [
IconButton(
icon: Icon(Icons.add),
color: Colors.white,
onPressed: () {},
)
]),
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
_buildTableCalendar(),
const SizedBox(height: 8.0),
//_buildButtons(),
const SizedBox(height: 8.0),
Expanded(
child: BuildEventList(_selectedEvents.length > 0
? _selectedEvents[0].toString()
: "No weight given!")),
],
),
);
}

Flutter textfield onChanged don't work when textfield bring value from controller

I'm working on flutter project and came across a problem. the textfield supposed to take a value from controller which work perfectly but onChanged function wont work. However it is not working.i must retype value to show results .
how can i get result from onchanged when my textfield brings the value ?
code:
// search function that call api
class _SearchPageTState extends State<SearchPageT> {
GlobalState _store = GlobalState.instance;
List<dynamic> searchResults = [];
searchT(value) async {
SearchServicet.searchApiT(value).then((responseBody) {
List<dynamic> data = jsonDecode(responseBody);
setState(() {
data.forEach((value) {
searchResults.add(value);
});
});
});
}
#override
///////
//code textfield search onchanged
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
autofocus: true,
controller: TextEditingController()
..text = '${_store.get('num')}',
onChanged: (value) {
searchResults.clear();
searchCodeighniterT(value);
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 25.0),
labelText: 'number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: null,
),
),
),
),
SizedBox(
height: 10.0,
),
ListView.builder(
physics: ScrollPhysics(),
shrinkWrap: true,
itemCount: searchResults.length,
itemBuilder: (BuildContext context, int index) {
return buildResultCard(context, searchResults[index]);
},
),
],
),
),
);
}
//here where shows the result
Widget buildResultCard(BuildContext context, data) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
new Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/bgwlgo.png"), fit: BoxFit.cover),
),
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
new Card(
child: ListTile(
title: Text(
"DATE:",
),
subtitle: Text(
data['DATETIME'].toString(),
),
),
),
new Card(
child: ListTile(
title: Text(
"TRAITEMENT:",
),
subtitle: Text(
data['TRAITEMENT_DETAIL'].toString()
),
),
),
new Padding(
padding: EdgeInsets.all(9.0),
),
],
),
),
Divider(
color: Colors.black,
)
],
),
);
}
As discussed and understood in the comments, here are a couple of possible solutions for the issue.
If you are using a stateful Widget and need a controller for your TextFormField, you can use it to set the text on the TextFormField. Otherwise, you can just use the initialValue property:
class TextFieldIssue64061076 extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Text('Page 1'),
TextFormField(
controller: _textEditingController,
),
FlatButton(
child: Text('Submit'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondPage64061076(text: _textEditingController.text);
}
)),
),
],
);
}
}
class SecondPage64061076 extends StatefulWidget {
final String text;
SecondPage64061076({
this.text
});
#override
_SecondPage64061076State createState() => _SecondPage64061076State();
}
class _SecondPage64061076State extends State<SecondPage64061076> {
final TextEditingController _textEditingController = TextEditingController();
#override
void initState() {
// You can either use a text controller to set the text on the text field
// or the initialValue below
if(widget.text != null && widget.text != ''){
_textEditingController.text = widget.text;
methodToCallOnChanged(widget.text);
}else{
_textEditingController.text = '';
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 2'),
TextFormField(
controller: _textEditingController,
onChanged: (value) => methodToCallOnChanged(value),
// As mentioned you could just use the initial value, it you don't need
// a Stateful Widget
// initialValue: widget.text ?? '',
),
]
),
);
}
void methodToCallOnChanged(data){
print('I was called onChange or when the view opened and there was data');
}
}
Widget build(BuildContext context) {
bool isRecording = false;
bool isShowSendButton = false;
/// make sure textcontroller and any variabloes or booleans are not defined
///in the build method
}
You can't be using the constructor of the TextEditingController and assign it to the controller.
The controller of the TextField only takes the variable assigned to the TextEditingController. Check the following code.
controller:theNameOfYourController
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 10),
child: TextFormField(
onChanged: (value){
messageFieldController.text=value;
},
//controller: messageFieldController,
minLines: 1,
maxLines: 2,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(Icons.add, color: Colors.blue),
),
suffixIcon: processing == true
? const CircularProgressIndicator()
: IconButton(
onPressed: () {
try {
/*for (int i = 0;
i < widget.selectedMembers.length;
i++) {
sendMessage(
widget.selectedMembers[i].number);
}*/
for (int i = 0;
i < widget.selectedGroups.length;
i++) {
if (widget.selectedGroups.isNotEmpty) {
sendMessageToGroup(
widget.selectedGroups[i].id,
widget.selectedGroups[i].groupName);
}
}
} catch (err) {
Fluttertoast.showToast(
msg: 'Unable to send Message');
setState(() {
processing = false;
});
}
},
icon:
const Icon(Icons.send, color: Colors.blue),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
),
),
),
)

Could not find the correct Provider<RetailerProvider> above this EditRetailer Widget

#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
children: <Widget>[
_myAppBar(context),
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 80,
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Stack(children: <Widget>[
// The containers in the background
Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 8.0, right: 8.0),
child: Container(
width: MediaQuery.of(context).size.width,
height: 80.0,
child: Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Material(
color: Colors.white,
elevation: 14.0,
shadowColor: Color(0x802196F3),
child: Center(
child: GestureDetector(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'${items[index].name}',
style: TextStyle(
color: Colors.black,
fontSize: 20.0),
),
],
),
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EditRetailer(items[index])));
},
),
),
),
),
),
),
]),
]);
}),
),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: Color(0xFFFA7397),
child: Icon(
FontAwesomeIcons.listUl,
color: Color(0xFFFDDE42),
),
onPressed: () {
//Navigator.push(context,MaterialPageRoute(builder: (context) => TaskScreen()),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RetailerScreen(Retailer('', '', '', '')),
fullscreenDialog: true),
);
},
),
);
}
The problem occurred when i add this line of code into my above widget :
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EditRetailer(items[index])));
},
My editRetailer.dart code
class EditRetailer extends StatefulWidget {
final Retailer retailer;
EditRetailer([this.retailer]);
#override
_EditRetailerState createState() => _EditRetailerState();
}
class _EditRetailerState extends State<EditRetailer> {
final nameController = TextEditingController();
final phoneController = TextEditingController();
final gstController = TextEditingController();
final addressController = TextEditingController();
#override
void dispose() {
nameController.dispose();
phoneController.dispose();
gstController.dispose();
addressController.dispose();
super.dispose();
}
#override
void initState() {
if (widget.retailer == null) {
//New Record
nameController.text = "";
phoneController.text = "";
gstController.text = "";
addressController.text = "";
new Future.delayed(Duration.zero, () {
final retailerProvider = Provider.of<RetailerProvider>(context,listen: false);
retailerProvider.loadValues(Retailer(null,null,null,null));
});
} else {
//Controller Update
nameController.text=widget.retailer.name;
phoneController.text=widget.retailer.phone;
gstController.text=widget.retailer.gst;
addressController.text=widget.retailer.address;
//State Update
new Future.delayed(Duration.zero, () {
final retailerProvider = Provider.of<RetailerProvider>(context,listen: false);
retailerProvider.loadValues(widget.retailer);
});
}
super.initState();
}
#override
Widget build(BuildContext context) {
final retailerProvider = Provider.of<RetailerProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('Edit Retailer')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
TextField(
controller: nameController,
decoration: InputDecoration(hintText: 'Retailer Name'),
onChanged: (value) {
retailerProvider.changeName(value);
},
),
TextField(
controller: phoneController,
decoration: InputDecoration(hintText: 'Retailer Phone'),
onChanged: (value) => retailerProvider.changePhone(value),
),
TextField(
controller: gstController,
decoration: InputDecoration(hintText: 'Retailer GST'),
onChanged: (value) => retailerProvider.changeGst(value),
),
TextField(
controller: addressController,
decoration: InputDecoration(hintText: 'Retailer Address'),
onChanged: (value) => retailerProvider.changeAddress(value),
),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text('Save'),
onPressed: () {
retailerProvider.saveRetailer();
Navigator.of(context).pop();
},
),
(widget.retailer !=null) ? RaisedButton(
color: Colors.red,
textColor: Colors.white,
child: Text('Delete'),
onPressed: () {
retailerProvider.removeProduct(widget.retailer.gst);
Navigator.of(context).pop();
},
): Container(),
],
),
),
);
}
}
This is the error im getting while running the app.
Error: Could not find the correct Provider<RetailerProvider> above this EditRetailer Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that EditRetailer is under your MultiProvider/Provider<RetailerProvider>.
This usually happen when you are creating a provider and trying to read it immediatly.

FLUTTER : How to make container size of children

By definition, a container with children grows enough to show them. In this example that I am developing, I am not able to make the container fit the size of the children, I have to hardcode the Weight and Height, both, otherwise the container disappear (is the one with a red background, I put the whole code so you can copy-paste but it is only that one that I can not control the behaviour).
import 'package:flutter/material.dart';
import 'package:littlebusiness/logic/Category.dart';
import 'package:hive/hive.dart';
class FormCategoryPage extends StatefulWidget {
#override
_FormCategoryPageState createState() => _FormCategoryPageState();
}
class _FormCategoryPageState extends State<FormCategoryPage> {
final _formKey = GlobalKey<FormState>();
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(true, 'A', 0xffe6194B));
sampleData.add(new RadioModel(false, 'B', 0xfff58231));
sampleData.add(new RadioModel(false, 'C', 0xffffe119));
sampleData.add(new RadioModel(false, 'D', 0xffbfef45));
sampleData.add(new RadioModel(true, 'A', 0xffe6194B));
sampleData.add(new RadioModel(false, 'B', 0xfff58231));
}
String _name;
Color _color;
String _selectedValue;
void addCategory(Category cat) {
Hive.box('categories').add(cat);
}
void getColor(String value) {
_selectedValue = value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PERFORMANCE'),
),
body: Center(
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
// hintText: 'Enter your email',
labelText: 'Name',
),
onSaved: (value) => _name = value,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: 100,
width: 320,
color: Colors.red,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() {
sampleData.forEach(
(element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: RadioItem(sampleData[index]),
);
},
),
),
],
),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(color: Colors.red)),
child: Text('Add New Contact'),
color: Colors.teal,
textColor: Colors.white,
onPressed: () {
_formKey.currentState.save();
// final newContact = Contact(_name, int.parse(_age));
// addContact(newContact);
},
),
],
),
),
),
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(15.0),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 35.0,
width: 35.0,
alignment: Alignment.center,
child: Container(
height: 25.0,
width: 25.0,
decoration: BoxDecoration(
color: Color(_item.colorCode),
borderRadius:
const BorderRadius.all(const Radius.circular(15)),
)),
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(
width: 3.0,
color: _item.isSelected
? Color(_item.colorCode)
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
Container(margin: EdgeInsets.only(left: 20.0))
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
RadioModel(this.isSelected, this.buttonText, this.colorCode);
}
This is the actuar result:
Anyone knows why it is happening that? I am lost, and giving a with of double.infinity does not work...
Thanks!
instead of using fixed values on wisth and height , you can use relative values to device by using
MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height
you can also use them like
MediaQuery.of(context).size.width * 0.5
which means 50% of the device screen
hope it will help
You have to specified both width and height because your Listview is a child of a Column and a Row
You can replace your Listview by a Row of RadioItem in a SingleChildScrollView
Container(
color: Colors.red,
child: Builder(
builder: (context) {
var childs = <Widget>[];
for (var item in sampleData) {
childs.add(RadioItem(item));
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: childs,
),
);
},
),
),