I’m experiencing an issue with TextFormField in Flutter. I’m trying to retrieve values from a JSON Array List and display them within a TextFormField. However, when I attempt to edit or change the value inside the TextFormField, it always reverts back to the initial value from the JSON Array List. How can I resolve this issue ?
This is my code :
class ButtonEdit extends StatefulWidget {
final int soalId;
const ButtonEdit({
Key? key,
required this.soalId,
}) : super(key: key);
@override
State<ButtonEdit> createState() => _ButtonEditState();
}
class _ButtonEditState extends State<ButtonEdit> {
String _selectedDifficulty = '';
String fileName = "Upload Gambar";
TextEditingController? _soalController;
XFile? _imageFile;
String? imageSoal;
List<TextEditingController> _jawabanControllers = [];
List<Jawaban> _jawabanList = [];
final _imageFiles = <XFile?>[];
final _existingImageUrls = <String?>[];
String? selectedOption;
Future<void> getImage(ImageSource source) async {
final ImagePicker picker = ImagePicker();
final XFile? photo =
await picker.pickImage(source: source, imageQuality: 50);
if (photo != null) {
_imageFile = photo;
} else {
_imageFile = null;
}
}
Future<XFile?> getImageJawaban(ImageSource source, int index) async {
final ImagePicker picker = ImagePicker();
final XFile? photo =
await picker.pickImage(source: source, imageQuality: 50);
if (photo != null) {
// Perbarui state secara langsung daripada memanggil setState()
_imageFiles[index] = photo;
_existingImageUrls[index] = null;
}
return photo;
}
@override
void initState() {
super.initState();
_soalController = TextEditingController();
_jawabanControllers.add(TextEditingController());
_initializeJawabanFields(2); // Initialize jawaban fields
_clearJawabanFields();
_fetchData(); // Clear jawaban fields
}
void _fetchData() async {
// Panggil API untuk mengambil data
final result = await EditSoalRemoteDatasource()
.getEditSoal(widget.soalId); // Misalnya, 32 adalah matkulId
result.fold(
(error) {
// Tangani error jika terjadi
print('Error: $error');
},
(data) {
setState(() {
_initializeJawabanList(data.jawaban ?? []);
_initializeJawabanControllers();
});
},
);
}
void _initializeJawabanList(List<Jawaban> jawabanList) {
_jawabanList = jawabanList;
}
void _initializeJawabanControllers() {
for (var jawaban in _jawabanList) {
_jawabanControllers.add(TextEditingController(text: jawaban.jawaban));
}
}
void _initializeJawabanFields(int length) {
// Initialize jawaban fields
for (int i = 0; i < length; i++) {
_jawabanControllers.add(TextEditingController());
_imageFiles.add(null);
_existingImageUrls.add(null);
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Perbarui data ketika ada perubahan di Bloc
_updateData();
}
void _clearAndInitializeJawabanFields(int length) {
_initializeJawabanFields(length);
setState(() {}); // Update the widget after clearing and initializing fields
}
void _updateData() {
final state = context.read<GetEditBloc>().state;
state.maybeWhen(
loaded: (data) {
setState(() {
_soalController?.text = data.soal?.soal ?? '';
_selectedDifficulty = data.soal?.tingkat ?? '';
imageSoal = data.soal?.gambarSoal;
_initializeJawabanFields(data.jawaban?.length ?? 0);
for (int i = 0; i < (data.jawaban?.length ?? 0); i++) {
_jawabanControllers[i].text = data.jawaban![i].jawaban ?? '';
_imageFiles[i] = null;
_existingImageUrls[i] = data.jawaban![i].gambarJawaban;
}
});
},
orElse: () {},
);
}
void _updateJawaban(int index, String value) {
setState(() {
_jawabanList[index].jawaban = value;
});
}
void _addJawabanField() {
setState(() {
_jawabanControllers.add(TextEditingController());
_imageFiles.add(null);
_existingImageUrls.add(null);
});
}
void _clearJawabanFields() {
// Clear jawaban fields
_jawabanControllers.clear();
_imageFiles.clear();
_existingImageUrls.clear();
}
@override
Widget build(BuildContext context) {
return Container(
height: 700,
decoration: const BoxDecoration(
color: ColorName.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
children: [
BlocBuilder<GetEditBloc, GetEditState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Text('Belum ada data'),
loading: () => const CircularProgressIndicator(),
loaded: (data) {
final soal = data.soal!.soal;
if (_soalController!.text.isEmpty) {
_soalController?.text = soal ?? '';
}
final tingkat = data.soal!.tingkat;
if (_selectedDifficulty == '') {
_selectedDifficulty = tingkat ?? '';
}
final gambarSoal = data.soal!.gambarSoal;
if (imageSoal == null) {
imageSoal = gambarSoal;
}
final datas = data.jawaban ?? [];
_clearJawabanFields(); // Membersihkan bidang yang sudah ada
if (datas.isNotEmpty) {
_jawabanControllers.clear();
_imageFiles.clear();
_existingImageUrls.clear();
for (int i = 0; i < datas.length; i++) {
_jawabanControllers.add(TextEditingController(
text: datas[i].jawaban ?? ''));
_imageFiles.add(null);
_existingImageUrls.add(datas[i].gambarJawaban);
}
}
// _addJawabanField(); // Tambahkan satu bidang jawaban setelah inisialisasi
// setState(() {}); // Panggil setState() di sini
print(
"ini adalah _jawabanControllers: $_jawabanControllers dan ${_jawabanControllers.length} ");
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () {
// Action for Bersihkan button
_clearJawabanFields();
},
style: ElevatedButton.styleFrom(
backgroundColor:
Colors.red, // Background color
),
child: const Text(
'Clear',
style: TextStyle(color: ColorName.white),
),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () {
_addJawabanField();
},
style: ElevatedButton.styleFrom(
backgroundColor: ColorName
.primary, // Use theme's primary color
),
child: const Text(
'Add',
style: TextStyle(color: ColorName.white),
),
),
]),
const SizedBox(height: 20),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _jawabanControllers.length,
itemBuilder: (context, index) {
return Column(
children: [
const SizedBox(height: 10),
TextFormField(
controller: _jawabanControllers[index],
autofocus: true,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(
vertical: 5, horizontal: 8),
labelText:
"Option ${String.fromCharCode(65 + index)}",
border: const OutlineInputBorder(),
),
onFieldSubmitted: (value) {
_updateJawaban(index, value);
},
),
const SizedBox(height: 10),
Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: index < _imageFiles.length &&
_imageFiles[index] != null
? Image.file(
File(_imageFiles[index]!.path),
height: 200,
width: MediaQuery.of(context)
.size
.width,
fit: BoxFit.cover,
)
: index <
_existingImageUrls
.length &&
_existingImageUrls[index] !=
null
? Image.network(
_existingImageUrls[index]!,
height: 200,
width:
MediaQuery.of(context)
.size
.width,
fit: BoxFit.cover,
)
: Container(
height: 100,
width:
MediaQuery.of(context)
.size
.width,
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: const Color
.fromARGB(
255, 238, 238, 238),
),
borderRadius:
const BorderRadius
.only(
bottomRight:
Radius.circular(8),
topRight:
Radius.circular(8),
),
),
child: const Center(
child: Text(
'Upload Image',
style: TextStyle(
color: Colors.grey),
),
),
),
),
GestureDetector(
onTap: () async {
final XFile? image =
await getImageJawaban(
ImageSource.gallery, index);
if (image != null) {
setState(() {
_imageFiles[index] = image;
_existingImageUrls[index] = null;
});
}
},
child: Container(
height: 48,
width: 100,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius:
const BorderRadius.only(
bottomLeft: Radius.circular(8),
topLeft: Radius.circular(8),
),
),
child: const Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(Icons.file_upload),
SizedBox(width: 8),
Text(
"Select",
style: TextStyle(
color: Colors.black),
),
],
),
),
),
],
),
],
);
},
),
const SizedBox(height: 20),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _jawabanControllers.length,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Correct Answer :',
style: TextStyle(color: Colors.blue),
),
ListTile(
title: Text(
'Option ${String.fromCharCode(65 + index)}'),
leading: Radio<String>(
value:
'Option ${String.fromCharCode(65 + index)}',
groupValue: selectedOption,
onChanged: (String? value) {
setState(() {
selectedOption = value;
});
},
),
),
],
);
},
),
const SizedBox(height: 20),
],
);
},
);
},
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.bottomRight,
child: BlocListener<EditSoalBloc, EditSoalState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
loaded: (data) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.greenAccent,
content: Text('Data Berhasil Diubah'),
),
);
Navigator.pop(context);
},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'$message',
)));
});
}, child: BlocBuilder<EditSoalBloc, EditSoalState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return TextButton(
onPressed: () {
final List<OpsiRequestModel> opsiRequestList = [];
for (var i = 0;
i < _jawabanControllers.length;
i++) {
opsiRequestList.add(
OpsiRequestModel(
opsiName: _jawabanControllers[i].text,
opsiImage: _imageFiles[i] == null
? null
: File(_imageFiles[i]!.path),
opsiNumber: i + 1,
),
);
}
final addSoalRequestModel = EditRequestModel(
matkulId: widget.soalId,
soal: _soalController?.text ?? '',
image: _imageFile == null
? null
: File(_imageFile!.path),
tingkat: _selectedDifficulty,
opsiRequest: opsiRequestList,
jawabanBenar: selectedOption ?? '',
);
context.read<EditSoalBloc>().add(
EditSoalEvent.edit(
addSoalRequestModel, widget.soalId));
},
style: TextButton.styleFrom(
backgroundColor: Colors.blue),
child: const Text(
'Simpan',
style: TextStyle(
color: Colors.white, // Warna teks tombol
),
),
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
)),
),
)
],
),
),
),
);
}
@override
void dispose() {
_soalController?.dispose();
for (var controller in _jawabanControllers) {
controller.dispose();
}
super.dispose();
}
}
I hope this issue can be resolved so that the value within the TextFormField can be edited, yet somehow it always reverts back every time the keyboard is closed. Please help
This is a recording of my application :
You will understand better by watching the following recording
This is the screenshot :
This is the initial view
This is when I edit/change the text
When exiting the keyboard, everything returns to its original state