reactive_forms
reactive_forms copied to clipboard
How to check only one checkbox?
I have array of checkboxes, I need to make it checkable only one of them.
I tried like this but I can check both of them:
final form = fb.group({
'question': FormArray<bool>(
[
FormControl<bool>(value: false),
FormControl<bool>(value: false),
],
});
ReactiveFormArray<bool>(
formArrayName: entry.key,
builder: (_, array, __) {
return Column(
children: array.value!
.asMap()
.entries
.map(
(e) => ReactiveCheckboxListTile(
key: ValueKey('question${e.key}'),
formControlName: '${e.key}',
title: Text(e.key == 1 ? 'No' : 'Yes'),
),
)
.toList(),
);
},
),
You need a radio buttons Checkboxes allow to check all of them. So basically you are using wrong control to achieve you goals. This is why you have issues and your users will be confused. In other case is you still want to achieve this behavior use listener from reactive_forms_lbc package and combining prev and current value - reset the undesired fields to false. But better use radio.
But better use radio.
tried with Radio but I can check both of them, should the control name should be same?
Hi @taskindrp,
Here is a simple code example:
import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
class RadioSample extends StatelessWidget {
const RadioSample({Key? key}) : super(key: key);
static const questionField = 'question';
FormGroup get form => FormGroup({
questionField: FormControl<bool>(value: false),
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ReactiveFormBuilder(
form: () => form,
builder: (context, form, child) {
return Column(
children: [
ReactiveRadioListTile(
formControlName: questionField,
title: const Text('Yes'),
value: true,
),
ReactiveRadioListTile(
formControlName: questionField,
title: const Text('No'),
value: false,
),
],
);
},
),
);
}
}
This is a very simple sample. I have used ReactiveFormBuilder
, but the result is the same if you use ReactiveForm
in combination with a StatefulWidget or if you use ReactiveForm
in combination with a State Management Library (We always recommend this last approach for a more robust Flutter applications).
Notice that the FormControl
is only one, the same control in bound to both ReactiveRadioListTile
.
I hope this clarifies your questions.
Using checkboxes and an array is also possible to implement but requires more code a little bit of complexity because you need to manually handle turn off/on all the checkboxes. So we recommend using Radios, it is simpler and intuitive to UI users.
Hi, @taskindrp any update on this issue? Was it useful the code above? Do you still want to use checkboxes or the radio buttons is enough for you?
Hi, @joanpablo
I used checkboxes as suggested by @vasilich6107 using reactive_forms_lbc package.
In other case is you still want to achieve this behavior use listener from reactive_forms_lbc package and combining prev and current value - reset the undesired fields to false.
also tried your implementation @joanpablo but still could check both radio buttons.
also tried your implementation @joanpablo but still could check both radio buttons.
Hi @taskindrp,
I'm glad that you have solved the issue using reactive_forms_lbc, but I want to say that
the implementation I had shown you above using ReactiveRadioListTile
is 100% fully functional.
I will create 2 more Fully Flutter App examples and I will post you here with a video so you can understand the implementation from scratch.
Are you using any specific State Management Library?
Are you using any specific State Management Library?
Yes, riverpod but it is not connected to the form.
Another question that I have for you:
What exactly is your expected result:
1- One answer to all questions, something like: { answer: 'Option1' }
2- Or do you want to get an array of booleans like:
{ answers: [false, true] }
Like { answers: [false, true] }
also { answers: [false, true, false] }
etc. number of checkboxes can grow dynamically.
Hi @taskindrp
sorry the delay. This is a fully functional version:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reactive_forms/reactive_forms.dart';
class Question {
final String label;
const Question({
required this.label,
});
}
class QuestionsRepository {
Future<List<Question>> loadQuestions() {
return Future.delayed(
const Duration(seconds: 3),
() => [
const Question(label: 'Yes'),
const Question(label: 'A lot'),
const Question(label: 'The best ever'),
const Question(label: 'I only dream with Flutter'),
],
);
}
}
enum QuestionsState {
init,
loading,
loaded,
}
class FormFields {
static const answers = 'answers';
}
class OptionsCubit extends Cubit<QuestionsState> {
final QuestionsRepository repository;
final List<Question> questions = [];
final FormGroup form = FormGroup({
FormFields.answers: FormArray<bool>([], validators: [
Validators.contains([true])
]),
});
OptionsCubit(this.repository) : super(QuestionsState.init);
FormArray<bool> get answers =>
form.control(FormFields.answers) as FormArray<bool>;
bool get isLoading => state == QuestionsState.loading;
void loadQuestions() async {
if (isLoading) {
return;
}
emit(QuestionsState.loading);
questions.addAll(await repository.loadQuestions());
createControlsFromQuestions();
emit(QuestionsState.loaded);
}
void createControlsFromQuestions() {
answers.addAll(
questions.map((_) => FormControl<bool>(value: false)).toList(),
emitEvent: false,
);
}
void onControlSelected(FormControl<bool> selectedControl) {
for (var control in answers.controls) {
if (control != selectedControl) {
control.value = false;
}
}
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider<OptionsCubit>(
create: (context) => OptionsCubit(QuestionsRepository()),
child: Scaffold(
body: BlocBuilder<OptionsCubit, QuestionsState>(
builder: (context, state) {
switch (state) {
case QuestionsState.loading:
return const Loading();
case QuestionsState.loaded:
return const QuestionsForm();
default:
return const LoadButton();
}
},
),
),
),
);
}
}
class Loading extends StatelessWidget {
const Loading({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}
class LoadButton extends StatelessWidget {
const LoadButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('Load Questions'),
onPressed: () => context.read<OptionsCubit>().loadQuestions(),
),
);
}
}
class QuestionsForm extends StatelessWidget {
const QuestionsForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final provider = context.read<OptionsCubit>();
return ReactiveForm(
formGroup: provider.form,
child: Column(
children: <Widget>[
const Text(
'Do you like Flutter?',
style: TextStyle(fontSize: 20.0),
),
for (var i = 0; i < provider.questions.length; i++)
_buildCheckbox(provider, i),
const FormAnswersIndicator(),
const FormStatusIndicator(),
],
),
);
}
Widget _buildCheckbox(OptionsCubit provider, int i) {
return ReactiveCheckboxListTile(
title: Text(provider.questions.elementAt(i).label),
formControlName: '${FormFields.answers}.$i',
onChanged: (control) {
if (control.value == true) {
provider.onControlSelected(control);
}
},
);
}
}
class FormAnswersIndicator extends StatelessWidget {
const FormAnswersIndicator({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ReactiveValueListenableBuilder(
formControlName: FormFields.answers,
builder: (context, control, child) {
return Text(control.value.toString());
},
);
}
}
class FormStatusIndicator extends StatelessWidget {
const FormStatusIndicator({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ReactiveFormConsumer(
builder: (context, formGroup, child) {
return formGroup.valid
? const Text('form VALID')
: const Text('form INVALID', style: TextStyle(color: Colors.red));
},
);
}
}
The previous example was implemented with the following dependencies:
bloc: ^8.0.0
flutter_bloc: ^8.0.0
reactive_forms: ^14.1.0