Can Widgets have one "joint" state? - flutter

I'd like to implement a custom radio button, but I can select all of them, and of course, the "radio" means, I can choose only one.
My code:
class RadioSelect extends StatefulWidget {
const RadioSelect(this.text, this.index, {Key key}) : super(key: key);
final String text;
final int index;
#override
_RadioSelectState createState() => _RadioSelectState();
}
class _RadioSelectState extends State<RadioSelect> {
int sizeIndex = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
sizeIndex = widget.index;
});
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
margin: EdgeInsets.only(right: 8, top: 8),
width: 70,
height: 55,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
width: 3,
color: sizeIndex == widget.index
? Color.fromARGB(255, 139, 195, 74)
: Colors.white,
),
borderRadius: BorderRadius.all(
Radius.circular(10) // <--- border radius here
)),
child: Text(
widget.text,
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
textAlign: TextAlign.center,
),
),
);
}
}
I know this is because every RadioSelect() is a different instance, but how can I make it work?

You can do it based on the Radio widget:
Change your widget to a StatelessWidget.
class RadioSelect extends StatelessWidget {
#override
Widget build(BuildContext context) {
// ...
}
}
Create a isSelected property and set your widget layout based on this property.
class RadioSelect extends StatelessWidget {
final bool isSelected;
const RadioSelect({this.isSelected});
#override
Widget build(BuildContext context) {
return Container(
// Some logic based on isSelected
color: isSelected ? Colors.red : Colors.white,
);
}
}
Create a onChanged property and, every time the user clicks on it, call this property.
class RadioSelect extends StatelessWidget {
final void Function() onChanged;
final bool isSelected;
const RadioSelect({this.isSelected, this.onChanged});
#override
Widget build(BuildContext context) {
return GestureDetector(
// Use onChanged as callback
onTap: () => onChanged(),
child: Container(
color: isSelected ? Colors.red : Colors.white,
),
);
}
}
Now, when you have to use multiple instances of it, you can use a index to keep track of what radio is currently selected:
class _MainWidgetState extends State<MainWidget> {
// Keeping track of the number of ratios you are using
final int _numRadios = 10;
int _currentIndex;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (int i = 0; i < _numRatios; i++)
RadioSelect(
// The radio is select if the current index of this widget
// is equal to the current index of this child in the Column
isSelected: _currentIndex == i,
// When the user taps one ratio, updte the current index
// of this widget and set a new state
onChanged: () => setState(() => _currentIndex = i),
)
]
);
}
}
Note: The code above considers the layout of all ratios are equal. If each ratio must have its own text, for example, you can replace _numRatios with a list of strings or a more descriptive data object:
class _MainWidgetState extends State<MainWidget> {
final List<String> texts = ["radio1", "radio2", "radio3", "radio4"];
int _currentIndex;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (int i = 0; i < texts.length; i++)
RadioSelect(
// Supposing you have a text property in your RadioSelect
// Access the text of the current child in the list of strings
text: texts[i],
isSelected: _currentIndex == i,
onChanged: () => setState(() => _currentIndex = i),
)
]
);
}
}

Related

how to call using rawchip widget

Basically, I have define a list of array but how to call the list on the label. can someone help me
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
color: AppColor.white,
width: MediaQuery.of(context).size.width,
child: RawChip(
showCheckmark: false,
label: Text(widget.list,
style: TextStyle(
color: isSelected ? Colors.black : const Color.fromARGB(255, 45, 110, 162),
fontFamily: 'Poppins'
),),
backgroundColor: isSelected ? Colors.white : const Color.fromARGB(255, 200, 222, 255),
shape: const StadiumBorder(
side: BorderSide(color: Colors.transparent)),
//selectedColor: Colors.red,
selected: isSelected,
onPressed: (){
setState(() {
isSelected = isSelected ? false : true;
});
},
widget.variableName is used to refer top level widget(StatefulWidget ). To get data within state class, just use local
class MyWidget extends StatefulWidget {
final int widgetData;
const MyWidget({
Key? key,
required this.widgetData,
}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final int stateData = 3;
late int makingLocal;
#override
void initState() {
super.initState();
makingLocal = widget.widgetData;
}
#override
Widget build(BuildContext context) {
...
}
}
in your case it will be just list but.
Container(
child: Column(children: list),
),

flutter dynamic Checkboxes don't check

Hi im new to Flutter and coding and tried do build my first to do app. I've created a textformfield to add new todos with a button in a container above. By pressing a button, a new todo will appear on the todo Container. I managed to dynamically give a column new todos with a CheckboxListtTitle. However, when adding a new todo, the todo with a checkbox appears but can't be checked. What did I do wrong here?
to_do_section.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_application_1/presentation/widgets/landing_page.dart';
import 'package:flutter_application_1/responsive_layout.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
var toDoList = <String>[userInput];
class ToDos extends StatefulWidget {
final List<String> list;
const ToDos({
Key? key,
required this.list,
}) : super(key: key);
#override
State<ToDos> createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
bool checked= false;
bool? value_1;
var isSelected = [false, false];
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Padding(
padding: EdgeInsets.only(
top: SizeConfig.blockSizeHorizontal * 10,
left: SizeConfig.blockSizeVertical * 2.5,
right: SizeConfig.blockSizeVertical * 2.5,
bottom: SizeConfig.screenHeight / 8),
child: SizedBox(
width: SizeConfig.blockSizeHorizontal * 100,
height: SizeConfig.blockSizeVertical * 40,
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.black45, style: BorderStyle.solid, width: 4)),
child: Padding(
padding: EdgeInsets.all(8.0),
child: ToDoColumn(setState, checked),
),
),
),
);
});
}
Column ToDoColumn(
StateSetter setState, bool checked) {
return Column(children: [
for (final value in widget.list)
CheckboxListTile(
value: checked,
onChanged: (_value_1) {
setState(() {
checked = _value_1!;
});
},
title: Text('${value}'),
activeColor: Colors.green,
checkColor: Colors.black,
)
]);
}
}
landing_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_application_1/presentation/widgets/to_do_section.dart';
final _textController = TextEditingController();
String userInput = "";
class LandingPage extends StatefulWidget {
const LandingPage({Key? key}) : super(key: key);
#override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Center(child: Text("To-Do-App")),
backgroundColor: Colors.redAccent,
),
body: SingleChildScrollView(
child: Column(
children: [ToDos(list: toDoList), ToDoAdd()],
),
),
);
}
Column ToDoAdd() {
return Column(
children: [
Padding(
padding: EdgeInsets.only(top: 8.0, left: 20, right: 20, bottom: 20),
child: TextField(
onChanged: (value) => setState(() => userInput = value) ,
textAlign: TextAlign.center,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Add a new ToDo",
) ,
),
),
MaterialButton(
color: Colors.redAccent,
onPressed: () {
setState(() {
toDoList.add(userInput);
});
},
child: Text("Admit", style: TextStyle(color: Colors.white),),
),
Text(userInput)
],
);
}
}
Every variable declared inside the build method will be redeclared on each build. You could move its declaration outside and assign a value in the initState method.
Also, the use of the StatefulBuilder is not justified in your code. You could simply use the StatefulWidget as it is.
I will take the model class from #yeasin-sheikh
class Task {
final String text;
final bool isChecked;
Task({
required this.text,
this.isChecked = false,
});
Task copyWith({
String? text,
bool? isChecked,
}) {
return Task(
text: text ?? this.text,
isChecked: isChecked ?? this.isChecked,
);
}
}
This example is a reduced one that accomplish what you are trying to do. Please notice that I'm not reproducing your design. The function addRandomToDo() could be replaced with your code for adding Tasks to the app.
class ToDoPage extends StatefulWidget {
const ToDoPage({Key? key}) : super(key: key);
#override
State<ToDoPage> createState() => _ToDoPageState();
}
class _ToDoPageState extends State<ToDoPage> {
final tasks = <Task>[];
void addRandomToDo() {
setState(() {
tasks.add(Task(text: 'Random ToDo ${tasks.length + 1}'));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ToDo App'),
),
body: ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
return CheckboxListTile(
key: ObjectKey(tasks[index]),
title: Text(tasks[index].text),
value: tasks[index].isChecked,
onChanged: (isChecked) {
setState(() {
tasks[index] = tasks[index].copyWith(isChecked: isChecked);
});
},
);
}),
floatingActionButton: FloatingActionButton(
onPressed: addRandomToDo,
tooltip: 'Add ToDo',
child: const Icon(Icons.add),
),
);
}
}
Tips:
The use of Column for this example is not recommended. The best is to use a ListView.builder() and the reason is that when you use a Column and you have more element than the screen can render the app will also render the ones that are offscreen and will result in a lack of performance but the use of ListView will be best because it only renders a few elements offscreen (maybe two or three) to avoid a memory overload and it will load and render the elements as you scroll.
Is a best practice to not use functions to return a piece of the screen but to split it into widgets. (Instead of having a ToDoColumn function just change it for a ToDoColumnWidget). The explanation for this is that will incurre in a lack of performance because all functions will be re-rendered on every widget build.
This piece of code :
EdgeInsets.only(top: 8.0, left: 20, right: 20, bottom: 20)
Could be changed for this one:
EdgeInsets.fromLTRB(20, 8, 20, 20)
You have defined isSelected inside the build method. The build method is invoked each time you set the state. Please move it to initState.
bool checked= false;
Define bool checked = false outside the build method or in initstate
Putting variables inside build method will reset the bool and always be false. Also on your snippet you are just holding text without check state. On evey-build it will also reset on this point.
You can remove StatefulBuilder while this is already a StatefulWidget. Try using state-management like riverpod or bloc instead of using global variables in project cases.
As for the solution, I am creating a model class.
class Task {
final String text;
final bool isChecked;
Task({
required this.text,
this.isChecked = false,
});
Task copyWith({
String? text,
bool? isChecked,
}) {
return Task(
text: text ?? this.text,
isChecked: isChecked ?? this.isChecked,
);
}
}
Now the list will be var toDoList = [Task(text: "")];
And your ToDos
class ToDos extends StatefulWidget {
final List<Task> list;
const ToDos({
Key? key,
required this.list,
}) : super(key: key);
#override
State<ToDos> createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
//todo: remove [StatefulBuilder]
return StatefulBuilder(builder: (BuildContext context, setStateSB) {
return Container(
child: Padding(
padding: EdgeInsets.all(8.0),
child: ToDoColumn(setStateSB),
),
);
});
}
//current
Column ToDoColumn(
StateSetter setStateSB,
) {
return Column(children: [
for (int i = 0; i < widget.list.length; i++)
CheckboxListTile(
value: widget.list[i].isChecked,
onChanged: (_value_1) {
widget.list[i] = widget.list[i].copyWith(isChecked: _value_1);
setState(() {});
setStateSB(() {});
},
title: Text('${widget.list[i].isChecked}'),
activeColor: Colors.green,
checkColor: Colors.black,
)
]);
}
}
More about StatefulWidget and cookbook on flutter

Flutter: change color for first item of the list in the top when stop scrolling in listview

I have the example code below.
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: //TODO: change the color only first item of the list when stoping the scroll
alignment: Alignment.center,
child: Text(
index.toString(),
style:
const TextStyle(fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
Screenshot:
If see the above screenshot, I stop the scrolling 3x times.
First, when the initial (open this screen), the first item of the list in the top is index 0
Second, when I stop and the first item of the list in the top is index 13
Third, when I stop and the first item of the list in the top is index 17.
So when I scroll the list and stop, first item of the list in the top should be change to green, and the others is white.
For above screenshot (example) I stop 3x times and the first item of the list in the top is in the index 0, 13, 17, so it's change to green.
1. Using ScrollController
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
late ScrollController _scrollController;
final int _itemCount = 20;
int _cardPosition = 0;
void _scrollListenerWithItemCount() {
int itemCount = _itemCount;
double scrollOffset = _scrollController.position.pixels;
double viewportHeight = _scrollController.position.viewportDimension;
double scrollRange = _scrollController.position.maxScrollExtent -
_scrollController.position.minScrollExtent;
int firstVisibleItemIndex =
(scrollOffset / (scrollRange + viewportHeight) * itemCount).floor();
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
_cardPosition = 0;
} else {
_cardPosition = firstVisibleItemIndex + 1;
}
} else {
_cardPosition = firstVisibleItemIndex + 1;
}
setState(() {});
}
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListenerWithItemCount);
}
#override
void dispose() {
super.dispose();
_scrollController.removeListener(_scrollListenerWithItemCount);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
controller: _scrollController,
itemCount: _itemCount,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: _cardPosition == index ? Colors.green : Colors.white,
alignment: Alignment.center,
child: Text(
index.toString(),
style: const TextStyle(
fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
References:
https://github.com/flutter/flutter/issues/19941#issuecomment-522587489
https://stackoverflow.com/a/54539182/8291686
2. Using Area
package: https://pub.dev/packages/inview_notifier_list
I'm not sure if I understand what you mean exactly. but try this out:
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
Timer? timer;
ScrollController controller = ScrollController();
#override
void initState() {
controller.addListener(() {
if (timer != null) {
timer!.cancel();
timer = Timer(Duration(milliseconds: 300), () {
setState(() {
timer = null;
});
});
} else {
timer = Timer(Duration(milliseconds: 300), () {
setState(() {
timer = null;
});
});
}
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 20,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: timer == null
? Colors.black
: Colors
.green, // I want change this color only for first item
alignment: Alignment.center,
child: Text(
index.toString(),
style: const TextStyle(
fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
in this code. all tiles change color. for changing only top tile. I recommend using VisibilityDetector

break a form into multiple widget and interact with those widget in flutter

i have a form which i decided to break into multiple widget for code re- usability. the problem i am having i dont know how to interact with each components. for example, if the main form declare a variable, how do i access that variable in the custom textfield widget which is store in a different dart file.
below is the code i have
form dart file (main.dart)
import 'package:flutter/material.dart';
import 'package:finsec/widget/row_text_input.dart';
import 'package:finsec/widget/text_form_field.dart';
import 'package:finsec/widget/save_button.dart';
import 'package:finsec/utils/strings.dart';
import 'package:finsec/utils/dimens.dart';
import 'package:finsec/utils/colors.dart';
import 'package:finsec/widget/column_text_input.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simple Interest Calculator App',
home: ThirdFragment(),
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
accentColor: Colors.indigoAccent),
));
}
class ThirdFragment extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ThirdFragmentState();
}
}
class _ThirdFragmentState extends State<ThirdFragment> {
var _formKey = GlobalKey<FormState>();
var _currentItemSelected = '';
bool isError = false;
bool isButtonPressed = false;
#override
void initState() {
super.initState();
}
TextEditingController amountController = TextEditingController();
TextEditingController frequencyController = TextEditingController();
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text('Simple Interest Calculator'),
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column (children: [
Padding(
padding: EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
child: CustomTextField(textInputType:TextInputType.number,
textController: amountController,
errorMessage:'Enter Income Amount',
labelText:'Income Amount for testing'),
),
RowTextInput(inputName: 'Frequency:',
textInputType: TextInputType.number,
textController: frequencyController,
errorMessage: 'Choose Income Frequency',
labelText: 'Income Amount for testing'
),
RowTextInput(inputName: 'Date Paid:',
textInputType: TextInputType.number,
textController: datePaidController,
errorMessage: 'Pick Income Payment Date',
labelText: 'Income Amount for testing'
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save),
onPressed: () => {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
},
splashColor: blueGrey,
),
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save_and_continue),
onPressed: () => {},
splashColor: blueGrey,
)
])
]
),
),
}
RowTextInput is a different dart file that contains this code. RowTextInput.dart
import 'package:flutter/material.dart';
import 'package:finsec/utils/hex_color.dart';
class CustomTextField extends StatelessWidget {
CustomTextField({
this.textInputType,
this.textController ,
this.errorMessage,
this.labelText,
});
TextInputType textInputType;
TextEditingController textController;
String errorMessage, labelText;
#override
Widget build(BuildContext context) {
bool isError = false;
return Container(
child: TextFormField(
keyboardType: textInputType,
style: Theme
.of(context)
.textTheme
.title,
controller: textController,
validator: (String value) {
if (value.isEmpty) {
return errorMessage;
}
},
decoration: InputDecoration(
labelStyle: TextStyle(
color: Colors.grey,
fontSize: 16.0
),
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //size of textfield
errorStyle: TextStyle(
color: Colors.red,
fontSize: 15.0
),
border: OutlineInputBorder(
borderSide: BorderSide(width:5.0),
borderRadius: BorderRadius.circular(5.0)
)
)
),
);
}
}
i want to access isError and isButtonPressed variables located in main.dart from RowTextInput.dart and be able to assign values. main.dart should then be able to see those values assign in RowTextInput.dart file.
also,i want to move the MaterialButton button in its own widget file (button.dart) but then i dont know how this dart file will interact with the main.dart file when button is click or to check values of isError and IS button pressed. basically, i am breaking the form into different components (textfield and button) and store them in their own separate file. but i want all the files main.dart, rowintputtext, button.dart(new) to be able to see values of variables in main.dart and change the values. is this possible? is there an easier way?
thanks in advance
If you think about it. In Flutter the Button and RawMaterialButton are already in other files. And the manage to do exactly what you want.
You should create a File mycustomButtons.dart.
In the file you should create a class that will build your Buttons...
But it must has two parameters in it's constructor actionSave actionSaveAndContinue.
You will then create two functions in your main something like:
void _save() {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
}
Then you should pass your created functions as parameters:
MyCustomButtons(actionSave: _save, actionSaveAndContinue: _saveAndContinue)
So the button will have all needed information to update your main.dart variables.
The textField is pretty much the same. But you will need pass a validation function and a TextEditingController.
You can see the font of RawnMaterialButton, TextFormField to see how they receive (and pass) data from one class to an other.
I was also looking for breaking a form into multiple classes. This is that I did :
Form
Pass the onSaved function at the form level.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_CustomFormField(
onSaved: (value) => _myModelForm.field1 = value),
),
_CustomFormField2(
onSaved: (value) => _myModelForm.field2 = value),
)
),
RaisedButton(
onPressed: () {
// Validate will return true if the form is valid, or false if
// the form is invalid.
if (_formKey.currentState.validate()) {
// Process data.
_formKey.currentState.save();
// Observe if your model form is updated
print(myModelForm.field1);
print(myModelForm.field2)
}
},
child: Text('Submit'),
),
],
),
);
}
_CustomFormField1
The onSaved function will be passed as argument. This class can be either in the same file than the form or in another dedicated file.
class _CustomFormField1 extends StatelessWidget {
final FormFieldSetter<String> onSaved;
//maybe other properties...
_CustomFormField1({
#required this.onSaved,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
// You can keep your validator here
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: onSaved,
),
);
}
}
Like onSaved, you can do the same way for focusNode, onFieldSubmitted, validator if needed in
I hope it will help you and others
There's probably a more elegant way to do it but I am currently experimenting with Singletons. See the code below:
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'dart:async';
class AppModel {
TextEditingController nameController;
TextEditingController surnameController;
StreamController<String> fullnameStreamController;
AppModel() {
nameController = TextEditingController();
surnameController = TextEditingController();
fullnameStreamController = StreamController.broadcast();
}
update() {
String fullname;
if (nameController.text != null && surnameController.text != null) {
fullname = nameController.text + ' ' + surnameController.text;
} else {
fullname = 'Please enter both names';
}
fullnameStreamController.add(fullname);
}
}
GetIt getIt = new GetIt();
final appModel = getIt.get<AppModel>();
void main() {
getIt.registerSingleton<AppModel>(AppModel());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Singleton Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text;
update() {
setState(() {
});
}
#override
void initState() {
text = 'waiting for input';
appModel.fullnameStreamController.stream.listen((data) {
text = data;
update();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amberAccent),
child: Column(
children: <Widget> [
Card(
color: Colors.white,
child: Text('Name'),
),
Card(
color: Colors.yellow,
child: NameTextField()
),
Divider(),
Card(
color: Colors.white,
child: Text('Surname'),
),
Card(
color: Colors.yellow,
child: SurnameTextField()
),
OkButton(),
Card(
color: Colors.white,
child: Text('Full name'),
),
Card(
color: Colors.orange,
child: FullnameText(text),
),
],
),
),
);
}
}
class NameTextField extends StatefulWidget {
NameTextField({Key key}) : super(key: key);
_NameTextFieldState createState() => _NameTextFieldState();
}
class _NameTextFieldState extends State<NameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.nameController,
),
);
}
}
class SurnameTextField extends StatefulWidget {
SurnameTextField({Key key}) : super(key: key);
_SurnameTextFieldState createState() => _SurnameTextFieldState();
}
class _SurnameTextFieldState extends State<SurnameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.surnameController,
),
);
}
}
class FullnameText extends StatefulWidget {
FullnameText(this.text,{Key key}) : super(key: key);
final String text;
_FullnameTextState createState() => _FullnameTextState();
}
class _FullnameTextState extends State<FullnameText> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
class OkButton extends StatefulWidget {
OkButton({Key key}) : super(key: key);
_OkButtonState createState() => _OkButtonState();
}
class _OkButtonState extends State<OkButton> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white10,
child: RaisedButton(
color: Colors.white,
child: Icon(Icons.check),
onPressed: () {appModel.update();},
),
);
}
}
Check how I use the three controllers in the update function of the AppModel class.
CustomTextFields must extends parent(widget where is form) in this case it is ThirdFragment
class CustomTextField extends ThirdFragment{
CustomTextField({
this.textInputType,
this.textController,
this.errorMessage,
this.labelText,
});

Flutter : Custom Radio Button

How can I create a custom radio button group like this in flutter
Here is the full code
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(false, 'A', 'April 18'));
sampleData.add(new RadioModel(false, 'B', 'April 17'));
sampleData.add(new RadioModel(false, 'C', 'April 16'));
sampleData.add(new RadioModel(false, 'D', 'April 15'));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
//highlightColor: Colors.red,
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 50.0,
width: 50.0,
child: new Center(
child: new Text(_item.buttonText,
style: new TextStyle(
color:
_item.isSelected ? Colors.white : Colors.black,
//fontWeight: FontWeight.bold,
fontSize: 18.0)),
),
decoration: new BoxDecoration(
color: _item.isSelected
? Colors.blueAccent
: Colors.transparent,
border: new Border.all(
width: 1.0,
color: _item.isSelected
? Colors.blueAccent
: Colors.grey),
borderRadius: const BorderRadius.all(const Radius.circular(2.0)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0),
child: new Text(_item.text),
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final String text;
RadioModel(this.isSelected, this.buttonText, this.text);
}
To use :
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Screenshot :
Screenshot (Null safe)
Full code:
Create this custom class.
class MyRadioListTile<T> extends StatelessWidget {
final T value;
final T groupValue;
final String leading;
final Widget? title;
final ValueChanged<T?> onChanged;
const MyRadioListTile({
required this.value,
required this.groupValue,
required this.onChanged,
required this.leading,
this.title,
});
#override
Widget build(BuildContext context) {
final title = this.title;
return InkWell(
onTap: () => onChanged(value),
child: Container(
height: 56,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
_customRadioButton,
SizedBox(width: 12),
if (title != null) title,
],
),
),
);
}
Widget get _customRadioButton {
final isSelected = value == groupValue;
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : null,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isSelected ? Colors.blue : Colors.grey[300]!,
width: 2,
),
),
child: Text(
leading,
style: TextStyle(
color: isSelected ? Colors.white : Colors.grey[600]!,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}
}
Use it in your widget like a regular RadioListTile.
class _MyPageState extends State<MyPage> {
int _value = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
MyRadioListTile<int>(
value: 1,
groupValue: _value,
leading: 'A',
title: Text('One'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 2,
groupValue: _value,
leading: 'B',
title: Text('Two'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 3,
groupValue: _value,
leading: 'C',
title: Text('Three'),
onChanged: (value) => setState(() => _value = value!),
),
],
),
);
}
}
I achieved that with the following logic.
reply if you need a detailed explanation
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
Parent({
Key key,
}) : super(key: key);
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int _selectedItem = 0;
selectItem(index) {
setState(() {
_selectedItem = index;
print(selectItem.toString());
});
}
#override
Widget build(BuildContext context) {
//...YOUR WIDGET TREE HERE
return ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (context, index) {
return CustomItem(
selectItem, // callback function, setstate for parent
index: index,
isSelected: _selectedItem == index,
title: index.toString(),
);
},
);
}
}
class CustomItem extends StatefulWidget {
final String title;
final int index;
final bool isSelected;
Function(int) selectItem;
CustomItem(
this.selectItem, {
Key key,
this.title,
this.index,
this.isSelected,
}) : super(key: key);
_CustomItemState createState() => _CustomItemState();
}
class _CustomItemState extends State<CustomItem> {
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Text("${widget.isSelected ? "true" : "false"}"),
RaisedButton(
onPressed: () {
widget.selectItem(widget.index);
},
child: Text("${widget.title}"),
)
],
);
}
}
You can create it with ListView and List Item with one local variable to store the selected item. And you can render the selected the ListItem based on the variable.
P.S. Let me know if you need code snippet.
[EDIT]
As you have requested, Here is code snipper which will show you how you can maintain the state of each ListView item.
Now you can play with it and make it the way you want. If you want only one selected item you can write the logic that way.
void main() {
runApp(new MaterialApp(
home: new ListItemDemo(),
));
}
class ListItemDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return new MyListItem(
title: "Hello ${index + 1}",
);
}),
);
}
}
class MyListItem extends StatefulWidget {
final String title;
MyListItem({this.title});
#override
_MyListItemState createState() => new _MyListItemState();
}
class _MyListItemState extends State<MyListItem> {
bool isSelected;
#override
void initState() {
super.initState();
isSelected = false;
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Text("${widget.title} ${isSelected ? "true" : "false"}"),
new RaisedButton(
onPressed: () {
if (isSelected) {
setState(() {
isSelected = false;
});
} else {
setState(() {
isSelected = true;
});
}
},
child: new Text("Select"),
)
],
);
}
}
https://i.stack.imgur.com/Hq0O2.png
Here is Custom Radio Group Button Widget. You can easily customize all property as per your requirement. Use:
GroupRadioButton(
label: [Text("A"), Text("B"), Text("C"), Text("D")],
padding: EdgeInsets.symmetric(vertical: 10),
spaceBetween: 5,
radioRadius: 10,
color: Const.mainColor,
onChanged: (listIndex) {
print(listIndex);
},
),
This is GroupRadioButton widget
import 'package:flutter/material.dart';
class GroupRadioButton extends StatefulWidget {
GroupRadioButton({
#required this.label,
#required this.padding,
#required this.onChanged,
this.color = Colors.blue,
this.radioRadius = 14.0,
this.spaceBetween = 5.0,
this.mainAxisAlignment = MainAxisAlignment.start,
this.crossAxisAlignment = CrossAxisAlignment.start,
});
final Color color;
final List<Widget> label;
final EdgeInsets padding;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
_GroupRadioButtonState createState() => _GroupRadioButtonState();
}
class _GroupRadioButtonState extends State<GroupRadioButton> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: widget.label != null ? widget.label.length : 0,
itemBuilder: (context, index) {
return LabeledRadio(
selectedIndex: selectedIndex,
color: widget.color,
onChanged: (value) {
setState(() {
selectedIndex = value;
widget.onChanged(value);
// print(value);
});
},
index: index,
label: widget.label[index],
crossAxisAlignment: widget.crossAxisAlignment,
mainAxisAlignment: widget.mainAxisAlignment,
radioRadius: widget.radioRadius,
spaceBetween: widget.spaceBetween,
padding: widget.padding,
);
});
}
}
class LabeledRadio extends StatelessWidget {
LabeledRadio({
#required this.label,
#required this.index,
#required this.color,
//#required this.groupValue,
//#required this.value,
#required this.onChanged,
#required this.radioRadius,
#required this.padding,
#required this.spaceBetween,
#required this.mainAxisAlignment,
#required this.crossAxisAlignment,
this.selectedIndex,
});
final Color color;
final int selectedIndex;
final Widget label;
final index;
final EdgeInsets padding;
//final bool groupValue;
//final bool value;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onChanged(index);
},
child: Padding(
padding: padding,
child: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Container(
decoration: BoxDecoration(
//color: Const.mainColor,
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
),
padding: EdgeInsets.all(2),
child: selectedIndex == index
? Container(
height: radioRadius,
width: radioRadius,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
)
: Container(
height: radioRadius,
width: radioRadius,
),
),
SizedBox(
width: spaceBetween,
),
label,
],
),
),
);
}
}
My RadioButton is like the 'Radio' widget:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class RadioButton<T> extends StatefulWidget {
RadioButton({
Key key,
#required this.value,
#required this.caption,
#required this.groupValue,
#required this.onChanged,
}) : assert(value != null),
assert(caption != null),
assert(groupValue != null),
assert(onChanged != null),
super(key: key);
final T value;
final T groupValue;
final String caption;
final Function onChanged;
#override
State<StatefulWidget> createState() => _RadioButtonState();
}
class _RadioButtonState extends State<RadioButton> {
#override
Widget build(BuildContext context) {
final bool selected = widget.value == widget.groupValue;
return GestureDetector(
onTap: () {
widget.onChanged(widget.value);
},
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: selected ? Colors.red : Colors.white),
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
widget.caption,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.button
.copyWith(color: selected ? Colors.white : Colors.red),
),
),
),
);
}
}
import 'package:flutter/material.dart';
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(true, 'A',0xffe6194B));
sampleData.add(new RadioModel(false, 'B',0xfff58231));
sampleData.add(new RadioModel(false, 'C',0xffffe119));
sampleData.add(new RadioModel(false, 'D',0xffbfef45));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 25.0,
width: 25.0,
alignment: Alignment.center,
child:Container(
height: 15.0,
width: 15.0,
decoration: new BoxDecoration(
color:Color(_item.colorCode),
borderRadius: const BorderRadius.all(const Radius.circular(15)),
)
),
decoration: new BoxDecoration(
color: Colors.transparent,
border: new Border.all(
width: 3.0,
color: _item.isSelected
? Color(_item.colorCode)
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0)
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
RadioModel(this.isSelected, this.buttonText,this.colorCode);
}
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Click here to check out put-> Here