How to display data from nested list in Flutter - flutter

When I try display data from nested list it gives me that data which is not list at all.
Please help how to get that options data as list and display on Flutter widget.
class QuizData {
List<BrainData> getData = [
BrainData(
questionID: "biology1",
question:
"Pine, fir, spruce, cedar, larch and cypress are the famous timber-yielding plants of which several also occur widely in the hilly regions of India. All these belong to",
options: [
"angiosperms",
"gymnosperms",
"monocotyledons",
"dicotyledons",
],
answer: [false, true, false, false],
),
];
}

From the question, I have created a sample example for you.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
List<BrainData> list = [];
var selectedValue;
List<BrainData> getData = [
BrainData(
questionID: "biology1",
question:
"Pine, fir, spruce, cedar, larch and cypress are the famous timber-yielding plants of which several also occur widely in the hilly regions of India. All these belong to",
options: [
"angiosperms",
"gymnosperms",
"monocotyledons",
"dicotyledons",
],
answer: [false, true, false, false],
),
];
#override
void initState() {
super.initState();
setState(() {
list = getData;
});
}
void showInSnackBar(String value, bool isCorrect) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
backgroundColor: isCorrect ? Colors.green : Colors.red,
content: Text(value),
duration: const Duration(milliseconds: 200),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: list.isEmpty
? Container()
: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
var item = list[index];
return Column(
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: Text('${index + 1} : ${item.question}'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: item.options.map((e) {
return RadioListTile(
title: Text(e),
value: e,
groupValue: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value;
var correctAnswerIndex = item.answer.indexWhere((element) => element == true);
var selectedItemIndex =
item.options.indexWhere((element) => element == value);
if (correctAnswerIndex == selectedItemIndex) {
showInSnackBar('Selected Correct Value', true);
} else {
showInSnackBar('Better luck next time', false);
}
});
},
);
}).toList(),
),
)
],
);
}),
);
}
}
class BrainData {
final String questionID;
final String question;
final List<String> options;
final List<bool> answer;
BrainData({
required this.questionID,
required this.question,
required this.options,
required this.answer,
});
}
This is the Sample UI:
Check the example and let me know if it works for you.

Related

Update Text with Dissmissble setState

I want to update my Text() value whenever I dismiss an item from the screen .
This is the MainScreen() :
Text.rich(
TextSpan(
text: total().toString() + " DT",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold),
),
The function total() is located in Product Class like this :
class Product {
final int? id;
final String? nameProd;
final String? image;
final double? price;
Product({this.id, this.nameProd, this.image, this.price});
}
List<Product> ListProduitss = [
Product(
price: 100, nameProd: 'Produit1', image: 'assets/images/freedomlogo.png')
];
double total() {
double total = 0;
for (var i = 0; i < ListProduitss.length; i++) {
total += ListProduitss[i].price!;
}
print(total);
return total;
}
I have this in the main screen .
After I remove the item from list , I want to reupdate the Text() because the function is printing a new value in console everytime I dismiss a product :
This is from statefulWidget CartItem() that I render inside MainScreen() :
ListView.builder(
itemCount: ListProduitss.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Dismissible(
key: Key(ListProduitss.toString()),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
total();
// What to add here to update Text() value everytime
});
},
I tried to refresh the main screen but It didn't work .
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
MainScreen();
});
},
One way is to declare a local string variable to use within the text. Then initialise the variable using total() within initState(). Then in setState do the same process.
However, it may be beneficial for you to look into a state management pattern such as BLoC pattern. https://bloclibrary.dev/#/
late String text;
void initState() {
super.initState();
text = Product.total();
}
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
appBar: AppBar(),
body: Column(
children: [
Text(text),
ElevatedButton(child: Text("Update"), onPressed:() => setState(() {
text = Product.total();
}),)
],
)
);
}
I am going to add another example as there was confusion to the above example. Below is an example of updated a text field with the length of the list. It is updated every time an item is removed.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> items = List<int>.generate(100, (int index) => index);
late String text;
#override
void initState() {
text = items.length.toString(); // << this is total;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
Expanded(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (BuildContext context, int index) {
return Dismissible(
background: Container(
color: Colors.green,
),
key: ValueKey<int>(items[index]),
onDismissed: (DismissDirection direction) {
setState(() {
items.removeAt(index);
text = items.length.toString(); // < this is total()
});
},
child: ListTile(
title: Text(
'Item ${items[index]}',
),
),
);
},
),
),
],
);
}
}

Flutter bloc not working after hot reload

My app using bloc/cubit to display a list of Todo items works fine until I hot reload/hot restart the application!
I have two buttons, when i click these, the cubit state is set to having 3 todo items. Additionally I have two periodic timer which sets the cubit state to having only 1 or 0 todo items again. So the number of items is constantly changing from 1 to 0 until, or if i press a button it momentarily becomes 3.
This works fine until hot reload/restart after which the buttons no longer work! The periodic changes do work however. I can only alleviate this problem by creating my ToDoBloc as a field Initializer within my "MyApp" base widget.
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
import 'package:todo/todo_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
late TodoBloc _todoBloc; //!!----IF I CREATE THE TODOBLOC HERE EVERYTHING WORKS--!!
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
class MyHomePage extends StatelessWidget {
TodoBloc _todoBloc;
MyHomePage(this._todoBloc, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocProvider<TodoBloc>.value(
value: _todoBloc,
child: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
)),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
}),
],
));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _todoBloc.LoadAll();
},
),
);
}
}
todo_api_controller.dart
class TodoApiController {
List<Todo> GetAll() {
return [
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
];
}
void Delete(String id) {}
void Update(Todo todo) {}
Todo Create() {
return Todo(id: "asdfsdf");
}
}
class Todo {
final String id;
String name;
bool isFinished;
DateTime? finishedOn;
Todo({required String id, String name = "", bool isFinished = false, DateTime? finishedOn = null})
: id = id,
name = name,
isFinished = isFinished,
finishedOn = finishedOn {}
}
todo_bloc.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
class TodoBloc extends Cubit<TodoBlocState> {
static int numberOfInstances = 0;
int myInstance = -1;
TodoApiController _apiController;
List<Todo> todos = [];
TodoBloc({required TodoApiController apiController})
: _apiController = apiController,
super(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)])) {
numberOfInstances++;
myInstance = numberOfInstances;
Timer.periodic(Duration(seconds: 2), (s) => emit(TodoBlocState()));
Future.delayed(
Duration(seconds: 1),
() => Timer.periodic(
Duration(seconds: 2), (s) => emit(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)]))));
}
Future<void> LoadAll() async {
/* var newTodos = _apiController.GetAll();
todos.clear();
todos.addAll(newTodos);
var newState = MakeState();
emit(newState);*/
emit(TodoBlocState(todos: [
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
]));
}
TodoBlocState MakeState() {
return TodoBlocState(
todos: todos
.map((e) => TodoItemState(
id: e.id,
finishedOn: e.finishedOn,
isFinished: e.isFinished,
name: e.name,
))
.toList(),
);
}
}
class TodoBlocState {
final List<TodoItemState> todos = [];
TodoBlocState({List<TodoItemState>? todos}) {
this.todos.addAll(todos ?? []);
}
}
class TodoItemState {
final String id;
final String name;
final bool isFinished;
final DateTime? finishedOn;
TodoItemState({required this.id, required this.name, required this.isFinished, required this.finishedOn});
}
I cant figure out why this is, especially with hot restart, as this should reset all application state.
EDIT: the issue appears after a hot reload(not hot restart) but cannot be fixed by hot restart
EDIT2: the issue is fixed by adding a GlobalKey() to the MyHomePage class. Though I cannot understand why. Can someone explain this to me?
This is happening because you're initializing your TodoBloc inside a build function.
Hot reload rebuilds your widgets so it triggers a new call to their build functions.
You should convert it into a StatefulWidget and initilize your TodoBloc inside the initState function:
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late TodoBloc _todoBloc;
#override
void initState() {
super.initState();
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
You really don't need to declare and initialize your TodoBloc at the top of the widget tree then pass it all the way down. BlocProvider creates a new instance that is accessible via context.read<TodoBloc>().
Your MyApp could look like this.
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => TodoBloc(apiController: TodoApiController()), // this is your bloc being created and initialized
child: MyHomePage(),
),
);
}
}
And MyHomePage could be simplified. Note the lack of BlocProvider.value and Builder. All you need is a BlocBuilder and the correct instance of TodoBloc is always accessible with context.read<TodoBloc>().
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
),
),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
},
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await context.read<TodoBloc>().LoadAll();
},
),
);
}
}

Could not find the correct Provider<Products> above this ProductItemScreen Widget

I am getting an Error: Could not find the correct Provider above this ProductItemScreen Widget whenever I press on a product Item even though I have set my provider as a parent of MaterialApp()
Below is the product_overview_screen.dart file which shows a gridview of the products on my screen
class ProductsOverviewScreen extends StatelessWidget {
static const routeName = '/prod_overview_Screen';
const ProductsOverviewScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodData = Provider.of<Products>(context); //Recieves data from provider file
return Scaffold(
appBar: AppBar(
title: const Text('My Shop'),
backgroundColor: Theme.of(context).primaryColor,
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3 / 2,
),
itemCount: prodData.items.length,
itemBuilder: (ctx, i) => ProductItem(
imageUrl: prodData.items[i].imageUrl,
title: prodData.items[i].title,
id: prodData.items[i].id,
),
),
);
}
}
Below is also the Product_item.dart file where I pass the Id of the product to the next screen where I display the data of that particular product on the screen
class ProductItemScreen extends StatelessWidget {
static const routeName = '/prod_item_screen';
const ProductItemScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodIdPassed = ModalRoute.of(context)!.settings.arguments as String;
final prod = Provider.of<Products>(context).findById(prodIdPassed);
return Scaffold(
appBar: AppBar(
title: Text(prod.title),
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
children: [Image.asset(prod.imageUrl), Text(prod.title)],
),
);
}
}
Also below is the main.dart file where I have setup my provider for all available listeners
void main() {
runApp(
ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
),
);
}
Right below runApp, explicitly declare the class:
ChangeNotifierProvider<Products>(
You did'nt provided providermodel class here Check this provider.
Mainmethod (not changed)
void main() {
var changeNotifierProvider = ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
);
runApp(changeNotifierProvider);
}
Products provider (Added)
class Products extends ChangeNotifier {
Product? _findById;
List<Product> _items = [];
List<Product> get items => _items;
set items(List<Product> value) {
_items = value;
notifyListeners();
}
set items2(List<Product> value) {
_items = value;
// notifyListeners();
}
Product? get findById => _findById;
set findById(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
notifyListeners();
}
set findById2(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
// notifyListeners();
}
}
Sample Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
var changeNotifierProvider = ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
);
runApp(changeNotifierProvider);
}
class ProductItemScreen extends StatelessWidget {
static const routeName = '/prod_item_screen';
const ProductItemScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodIdPassed = ModalRoute.of(context)!.settings.arguments as String;
var product = new Product(imageUrl: "", title: "", id: prodIdPassed);
Provider.of<Products>(context).findById2 = product;
final prod = Provider.of<Products>(context).findById;
return Scaffold(
appBar: AppBar(
title: Text(prod!.title),
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
children: [Image.network(prod.imageUrl), Text(prod.title)],
),
);
}
}
class ProductsOverviewScreen extends StatelessWidget {
static const routeName = '/prod_overview_Screen';
const ProductsOverviewScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodData = Provider.of<Products>(context);
List<Product> item = [];
item.add(Product(
imageUrl:
"https://cdn.pixabay.com/photo/2013/07/13/14/08/apparel-162192_1280.png",
title: "lava",
id: "1"));
item.add(Product(
imageUrl:
"https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/classic-accessories-1516305397.jpg",
title: "hi",
id: "2"));
item.add(Product(
imageUrl: "https://images.indianexpress.com/2019/09/toys.jpg",
title: "hi",
id: "4"));
item.add(Product(
imageUrl: "https://m.media-amazon.com/images/I/51zEsraniRL._UX569_.jpg",
title: "hi",
id: "3"));
prodData.items2 = item; //Recieves data from provider file
return Scaffold(
appBar: AppBar(
title: const Text('My Shop'),
backgroundColor: Theme.of(context).primaryColor,
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3 / 2,
),
itemCount: prodData.items.length,
itemBuilder: (ctx, i) => ProductItem(
imageUrl: prodData.items[i].imageUrl,
title: prodData.items[i].title,
id: prodData.items[i].id,
),
),
);
}
}
class ProductItem extends StatelessWidget {
var imageUrl;
var id;
var title;
ProductItem({Key? key, this.imageUrl, this.title, this.id}) : super(key: key);
#override
Widget build(BuildContext context) {
var column = Column(
children: [
Expanded(
child: Container(
// height: 125,
child: Image.network(
imageUrl,
alignment: Alignment.center,
fit: BoxFit.cover,
),
),
),
Center(
child: Text(
title + " " + id,
style: TextStyle(fontSize: 12),
))
],
);
return InkWell(
onTap: () {
Navigator.pushNamed(context, ProductItemScreen.routeName,
arguments: id);
},
child: column);
}
}
class Product {
var title;
var imageUrl;
var id;
Product({this.imageUrl, this.title, this.id});
}
class Products extends ChangeNotifier {
Product? _findById;
List<Product> _items = [];
List<Product> get items => _items;
set items(List<Product> value) {
_items = value;
notifyListeners();
}
set items2(List<Product> value) {
_items = value;
// notifyListeners();
}
Product? get findById => _findById;
set findById(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
notifyListeners();
}
set findById2(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
// notifyListeners();
}
}

Flutter unable to update dynamic TextEditingController text

I'm generating TextFormFields dynamically and assigning unique TextEditingControllers individually. I then only update the text of the TextFormField that's currently in focus
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
with
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
I'm able to successfully fetch the TextEditingController, but unable to update their text. Any idea why TextEditingController.text doesnt work?
Minimal repro
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
TextEditingValue() will work:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField = TextEditingController();
List<Widget> listForm = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
while (n > 0) {
TextEditingController _textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: _textEditingController,
onTap: () {
_selectedField = _textEditingController;
debugPrint( 'selected' + _selectedField!.value.text );
debugPrint('main' + _textEditingController.toString());
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value =
TextEditingValue(text: 'Item $index'); // doesn't work
debugPrint(_selectedField?.value.text);
debugPrint(_selectedField.hashCode.toString());
debugPrint('Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}

Flutter Trouble with multiselect checkboxes - data from Firestore

The following code displays the items listed in my collection (Firestore)
I am attempting to create the ability to check any item(s) and then have those items store into a "Favorites" on the next screen.
Currently, the checkboxes are an all or nothing. Either all items are unchecked or checked once tapped.
class _SelectScreenState extends State<SelectScreen> {
bool _isChecked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select Exercises')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('exercises').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot)
{
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context,
data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}
}
class Record {
final String name;
final DocumentReference reference;
Record(this.name, this.reference);
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:>";
}
It is because you are making use of a single variable for all the checkboxes.
To fix that you could create a dedicated stateful widget, which would handle the state of each of the checkbox's separately from the rest.
So you could replace your ListTile with something like
Exercise(
title: record.name,
)
and then you could define the Exercise widget as follows
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Here is a complete working example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Exercise(
title: "Exercises 1",
),
Exercise(
title: "Exercises 2",
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Because you have a global variable _isChecked, this needs to be created with each listTile.
Try moving the variable
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
bool _isChecked = false; //try moving it here
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}