i can't call setState() method in stf Widget - flutter

i try to save value in EMAIL variable with onChanged by used setState but the setState function not defined. i can't call it .
MyTextField is statefulWidget. what is problem i don't know .is problem related with Widget class (_buildAllTextFormField)? or what i'm beginner can anyone help me please?
import 'package:ecommernce_application/screens/signup.dart';
import 'package:flutter/material.dart';
import '../widgets/move_to_sign_or_login_screen.dart';
import '../widgets/sign_login_button.dart';
import '../widgets/text_field.dart';
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
#override
State<Login> createState() => _LoginState();
}
final _formKey = GlobalKey<FormState>();
bool obscureText = true;
// for validate Email
String pEmail = r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = RegExp(pEmail);
void validate(){
final FormState? form = _formKey.currentState;
if(form!.validate())
{ debugPrint('Yes'); }
else { debugPrint('No'); }
}
String EMAIL='';
Widget _buildAllTextFormField(BuildContext context){
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.only(top: 100),
width: double.infinity,
height: 240,
alignment: Alignment.center,
child: const Text('Login',style: TextStyle(fontSize: 40,),),
),
const SizedBox(height: 10,),
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 10,),
MyTextField(
onChanged:(v){
///////////////cant call setState The function 'setState' isn't defined
//setState((){});
},
controller: email,
validator: (String? value) {
if(value == null || value.trim().isEmpty) {
return 'Please enter your email address';
} // Check if the entered email has the right format
if (!regExp.hasMatch(value)) {
return 'Please enter a valid email address';
} // Return null if the entered email is valid
return null;
},
name: 'Email',),
const SizedBox(height: 10,),
MyTextField(
onChanged: (value){
},
controller: password,
name: 'Password',
validator: (value){
if (value == null || value.trim().isEmpty) {
return 'This field is required';
}
if (value.trim().length < 8) {
return 'Password must be at least 8 characters in length';
} // Return null if the entered password is valid
return null;
}),
const SizedBox(height: 10,),
_buildBottomPart(context),
],
),
),
],
);
}
Widget _buildBottomPart(BuildContext context){
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SignLoginButton(
name: 'Login',
onPressed:() { validate(); } ,
color: Colors.grey,
),
const SizedBox( height: 10, ),
Padding(
padding: const EdgeInsets.only( left:8.0 ),
child:MoveToScreen(
text1:'I Have Noy Account',
text2:'SignUp',
onTap: () {
Navigator.of(context).pushReplacement ( MaterialPageRoute(builder: (context) => const SignUP() ) );
},
),
),
],
);
}
final TextEditingController email = TextEditingController();
final TextEditingController password = TextEditingController();
class _LoginState extends State<Login> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child:TextField(
onChanged: (v){
setState(() {
});
},
),
// _buildAllTextFormField(context),
),
),
);
}
MyTextField
import 'package:flutter/material.dart';
class MyTextField extends StatefulWidget {
final TextEditingController controller;
final String name;
final ValueChanged<String> onChanged;
final FormFieldValidator<String> validator;
const MyTextField({Key? key ,required this.onChanged,required this.controller, required this.name, required this.validator,}) : super(key: key);
#override
State<MyTextField> createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
#override
Widget build(BuildContext context) {
return SizedBox(
width: 400,
height: 50,
child: TextFormField(
controller: widget.controller,
validator: widget.validator,
onChanged: widget.onChanged,
decoration: InputDecoration(
labelText: widget.name,
border: const OutlineInputBorder(),
),
),
);
}
}
PasswordTextField
import 'package:flutter/material.dart';
class PasswordTextField extends StatefulWidget {
final TextEditingController controller;
final String name;
final FormFieldValidator<String> validator;
//final ValueChanged<String> onChange;
const PasswordTextField({Key? key, required this.controller, required this.name, required this.validator}) : super(key: key);
#override
State<PasswordTextField> createState() => _PasswordTextFieldState();
}
class _PasswordTextFieldState extends State<PasswordTextField> {
bool _obscureText = true ;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 400,
height: 50,
child: TextFormField(
obscureText: _obscureText,
validator: widget.validator,
//onChanged: widget.onChange,
controller: widget.controller,
decoration: InputDecoration(
suffixIcon: GestureDetector(
onTap: (){
setState(() {
_obscureText =!_obscureText;
});
FocusScope.of(context).unfocus();
},
child: Icon( _obscureText == true ? Icons.visibility : Icons.visibility_off,color: Colors.black,),),
labelText: widget.name,
border: const OutlineInputBorder(),
),
),
);
}
}

You can do it in this way:
Widget _buildAllTextFormField(BuildContext context, Function() changeCallback){...}
then replace:
_buildAllTextFormField(context)
with
_buildAllTextFormField(context, () => setState((){}))
and then replace:
onChanged:(v){
///////////////cant call setState The function 'setState' isn't defined
//setState((){});
},
with:
onChanged:(v){
changeCallback.call();
},

Related

My Bloc Consumer doesn't listen to states

when i call the method fetchData from ColorsCubit the BlocConsumer doesn't listen to states i call fetch data method in initial state and it must liseten to loading state before fetching method and loaded state after fetching method but it doesn't listen to states , in listener i print the state of each one but it doesn't print it i don't know the reason
class ColorsCubit extends Cubit<ColorsStates>{
ColorsCubit() : super(InitialState());
static ColorsCubit get(context)=>BlocProvider.of(context);
List items=[];
String color="";
String errorMessage="";
String errorValue="";
String redColor="";
String value="mohamed";
List<dynamic> colors=[];
Future<void> fetchData()async{
emit(LoadingState());
final String response = await rootBundle.loadString('assets/data.json');
final data = await json.decode(response);
emit(LoadedState());
items=data["groupOfColors"]["asyncValidationColors"];
color=items[0]["color"];
errorMessage=items[0]["errorMessage"];
errorValue=items[0]["error"];
colors=data["groupOfColors"]["autoSuggestionsColors"];
redColor=data["red"];
}
List<dynamic> getSuggestions(String query)=>
colors.where((element) {
final elementLower=element.toLowerCase();
final queryLower=query.toLowerCase();
return elementLower.contains(queryLower);
}).toList();
bool checkFirstColor(String query){
return query=="Bluee"?true:false;
}
Future<void> sendDataToApi(String firstColor,String secondColor,String thirdColor,String fourthColor)async{
await ApiProvider().saveDataToApi(firstColor, secondColor, thirdColor, fourthColor);
}
}
//this is the states class
abstract class ColorsStates{}
class InitialState extends ColorsStates{}
class LoadingState extends ColorsStates{}
class LoadedState extends ColorsStates{}
//this is the BlocConsumer
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
ColorsCubit cubit = ColorsCubit();
TextEditingController firstColorController = TextEditingController();
TextEditingController secondColorController = TextEditingController();
TextEditingController thirdColorController = TextEditingController();
late TextEditingController fourthColorController =
TextEditingController(text: "redColor");
bool _enableButton = false;
bool _secondVisible = true;
final key = GlobalKey<FormState>();
void _saveData() {
cubit.sendDataToApi(firstColorController.text, secondColorController.text,
thirdColorController.text, fourthColorController.text);
}
#override
void initState() {
cubit.fetchData();
super.initState();
}
#override
void dispose() {
firstColorController.dispose();
secondColorController.dispose();
thirdColorController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ColorsCubit(),
child: BlocConsumer<ColorsCubit, ColorsStates>(
listener: (context, state) {
if(state is InitialState) print("in initial state");
if (state is LoadingState) print("in loading state");
if (state is LoadedState) print("in loaded state");
},
builder: (context, state) {
if (state is LoadedState) {
return _buildLoadingIndicator();
} else {
return Scaffold(
appBar: AppBar(
title: const Text("Colors"),
actions: [
IconButton(onPressed: ()=>Navigator.of(context).push(MaterialPageRoute(builder: (context)=>const SecondScreen())), icon:const Icon(Icons.arrow_forward))
],
),
body: Center(
child: Form(
onChanged: () => setState(() {
_enableButton = key.currentState!.validate();
}),
key: key,
child: Padding(
padding: const EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
keyboardType: TextInputType.text,
controller: firstColorController,
onChanged: (value) {
setState(() {
_secondVisible =
!firstColorController.text.startsWith('a');
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "first color"),
validator: (query) {
if (query!.isEmpty) {
return "color can't be empty";
} else if (query.trim().length < 5 ||
query.trim().length > 9) {
return "colors can't be less than 5 chars or greater than 9 chars";
} else if (cubit.checkFirstColor(query.trim())) {
return cubit.errorMessage;
}
},
),
const SizedBox(
height: 15,
),
Visibility(
visible: _secondVisible,
child: TextFormField(
keyboardType: TextInputType.text,
controller: secondColorController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "second color"),
validator: (value) {
if (value!.isEmpty) {
return "value can't be empty";
}
return null;
},
),
),
const SizedBox(
height: 15,
),
TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
controller: thirdColorController,
decoration: const InputDecoration(
hintText: "third color",
border: OutlineInputBorder())),
onSuggestionSelected: (suggestion) {
thirdColorController.text =
suggestion.toString();
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion.toString()),
);
},
noItemsFoundBuilder: (context) => const SizedBox(
height: 100,
child: Center(
child: Text(
"no colors",
style: TextStyle(
fontWeight: FontWeight.bold),
)),
),
validator: (query) {
if (query!.isEmpty) {
return "second color can't be empty";
}
return null;
},
suggestionsCallback: cubit.getSuggestions),
const SizedBox(
height: 15,
),
TextFormField(
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
enabled: false,
initialValue: cubit.redColor,
),
TextButton(
onPressed: _enableButton ? _saveData : null,
child: const Text("Save Data"))
],
),
),
),
),
);
}
},
)
);
}
}
Widget _buildLoadingIndicator() {
return const Center(
child: CircularProgressIndicator(),
);
}
enter code here

Flutter: How can I set initial value to a textController and change it after?

I have a stepper and some TextFormFields with controller in the first step. I want to show in the TextField an initialValue and if I change the value want to set the new value to the controller and keep it on change step.
Now, I can change the value of the controller but not keep it on change step
Edit Page
class EditProfilePage extends StatefulWidget {
final String uid;
const EditProfilePage({Key? key, required this.uid}) : super(key: key);
#override
_EditProfilePageState createState() => _EditProfilePageState();
}
class _EditProfilePageState extends State<EditProfilePage> {
final TextEditingController _nameController = TextEditingController();
int _index = 0;
#override
void initState() {
super.initState();
_nameController.addListener(() {
final String text = _nameController.text;
_nameController.value = _nameController.value.copyWith(
text: text,
);
});
}
#override
void dispose() {
super.dispose();
_nameController.dispose();
}
#override
Widget build(BuildContext context) {
;
final UserProvider userProvider = Provider.of<UserProvider>(context);
_nameController.text = userProvider.getUser.name;
return Scaffold(
...
body: Stepper(
controlsBuilder: (BuildContext context, ControlsDetails details) {
if (_index == 2) {
return Container(
padding: const EdgeInsets.only(right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
userProvider.updateName(
_nameController.text, userProvider.getUser.uid);
},
child: const Text('SAVE'),
),
],
),
);
} else {
return Row();
}
},
currentStep: _index,
onStepCancel: () {
_index > 0 ? setState(() => _index -= 1) : null;
},
onStepContinue: () {
_index < 2 ? setState(() => _index += 1) : null;
},
onStepTapped: (int index) {
setState(() {
_index = index;
});
},
steps: <Step>[
Step(
title: const Text('Personal Info'),
content: Container(
padding: const EdgeInsets.all(5),
child: Column(children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Name',
labelStyle: const TextStyle(color: Colors.black54),
border: const OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: Divider.createBorderSide(context),
),
enabledBorder: OutlineInputBorder(
borderSide: Divider.createBorderSide(context),
),
filled: true,
contentPadding: const EdgeInsets.all(8),
),
),
const SizedBox(
height: 24,
),
],
),
),
)
],
),
);
}
}
UserProvider
class UserProvider with ChangeNotifier {
User? _user;
final AuthMethods _authMethods = AuthMethods();
User get getUser => _user!;
Future<void> refreshUser() async {
User user = await _authMethods.getUserDetails();
_user = user;
notifyListeners();
}
}
You can use
TextEditingController.fromValue(TextEditingValue(text: "initial value"));
or
TextEditingController(text: "initial value")
Update
using nullable TextEditingController and getting context using WidgetsBinding.instance?.addPostFrameCallback inside initState to read provider.
class UserProvider with ChangeNotifier {
String userName = "intial UserName";
Future<void> setUser(String name) async {
userName = name;
notifyListeners();
}
}
class EditProfilePage extends StatefulWidget {
final String uid;
const EditProfilePage({Key? key, required this.uid}) : super(key: key);
#override
_EditProfilePageState createState() => _EditProfilePageState();
}
class _EditProfilePageState extends State<EditProfilePage> {
TextEditingController? _nameController;
int _index = 0;
#override
void initState() {
super.initState(); // to have context
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
final provider = Provider.of<UserProvider>(context, listen: false);
debugPrint("Got Name : ${provider.userName}");
_nameController = TextEditingController(text: provider.userName);
_nameController?.addListener(() {
final text = _nameController == null ? "" : _nameController!.text;
provider.setUser(text);
});
setState(() {});
});
}
#override
void dispose() {
super.dispose();
_nameController?.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Consumer<UserProvider>(
builder: (context, value, child) {
return Text(value.userName);
},
),
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Name',
labelStyle: const TextStyle(color: Colors.black54),
border: const OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: Divider.createBorderSide(context),
),
enabledBorder: OutlineInputBorder(
borderSide: Divider.createBorderSide(context),
),
filled: true,
contentPadding: const EdgeInsets.all(8),
),
),
const SizedBox(
height: 24,
),
],
),
);
}
}
More about TextEditingController.
To set the initial value to a TextEditingController
final _nameController = TextEditingController(text: 'initial value');
And to update the value:
_nameController.text='new value';

Map keeps returning null values

I have an issue with creating POST request. No matter what I input, I keep getting null as email and password.
class _LoginPageState extends State<LoginPage> {
late LoginRequestModel requestModel;
#override
void initState() {
super.initState();
requestModel = LoginRequestModel();
}
I have two TextFormFields where I added
onSaved: (input) => requestModel.email = input
Then whenever when input values and I submit on my button it keeps printing {email: null, password: null }
print(requestModel.email); //null
import 'package:flutter/foundation.dart';
class LoginRequestModel {
String? email;
String? password;
LoginRequestModel({ this.email, this.password} );
factory LoginRequestModel.fromJson(Map<String, dynamic> json) {
return LoginRequestModel(
email: json['email'],
password: json['password']
);
}
}
class Auth {
static String baseUrl = "MY_API";
var client = http.Client();
void login(LoginRequestModel model) async {
await client.post(Uri.parse(baseUrl), body: model);
}
}
TextFormField(
cursorColor: Colors.black,
obscureText: false,
onSaved: (input) => requestModel.email = input,
How do I successfully make a POST request?
Simply you can do with the TextEditingController for responsive input and for post network call you need to jsonEncode
class ResponsiveInput extends StatelessWidget {
ResponsiveInput({Key key}) : super(key: key);
TextEditingController emailTextEditingController = TextEditingController();
TextEditingController passwordTextEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.only(left: 30, right: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: emailTextEditingController,
cursorColor: Colors.black,
obscureText: false,
onSaved: (input) {},
),
TextFormField(
controller: passwordTextEditingController,
cursorColor: Colors.black,
obscureText: false,
onSaved: (input) {},
),
TextButton(onPressed: (){
LoginRequestModel login = LoginRequestModel(
email: emailTextEditingController.text,
password: passwordTextEditingController.text,
);
Auth().login(login);
}, child: Text("Login"))
],
),
);
}
}
For complete source code: link
output:
I think that there's some problem while saving your Form Try below code
class _MyHomePageState extends State<MyHomePage> {
LoginRequestModel _requestModel = LoginRequestModel();
GlobalKey<FormState> _globalKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
body: Form(
key: _globalKey,
child: Column(
children: [
TextFormField(
cursorColor: Colors.black,
obscureText: false,
onSaved: (input) => _requestModel.email = input,
),
TextFormField(
cursorColor: Colors.black,
obscureText: false,
onSaved: (input) => _requestModel.email = input,
),
Divider(),
ElevatedButton(
onPressed: () {
if (_globalKey.currentState!.validate()) {
_globalKey.currentState!.save();
Auth().login(_requestModel);
}
},
child: Text("Submit"))
],
),
),
);
}
}

Disable button while typing on Input Flutter

I want disable a button while I'm typing on input.
But the code below that I 've wrote doesn't work because the button is disabled only when I "confirm" input with keyboard, but I want disabled input while I'm typing on input.
TextEditingController myController = TextEditingController();
bool isValid = false;
#override
Widget build(BuildContext context) {
Column(
children: <Widget>[
TextField(
controller: myController,
onChanged: (value){
setState(() {
isValid = (value.isEmpty || double.tryParse(value) == null) ? false : true;
});
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
RaisedButton(
disabledColor: Colors.grey,
child: Text("${AppLocalizations.of(context).translate("test")}"),
onPressed: isValid ? () { print("test") }:null,
),
],
)
}
You can Also use myController.addListener()
To check result just copy paste below code in DartPad
When you enter number in TextField the button will enable
SAMPLE CODE
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController myController = TextEditingController();
bool isValid = false;
#override
void dispose() {
// Clean up your controller when the Widget is disposed
myController.dispose();
super.dispose();
}
#override
void initState() {
// TODO: implement initState
super.initState();
myController.text = '';
myController.addListener((){
print("Get Value: ${myController.text}");
setState(() {
isValid = (myController.text.isEmpty || double.tryParse(myController.text) == null)
? false
: true;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
TextField(
controller: myController,
onChanged: (value) {
setState(() {
});
},
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter a search term'),
),
RaisedButton(
disabledColor: Colors.grey,
child: Text("Click Me"),
onPressed: isValid
? () {
print("test");
}
: null,
),
],
),
);
}
}
Use FocusNode
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DemoPage(),
debugShowCheckedModeBanner: false,
theme: ThemeData(primaryColor: Colors.white),
);
}
}
class DemoPage extends StatefulWidget {
#override
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
TextEditingController textField1Ctrl;
TextEditingController textField2Ctrl;
FocusNode focusNode1;
FocusNode focusNode2;
#override
void initState() {
textField1Ctrl = TextEditingController();
textField2Ctrl = TextEditingController();
focusNode1 = FocusNode()..addListener(_rebuildOnFocusChange);
focusNode2 = FocusNode()..addListener(_rebuildOnFocusChange);
super.initState();
}
void _rebuildOnFocusChange() => setState(() {});
void _onButton1Pressed() {}
void _onButton2Pressed() {}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Disable Button When Text Field has focus"),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: textField1Ctrl,
focusNode: focusNode1,
),
),
RaisedButton(
child: Text("Button 1"),
onPressed: focusNode1.hasFocus ? null : _onButton1Pressed,
)
],
),
const SizedBox(height: 40.0),
Text("Disable Button When TextField is Empty or has focus"),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: textField2Ctrl,
focusNode: focusNode2,
),
),
RaisedButton(
child: Text("Button 2"),
onPressed: focusNode2.hasFocus || textField2Ctrl.text.isEmpty
? null
: _onButton2Pressed,
)
],
),
],
),
),
);
}
}
Demo: DartPad

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