How Can I Make This Dropdown Button Widget Reusable Iin Flutter? - flutter

I have the DropDownButton widget below, The widget works well as intended. However, I want to reuse this widget within the app, and simply pass options to it.
As an example, I want to call the same widget, but pass a different Title to the "brand" title, which is in the Row, then change the values in the dropdown as well.
How can I do that?
Below is the code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'FLUTTER DROPDOWN BUTTON',
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
#override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final brand = [
'ACER',
'APPLE',
'ASUS',
'DELL',
'HP',
'LENOVO',
'MICROSOFT',
'TOSHIBA',
];
String? value;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Dropdown Menu',
),
centerTitle: true,
),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: const [
Padding(
padding: EdgeInsets.only(
left: 30.0,
bottom: 5.0,
),
child: Text(
"Brand",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
Container(
margin: const EdgeInsets.only(
left: 16.0,
right: 16.0,
),
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 4.0,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
16,
),
border: Border.all(
color: Colors.blue,
width: 3.0,
),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.blue,
),
iconSize: 40.0,
isExpanded: true,
items: brand.map(buildMenuItem).toList(),
onChanged: (value) => setState(
() => this.value = value,
),
),
),
),
],
),
);
}
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
);
}

VsCode have support this method can help Extract an Widget to reuseable. Of course you if you want some more functional, you need to add your constructor property for your ow.
Follow below instruction.
Left your cursor on the Widget you want extract, click on the light bub
Select Extract Widget
Type the name for new Widget, then enter.

Create a class and return your dropdown Widget from it's build method.

import 'package:digital_show_room/utils/app_colors.dart';
import 'package:digital_show_room/utils/app_styles.dart';
import 'package:flutter/material.dart';
class AppDropDownButton<T> extends StatefulWidget {
final List<DropdownMenuItem<T>> dropdownMenuItemList;
final ValueChanged<T> onChanged;
final T value;
final bool isEnabled;
final bool isBorder;
final double radius;
final TextStyle? textStyle;
final Color? color;
final Widget? icon;
const AppDropDownButton({
Key? key,
required this.dropdownMenuItemList,
required this.onChanged,
required this.value,
this.isEnabled = true,
this.isBorder = false,
this.radius = 10.0,
this.textStyle,
this.color,
this.icon,
}) : super(key: key);
#override
_AppDropDownButtonState createState() => _AppDropDownButtonState();
}
class _AppDropDownButtonState extends State<AppDropDownButton> {
#override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: !widget.isEnabled,
child: Container(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(widget.radius)),
border: widget.isBorder
? Border.all(
color: AppColors.darkGrey,
width: 0,
)
: Border(),
color: widget.isEnabled
? (widget.color ?? AppColors.indigoLight)
: AppColors.lightGrey),
child: DropdownButtonHideUnderline(
child: DropdownButton(
isExpanded: true,
itemHeight: 50.0,
style: (widget.textStyle ?? AppStyles.subTitle16Style).copyWith(
color:
widget.isEnabled ? AppColors.darkBlue : AppColors.darkGrey),
items: widget.dropdownMenuItemList,
onChanged: widget.onChanged,
value: widget.value,
dropdownColor: AppColors.white,
iconEnabledColor: AppColors.grey,
icon: widget.icon ?? Icon(Icons.arrow_drop_down),
),
),
),
);
}
}

Related

How to achieve animated search app bar in flutter?

I want to achieve the animated search app bar: https://drive.google.com/file/d/1BnykuOZExHusxIRgareKmdaPM6RlswYe/view?usp=sharing
I tried to use a stack and an animated container, to achieve it. however, it was giving renderflex error.
I would like to know if anyone has other suggestions to achieve it.
Following is the code for the custom appbar and search widget:
AppBar:
class CustomHomeAppBar extends ConsumerStatefulWidget with PreferredSizeWidget {
#override
final Size preferredSize;
CustomHomeAppBar({Key? key, required this.title})
: preferredSize = const Size.fromHeight(60.0),
super(key: key);
final String title;
#override
ConsumerState<ConsumerStatefulWidget> createState() =>
_CustomHomeAppBarState();
}
class _CustomHomeAppBarState extends ConsumerState<CustomHomeAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
title: Stack(children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {},
child: const CircleAvatar(
radius: 18,
backgroundColor: Colors.teal,
child: CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"),
radius: 28,
),
),
),
const SizedBox(
width: 10,
),
Text(
widget.title,
style:
const TextStyle(fontWeight: FontWeight.w400, fontSize: 22),
),
],
),
Positioned(right: 0, child: AnimatedSearchBar())
]));
}
}
search widget:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
class AnimatedSearchBar extends StatefulWidget {
const AnimatedSearchBar({Key? key}) : super(key: key);
#override
State<AnimatedSearchBar> createState() => _AnimatedSearchBarState();
}
class _AnimatedSearchBarState extends State<AnimatedSearchBar> {
bool _folded = true;
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 400),
width: _folded ? 24 : MediaQuery.of(context).size.width - 16,
// height: 56,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.white,
),
child: Row(children: [
Expanded(
child: Container(
// padding: const EdgeInsets.only(left: 16),
decoration:
BoxDecoration(color: Color.fromARGB(255, 212, 207, 207)),
child: !_folded
? const TextField(
decoration: InputDecoration(
hintText: 'Search',
hintStyle: TextStyle(color: Colors.blue),
border: InputBorder.none))
: null,
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: InkWell(
onTap: () {
print("clicks");
setState(() {
_folded = !_folded;
});
},
// child: Padding(
// padding: const EdgeInsets.all(16.0),
child: Icon(
_folded ? Icons.search : Icons.close,
color: Colors.blue[900],
),
),
),
// )
]),
);
}
}
It results in the following appbar:
search with stack

How can I create a ListView correctly?

I'm not able to create a Listview in Flutter because of when I create a Listview of widgets the screen stays empty, it's something like that 1
This is the Code that I wrote and returns a list view:
import 'package:dietapp/pages/homepage.dart';
import 'package:dietapp/pages/list.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:dietapp/pages/profile.dart';
import 'package:dietapp/pages/createReg.dart';
import 'package:percent_indicator/percent_indicator.dart';
void main() {}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
const SafeArea(child: TopBar()),
const Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.only(left: 25, bottom: 20),
child: Text('Seguiment Diari', style: TextStyle(fontSize: 20)),
)),
Align(alignment: Alignment.center, child: TypesListView()),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const CreateReg()));
},
label: const Text('Crear'),
icon: const Icon(Icons.add),
),
);
}
}
class TopBar extends StatelessWidget {
const TopBar({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(25.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
"Dietapp",
style: TextStyle(
color: Colors.black, fontSize: 30, fontWeight: FontWeight.bold),
),
],
),
);
}
}
class TotalLabel extends StatefulWidget {
final String typeOf;
final String subtitle;
final Function() onPressed;
final double fillBar;
const TotalLabel(
{required this.typeOf,
required this.subtitle,
required this.onPressed,
required this.fillBar,
Key? key})
: super(key: key);
#override
State<TotalLabel> createState() => _TotalLabelState();
}
class _TotalLabelState extends State<TotalLabel> {
Color getColor(double fillBar) {
if (fillBar < 0.5) {
return Colors.orange;
} else {
return Colors.green;
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onPressed,
child: Container(
width: 350,
height: 125,
padding: const EdgeInsets.all(15.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.5),
boxShadow: [
BoxShadow(
offset: const Offset(10, 20),
blurRadius: 10,
spreadRadius: 0,
color: Colors.grey.withOpacity(.05)),
],
),
child: Column(
children: [
Text(widget.typeOf,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
)),
const SizedBox(
height: 5,
),
Text(
widget.subtitle,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.normal,
fontSize: 12),
),
const SizedBox(
height: 10,
),
const Spacer(),
LinearPercentIndicator(
width: 300,
lineHeight: 10,
barRadius: const Radius.circular(50),
backgroundColor: Colors.black12,
progressColor: getColor(widget.fillBar),
percent: widget.fillBar,
),
const Spacer()
],
),
),
);
}
}
class TypesListView extends StatelessWidget {
const TypesListView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
TotalLabel(
typeOf: 'Proteines',
subtitle: 'Range',
onPressed: () {},
fillBar: 0.2),
],
);
}
}
When I run the code, the error view is the following:
I have also tried to use a Stateless widget returning a list view but didn't worked.
Thanks you so much :)
The following is an example of how to use a ListView. Note that I created a MaterialApp since ListView is a Material Widget. You can replace ListViewExample with your own Widget containing a ListView.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ListView Example',
home: ListViewExample(),
);
}
}
class ListViewExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
Text('Text Widget 1'),
Text('Text Widget 2'),
Text('Text Widget 3'),
],
);
}
}
ListView.builder(
itemCount: 5
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(10),
child: Text("Some text $index")
),
);
}),
More about listview

Making reusable flutter widget

I've been trying to make my custom widget reusable but kept on hitting dead ends... I want to be able to use change the colors of the icons, text within the card, the first card, and the enclosed card... Also, I should be able to change the icons, text that is within the icon and also the on tap function for each of the buttons should be able to do something different whenever it's tapped
class _CustomCardState extends State<CustomCard> {
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.fromLTRB(20, 10, 20, 1),
color: Colors.red,
child: InkWell(
onTap: (){},
child: ListTile(
leading: Card(
color: Colors.white,
margin: EdgeInsets.all(5),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
KycIcons.add_a_photo,
size: 20,
color: Colors.red,
),
),
),
title: Text('Uplaod your selfie',
style: TextStyle(color: Colors.white, fontSize: 16)),
),
),
);
}
}`
Here is a very simple example of how you can build reusable widgets:
import 'package:flutter/material.dart';
class ContainerMain extends StatelessWidget {
String text;
Color color;
ContainerMain(this.text, {this.color = Colors.white});
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
alignment: Alignment.center,
height: size.height / 10,
width: size.width,
child: Text(
text,
style: TextStyle(fontSize: 20, color: color),
),
);
}
}
Here is my contribution to your class. BTW, it should be a StatelessWidget instead of a StatefulWidget since there are no state variables involved.
import 'package:flutter/material.dart';
class CustomCard extends StatefulWidget {
final Color iconColor;
final double iconSize;
final Color cardColor;
final Color innerCardColor;
final String title;
final TextStyle style;
final void Function()? onTap;
const CustomCard({
Key? key,
this.iconColor = Colors.red,
this.iconSize = 20.0,
this.cardColor = Colors.red,
this.innerCardColor = Colors.white,
this.title = 'Upload your selfie',
this.style = const TextStyle(color: Colors.white, fontSize: 16),
this.onTap,
}) : super(key: key);
#override
_CustomCardState createState() => _CustomCardState();
}
class _CustomCardState extends State<CustomCard> {
#override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.fromLTRB(20, 10, 20, 1),
color: widget.cardColor,
child: InkWell(
child: ListTile(
leading: Card(
color: widget.innerCardColor,
margin: const EdgeInsets.all(5),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
KycIcons.add_a_photo,
size: iconSize,
color: widget.iconColor,
),
),
),
title: Text(widget.title, style: widget.style),
onTap: widget.onTap,
),
),
);
}
}

Flutter : Persist Data

I am building an online shop. I have a counter[-]0[+] next to every product on the home page. I am able to add and remove products to the cart but when I switch to a different screen, my counter on the home page is reset back to 0. I would like to keep that data persistent. what would be the best way to achieve this?
cart_counter.dart
import 'package:flutter/material.dart';
import '../providers/cart.dart';
import 'package:provider/provider.dart';
import '../providers/products.dart';
class CartCounter extends StatefulWidget {
const CartCounter({Key? key}) : super(key: key);
#override
_CartCounterState createState() => _CartCounterState();
}
class _CartCounterState extends State<CartCounter> {
int numOfItems = 0;
#override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context, listen: false);
final cart = Provider.of<Cart>(context, listen: false);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterButton(
icon: Icons.remove,
onPress: () {
if (numOfItems > 0) {
setState(() {
numOfItems--;
});
cart.removeSingleItem(product.id);
}
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 13.0),
child: Text(
numOfItems.toString(),
style: const TextStyle(
fontSize: 20.0,
),
),
),
CounterButton(
icon: Icons.add,
onPress: () {
setState(() {
numOfItems++;
cart.addItem(
product.id,
product.price,
product.title,
);
});
},
),
],
);
}
}
class CounterButton extends StatelessWidget {
final IconData icon;
final void Function() onPress;
const CounterButton({
Key? key,
required this.icon,
required this.onPress,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
width: 60.0,
height: 45.0,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
elevation: 2,
backgroundColor: Theme.of(context).primaryColor,
primary: Theme.of(context).primaryColor,
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
onPressed: onPress,
child: Icon(
icon,
color: Colors.white,
size: 30.0,
),
),
);
}
}
product_item.dart
import 'package:flutter/material.dart';
import 'cart_counter.dart';
class ProductItem extends StatelessWidget {
final String id;
final String title;
final double price;
final String image;
const ProductItem(this.id, this.title, this.price, this.image, {Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: Card(
elevation: 2.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Column(
children: [
Row(
children: [
Container(
height: 50,
width: 70.0,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20.0),
bottomRight: Radius.circular(20.0),
),
color: Theme.of(context).primaryColor,
),
child: Center(
child: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.white,
),
),
),
),
],
),
Image.asset(image),
const SizedBox(
height: 10.0,
),
Text(
price.toStringAsFixed(2),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
const SizedBox(
height: 10.0,
),
const CartCounter(),
const SizedBox(
height: 20.0,
),
],
),
),
);
}
}
Use Shared Preferences or Secure Storage to store the values.
See this answer to a similar question.

Creating a custom Flutter DropDown button as a separate widget

I am working on a Flutter project, which has multiple dropdown buttons on different pages. I have created a custom dropdown button and it's working fine. But when I try to make it a separate widget (to use in other pages) in a file having some errors. below is the code I tried.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Activities> _activities = [
Activities(id: 1, icon: Icons.place, title: 'Travel'),
Activities(id: 2, icon: Icons.food_bank, title: 'Eat'),
];
List<DropdownMenuItem<Activities>> activityListDropDownItems = [];
Activities selectedActivity;
int selectedActivityId = 0;
String selectedActivityTitle = '';
IconData selectedActivityIcon = Icons.addchart;
List<DropdownMenuItem<Activities>> buildActivityList(List activities) {
List<DropdownMenuItem<Activities>> items = [];
for (Activities activity in activities) {
items.add(
DropdownMenuItem(
value: activity,
child: Row(
children: [
Text(
'${activity.title}',
style: TextStyle(
color: Colors.red,
),
),
],
),
),
);
}
return items;
}
onChangeActivityListDropDownItem(Activities selected) {
setState(() {
selectedActivity = selected;
selectedActivityId = selected.id;
selectedActivityTitle = selected.title;
selectedActivityIcon = selected.icon;
});
}
#override
void initState() {
activityListDropDownItems = buildActivityList(_activities);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom DropDown'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$selectedActivityTitle'),
SizedBox(height: 10.0),
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey,
width: 0.3,
),
borderRadius: BorderRadius.all(
Radius.circular(
30.0,
),
),
),
child: Row(
children: [
SizedBox(width: 10.0),
Icon(
selectedActivityIcon,
color: Colors.red.withOpacity(0.7),
),
SizedBox(width: 10.0),
Expanded(
child: DropdownButton(
hint: Text(
'Activity',
style: TextStyle(),
),
isExpanded: true,
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
underline: Container(),
),
)
],
),
),
SizedBox(height: 20.0),
//MyDropDown(
// value: selectedActivity,
// items: activityListDropDownItems,
// onChanged: onChangeActivityListDropDownItem,
//),
],
),
),
);
}
}
class MyDropDown extends StatelessWidget {
final List<DropdownMenuItem> items;
final IconData icon;
final dynamic value;
final String hintText;
final ValueChanged onChanged;
const MyDropDown(
{Key key,
#required this.items,
this.icon,
this.value,
this.hintText,
this.onChanged})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey,
width: 0.3,
),
borderRadius: BorderRadius.all(
Radius.circular(
30.0,
),
),
),
child: Row(
children: [
SizedBox(width: 10.0),
Icon(
icon,
color: Colors.red.withOpacity(0.7),
),
SizedBox(width: 10.0),
Expanded(
child: DropdownButton(
hint: Text(
hintText,
style: TextStyle(),
),
isExpanded: true,
value: value,
items: items,
onChanged: onChanged,
underline: Container(),
),
)
],
),
);
}
}
class Activities {
final int id;
final IconData icon;
final String title;
Activities({#required this.id, #required this.icon, #required this.title});
}
when I use the MyDropDown shown an error. How to pass value and onChanged to DropdownButton?
MyDropDown(
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
),
Your value expects a datetype dynamic while your passing Activities
class MyDropDown<T> extends StatelessWidget {
final T value;
const MyDropDown({Key key, this.value}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
Declare Object type
MyDropDown<Activies>(
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
),