How do I extract this switch widget - flutter

I have a StatefulWidget with a ListView, the ListView has the bunch of switches with text next to them.
Now i want to extract this into a custom switch widget because i have this more than once.
I don't know how to do this, also I need to know inside my parent widget what state each switch has.
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Use custom dhcp server"),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Switch(
value: _dhcp,
activeColor: Colors.blue,
onChanged: (bool value) {
setState(() {
_dhcp = value;
});
},
),
),
],
),
),

You can create your own stateless widget like this:
class CustomSwitch extends StatelessWidget {
const CustomSwitch({
Key key,
#required this.value,
#required this.onChanged,
}) : super(key: key);
final bool value;
final void Function(bool) onChanged;
#override
Widget build(BuildContext context) {
return Switch(
value: value,
activeColor: Colors.blue,
onChanged: onChanged,
);
}
}
Where you can use it anywhere like this:
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool switchValue = false;
#override
Widget build(BuildContext context) {
return ListView(
children: [
CustomSwitch(
value: switchValue,
onChanged: (newValue) {
setState(() {
switchValue = newValue;
});
},
),
],
);
}
}

Related

Provider does not refresh the list of a DropDownMenu - Flutter

I have a personal API from where I retrieve all the information I need, the connection works fine, but the problem is when I want to show this values, I have a provider where I put all the data I need from the API, and I call it to put those items in the DropDownMenu, but the problem is that the values does not appear, even after the data have arrived, I know this thanks to debugging, here is some code:
this is the code of the widget where I'm calling the provider:
Column(
children: [
Container(
color: Colors.green,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(top: 20, bottom: 20),
height: 10,
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey),
)
]),
),
IconList(
labelText: 'Landmarks',
icon: Icons.panorama_horizontal_outlined,
items: context.watch<Landmarks>().getLandmarksNameList(), //Here is my provider
),
const IconInput(
labelText: '1', icon: Icons.addchart_outlined),
const IconInput(
labelText: '1', icon: Icons.addchart_outlined),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return UserCard(user: usersList[index]);
},
),
],
),
this is the widget iconList, where I have the dropDownMenu:
import 'package:flutter/material.dart';
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items = [];
IconList({super.key, required this.labelText, required this.icon, items});
#override
State<IconList> createState() => _IconListState();
}
class _IconListState extends State<IconList> {
#override
Widget build(BuildContext context) {
String selectedItem = '';
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
margin: const EdgeInsets.all(10.0),
child: Row(
children: [
Icon(widget.icon),
DropdownButton<String>(
value: selectedItem,
items: widget.items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedItem = value!;
});
},
),
],
),
);
}
}
And this is my provider:
class Landmarks with ChangeNotifier {
final List<Landmark> _items = [];
List<Landmark> get landmarks => _items;
void addLandmarks(List<Landmark> value) {
_items.addAll(value);
notifyListeners();
}
List<String> getLandmarksNameList() {
List<String> list = [];
print(_items.length);
for (int i = 0; i < _items.length; i++) {
list.add(_items[i].name);
}
return list;
}
}
You have two problems with you code:
Problem 1
In your IconList constructor
IconList({super.key, required this.labelText, required this.icon, items});
you are accessing items, but really you should access this.items.
To do so, you also shouldn't initialize items with:
final List<String> items = [];
Your code should look like this:
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items;
IconList(
{super.key,
required this.labelText,
required this.icon,
required this.items});
Problem 2
Within your build method:
#override
Widget build(BuildContext context) {
String selectedItem = '';
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
you are initializing selectedItem, which means that every setState, selectedItem will get reset to its original value since it's within the build method. Instead, move selectedItem to outside the build, to your _state class:
class _IconListState extends State<IconList> {
String selectedItem = ''; //<-- Initialize it here
#override
Widget build(BuildContext context) {
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
Your IconList should look like this:
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items;
IconList(
{super.key,
required this.labelText,
required this.icon,
required this.items});
#override
State<IconList> createState() => _IconListState();
}
class _IconListState extends State<IconList> {
String selectedItem = ''; //<-- Initialize it here
#override
Widget build(BuildContext context) {
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
margin: const EdgeInsets.all(10.0),
child: Row(
children: [
Icon(widget.icon),
DropdownButton<String>(
value: selectedItem,
items: widget.items.map<DropdownMenuItem<String>>((String value) {
print("item is $value");
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedItem = value!;
});
},
),
],
),
);
}
}

What we should do when the file gets so long

when I'm done with a page I can extract widget as I can for more readable, But this makes the file longer, So I find a solution I can use "part and part of " I can take extract widgets inside part of (E.g login_view_part.dart), this solves the problem, But I have read opinions about the non-use/bad-use of part and part of.
1- this is a good solution for problems like this? If not what should we
do?
2- we should use part and part of? anywhere. (except code generation)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:second_hand/core/constants/navigation/navigation_constants.dart';
import 'package:second_hand/core/extensions/context_extension.dart';
import 'package:second_hand/core/init/navigation/navigation_service.dart';
import 'package:second_hand/core/init/notifier/product_notifer.dart';
import 'package:second_hand/view/_product/_widgets/textformfield/custom_text_form_field.dart';
import 'package:second_hand/view/app/addproduct/include_some_details/viewmodel/include_some_details_view_model.dart';
enum ProductState {
verybad(name: 'Very Bad'),
bad(name: 'Bad'),
normal(name: 'Normal'),
good(name: 'Good'),
verygood(name: 'Very Good');
const ProductState({required this.name});
final String name;
}
class IncludeSomeDetailsView extends StatefulWidget {
const IncludeSomeDetailsView({super.key});
#override
State<IncludeSomeDetailsView> createState() => IncludeSomeDetailsViewState();
}
class IncludeSomeDetailsViewState extends IncludeSomeDetailsViewModel {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Include some details'),
),
body: Form(
key: formKey,
child: Padding(
padding: context.paddingAllMedium,
child: Column(
children: [
TitleTextFormField(titleController: titleController),
DescriptionTextFormField(describeController: describeController),
DropdownButton<ProductState>(
value: valueProductState,
items: ProductState.values
.map<DropdownMenuItem<ProductState>>(
(value) => DropdownMenuItem<ProductState>(
value: value,
child: Text(value.name),
),
)
.toList(),
onChanged: (productState) {
setState(
() {
stateController.text = productState!.name;
valueProductState = productState;
},
);
},
),
const Spacer(),
NextButton(
formKey: formKey,
titleController: titleController,
stateController: stateController,
describeController: describeController),
],
),
),
),
);
}
}
class TitleTextFormField extends StatelessWidget {
const TitleTextFormField({
Key? key,
required this.titleController,
}) : super(key: key);
final TextEditingController titleController;
#override
Widget build(BuildContext context) {
return Padding(
padding: context.paddingOnlyTopSmall,
child: CustomTextFormField(
controller: titleController,
labelText: 'title',
prefix: const Icon(Icons.title_outlined),
),
);
}
}
class DescriptionTextFormField extends StatelessWidget {
const DescriptionTextFormField({
Key? key,
required this.describeController,
}) : super(key: key);
final TextEditingController describeController;
#override
Widget build(BuildContext context) {
return Padding(
padding: context.paddingOnlyTopSmall,
child: CustomTextFormField(
controller: describeController,
labelText: 'description',
prefix: const Icon(Icons.description),
),
);
}
}
class NextButton extends StatelessWidget {
const NextButton({
Key? key,
required this.formKey,
required this.titleController,
required this.stateController,
required this.describeController,
}) : super(key: key);
final GlobalKey<FormState> formKey;
final TextEditingController titleController;
final TextEditingController stateController;
final TextEditingController describeController;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
context.read<ProductNotifier>().setProduct(
title: titleController.text,
state: stateController.text,
description: describeController.text,
);
NavigationService.instance.navigateToPage(path: NavigationConstants.UPLOAD_PHOTOS);
}
},
child: const Text(
'Next',
),
);
}
}

is there a way to get a value from a class to outside the class?

i have a drop down class which has in its build widget a dropdown widget which on changed it set state the value of selected option to a value in the class.
i used the class in my screen and I don't find a way to use the selected value :(
here is my code
class ServiceCategoryDropDownList extends StatefulWidget {
const ServiceCategoryDropDownList({ Key key }) : super(key: key);
#override
State<ServiceCategoryDropDownList> createState() => _ServiceCategoryDropDownListState();
}
class _ServiceCategoryDropDownListState extends State<ServiceCategoryDropDownList> {
String value;
#override
Widget build(BuildContext context) {
final servicecategories = Provider.of<List<ServiceCategory>> (context);
return Container(
child:
DropdownButtonHideUnderline(
child:DropdownButton<String>(
value: value,
isExpanded: true,
icon: Icon(Icons.arrow_drop_down),
iconSize: 36,
items: servicecategories.map((item){
print(item.serviceCategoryName);
return DropdownMenuItem<String>(
value: item.serviceCategoryID,
child: Text(item.serviceCategoryName,style: TextStyle(color: Colors.black,fontSize: 14),),
);
}).toList(),
onChanged: (value) {
setState(() =>this.value = value);
print(value);
}
),
)
);
}
}
and here where i call for the class dropdown
class ServicesContent extends StatefulWidget {
const ServicesContent({ Key key }) : super(key: key);
#override
State<ServicesContent> createState() => _ServicesContentState();
}
class _ServicesContentState extends State<ServicesContent> {
#override
Widget build(BuildContext context) {
return
Scaffold(
body: StreamProvider<List<ServiceCategory>>.value(
value: Database().serviceCategory,
initialData: [],
child: Column(
children: [
ServiceCategoryDropDownList(),
ElevatedButton(
child: Text("Add"),
onPressed: () => print(ServiceCategoryDropDownList().value)
)
])));}
}
please any one help me :(
If I understood correctly your problem, do you need to get the selected value out of the dropdown widget? if it is yes, check this explanation and the code
Accept a callback property in your widget constructor
After that, call your callback passing the selected value
Now, you can read the selected value in your ServicesContent as the widget callback parameter
Dropdown widget
class ServiceCategoryDropDownList extends StatefulWidget {
// 1. Accept a callback property in you widget constructor
final Function onChanged;
const ServiceCategoryDropDownList({
Key? key,
required this.onChanged,
}) : super(key: key);
#override
State<ServiceCategoryDropDownList> createState() =>
_ServiceCategoryDropDownListState();
}
class _ServiceCategoryDropDownListState
extends State<ServiceCategoryDropDownList> {
String? value = '';
#override
Widget build(BuildContext context) {
final servicecategories = Provider.of<List<ServiceCategory>>(context);
return Container(
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
isExpanded: true,
icon: const Icon(Icons.arrow_drop_down),
iconSize: 36,
items: servicecategories.map((item) {
print(item.serviceCategoryName);
return DropdownMenuItem<String>(
value: item.serviceCategoryID,
child: Text(
item.serviceCategoryName,
style: const TextStyle(
color: Colors.black,
fontSize: 14,
),
),
);
}).toList(),
onChanged: (value) {
setState(() => this.value = value);
// 2. Call your callback passing the selected value
widget.onChanged(value);
},
),
),
);
}
}
ServiceContent
class _ServicesContentState extends State<ServicesContent> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamProvider<List<ServiceCategory>>.value(
value: Database().serviceCategory,
initialData: [],
child: Column(
children: [
ServiceCategoryDropDownList(
onChanged: (String value) {
// 3. Now you can read the selected value here and make cool things
print(value);
},
),
ElevatedButton(
child: Text("Add"),
// 4. You can't do this ServiceCategoryDropDownList().value, so delete it and get the value at point 3
onPressed: () => print(ServiceCategoryDropDownList().value),
)
],
),
),
);
}
}
Let me know if you can solve your issue :)

Change the value of the radio button when selected

Ive got a list of strings
List<String> answerOptions=['Apple','Orange','Grapes','Kiwi'];
and I've created a custom radio button file named QuizRadioButton
class QuizRadioButton extends StatefulWidget {
final String label;
final void Function(dynamic) onChanged;
const QuizRadioButton(
{required this.label, required this.onChanged, Key? key})
: super(key: key);
#override
_QuizRadioButtonState createState() => _QuizRadioButtonState();
}
class _QuizRadioButtonState extends State<QuizRadioButton> {
int? _value = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Radio<int>(
value: 1,
groupValue: _value,
onChanged: (value) => setState(() => _value = value),
),
Text(widget.label, style: Theme.of(context).textTheme.headline3),
],
),
);
}
}
I've used this radio button class and I've populated 4 radio buttons using the list mentioned above
Widget content(BuildContext context){
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Text('which fruit of these are red in color ?'),
SizedBox(height: 30.0,),
for(int i=0;i<answerOptions.length;i++)Container(
child:QuizRadioButton(label: answerOptions[i], onChanged: (value){}) ,
)
],
),
);
}
and I get the output as
Right now we can select all 4 radio buttons at once, what I want is if I want to pick apple, the radio button with apple as the label should be true and others as false. Please help
int groupVal=0;
Implementation:
Widget content(BuildContext context){
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Text('which fruit of these are red in color ?'),
SizedBox(height: 30.0,),
for(int i=0;i<answerOptions.length;i++)
Container(
child:QuizRadioButton(label: answerOptions[i], onChanged: (value){
setState(() {
groupVal=value;
});
}, index: i, groupVal: groupVal) ,
)
],
),
),
}
Your QuizRadioButton:
class QuizRadioButton extends StatefulWidget {
final String label;
final void Function(dynamic) onChanged;
int index,groupVal;
QuizRadioButton(
{required this.label, required this.groupVal, required this.onChanged, required this.index, Key? key})
: super(key: key);
#override
_QuizRadioButtonState createState() => _QuizRadioButtonState();
}
class _QuizRadioButtonState extends State<QuizRadioButton> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Radio<int>(
value: widget.index,
groupValue: widget.groupVal,
onChanged: widget.onChanged,
),
Text(widget.label, style: Theme.of(context).textTheme.headline3),
],
),
);
}
}

Flutter How to Get Rid of Default fillColor for the First Item of DropdownButton

Question: title
So I have this DropdownButton
And here is the DropdownMenuItem
As you can see, the first item has a grey color on the background. So how to remove it?
To be more clear, here is what I want to achieve
Navbar Snippet:
class Navbar extends StatefulWidget {
#override
_NavbarState createState() => _NavbarState();
}
class _NavbarState extends State<Navbar> {
action() {
print('test');
}
#override
Widget build(BuildContext context) {
return Container(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
NavbarLogo(),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
NavbarItem(
title: 'welcome',
navigationPath: RouterDashboardView.welcomeView,
),
SizedBox(width: 50.0),
NavbarItemDropdown(
items: ['labelExitLabel', 'labelExitLabel', 'actionExitAction'],
functions: [action],
),
],
),
],
),
);
}
}
Nothing fancy, just contains a logo, a text and the dropdown
NavbarDropdown Snippet:
class NavbarItemDropdown extends StatefulWidget {
final List<String> items;
final List<Function> functions;
const NavbarItemDropdown({
Key key,
#required this.items,
this.functions,
}) : super(key: key);
#override
_NavbarItemDropdownState createState() => _NavbarItemDropdownState();
}
class _NavbarItemDropdownState extends State<NavbarItemDropdown> {
#override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton(
items: widget.items.map((String _value) {
return DropdownMenuItem<String>(
value: _value,
child: _value.contains('label')
? Text(
'${_value.split('label')[1]}',
style: TextStyle(fontSize: 13.0),
)
: Text('${_value.split('action')[1]}'),
);
}).toList(),
onChanged: (String _value) {
if (_value.contains('action')) widget.functions[0]();
},
hint: Text('TestDropDown'),
),
);
}
}