Related
I'm using the next piece of code to render an item for a Dropdown component on Flutter web:
Widget _getColorInfo(int color, String text) {
return Row(
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Color(color),
borderRadius: BorderRadius.circular(5),
),
),
const SizedBox(width: 5),
Text(
text,
style: TextStyle(
color: Color(color),
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.bold,
fontSize: ShapeStyle.standardFontSize,
),
),
],
);
}
Now the problem is that if I do not use Flex or Expanded the widget will not recognize the overflow:
Overflow on the selected item
And the dropdown is working:
Overflow on the expanded panel
Now, if I use Flex the overflow is now recognized by the main component:
Flexible(
child: Text(
text,
style: TextStyle(
color: Color(color),
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.bold,
fontSize: ShapeStyle.standardFontSize,
),
),
),
Overlfow handled on the selected item
But now the dropdown is not working since it detects the 'size' property is missing:
Error on the expanded panel of the dropdown
Here is the error:
Image of the error
The problem is that I can't use the size of the screen to determine how much width is the component taking since all the other components are being expanded dynamically, so it will break the UI symmetry. Is there any other way to control the overflow when using a dropdown or something to differentiate the dropdown panel from the selected item so I can apply different strategies like using a fixed width for the dropdown panel and a Flex for the selected item?
I have already tried using Flex, Expanded, try catch, Wrap, ConstrainedBox, third party packages... nothing solved the problem.
UPDATE
Here is the full code of the component:
class ColorSelect extends StatelessWidget {
final AgendaFormHandler form;
final int? defaultColor;
final Map<String, int> elements = {
'Team 1': 0xFFFEC000,
'Team 2': 0xFF65CBFD,
'Team 3': 0xFFF2B085,
'Team 4': 0xFF01AE51,
'USO': 0xFF3764F7,
'Financiero': 0xFF702FA0,
'Procesos': 0xFF8FA9DA,
'TI': 0xFFFF7C81,
'Alterno 1': 0xFF00F3EB,
'Alterno 2': 0xFFA46B6B,
'Alterno 3': 0xFFDCBFB2,
'Alterno 4': 0xFFEF5CEF,
'Alterno 5': 0xFFC5D89F,
};
ColorSelect({super.key, required this.form, this.defaultColor});
#override
Widget build(BuildContext context) {
return SimpleSelectInput<int>(
title: 'Color',
customEmptyText: 'Ninguno',
activeOption: form.getValue<int?>('color') ?? defaultColor ?? 0xFFFEC000,
onOptionSelected: (color) {
form.updateValue('color', color);
},
options: List<SelectItem<int>>.from(elements.keys.map((key) {
return SelectItem(
value: elements[key]!,
text: key,
label: _getColorInfo(elements[key]!, key),
);
})),
);
}
Widget _getColorInfo(int color, String text) {
return Row(
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Color(color),
borderRadius: BorderRadius.circular(5),
),
),
const SizedBox(width: 5),
Flexible(
fit: FlexFit.tight,
child: Text(
text,
style: TextStyle(
color: Color(color),
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.bold,
fontSize: ShapeStyle.standardFontSize,
),
),
),
],
);
}
}
Here is the code of the SimpleSelectInput
/// A class to create a new option entry for the [SimpleSelectInput]
/// component with [T] as the datatype for the [value] and [String]
/// for the [label]
class SelectItem<T> {
final T value;
/// This text is only to be able to compare the content of the
/// input when applying any search filter. So, the same you put
/// in the [label] property is the one you should put here.
final String text;
final Widget? label;
SelectItem({
required this.value,
this.label,
required this.text,
});
}
/// A simple input with a bottom border and a blue title with
/// [T] as the value datatype for the dropdown options.
class SimpleSelectInput<T> extends StatelessWidget {
/// The options to be displayed in the dropdown
final List<SelectItem<T>> options;
/// The item to be rendered as the default selected item
final T activeOption;
final Function(T?) onOptionSelected;
final String Function(T?)? validator;
final String? title;
/// The text to be displayed when no option is selected
final String? customEmptyText;
final bool shouldDecorate;
final bool showSearchBox;
const SimpleSelectInput({
super.key,
required this.options,
required this.activeOption,
required this.onOptionSelected,
this.title,
this.validator,
this.shouldDecorate = true,
this.showSearchBox = false,
this.customEmptyText = '-- Seleccione --',
});
#override
Widget build(BuildContext context) {
if (activeOption == null) {
return Container();
}
// If there is no opportunity it will raise an error.
// TODO: check if it's ok to raise the BadState error.
late final SelectItem<T>? selectedOption;
try {
selectedOption = options.firstWhere(
(SelectItem<T> option) {
return option.value == activeOption;
},
);
} catch (_) {
selectedOption = null;
}
return DropdownSearch<SelectItem<T>>(
items: options,
selectedItem: selectedOption,
filterFn: (item, filter) {
return item.text.toLowerCase().contains(filter.toLowerCase());
},
dropdownButtonProps: const DropdownButtonProps(
focusColor: ColorStyle.blue,
color: Colors.black,
icon: Icon(
Icons.arrow_drop_down,
size: ShapeStyle.standardFontSize * 1.4,
),
),
dropdownBuilder: (context, value) {
return value?.label ??
Text(
customEmptyText ?? '-- Seleccione --',
style: const TextStyle(
fontSize: ShapeStyle.standardFontSize,
),
);
},
validator: (selected) {
return validator?.call(selected?.value);
},
popupProps: PopupPropsMultiSelection.menu(
showSearchBox: showSearchBox,
showSelectedItems: true,
itemBuilder: _itemBuilder,
searchFieldProps: const TextFieldProps(
scrollPadding: EdgeInsets.zero,
style: TextStyle(
color: ColorStyle.blue,
fontSize: ShapeStyle.standardFontSize,
),
),
),
compareFn: (i, s) => i.value == s.value,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
fillColor: Colors.black,
border: shouldDecorate ? null : InputBorder.none,
labelStyle: const TextStyle(
color: ColorStyle.blue,
fontSize: ShapeStyle.standardFontSize * 1.4,
),
label: title != null
? Text(
title!,
style: const TextStyle(
fontSize: ShapeStyle.standardFontSize * 1.4,
),
)
: null,
),
),
onChanged: (x) => onOptionSelected(x?.value),
);
}
Widget _itemBuilder(
BuildContext context, SelectItem<T> item, bool isSelected) {
final size = MediaQuery.of(context).size;
return Container(
width: size.width,
padding: const EdgeInsets.symmetric(
vertical: 12.0,
horizontal: 8.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
item.label ??
Text(
item.text,
style: const TextStyle(
color: Colors.black,
fontSize: ShapeStyle.standardFontSize,
),
),
// a check icon
if (isSelected)
const Icon(
Icons.check,
color: Colors.green,
size: ShapeStyle.standardIconSize,
),
],
),
);
}
}
When i select category the items will show based on the category but when i submit in the products dropdown getting error
Here is my Code...please help guys.
Widget _prepareCategoryDDL() {
return Container(
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(225, 95, 27, .3),
blurRadius: 20,
offset: Offset(0, 10))
],
),
child: DropdownButton<CategoryModel>(
hint: Text(" Categories"),
value: this._selectedCategory,
onChanged: (CategoryModel value) {
setState(() {
this._selectedCategory = value;
this._filteredstoreProdList = widget.userData.products
.where((data) => data.categoryId == value.id)
.toList();
});
},
items: widget.userData.categories.map((CategoryModel category) {
return DropdownMenuItem<CategoryModel>(
value: category,
child: Row(
children: <Widget>[
Text(
" " + category.name,
style: TextStyle(color: Colors.black),
),
],
),
);
}).toList(),
),
);
}
Widget _prepareProductsDDL() {
return Container(
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(225, 95, 27, .3),
blurRadius: 20,
offset: Offset(0, 10),
),
],
),
child: DropdownButton<ProductModel>(
hint: Text(" Product"),
value: this._selectedProducts,
onChanged: (ProductModel value) {
setState(
() {
this._selectedProducts = value;
},
);
},
items: this._filteredstoreProdList.map((ProductModel product) {
return DropdownMenuItem<ProductModel>(
value: product,
child: Row(
children: <Widget>[
Text(
product.name.length > 30
? " " + product.name.substring(0, 30)
: product.name,
style: TextStyle(color: Colors.black),
),
],
),
);
}).toList(),
),
);
}
I am trying to create a dropdown button in Flutter. I am getting a List from my json then I pass the list to my dropdownButton everything works the data is shown as intended but when I choose an element from it I get this error:
Error:
There should be exactly one item with [DropdownButton]'s value: Instance of 'ProductModel'.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
package:flutter/…/material/dropdown.dart:1
Failed assertion: line 890 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
I tried to get selected product according to category when i do that error is comming
when i tried widget.userdata.products there is no erro but all the products are coming not according to the category..
There are duplicate values in the list you're passing to the items property.
If that list is dynamic, you can try removing the duplicates.
You can just convert the List to Set which will remove the duplicate items.
E.g.:
items: myList.map(...).toSet().toList()
I want to add page ==> Like tab bar
I want to add like 2 pages - in this SegmentsBar...
How can I achieve it?
this is my code ---
[
final _segments = <String, String>{
'freelancher': 'Freelancer',
'employer': 'Employer',
};
var _value = 'freelancher';
AdvancedSegment(
sliderOffset: 5,
sliderColor: Colors.white,
backgroundColor: Colors.white24,
shadow: [
BoxShadow(
color: Colors.transparent,
),
],
activeStyle: TextStyle(
fontFamily: AppTheme.medium,
color: AppTheme.primaryColor,
),
inactiveStyle: TextStyle(
fontFamily: AppTheme.medium,
color: AppTheme.white,
),
itemPadding: EdgeInsets.symmetric(
horizontal: 45,
vertical: 10,
),
segments: _segments,
controller: _advanceC,
onValueChanged: (value) {
_value == value ? ChatPage() : ProfilePage();
},
value: _value,
),
I need help please.
Thanks Buddy.
I don't know what is AdvancedSegment, use instead AnimatedPositioned
onValueChanged:
(value) {
setState(() {
_value == value ? ChatPage() : ProfilePage();
});
}
I am new in flutter app development. I have a issue in radio buttons. i have created a logic that works with flatButtons fine but i want to use this login with radio buttons. But my login gives me error that function name can't be assigned to function expressions.
Here is the image screenshot of error.
Material Button COde:
Widget choicebutton(String option, String k) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: MaterialButton(
// materialTapTargetSize: MaterialTapTargetSize.padded,
onPressed: () => checkanswer(k),
child: Text(
mydata[1][i.toString()][k],
// textDirection:TextDirection.ltr,
//textAlign: TextAlign.left,
style: TextStyle(
fontFamily: "Alike",
fontSize: 16.0,
color: Colors.white,
),
// maxLines: 1,
),
// padding: EdgeInsets.fromLTRB(2.0, 2.0, 50.0, 50.0),
color: btncolor[k],
splashColor: Colors.indigo[700],
highlightColor: Colors.indigo[700],
minWidth: 320.0,
height: 90.0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
),
);
}
btncolor["a"] = Colors.indigo;
btncolor["b"] = Colors.indigo;
btncolor["c"] = Colors.indigo;
btncolor["d"] = Colors.indigo;
void checkanswer(String k) {
if (mydata[2][i.toString()] == mydata[1][i.toString()][k]) {
marks = marks + 1;
colortoshow = right;
} else {
colortoshow = wrong;
}
setState(() {
btncolor[k] = colortoshow;
canceltimer = true;
});
Timer(Duration(seconds: 1), nextquestion);
}
Widget Code:
Widget radioButton( k){
return RadioListTile(
value: 1,
groupValue: k ,
onChanged: (value){
checkanswer(k);
// btncolor[k];
},
title: Text(mydata[1][i.toString()][k],
style: TextStyle(
fontFamily: "Roboto",
fontSize: 18.0,
),
),
activeColor: btncolor[k],
);
}
Thanks in advance.
when passing functions as paramters you don't put the function name
For example
Widget radioBtn(){
return Radio(
value: null,
groupValue: null,
onChanged: (value){
},
);
}
If you however want to pass the function name, consider doing something like this
Widget radioBtn(){
return Radio(
value: null,
groupValue: null,
onChanged: whenRadioButtonChanges,
);
}
void whenRadioButtonChanges(value){
}
Hoep this helps you.
I'm trying to implement a button animation when on clicked it shrinks and shows the circular progress indicator (while loading) then expands and shows the result of the executed operation (in this case it is login).
The code idea came from this link. The design idea came from this link. Now I implemented this before and it worked exactly as it was supposed to. However when implementing it again here, on button pressed -> the person logs in successfully and the button changes color as per design an all. The only problem is that the button animation does not happen. I tried printing the values of the _loginButtonWidth and can actually see it decreasing and increasing as per design, but visually the width stays the same.
Code:
import 'package:flutter/material.dart';
import 'package:garuda_academy_app/Login/Authentication.dart';
import 'package:garuda_academy_app/Tools/FixedColors.dart';
import 'dart:async';
class LoginPage extends StatefulWidget {
LoginPage({this.auth, this.onLoggedIn});
#override
_LoginPageState createState() => _LoginPageState();
final BaseAuth auth;
final VoidCallback onLoggedIn;
}
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
// for device type
bool _isIos;
// text form field
String _userEmail = "";
String _userPassword = "";
final _formKey = GlobalKey<FormState>();
// for login button
int _loginButtonState = 0;
double _loginButtonWidth = double.maxFinite;
Color _loginButtonColor = primaryColor;
Color _loginButtonOutlineColor = primaryColor;
Color _loginButtonTextColor = secondaryColor;
GlobalKey _loginButtonKey = GlobalKey();
Animation _loginButtonAnimation;
AnimationController _loginButtonController;
Widget _loginButton() {
if (_loginButtonState == 0) {
return Text(
"Log In",
style: TextStyle(
color: _loginButtonTextColor,
fontSize: 20,
),
);
} else if (_loginButtonState == 1) {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(secondaryColor),
);
} else if (_loginButtonState == 2) {
return Icon(
Icons.check,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 3) {
return Icon(
Icons.close,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 4) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.check,
color: _loginButtonTextColor,
),
Icon(
Icons.check,
color: transparent,
),
Text(
"Successful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
} else if (_loginButtonState == 5) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.close,
color: _loginButtonTextColor,
),
Icon(
Icons.close,
color: transparent,
),
Text(
"Unsuccessful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
}
}
bool _validateLoginAndSave() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return true;
}
return false;
}
_animateLoginButton() async {
String userId = "";
String errorMsg = "";
setState(() {
_loginButtonState = 1;
});
// animation
double initialWidth = _loginButtonKey.currentContext.size.width;
_loginButtonController =
AnimationController(duration: Duration(milliseconds: 300), vsync: this)
..addStatusListener((AnimationStatus status) async {
if (status == AnimationStatus.completed) {
// firebase signin
try {
userId = await widget.auth.signIn(_userEmail, _userPassword);
} catch (e) {
setState(() {
errorMsg = _isIos ? e.details : e.message;
print(errorMsg);
});
}
// loading timer
Timer(Duration(seconds: 1), () {
// set login state
_loginButtonState =
(userId.length > 0 && userId != null) ? 2 : 3;
// change colors
if (_loginButtonState == 2) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = successfulColor;
_loginButtonTextColor = successfulColor;
} else if (_loginButtonState == 3) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = unsuccessfulColor;
_loginButtonTextColor = unsuccessfulColor;
}
_loginButtonController.reverse();
});
} else if (status == AnimationStatus.dismissed) {
if (_loginButtonState == 2) {
_loginButtonState = 4;
} else if (_loginButtonState == 3) {
_loginButtonState = 5;
}
// minimal time before it is done
Timer(Duration(seconds: 1), () {
setState(() {
if (_loginButtonState == 4) widget.onLoggedIn();
// reset state
_loginButtonState = 0;
// reset colors
_loginButtonColor = primaryColor;
_loginButtonOutlineColor = primaryColor;
_loginButtonTextColor = secondaryColor;
});
});
}
});
_loginButtonAnimation =
Tween(begin: 0.0, end: 1.0).animate(_loginButtonController)
..addListener(() {
setState(() {
_loginButtonWidth = initialWidth -
((initialWidth - 80.0) * _loginButtonAnimation.value);
});
print("initial: " + initialWidth.toString());
print("current: " + _loginButtonWidth.toString());
});
_loginButtonController.forward();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
_isIos = Theme.of(context).platform == TargetPlatform.iOS;
return Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
child: Center(
child: Theme(
data: ThemeData(primaryColor: primaryColor),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.all(40),
child: Text(
"Log in to continue",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Email Address",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Email cannot be empty" : null,
onSaved: (value) => _userEmail = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
obscureText: true,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Password cannot be empty" : null,
onSaved: (value) => _userPassword = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Container(
height: 60,
width: _loginButtonWidth,
child: PhysicalModel(
color: transparent,
borderRadius: BorderRadius.circular(10.0),
child: RaisedButton(
elevation: 8.0,
color: _loginButtonColor,
key: _loginButtonKey,
shape: OutlineInputBorder(
borderSide: BorderSide(
color: _loginButtonOutlineColor,
),
borderRadius: BorderRadius.circular(10.0),
),
child: _loginButton(),
onPressed: () {
setState(() {
if (_loginButtonState == 0 &&
_validateLoginAndSave()) {
_animateLoginButton();
}
});
},
),
),
),
),
],
),
),
),
),
),
);
}
}
Button width stays the same when it is supposed to shrink:
Easy fix, add a Center or Align widget as a parent of your Container button.
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Center(
child: Container(
height: 60,
width: _loginButtonWidth,
To get more info check Layout Behavior