Flutter listview not updating after data update - android-studio

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.

Related

Flutter Duplicate Global Key detected on widget tree

I have this issue when implementing flutter's showcaseview despite following its official documentation. The errors I get have to do with a duplicate global state and here's a sample if my code:
final GlobalKey _timeline_filter = GlobalKey();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 400), () {
print("showcasing..");
ShowCaseWidget.of(context).startShowCase([_timeline_filter]);
});
});
}
Build method Code:
#override
Widget build(BuildContext context) {
return Material(
child: Container(
child: ListView(
controller: scroll_controller,
shrinkWrap: true,
children: <Widget>[
Container(
child: Column(
children: [
Container(
margin: EdgeInsets.only(
left: 83.5.w,
),
child: SizedBox(
width: 10.w,
child: FloatingActionButton(
elevation: 0.4.h,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FilterTags()));
},
child: Showcase(
key: _timeline_filter,
description: "tap to filter by category",
child: Icon(FontAwesomeIcons.filter, size: 11.sp)),
),
)),
],
)),
buildTimelinePosts(),
],
),
),
);
}

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 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

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: () {
/* ... */
},
),
],
),
),
],
),
);
},
),
),
],
),
);
}
}

Flutter: How to make a Card overlap the AppBar?

How can I make a Card in Flutter that overlaps the AppBar? Negative margins are not possible as far as I know.
See the image for clarity.
For one card it could be easily done with Stack widget
E.g.
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
HomeState createState() {
return new HomeState();
}
}
class HomeState extends State<Home> {
bool _hasCard;
#override
void initState() {
super.initState();
_hasCard = false;
}
#override
Widget build(BuildContext context) {
List<Widget> children = new List();
children.add(_buildBackground());
if (_hasCard) children.add(_buildCard());
return MaterialApp(
home: Stack(
children: children,
),
);
}
void _showCard() {
setState(() => _hasCard = true);
}
void _hideCard() {
setState(() => _hasCard = false);
}
Widget _buildCard() => new Container(
child: new Center(
child: new Container(
height: 700.0,
width: 200.0,
color: Colors.lightBlue,
child: new Center(
child: new Text("Card"),
),
),
),
);
Widget _buildBackground() => new Scaffold(
appBar: new AppBar(
title: new Text("AppBar"),
),
body: new Container(
child: _hasCard
? new FlatButton(
onPressed: _hideCard, child: new Text("Hide card"))
: new FlatButton(
onPressed: _showCard, child: new Text("Show card")),
),
);
}
void main() {
runApp(
new Home(),
);
}
If there are many cards, you can wrap them into ListView.
class Sample2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
child: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverAppBar(expandedHeight: 200),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) => ListTile(
title: Text("Index: $index"),
),
),
)
],
),
),
);
}
}
class MySliverAppBar extends SliverPersistentHeaderDelegate {
final double expandedHeight;
MySliverAppBar({#required this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
Image.network(
"https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
fit: BoxFit.cover,
),
Center(
child: Opacity(
opacity: shrinkOffset / expandedHeight,
child: Text(
"MySliverAppBar",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 23,
),
),
),
),
Positioned(
top: expandedHeight / 2 - shrinkOffset,
left: MediaQuery.of(context).size.width / 4,
child: Opacity(
opacity: (1 - shrinkOffset / expandedHeight),
child: Card(
elevation: 10,
child: SizedBox(
height: expandedHeight,
width: MediaQuery.of(context).size.width / 2,
child: FlutterLogo(),
),
),
),
),
],
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

Resources