We are busy creating a mobile application in Flutter that relates to credit cards. In our design, we have planned to capture the numeric fields for the card in VIN number and expiry date using 4 separate fields, as shown in the below image (I am sure you have seen similar implementations on other apps):
What is important here is that these 4 fields still need to act like a single field, namely if you type it needs to jump to the next one, if you delete or press left it needs to jump to the previous one.
We have tried two different approaches that are both giving us issues:
Use four different fields - This worked somewhat well, but became buggy really fast when the user deletes or tried to navigate between numbers.
Use a single input field with expanded letterSpacing four lines drawn underneath - This again works somewhat well, but since the font we are using is not monospaced (letters and numbers that are all the same size) the numbers do not really line up properly with the lines underneath, making it look rather terrible.
I have done some searching, but have yet to find someone who has done something similar that have posted about it online. It is somewhat difficult to make Google understand what I am asking, which is also adding to the difficulty in finding a solution.
Does anyone know of an example out there that might point me in the right direction, or does anyone on here perhaps have any clever ideas about how this might be achieved? Any advice would be greatly appreciated!
PS:
Although it is a but convoluted, please see our multiple input field code below:
Multiple texts fields:
import 'package:creditcardcurator/widgets/textFields/single_number_textfield.dart';
import 'package:flutter/material.dart';
import 'package:print_color/print_color.dart';
var focusNodes;
bool getFirstFieldFocus = true;
class ExpiryDateTextField extends StatefulWidget {
final List<TextEditingController> controllers;
final Function onChangeF;
final Function onComplete;
final bool startFocus;
final FocusNode focusNode;
final bool isDisbaled;
void requestFocus() {
Print.red(getFirstFieldFocus.toString());
if (getFirstFieldFocus) focusNodes[0].requestFocus();
}
ExpiryDateTextField(
{this.controllers,
this.onChangeF,
this.onComplete,
this.startFocus = false,
this.focusNode,
this.isDisbaled = false});
#override
_ExpiryDateTextFieldState createState() => _ExpiryDateTextFieldState();
}
class _ExpiryDateTextFieldState extends State<ExpiryDateTextField> {
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
void initState() {
super.initState();
focusNodes = [
widget.focusNode,
FocusNode(),
FocusNode(),
FocusNode(),
];
getFirstFieldFocus = true;
if (widget.startFocus) widget.requestFocus();
}
#override
Widget build(BuildContext context) {
if (widget.startFocus) widget.requestFocus();
bool onSwitchChanged(value, context, index) {
getFirstFieldFocus = false;
if (index == 4) {
//if it is on the last text box, do nothing
} else {
if (widget.controllers[index].text.length > 0) {
index++;
FocusScope.of(context).requestFocus(focusNodes[index]);
widget.controllers[index].selection = TextSelection(
baseOffset: 0,
extentOffset: widget.controllers[index].text.length);
// FocusScope.of(context).focus
}
}
return true;
}
return Row(
children: <Widget>[
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 0);
// print(this.toString(minLevel: DiagnosticLevel.debug));
onSwitchChanged(value, context, 0);
},
fNode: focusNodes[0],
cController: widget.controllers[0]),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 1);
onSwitchChanged(value, context, 1);
},
fNode: focusNodes[1],
cController: widget.controllers[1]),
),
Expanded(
flex: 1,
child: Text("/",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.display2),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 2);
onSwitchChanged(value, context, 2);
},
fNode: focusNodes[2],
cController: widget.controllers[2]),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 3);
if (value != null && value.length != 0) {
try {
widget.onComplete();
} catch (e) {
print(e.toString());
}
}
onSwitchChanged(value, context, 3);
},
fNode: focusNodes[3],
cController: widget.controllers[3],
onComplete: widget.onComplete,
),
),
],
);
}
}
Single text field:
import 'package:creditcardcurator/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SingleDigitTextField extends StatelessWidget {
final Function onChangedF;
final FocusNode fNode;
final TextEditingController cController;
final Function onComplete;
final bool isDisbaled;
// final Widget child;
// DollarTextField({this.child});
SingleDigitTextField(
{this.onChangedF,
this.fNode,
this.cController,
this.onComplete,
this.isDisbaled = false});
void defaultFunction() {
print("default function actioned");
}
#override
Widget build(BuildContext context) {
return Container(
// margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 0.0),
width: 50,
child: TextField(
enabled: !isDisbaled,
inputFormatters: [
WhitelistingTextInputFormatter(new RegExp('[0-9,.]'))
],
controller: cController,
onTap: () {
// this.cController.selection = TextSelection.fromPosition(
// TextPosition(offset: this.cController.text.length)); //cursor to end of textfield
cController.selection = TextSelection(
baseOffset: 0,
extentOffset: cController.text.length); //to begining of textfield
},
focusNode: fNode,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: getd3FontSize(context),
fontFamily: getFontFamily(context),
color: Colors.white,
),
maxLength: 1,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(bottom: -20),
counter: null,
helperStyle: TextStyle(
color: Colors.transparent,
), //hiding the max length hint text
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
keyboardType:
TextInputType.numberWithOptions(signed: true, decimal: true),
// keyboardType: TextInputType.number,
onChanged: (e) {
onChangedF(e);
},
onEditingComplete: () {
try {
onComplete();
} catch (e) {
print(e.toString());
}
},
),
);
}
}
Related
I have been working on my Store App for a time now. There is a button on my page with controlls the sizes from the T-Shirts. Like this:
As we can see in the image, when the size is at the first position or last position, the button is disabled because we cannot move that size anymore.
When I attempt to do so on my app, I get that:
The relevant error-causing widget was
EditItemSize
lib\…\components\sizes_form.dart:39
When the exception was thrown, this was the stack
#0 EditItemSize.build
package:loja_virtual_nnananene/…/components/edit_item_size.dart:60
#1 StatelessElement.build
package:flutter/…/widgets/framework.dart:4648
#2 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4574
#3 Element.rebuild
package:flutter/…/widgets/framework.dart:4267
#4 ComponentElement._firstBuild
package:flutter/…/widgets/framework.dart:4553
...
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by widgets library ═══════════════════════════════════
Null check operator used on a null value
The relevant error-causing widget was
EditItemSize
lib\…\components\sizes_form.dart:39
════════════════════════════════════════════════════════════════════════════════
Reloaded 1 of 784 libraries in 2.220ms.
This is my size_form, which builds the sizes in the page:
import 'package:flutter/material.dart';
import 'package:loja_virtual_nnananene/common/custom_drawer/custom_icon_button.dart';
import 'package:loja_virtual_nnananene/models/item_size.dart';
import 'package:loja_virtual_nnananene/models/product.dart';
import 'package:loja_virtual_nnananene/screens/edit_product/components/edit_item_size.dart';
class SizesForm extends StatelessWidget {
const SizesForm(this.product);
final Product product;
#override
Widget build(BuildContext context) {
return FormField<List<ItemSize>>(
initialValue: List.from(product.sizes),
builder: (state) {
return Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text(
'Tamanhos',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
),
CustomIconButton(
Icons.add,
Colors.black,
() {
state.value!.add(ItemSize());
state.didChange(state.value);
},
)
],
),
Column(
children: state.value!.map((size) {
return EditItemSize(
ObjectKey(key),
size,
() {
state.value!.remove(size);
state.didChange(state.value);
},
size != state.value!.first
? () {
final index = state.value!.indexOf(size);
state.value!.remove(size);
state.value!.insert(index - 1, size);
state.didChange(state.value);
}
: null,
size != state.value!.last
? () {
final index = state.value!.indexOf(size);
state.value!.remove(size);
state.value!.insert(index + 1, size);
state.didChange(state.value);
}
: null,
);
}).toList(),
),
],
);
},
);
}
}
This is the item size tile, which build each row from the sizes:
import 'package:flutter/material.dart';
import 'package:loja_virtual_nnananene/common/custom_drawer/custom_icon_button.dart';
import 'package:loja_virtual_nnananene/models/item_size.dart';
class EditItemSize extends StatelessWidget {
const EditItemSize(
Key key, this.size, this.onRemove, this.onMoveDown, this.onMoveUp);
final ItemSize size;
final VoidCallback? onRemove, onMoveUp, onMoveDown;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
flex: 30,
child: TextFormField(
initialValue: size.name,
decoration: const InputDecoration(
labelText: 'Título',
isDense: true,
),
),
),
const SizedBox(
width: 4,
),
Expanded(
flex: 30,
child: TextFormField(
initialValue: size.stock.toString(),
decoration: const InputDecoration(
labelText: 'Estoque',
isDense: true,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(
width: 4,
),
Expanded(
flex: 40,
child: TextFormField(
initialValue: size.price.toStringAsFixed(2),
decoration: const InputDecoration(
labelText: 'Preço',
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
),
CustomIconButton(
Icons.remove,
Colors.red,
onRemove!,
),
CustomIconButton(Icons.arrow_drop_up, Colors.black, onMoveUp!),
CustomIconButton(Icons.arrow_drop_down, Colors.black, onMoveDown!)
],
);
}
}
I'm having problem in this specific part of the code:
Column(
children: state.value!.map((size) {
var defaultSize;
return EditItemSize(
ObjectKey(key),
size,
() {
state.value!.remove(size);
state.didChange(state.value);
},
state.value != null && size != state.value!.first
? () {
final index = state.value!.indexOf(size);
state.value!.remove(size);
state.value!.insert(index - 1, size);
state.didChange(state.value);
}
: null,
state.value != null && size != state.value!.last
? () {
final index = state.value!.indexOf(size);
state.value!.remove(size);
state.value!.insert(index + 1, size);
state.didChange(state.value);
}
: null,
);
}).toList(),
),
If I remove the size != state.value!.last the code will work, but not the way it is supposed to
EXPLANATION:
This error occurs when you use a bang operator (!) on a nullable instance which wasn't initialized.
For example:
String? str; // Nullable String
void main() {
int length = str!.length; // Bang ! operator is used before the field was initialized.
}
You can tackle this problem by using a combination of ?. and ??
int stringLength = str?.length ?? 0; // No error
The above code line has no error as in this case when str is null, the .length won't be called and so str?.length will be equivalent to null and since stringLength cannot be null, we provide a default value of 0
Just to be more clear ?? is just a shorthand notation for null check.
For example:
x = y ?? z is equivalent to the below code:
if(y != null) {
x = y;
}
else {
x = z;
}
SOLUTION:
In your case if you replace size != state.value!.last with size != (state.value?.last ?? defaultSize) where defaultSize represents the default Size value which will be compared to the variable size when state.value is null.
If you totally want to skip comparing to a default size then you can do something like below:
(state.value != null ? (size != state.value!.last) : false) ? ()
{
// add code here that you want to run if size != state.value!.last is true
} : () {
print ("size != state.value!.last was not satisfied");
}
I am trying to use my ValueListenableBuilder, which generates Yes/ No buttons, to determine whether or not a textformfield will be visible. I.e. user selects the "Yes" button and the state of the app changes to allow the hidden textformfield to be displayed. I am extremely puzzled as to how I can either use setState, NotifyListeners, or ChangeNotifier to accomplish this task.
I am trying to avoid using either a radio button or making buttons outside of the ValueListenableBuilder because my ValueListenableBuilder is designed to generate a lot of my other buttons and I was hoping to incorporate one more function into them. Thanks in advance!
ValueListenableBuilder
ValueListenableBuilder<Option>(
valueListenable: yesNo,
builder: (context, option, _) => MakeButtons(
num0: 0,
num1: 1,
makeButtonWidth: MediaQuery.of(context).size.width * 0.20,
selected: option,
onChanged: (newOption) =>
yesNo.option = newOption,
ifSelected: (newOption) {
setState(() {
yesNo.option = newOption;
yesNo;
});
},
),
),
Make Buttons
enum Option {
option0,
option1,
}
class MakeButtons extends StatefulWidget {
MakeButtons({
this.num0,
this.num1,
this.selected,
this.onChanged,
this.ifSelected,
this.makeButtonWidth,
});
final int num0;
final int num1;
final double makeButtonWidth;
final Option selected;
final Function ifSelected;
final ValueChanged<Option> onChanged;
#override
_MakeButtonsState createState() => _MakeButtonsState();
}
class _MakeButtonsState extends State<MakeButtons> {
List<Widget> makeButtons(int num0, int num1, List<Widget> children,
List<Color> colors, List<Function> onPresses) {
List<Widget> buttons = new List();
for (int i = num0; i < num1; i++) {
buttons.add(Container(
constraints: BoxConstraints(
minWidth: widget.makeButtonWidth,
),
child: RectButton(
buttonChild: children[i],
bgColor: colors[i],
onPress: onPresses[i]),
));
}
return buttons;
}
Option selectedOption;
#override
Widget build(BuildContext context) {
List<Widget> children = [
AutoSizeText(
'Yes',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.w600, color: Colors.white),
),
AutoSizeText(
'No',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.w600, color: Colors.white),
),
];
List<Color> colors = [
selectedOption == Option.option0
? kActiveButtonColor
: kInactiveButtonColor,
selectedOption == Option.option1
? kActiveButtonColor
: kInactiveButtonColor,
];
List<Function> onPresses = [
() {
setState(() {
selectedOption = Option.option0;
});
return widget.onChanged(Option.option0);
},
() {
setState(() {
selectedOption = Option.option1;
});
return widget.onChanged(Option.option1);
},
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
makeButtons(widget.num0, widget.num1, children, colors, onPresses),
);
}
}
Visibility
visible: yesNo.title == 'A' ||
yesNo == 'Yes',
child: InputRow(
myUnit: defaultUnit,
inputParameter: 'Units',
textField: unitController,
colour: kEmoryDBlue,
),
),
Can anyone please help to implement this feature of Gmail that shows the counter to number of emails hidden when the email list becomes large ? I want to implement this in row widget where instead of being scrollable extra elements count is shown when overflow occurs.Gmail shows +15 counter for hidden emails
I was Curious to give a try to achieve the same effect, as asked.
Just in case, If anyone want a start for writing a custom one, then below code may help.
Here is my Code, Feel free to give any suggestions,
(For Now delete button in chips is not working bcoz of some logic problem, I will make it work another day)
import 'package:flutter/material.dart';
class Demo3 extends StatefulWidget {
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
String temp = "";
bool showChips = false;
List<Widget> chipsList = new List();
TextEditingController textEditingController = new TextEditingController();
final _focusNode = FocusNode();
int countChipsToDeleteLater = 0;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (!_focusNode.hasFocus) {
showChips = false;
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
height: 500,
child: new Center(
child: Container(
width: 300,
child: !showChips
? Row(
children: [
buildTextField(),
showNumberWidgetIfAny(),
],
)
: Center(
child: Wrap(
children: [
Wrap(
children: buildChips(),
),
buildTextField(),
],
),
),
),
),
),
),
);
}
buildChips() {
return chipsList;
}
buildTextField() {
return Container(
width: 200,
child: new TextField(
showCursor: true,
focusNode: _focusNode,
autofocus: true,
cursorColor: Colors.black,
style: new TextStyle(fontSize: 22.0, color: Colors.black),
controller: textEditingController,
// decoration: InputDecoration.collapsed(
// hintText: "",
// ),
onChanged: (value) {
if (value.contains(" ")) {
checkWhatToStoreInChips(value, countChipsToDeleteLater);
textEditingController.clear();
setState(() {
showChips = true;
});
countChipsToDeleteLater++;
}
},
),
);
}
checkWhatToStoreInChips(String val, int chipsIndex) {
temp = "";
for (int i = 0; i < val.length; i++) {
if (val[i] == " ") {
break;
}
temp = temp + val[i];
}
addToChips(temp, chipsIndex);
}
addToChips(String tmp, int chipsIndex) {
chipsList.add(Chip(
// onDeleted: () {
// if (chipsList.length == 0) {
// countChipsToDeleteLater = 0;
// }
// chipsList.removeAt(chipsIndex);
// print(chipsList.length);
// print(chipsIndex);
// setState(() {});
// },
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text(tmp[0]),
),
label: Text(temp),
));
}
showNumberWidgetIfAny() {
int len = chipsList.length;
if (len >= 1) {
return GestureDetector(
onTap: () {
showChips = true;
setState(() {});
},
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
"${chipsList.length.toString()} ",
style: new TextStyle(color: Colors.white, fontSize: 22),
),
),
),
);
}
return Container();
}
}
How it works:
Write something in text field, then press space, showChips boolean will become true
onChanged will detect the space and will send the string to a function.
That function will extract the string before space and then will add the string to a chip,
Finally the chip will be added to a chipslist.
We will have a boolean variable to check if the textfield is in focus and when to show the textfield and numberwidget (a widget which will keep count of the total chips, same like you asked in your question) or when to show the chipslist and textfield wraped in a wrap widget.
You can play around by changing the decoration of textfield to collapsed, to it look like the same as gmail.
Check this package, if you want to use custom package for ease.
I was facing a similar issue. I found a way to implement the Overflow count text.
Sample image
You basically have to paint the overflow text, and get its width like below
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
var textSize = textPainter.size;
textSize.width;
Then subtract that from the width available. Lets call it x.
Then create a sum of width for each row item(using TextPainter.layout() method mentioned above), till its value is less than x.
This way you'll know how many items can be shown in the row.
I have created a Flutter library to help with this.
Sorry if this has been already answered somewhere else but I am new to Flutter. I have a toString method in my widget below that needs to access the state of the widget to output the string. The widget is a card that contains a text field and other text-related operations. To store information on what a user types into the card I need to get all the data into one string which toString returns.
class TextCard extends StatefulWidget {
_TextCardState cardState = _TextCardState();
TextCard({String text = ""}) {
cardState.textController.text = text;
}
#override
_TextCardState createState() => cardState = new _TextCardState();
String toString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
return delimiter2 +
"TextCard" +delimiter3 +
cardState.getText() +
delimiter3 +
(cardState.center.toString()) +
delimiter3 +
cardState.bold.toString() +
delimiter3 +
cardState.italic.toString() +
delimiter3 +
cardState.size.toString() +
delimiter2;
}
}
The widget also takes in a string value to set the initial value of a text field in the state below
class _TextCardState extends State<TextCard> {
double size = 18;
bool bold = false;
bool italic = false;
bool center = false;
var textController = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
height: _cardSizeY,
width: _cardSizeX,
child: Card(
elevation: _elevation,
child: Center(
child: Column(children: [
ListTile(leading: Icon(Icons.text_fields)),
ButtonBar(children: [
IconButton(
icon: Icon(Icons.format_bold),
onPressed: () {
updateText(size, !bold, italic, center);
},
),
IconButton(
icon: Icon(Icons.format_italic),
onPressed: () {
updateText(size, bold, !italic, center);
},
),
Slider(
value: size,
max: 80,
min: 1,
onChanged: (size) {
updateText(size, bold, italic, center);
})
]),
TextField(
maxLines: null,
style: TextStyle(
fontWeight: (bold) ? FontWeight.bold : FontWeight.normal,
fontStyle: (italic) ? FontStyle.italic : FontStyle.normal,
fontSize: size),
textAlign: (center) ? TextAlign.center : TextAlign.start,
controller: textController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(10)))))
]))));
}
void updateText(double size, bool bold, bool italic, bool center) {
setState(() {
this.size = size;
this.bold = bold;
this.italic = italic;
this.center = center;
});
}
String getText() {
return textController.value.text;
}
}
When I run this code I get the error the create state function returned an old invalid state instance.
I have looked into putting the text controller into the _TextCardState() class but I would not be able to change the initial value of the TextField.
So I see what you are trying to do here but there are better ways to access the value of a textfield from outside of the class.
Instead of access your toString method from outside, which relies on a values from the private state class, I suggest a state management solution that will make this way easier and cleaner. You'll also have easier access to all those variables you need.
What you're doing here is not something that's meant to be done, which is why you're getting those state errors.
_TextCardState cardState = _TextCardState();
Here's a way to do it using GetX.
All your data will live in a GetX Controller class below and will be used in your now stateless TextCard widget.
class Controller extends GetxController {
var textController = TextEditingController();
String textfieldString = '';
double size = 18;
bool bold = false;
bool italic = false;
bool center = false;
#override
void onInit() {
super.onInit();
// updates the value of textfieldString anytime the user types
textController.addListener(() {
textfieldString = textController.text;
debugPrint(textController.text);
});
}
// this method lives in this class and is accessible from anywhere. The
// only thing not clear is what delimier2 is and where it comes from
// toString is not a good name because that is an overridden method that lives
// in most Dart classes
String buildString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
return delimiter2 +
"TextCard" +delimiter3 +
textfieldString +
delimiter3 +
(center.toString()) +
delimiter3 +
bold.toString() +
delimiter3 +
italic.toString() +
delimiter3 +
size.toString() +
delimiter2;
}
// single responsibility methods as opposed to firing one big function
// multiple times when its only affecting one variable
void toggleBold() {
bold = !bold;
update();
}
void toggleItalic() {
italic = !italic;
update();
}
void toggleCenter() {
center = !center;
update();
}
void updateSize(double sliderValue) {
size = sliderValue;
update();
}
}
Put this in your main before running your app. Can be done anywhere as long as its before you try and access the controller.
Get.put(Controller());
And here is your TextCard widget
class TextCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller =
Get.find<Controller>(); // finding the initalized controller
return Container(
height: _cardSizeY,
width: _cardSizeX,
child: Card(
elevation: 20,
child: Center(
child: Column(
children: [
ListTile(leading: Icon(Icons.text_fields)),
ButtonBar(children: [
IconButton(
icon: Icon(Icons.format_bold),
onPressed: () {
controller.toggleBold();
},
),
IconButton(
icon: Icon(Icons.format_italic),
onPressed: () {
controller.toggleItalic(); // accessing method via controller
},
),
// GetBuilder rebuilds children when value of controller variable changes
GetBuilder<Controller>(
builder: (_) {
return Slider(
value: controller
.size, // accessing size in other class via controller
max: 80,
min: 1,
onChanged: (value) {
controller.updateSize(value);
});
},
)
]),
GetBuilder<Controller>(
builder: (_) {
return TextField(
maxLines: null,
style: TextStyle(
fontWeight: (controller.bold)
? FontWeight.bold
: FontWeight.normal,
fontStyle: (controller.italic)
? FontStyle.italic
: FontStyle.normal,
fontSize: controller.size),
textAlign: (controller.center)
? TextAlign.center
: TextAlign.start,
controller: controller.textController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
);
},
)
],
),
),
),
);
}
}
So where ever you are in your app where you need that function, find the controller and get your value.
final controller = Get.find<Controller>():
final newString = controller.buildString();
This will be easier and use less memory because TextCard is now stateless.
I am new to Flutter and currently working on a project where I need to show user a list of matched members so that a user can easily select one of them. For that I use AutoCompleteTextField. It is working fine as long as provided by already fetched list of members to it's suggestion property. But I wonder, why it's not working when I put it under BlocBuilder. Event hits on textChanged method and the state also returns a list but the suggestions are invisible.
Widget autoCompleteSearchBar() {
return BlocBuilder<OrderInfoBloc, MyOrderInfoStates>(
builder: (context, state) {
return AutoCompleteTextField<Member>(
clearOnSubmit: false,
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
decoration: InputDecoration(
hintText: 'Search Member Here..',
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(Icons.cancel),
iconSize: 20,
color: Colors.yellow[700],
onPressed: () {
_autoCompleteController.text = "";
},
),
contentPadding: EdgeInsets.fromLTRB(10, 30, 10, 20),
hintStyle: TextStyle(color: Colors.grey),
),
keyboardType: TextInputType.text,
controller: _autoCompleteController,
textChanged: (value) {
context.read<OrderInfoBloc>().add(SearchTextChanged(text: value));
},
itemSubmitted: (item) async {
_autoCompleteController.text = state.radioGroupValue == 'By Code'
? item.memberNo
: item.memberName;
context.read<OrderInfoBloc>().add(SelectedMember(member: item));
},
key: _key,
suggestions: state.membersList,
itemBuilder: (context, item) {
print(item);
// return state.radioGroupValue == 'By Code'
// ? autoCompleteSearchBarRow(
// item: item.memberNo, icon: Icon(Icons.person))
// : autoCompleteSearchBarRow(
// item: item.memberName, icon: Icon(Icons.person));
return autoCompleteSearchBarRow(
item: item.memberNo, icon: Icon(Icons.person));
},
itemFilter: (item, query) {
print(query);
// bool _itemFilter;
// if (_autoCompleteController.text.isNotEmpty) {
// _itemFilter = state.radioGroupValue == 'By Code'
// ? item.memberNo
// .toLowerCase()
// .startsWith(query.toLowerCase())
// : item.memberName
// .toLowerCase()
// .startsWith(query.toLowerCase());
// } else {
// _autoCompleteController.text = '';
// _itemFilter = false;
// }
// return _itemFilter;
return item.memberNo.toLowerCase().startsWith(query.toLowerCase());
},
itemSorter: (a, b) {
// return state.radioGroupValue == 'By Code'
// ? a.memberNo.compareTo(b.memberNo.toLowerCase())
// : a.memberName.compareTo(b.memberName.toLowerCase());
print(b);
return a.memberNo.compareTo(b.memberNo.toLowerCase());
},
);
}
);
}
Widget autoCompleteSearchBarRow(
{#required String item, #required Icon icon}) {
return ListTile(
leading: icon,
title: Text(item),
);
}
Use the flutter_typeahead package which works well with flutter bloc
Now, come to the bloc side you don't need to wrap your autocomplete widget with blocbuilder cause if you do so, the bloc will always repaint the widget whenever an event fires. so in your case when you are typing in the text box, event fires and bloc rebuild the widget and because of that suggestion don't show up and even if you see suggestion they will be gone once the corresponding bloc state occurs and rebuild the widget
the recommended solution would be seen below
Don't add any state to get suggestions just return the result or records from event as below. (below function added to Cubit file)
Future<List<Item>> getProductItemsBySearchString(String item) async {
return await itemRepository.getItemsByName(item);
}
as you can see above I am returning item records directly from the getProductItemsBySearchString() event method (no bloc state)
Then use It like below
class ItemScreen extends StatelessWidget {
// then you can call bloc event in function as below
Future<List<Item>> getItemSuggestionsList(
BuildContext context, String text) async {
final bloc = context.read<ItemCubit>();
List<Item> data = await bloc.getProductItemsBySearchString(text);
if (data != null) {
return data;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
return TypeAheadField(
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
controller: _itemEditingController,
autofocus: false),
suggestionsCallback: (pattern) {
// call the function to get suggestions based on text entered
return getItemSuggestionsList(context, pattern);
},
itemBuilder: (context, suggestion) {
// show suggection list
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(suggestion.name),
trailing: Text(
'Item Code: ${suggestion.code}',
),
),
);
},
onSuggestionSelected: (suggestion) {
// get selected suggesion
},
);
}
}