OnChanged State of Checkbox in Flutter - android-studio

I am working on the todo list app and used CheckBox to check off the todo on the list.
But it keeps going back to the unchecked state on refreshing the page.
I want to save the state in the database.
I am populating the todoList in getAllTodos
Here is the code:
List<Todo>_todoList=List<Todo>();
#override
initState(){
super.initState();
getAllTodos();
}
getAllTodos()async{
_todoService=TodoService();
_todoList=List<Todo>();
var todos= await _todoService.readTodo();
todos.forEach((todo){
setState(() {
var model=Todo();
model.id=todo['id'];
model.title=todo['title'];
model.dueDate=todo['dueDate'];
model.category=todo['category'];
model.isFinished=todo['isFinished'];
_todoList.add(model);
});
});
}
body: ListView.builder(itemCount: _todoList.length,itemBuilder: (context, index){
return Padding(
padding: EdgeInsets.only(top:8.0, left: 8.0, right: 8.0),
child: Card (
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0)
),
child: InkWell(
onTap: (){
setState(() {
_todoList[index].isChecked=!_todoList[index].isChecked;
});
},
child: ListTile(
leading: Checkbox(
checkColor: Colors.indigo,
value: _todoList[index].isChecked,
onChanged: (bool value){
setState(() {
_todoList[index].isChecked=value;
_todoService.saveTodo(_todoList[index]);
});
},
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(_todoList[index].title ?? 'No Title',
style: TextStyle(decoration: (_todoList[index].isChecked? TextDecoration.lineThrough: TextDecoration.none),
),
),
IconButton(icon: Icon(Icons.delete,color: Colors.red,
),
onPressed: (){
_deleteFormDialog(context,_todoList[index].id);
}
),
],
),
subtitle: Text(_todoList[index].dueDate ?? 'No Due Date'),
),
),
),
);
}),
Here is the isChecked value:
class Todo{
bool isChecked=false;
}
Please help me out.
Update: Added a line in the setState() of onChanged callback calling the service method to change the state of of the checkbox via _todoService.saveTodo(_todoList[index]);
Now the problem is that onChange() is called twice on a single tap. How do I correct the multi-calls in the onChange callback?

Follow the steps from here w.r.t checkbox manipulation.
Hopefully, everything works!

Related

How cam we place the layouts in flutter at center

I want to place the card in the center
here the code
class _HomeState extends State<Home>{
#override
Widget build(BuildContext context) {
var myActivity=["Join Meeting","Create Meeting", "Schedule Meeting","Yet to be decided"];
var myGridView = new GridView.builder(
itemCount: myActivity.length,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context,int index) {
return new GestureDetector(
child: Card(
elevation: 5.0,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0),
child: Text(myActivity[index]),
),
),
onTap: () {
showDialog(
barrierDismissible: false,
context: context,
child: CupertinoAlertDialog(
content: Text(myActivity[index],),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Ok"))
],
)
);
},
);
},
);
return Scaffold(
body: myGridView,
);
}
}
Two things required to do that first wrap Grid Widget inside Center Widget & give GridView property as shrinkWrap: true,
#override
Widget build(BuildContext context) {
print("In Test Widget");
// TODO: implement build
var myActivity=["Join Meeting","Create Meeting", "Schedule Meeting","Yet to be decided"];
var myGridView = new GridView.builder(
itemCount: myActivity.length,
shrinkWrap: true,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context,int index) {
return new GestureDetector(
child: Card(
elevation: 5.0,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0),
child: Text(myActivity[index]),
),
),
onTap: () {
showDialog(
barrierDismissible: false,
context: context,
child: CupertinoAlertDialog(
content: Text(myActivity[index],),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Ok"))
],
)
);
},
);
},
);
return Scaffold(
body: Center(child: myGridView),
);
}
You can wrap your view(Card) with Row component and set mainAxisAlignment attribute to MainAxisAlignment.center like below.
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 5.0,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0),
child: Text(myActivity[index]),
),
)
],
),
To display the GridView at the center of the screen, you can simply wrap myGridView inside a Center widget like this..
return Scaffold(
body: Center(child: myGridView),
);
You also need to set the GridView's shrinkWrap parameter to true. Otherwise gridView will take up the whole screen and so visually the Center widget will have no effect on its position.
Wrap your layout in a Column() and set MainAxisAlignment to center.
Column(
mainAxisAlignment: MainAxisAlignment.center,
child: <--YOUR EXISTING LAYOUT -->)
It can also center horizontally with crossAxisAlignment.

Flutter listview not updating after data update

I have a ListView inside a bottomSheet, that is built using an array of elements. Currently I have one item in there "empty" which is then .clear()ed and populated after an async DB call.
The variable update is correct, and I try to use setState((){}) but the ListView isn't updated at all. I need to close the bottomSheet, reopen it, and the ListView then has the correct items.
Do I need to just call setState or does the bottomSheet builder need to be flagged to update?
Main ListView section:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My Map'),
backgroundColor: Colors.green[700],
),
//Put in a stack widget so can layer other widgets on top of map widget
body: Stack(
children: <Widget>[
GoogleMap(
mapType: _currentMapType,
markers: _markers,
onMapCreated: _onMapCreated,
onCameraMove: _onCameraMove,
initialCameraPosition: CameraPosition(
target: _center,
zoom: 11.0,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
SizedBox(width: 16.0),
Builder(
builder: (context) => FloatingActionButton(
...
),
),
SizedBox(width: 16.0),
FloatingActionButton(
...
),
SizedBox(width: 16.0),
Builder(
builder: (context) => FloatingActionButton(
child: Icon(Icons.file_download, size: 36.0),
backgroundColor: Colors.green,
onPressed: () {
showBottomSheet(
context: context,
builder: (context) {
return ListView(
padding: EdgeInsets.all(15.0),
children: <Widget>[
...
Divider(),
ListTile(
title: Text("Remote JSON Download"),
trailing:
Icon(Icons.refresh),
selected: true,
onTap: _OnPressedReloadJSON, <-------------
),
Divider(),
Container(
height: 150.0,
child: ListView.builder( <-------------
scrollDirection: Axis.horizontal,
itemCount : testList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
padding: EdgeInsets.all(5.0),
margin: EdgeInsets.all(5.0),
width: 150.0,
color: Colors.red,
child: Text(testList[index]),
);
},
),
),
Async Get:
class _MyAppState extends State<MyApp> {
...
_OnPressedReloadJSON() {
fetchJSONAndMakeListTile();
}
...
List<String> testList= ["empty"];
Future<http.Response> fetchJSONAndMakeListTile() async {
final response = await http.get('https://..... file.json');
// If the server did return a 200 OK response, then parse the JSON.
if (response.statusCode == 200) {
List<db_manager.MyObject> myObjects = (json.decode(response.body) as List)
.map((e) => db_manager.MyObject.fromJson(e))
.toList();
testList.clear();
myObjects.forEach((db_manager.MyObject al) {
testList.add(al.code);
print("debug:"+al.code+" - "+al.name); <------------- prints correctly
});
//TODO Does this even work?
//Trigger update of state - ie redraw/reload UI elements
setState(() {});
} else {
// If the server did not return a 200 OK response, then throw an exception.
print(response);
throw Exception('Failed to load json');
}
}
UPDATE:
I've abstracted the BottomSheet builder into another class (as per another answer) as its own StatefulWidget but I can't seem to access the void onPress() method from my main dart file. If the BottomSheet creation/builder is in this separate dart file, how do I call it to build and then update its state with the async call updating the listview contents List?
BottomSheetWidget.dart
class BottomSheetDatabases extends StatefulWidget {
#override
_BottomSheetDatabases createState() => _BottomSheetDatabases();
}
class _BottomSheetDatabases extends State<BottomSheetDatabases> {
void _onpress() {
}
void loadMe() {
}
List<String> testList= ["empty"];
#override
Widget build(BuildContext context) {
return BottomSheet(
builder: (context) {
return ListView(
padding: EdgeInsets.all(15.0),
children: <Widget>[
ListTile(
...
),
Divider(),
Container(
height: 150.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: testList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
key: UniqueKey(),
//TODO ??
padding: EdgeInsets.all(5.0),
margin: EdgeInsets.all(5.0),
width: 150.0,
color: Colors.red,
child: Text(testList[index]),
);
},
),
//),
//),
),
...
Main.dart:
void _loadSheetDatabases() {
BottomSheetWidget bottomSheetwidget = BottomSheetWidget();
bottomSheetwidget.loadMe();
}
Seems to me that a Key are missing into widget that ListView.Builder returns, try to put a UniqueKey() or ValueKey into Container or Text:
Container(
key: UniqueKey(),
padding: EdgeInsets.all(5.0),
margin: EdgeInsets.all(5.0),
width: 150.0,
color: Colors.red,
child: Text(testList[index]),
);
Your ListView is only built when onPressed is called on your FloatingActionButton.
I assume that's why it doesn't get built again when the state changes.
You could wrap the code to build the ListView in a new StatefulWidget with its own state and then update it when testList changes.

Flutter bottomSheet to change main app State

My main.dart has become quite lengthy, so I'm splitting it up into various other .dart files for maintainability.
My main app uses a Google Map object and I place various red location markers on it. Now, I have various FloatingActionButton() along the bottom - each one opens a Bottom Sheet, using showBottomSheet() or showModalBottomSheet().
The only way I can currently think to split the main app into various files (to keep tidy) is to have the contents of these various bottom sheets in different .dart files which then are called from the main.dart - probably the wrong way.
Main.dart
...
import 'package:flutter_app/db_manager.dart' as db_manager;
import 'package:flutter_app/section_about.dart';
import 'package:flutter_app/section_settings.dart';
void main() => runApp(MyApp());
SectionAbout sectionAbout = SectionAbout();
SectionSettings sectionSettings = SectionSettings();
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GoogleMapController mapController;
static const LatLng _center = const LatLng(xxxxxxx, xxxxxxx);
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
}
void _onCameraMove(CameraPosition position) {
_lastMapPosition = position.target;
}
final Set<Marker> _markers = {};
MapType _currentMapType = MapType.normal;
LatLng _lastMapPosition = _center;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My Map'),
backgroundColor: Colors.green[700],
),
//Put in a stack widget so can layer other widgets on top of map widget
body: Stack(
children: <Widget>[
GoogleMap(
mapType: _currentMapType,
markers: _markers,
onMapCreated: _onMapCreated,
onCameraMove: _onCameraMove,
initialCameraPosition: CameraPosition(
target: _center,
zoom: 11.0,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
SizedBox(width: 16.0),
Builder(
builder: (context) => FloatingActionButton(
child: Icon(Icons.settings, size: 36.0),
backgroundColor: Colors.green,
onPressed: () {
sectionSettings.onSheetShowContents(context); <------
}),
),
SizedBox(width: 16.0),
FloatingActionButton(
onPressed: _onDownloadTestPressed,
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.green,
child: const Icon(Icons.autorenew, size: 36.0),
),
SizedBox(width: 16.0),
Builder(
builder: (context) => FloatingActionButton(
child: Icon(Icons.help, size: 36.0),
backgroundColor: Colors.green,
onPressed: () {
sectionAbout.onSheetShowContents(context); <------
}),
),
SizedBox(width: 16.0),
FloatingActionButton(
onPressed: _onDBActions,
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.green,
child: const Icon(Icons.change_history, size: 36.0),
),
])),
),
],
),
),
);
}
}
Settings.dart
import 'package:flutter/material.dart';
import 'package:flutter_app/db_manager.dart' as db_manager;
class SectionSettings {
int mapTypeView = 0;
void onSheetShowContents(Context context) {
showModalBottomSheet(
//showBottomSheet(
context: context,
builder: (context) {
return ListView(
padding: EdgeInsets.all(15.0),
children: <Widget>[
ListTile(
title: Text("Map Settings"),
selected: true,
),
Divider(),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Column(
children: <Widget>[
Text("Map View"),
],
),
Column(
children: <Widget>[
Row(
children: <Widget>[
ChoiceChip(
label: Text("Normal Map"),
selected: mapTypeView == 0,
onSelected: (value) {
setState(() {
mapTypeView = 0;
_currentMapType =
MapType.normal;
});
},
),
SizedBox(width: 8),
ChoiceChip(
label: Text("Satelite Map"),
selected: mapTypeView == 1,
onSelected: (value) {
setState(() {
mapTypeView = 1;
_currentMapType =
MapType.satellite;
});
},
),
],
),
],
)
],
),
],
// ),
);
});
}
}
Notice how I use <OtherDartFileName>.onSheetShowContents(); and that code is moved to <OtherDartFileName> rather than have a huge section here in the main dart file.
This has introduced a problem, where I cannot modify the State of the Google Map from within this Bottom Sheet as it has no reference (and I can't seem to pass one along) of the main app state.
I want to have a Bottom Sheet that contains a button that toggles Normal View and Satellite View on the main Map (and eventually other options)
Have I structured this project completely incorrectly, or can I just reference the Map state somehow?
I also have separate .dart files for one SQFlite instance and managing all DB operations. Coding for android and will be pushing to iOS eventually.
Many thanks

Adding routes to a menu made of a list of models

I have a menu in my flutter app that use a list of model for the navigation and it look like this:
When i click on one of the element on the list i want to folow one of the material route, since i did not make the menu and that i have not a lot of knowledge in flutter i have no idea how to do that or if it's even possible.
Every suggestion is welcom !!
Here is a view of the custom list:
class NavigationModel{
String title;
IconData icon;
NavigationModel({this.title,this.icon});
}
List<NavigationModel> navigationItems = [
NavigationModel(title: "Dashboard",icon: Icons.insert_chart),
NavigationModel(title: "Calendar",icon:Icons.calendar_today),
NavigationModel(title: "terrain",icon:Icons.landscape),
NavigationModel(title: "professeur",icon:Icons.person_pin),
NavigationModel(title: "joueur",icon:Icons.person_add),
];
sample of the menu builder:
children: <prefix0.Widget>[
SizedBox(
height: 50.0,
),
CollapsingListTile(
title: '$nickname',
icon: Icons.person,
animationController: _animationController,
),
Expanded(
child: ListView.builder(
itemBuilder: (context, counter){
return CollapsingListTile(
title: navigationItems[counter].title,
icon: navigationItems[counter].icon,
animationController: _animationController,
);
},
itemCount: navigationItems.length,
),
),
SizedBox(
height: 50.0,
)
],
Routes in the app:
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Recipes',
initialRoute: '/connection',
routes: {
'/': (context) => Connection(),
'/connection': (context) => Connection(),
'/connexion': (context) => Connexion(),
'/newclub': (context) => NewClub(),
},
);
I'm not familiar with that CollapsingListTile widget, I guess it expands when pressed upon? Anyways, for example if you wish to navigate to a route when pressing a ListTile widget, you would do this:
ListTile(
title: navigationItems[counter].title,
icon: navigationItems[counter].icon,
animationController: _animationController,
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => SomePage()));
)
In the end i get rid of the issue buy getting arround the problem.
What i did is put every element of my list in an InkWell().
With this solution the menu is unchanged and i can do any treatment i want in the onTap().
Expanded(
child: ListView.builder(
itemBuilder: (context, counter){
return new InkWell(
child: CollapsingListTile(
title: navigationItems[counter].title,
icon: navigationItems[counter].icon,
animationController: _animationController,
),
onTap: () {
print('button click: $counter');
}
);
},
itemCount: navigationItems.length,
),
),
SizedBox(
height: 50.0,
)
],

Flutter layout issue about Flexible

I am developing a flutter app, but it show error when I run the app.
I don't understand what is the problem. I think I mix up the logic of how the widget expand in layout.
Please kindly help to solve this issue.
error message:
flutter: The following assertion was thrown during performResize():
flutter: Vertical viewport was given unbounded height.
Viewports expand in the scrolling direction to fill their container.In this case, a vertical
viewport was given an unlimited amount of vertical space in which to expand. This situation
typically happens when a scrollable widget is nested inside another scrollable widget.
Here with my code:
body: Container(
child: Flexible(
child: FirebaseAnimatedList(
query: databaseReference,
itemBuilder: (_, DataSnapshot snapshot,
Animation<double> animation,
int index) {
return new Card(
color: Colors.black38,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListTile(
leading: IconButton(
icon: Icon(Icons.format_list_bulleted),
color: Colors.blueAccent,
splashColor: Colors.greenAccent,
onPressed: () {
// Perform some action
debugPrint('button ok');
},
),
title: Text(shopList[index].shopName),
subtitle: Text(shopList[index].address),
),
Container(
child: Flexible(
child: Form(
key: formShopKey,
child: ListView(
children: <Widget>[
ListTile(
leading: Icon(
Icons.money_off,
color: Colors.white,
),
title: TextFormField(
maxLength: 100,
initialValue: "",
maxLines: 3,
//onSaved: (val) => booking.seafoodRequest = val,
//validator: (val) => val == "" ? val : null,
decoration: new InputDecoration(
),
),
),
],
),
),
),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
new FlatButton(
child: const Text('LISTEN'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
},
),
),
);
[1]: https://i.stack.imgur.com/5vAsv.png
[2]: https://i.stack.imgur.com/LuZEl.png
I had to fill in a few gaps but the below should build for you. I also swapped FirebaseAnimatedList with a regular AnimatedList to get it to build. You can compare and adjust the layout.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: AnimatedList(
initialItemCount: 10,
itemBuilder: (BuildContext context, int index,
Animation<double> animation) {
return new Card(
color: Colors.black38,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListTile(
leading: IconButton(
icon: Icon(Icons.format_list_bulleted),
color: Colors.blueAccent,
splashColor: Colors.greenAccent,
onPressed: () {
// Perform some action
debugPrint('button ok');
},
),
title: Text('Name'),
subtitle: Text('Address'),
),
Container(
constraints: BoxConstraints(
minHeight: 100.0,
maxHeight: 200.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Form(
child: ListView(
children: <Widget>[
ListTile(
leading: Icon(
Icons.money_off,
color: Colors.white,
),
title: TextFormField(
maxLength: 100,
initialValue: "",
maxLines: 3,
//onSaved: (val) => booking.seafoodRequest = val,
//validator: (val) => val == "" ? val : null,
decoration: new InputDecoration(),
),
),
],
),
),
),
],
),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
new FlatButton(
child: const Text('LISTEN'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
},
),
),
],
),
);
}
}

Resources