Maps showing blank screen when build in release mode - ios

I am building an application where I need to use Flutter maps, everything work as expected, however if I make a build through Jankins for release mode then for some reason the maps on iOS displays blank. I have another page where the same widget is opened in a full page and there it works. My thoughts are that placing google maps in SingleChildScrolView causes the issue. Below I am attaching the source code.I have replaced the SingleChildScrolView with with list view where I pass all widgets as children but the effect is the same, the map is displayed in debug mode but when building through Jankins the map is blank , but on another page the map widget is working as expected.
Any help will be greatly appreciated as I am banging my head for hours.
Regards
class DashboardPage extends StatefulWidget {
final Model _model;
DashboardPage(this._model);
#override
_DashboardPageState createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
List<Dossier> _dossiers = List();
_DashboardPageState();
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(shrinkWrap: true, children: <Widget>[
_header(StringResources.dashboardTitle),
_mapWidget(),
_header(StringResources.myFiles),
_listWidgets()
],),
);
}
Widget _mapWidget() {
return Container(
height: 300,
child: DossierMap(
compassEnabled: false,
model: widget._model,
onDossierMapViewCreated: _onMapWidgetCreated,
onDossierInfoWindowTap: _selectedMarker,
),
);
}
Widget _listWidgets() {
return StreamBuilder<List<Dossier>>(
initialData: [],
stream: widget._model.dossierService
.loadDossiers(widget._model.loginService.token)
.catchError((e) {
if (widget._model.loggedIn) {
widget._model.forcedServerLogout();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) {
return LoginPage(
model: widget._model,
errorText: "",
onLoggedInCallback: (context) => Navigator.of(context)
.pushReplacement(MaterialPageRoute(
builder: (context) => DossiersPage(widget._model))),
);
},
));
}
}).asStream(),
builder: (_context, snapshot) {
if (snapshot.hasData) {
_dossiers = snapshot.data;
return snapshot.data.length != 0
? _buildList(snapshot.data)
: _noDossierContentWidget();
} else if (snapshot.hasError) {
return _progressIndicator();
} else {
return _progressIndicator();
}
});
}
Widget _header(String text) {
return new Container(
decoration: new BoxDecoration(color: Color(ColorResources.darkGray)),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 12, 0, 12),
child: Align(
alignment: Alignment.centerLeft,
child: Text(text,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
fontWeight: FontWeight.w500,
fontSize: 17,
color: Colors.white))),
),
);
}
void _onMapWidgetCreated(dynamic controller) {
controller.setMarkers(_dossiers);
}
Widget _buildList(List<Dossier> dossierList) {
return Container(
color: Color(ColorResources.dividerColor),
height: 400,
child: ListView.separated(
padding: EdgeInsets.all(0.0),
separatorBuilder: (context, index) =>
Divider(color: Colors.grey.shade300, height: 1.5),
itemCount: dossierList != null ? dossierList.length : 0,
shrinkWrap: true,
physics: ScrollPhysics(),
itemBuilder: (BuildContext context, int index) => DossierListItem(
dossier: dossierList[index],
fromSearch: true,
widget: Icon(
Icons.keyboard_arrow_right,
size: 30,
color: Color(ColorResources.gray),
),
onListItemClickListener: () =>
onListItemClicked(dossierList[index]),
)));
}
void onListItemClicked(Dossier dossier) {
_selectedDossier(dossier);
}
Widget _progressIndicator() {
return Center(child: CircularProgressIndicator());
}
Widget _noDossierContentWidget() {
return Container(
color: Theme.of(context).cardColor,
child: ListTile(
title: Text(
StringResources.noOwnedFiles,
softWrap: true,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).primaryTextTheme.caption,
),
));
}
void _selectedMarker(DossierCluster cluster) async {
if (cluster.isCluster) {
Dossier selected = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 24.0),
title: Center(child: Text(StringResources.selectFile)),
content: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
children:
dossiersFromCluster(cluster, onDossierListMarkerClick),
),
),
actions: <Widget>[
FlatButton(
child: Text(StringResources.cancel),
onPressed: () => Navigator.pop(context),
)
],
);
});
if (selected != null) _selectedDossier(selected);
} else {
_selectedDossier(cluster.getFirst);
}
}
List<Widget> dossiersFromCluster(
DossierCluster cluster, Function onDossierListMarkerClick) {
List<Widget> widgets = List();
for (int i = 0; i < cluster.size; i++) {
Dossier dossier = cluster.get(i);
widgets.add(DossierListItem(
dossier: dossier,
fromSearch: false,
widget: Container(),
onListItemClickListener: () => onDossierListMarkerClick(dossier),
));
}
return widgets;
}
void _selectedDossier(Dossier dossier) {
widget._model.setDossier(dossier);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DossierDetailsPage(widget._model)),
);
}
void _openFullSize() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => FullScreenMapPage(widget._model, _dossiers),
),
);
}
void onDossierListMarkerClick(Dossier dossier) {
widget._model.setDossier(dossier);
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DossierDetailsPage(widget._model)));
}
}

Related

Flutter - setState not updating a List inside a ListView.separated

I am saving a list of favorites in my game app, I can add or remove games from the list, but on the removeFavorite method, when I use setState in "games.remove(index)" the listview.separeted doesn't update. If close and open favorite screen, the list is updated but while I am at the favorite screen it doesn't update.
class _FavoriteScreenState extends State<FavoriteScreen> {
List<dynamic> favoriteList = [];
List<Game> games = [];
#override
void initState() {
loadFavorites();
super.initState();
}
#override
Widget build(BuildContext context) {
Widget _buildListView(Game game, int index){
return InkWell(
child: Container(
height: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(left: 5),
child: Image.network(game.cover),
),
),
Expanded(
flex: 2,
child: Container(
child: Row(
children: <Widget>[
SizedBox(width: 15,),
Text(
game.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w300,
fontSize: 14
),
),
],
),
),
)
],
),
),
onLongPress: (){
_showDialog(index);
},
);
}
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
appBar: AppBar(
title: Text("Favorites"),
),
body: Container(
margin: EdgeInsets.fromLTRB(16, 16, 16, screenHeight > 720 ? 90 : 62),
child: ListView.separated(
separatorBuilder: (BuildContext context, int index) => Divider(color: Colors.black,),
itemCount: games.length,
itemBuilder: (context, index){
return _buildListView(games[index], index);
},
)
),
);
}
Future<File> getFile() async{
final directory = await getApplicationDocumentsDirectory();
return File("${directory.path}/favorites.json");
}
Future<String> readFavorite() async{
try{
// Le e retorna o arquivo como String
final file = await getFile();
return file.readAsString();
} catch (e){
return null;
}
}
void loadFavorites() {
readFavorite().then((data){
// Transforma o arquivo JSON numa List
favoriteList = json.decode(data);
if(favoriteList.length > 0 && favoriteList != null){
favoriteList.forEach((map){
Game game = Game(map["cover"], map["title"], map["description"], map["url"]);
setState(() {
games.add(game);
});
});
} else {
}
print(games.length);
});
}
Future<File> saveFile() async{
String data = json.encode(favoriteList);
final file = await getFile();
return file.writeAsString(data);
}
void _showDialog(int index){
showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
content: Text("?",
style: TextStyle(fontSize: 18) ,),
actions: <Widget>[
FlatButton(
child: Text("YES"),
onPressed: (){
Navigator.of(context).pop();
removeFavorite(index);
}
),
FlatButton(
child: Text("NO"),
onPressed: (){
Navigator.of(context).pop();
}
),
],
);
}
);
}
void removeFavorite(int index){
favoriteList.forEach((m) {
Map<String, dynamic> map = m;
if (map.containsValue(games[index].title)) {
favoriteList.remove(m);
saveFile();
setState(() {
games.remove(index);
});
}
});
}
}
My bad, I replaced the remove with removeAt and it worked.
setState(() {
games.removeAt(index);
});

How to use conditional statement within onTap

I have a problem regarding if statement in dart, I want the user to tap the city to go to a new screen. this code work perfectly fine
class citySec extends StatelessWidget {
Widget getListView(BuildContext context) {
var listView = ListView(
children: <Widget>[
Text(
"choose ur city:",
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
),
ListTile(
leading: Icon(Icons.location_city),
title: Text("Toronto ", textDirection: TextDirection.rtl),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TorontoUniversitySection(),
),
);
},
),
],
);
return listView;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: getListView(context));
}
}
Since I have a long list of cities and the previous code will make my code very long so I had to change my code. However, I faced some errors with if statements, here is what I did so far.
import 'package:flutter/material.dart';
import 'package:rate/screens/firstScreen.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Rate',
home: Scaffold(
appBar: AppBar(
title: Text("jgfnjfnj ", textDirection: TextDirection.rtl),
),
body: ListDisplay(),
),
));
}
class ListDisplay extends StatelessWidget {
List<String> litems = ["Toronto","NewYork","London","Riyadh","Dubai","Istanbul"];
#override
Widget build (BuildContext ctxt) {
return new Scaffold(
appBar: AppBar(title: Text("Please Choose your city: ", textDirection: TextDirection.ltr,),
),
body: new ListView.builder
(
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int index) {
return new ListTile(
leading: Icon(Icons.location_city),
title: Text(litems[index], textDirection: TextDirection.rtl),
onTap: () {
// begin of all IF statements
if (litems.contains("Totonto")){
Navigator.push(
ctxt,
MaterialPageRoute(
builder: (ctxt) => TorontoUniversitySection()
),
);
}
if (litems.contains("London")){
Navigator.push(
ctxt,
MaterialPageRoute(
builder: (ctxt) => LondonUniversitySection()
),
);
}
// end of all If statements
},
);
}
)
);
}
}
for example, when I press Toronto it will take me to LondonUniversitySection()
That is because in your if statements, you check whether your list contains Toronto/London and not if currently pressed one is Toronto/London. Changing litems.contains("x") to litems[index] == "x" will do the trick. Here's edited fragment:
return new ListTile(
leading: Icon(Icons.location_city),
title: Text(litems[index], textDirection: TextDirection.rtl),
onTap: () {
if (litems[index] == "Toronto") {
Navigator.push(
ctxt,
MaterialPageRoute(builder: (ctxt) => TorontoUniversitySection()),
);
} else if (litems[index] == "London") {
Navigator.push(
ctxt,
MaterialPageRoute(builder: (ctxt) => LondonUniversitySection()));
}
},
);
Also, I recommend using a switch or else-if for that, not a bunch of ifs.
Try onTap: litems.contains("Totonto")?
Navigator.push( ctxt, MaterialPageRoute( builder: (ctxt) => TorontoUniversitySection() ), )
: null
class _RegisterBodyState extends State<RegisterBody> {
FocusNode myFocusNode = new FocusNode();
FocusNode myFocusNode2 = new FocusNode();
void initState() {
super.initState();
myFocusNode = FocusNode();
myFocusNode2 = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
myFocusNode.dispose();
myFocusNode2.dispose();
super.dispose();
}
Color color;
#override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Register",
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 70.0,
fontWeight: FontWeight.bold,
),
),
Form(
child: Column(
children: [
TextFormField(
onTap: () {
setState(() {
color = Colors.red;
});
},
autofocus: true,
focusNode: myFocusNode,
decoration: InputDecoration(
icon: Icon(
Icons.supervised_user_circle,
size: 40.0,
),
labelText: "User Name",
labelStyle: TextStyle(
color:
myFocusNode.hasFocus ?color : Colors.yellow),
),
),
TextFormField(
focusNode: myFocusNode2,
onTap: () {
setState(() {
color = Colors.black;
});
},
autofocus: false,
decoration: InputDecoration(
icon: Icon(
Icons.supervised_user_circle,
size: 40.0,
),
labelText: "User Name",
labelStyle: TextStyle(
color: myFocusNode2.hasFocus ? color : Colors.teal,
),
),
),
],
))
],
),
),
);
}
}

SliverAppbar remains visible when CustomScrollView with ScrollController is scrolled

Adding a ScrollController to function timelineList() causes the SliverAppBar to remain visible on scroll (when counter list is scrolled, SliverAppBar should hide). The issue goes away if the _scrollController is removed from the list (see timelineList function) but this gives rise to a new problem, I need to listen for when the scrollbar reaches bottom (to get more content).
See sample app below, copy/paste and run.
void main() => runApp(TestApp());
class TestApp extends StatelessWidget {
final _scrollController = new ScrollController();
TestApp(){
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('Get more data');
}
});
}
#override
Widget build(BuildContext context) {
TestBloc bloc = TestBloc();
bloc.fetchTestTimeline();
bloc.fetchTestAppBarTxt1();
bloc.fetchTestAppBarTxt2();
return MaterialApp(
home: new Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
backgroundColor: Colors.blueGrey,
elevation: 0.0,
),
body: NestedScrollView(
headerSliverBuilder:
(BuildContext contrxt, bool innerBoxIsScrolled) {
return <Widget>[
buildSliverAppBar(context, bloc),
];
},
body: Column(
children: <Widget>[
timelineList(bloc),
],
)
)),
);
}
buildSliverAppBar(context, TestBloc bloc){
return SliverAppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.grey[400],
expandedHeight: 200.0,
floating: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 2.0),
height: 200,
child: Column(
children: <Widget>[
StreamBuilder(
stream: bloc.testAppBarTxt1,
initialData: null,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
if (snapshot.data == null)
return buildProgressIndicator(true);
return Expanded(
child: Text('${snapshot.data}'));
}),
StreamBuilder(
stream: bloc.testAppBarTxt2,
initialData: null,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
if (snapshot.data == null)
return buildProgressIndicator(true);
return Expanded(
child: Text('${snapshot.data}'));
}),
],
),
)
],
),
));
}
timelineList(TestBloc bloc) {
return StreamBuilder(
stream: bloc.getTestTimeline,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Expanded(child: buildProgressIndicator(true));
}
List<int> val = snapshot.data;
if (val.isNotEmpty) {
addToTimelineList(val, bloc);
return Expanded(
child: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(new List<Widget>.generate(bloc.listTest.length, (int index) {
if (index == bloc.listTest.length) {
return buildProgressIndicator(bloc.isPerformingRequest);
} else {
return bloc.listTest[index];
}
})
))
],
),
);
}
});
}
void addToTimelineList(List<int> list, TestBloc bloc) {
for (var val in list) {
bloc.listTest.add(Text('$val'));
}
}
}
Widget buildProgressIndicator(showIndicator) {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: showIndicator ? 1.0 : 0.0,
child: Container(
width: 10.0,
height: 10.0,
child: new CircularProgressIndicator(
)),
),
),
);
}
class TestBloc {
String appbar1Val;
String appbar2Val;
List<Text> listTest = new List<Text>();
bool isPerformingRequest = false;
final _testAppBarText1 = BehaviorSubject<String>();
Observable<String> get testAppBarTxt1 => _testAppBarText1.stream;
final _testAppBarText2 = BehaviorSubject<String>();
Observable<String> get testAppBarTxt2 => _testAppBarText2.stream;
final _testTimeline = PublishSubject<List<int>>();
Observable<List<int>> get getTestTimeline => _testTimeline.stream;
fetchTestTimeline() async {
List item = await Future.delayed(
Duration(seconds: 2), () => List<int>.generate(100, (i) => i));
_testTimeline.sink.add(item);
}
fetchTestAppBarTxt1() async {
appbar1Val = await Future.delayed(Duration(seconds: 2), () => "Text One");
_testAppBarText1.sink.add(appbar1Val);
}
fetchTestAppBarTxt2() async {
appbar2Val = await Future.delayed(Duration(seconds: 2), () => "Text Two");
_testAppBarText2.sink.add(appbar2Val);
}
dispose() {
_testAppBarText1.close();
_testAppBarText2.close();
_testTimeline.close();
}
}
It's possible to achive the same result with wrapping your list with a Notification Listener.
NotificationListener<ScrollNotification>(
onNotification: (sn) {
if (sn.metrics.pixels ==
sn.metrics.maxScrollExtent) {
print('Get more data');
}
},
child: CustomScrollView(...
Edit: Since my initial answer didn't cover the animateTo use case, I got it working by removing the outer NestedScrollView. Here is the modified example.
void main() => runApp(TestApp());
class TestApp extends StatelessWidget {
final _scrollController = new ScrollController();
TestApp() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('Get more data');
}
});
}
#override
Widget build(BuildContext context) {
TestBloc bloc = TestBloc();
bloc.fetchTestTimeline();
bloc.fetchTestAppBarTxt1();
bloc.fetchTestAppBarTxt2();
return MaterialApp(
home: new Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
backgroundColor: Colors.blueGrey,
elevation: 0.0,
),
body: Column(
children: <Widget>[
timelineList(bloc),
],
),
),
);
}
buildSliverAppBar(context, TestBloc bloc) {
return SliverAppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.grey[400],
expandedHeight: 200.0,
floating: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 2.0),
height: 200,
child: Column(
children: <Widget>[
StreamBuilder(
stream: bloc.testAppBarTxt1,
initialData: null,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
if (snapshot.data == null)
return buildProgressIndicator(true);
return Expanded(child: Text('${snapshot.data}'));
}),
StreamBuilder(
stream: bloc.testAppBarTxt2,
initialData: null,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
if (snapshot.data == null)
return buildProgressIndicator(true);
return Expanded(child: Text('${snapshot.data}'));
}),
],
),
)
],
),
));
}
timelineList(TestBloc bloc) {
return StreamBuilder(
stream: bloc.getTestTimeline,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Expanded(child: buildProgressIndicator(true));
}
List<int> val = snapshot.data;
if (val.isNotEmpty) {
addToTimelineList(val, bloc);
return Expanded(
child: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
buildSliverAppBar(context, bloc),
SliverList(
delegate: SliverChildListDelegate(
new List<Widget>.generate(bloc.listTest.length,
(int index) {
if (index == bloc.listTest.length) {
return buildProgressIndicator(bloc.isPerformingRequest);
} else {
return bloc.listTest[index];
}
})))
],
),
);
}
});
}
void addToTimelineList(List<int> list, TestBloc bloc) {
for (var val in list) {
bloc.listTest.add(Text('$val'));
}
}
}
Widget buildProgressIndicator(showIndicator) {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: showIndicator ? 1.0 : 0.0,
child: Container(
width: 10.0, height: 10.0, child: new CircularProgressIndicator()),
),
),
);
}
class TestBloc {
String appbar1Val;
String appbar2Val;
List<Text> listTest = new List<Text>();
bool isPerformingRequest = false;
final _testAppBarText1 = BehaviorSubject<String>();
Observable<String> get testAppBarTxt1 => _testAppBarText1.stream;
final _testAppBarText2 = BehaviorSubject<String>();
Observable<String> get testAppBarTxt2 => _testAppBarText2.stream;
final _testTimeline = PublishSubject<List<int>>();
Observable<List<int>> get getTestTimeline => _testTimeline.stream;
fetchTestTimeline() async {
List item = await Future.delayed(
Duration(seconds: 2), () => List<int>.generate(100, (i) => i));
_testTimeline.sink.add(item);
}
fetchTestAppBarTxt1() async {
appbar1Val = await Future.delayed(Duration(seconds: 2), () => "Text One");
_testAppBarText1.sink.add(appbar1Val);
}
fetchTestAppBarTxt2() async {
appbar2Val = await Future.delayed(Duration(seconds: 2), () => "Text Two");
_testAppBarText2.sink.add(appbar2Val);
}
dispose() {
_testAppBarText1.close();
_testAppBarText2.close();
_testTimeline.close();
}
}

CheckboxListTile not working while working with real time database from firebase

I'm trying to create a List of data from online server Firebase using StreamBuilder bu the checkbox won't get checked.
I have used StreamBuilder to get the data and used LisTile widget to build the list items but the checkboxtilelist widget won't work after defining setState() function. And buildBody is defined under build Widget class.
Widget buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('hisab').snapshots(),
builder: (context, snapshots) {
if (!snapshots.hasData) {
return LinearProgressIndicator();
}
return _buildList(context, snapshots.data.documents);
}
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListitem(context, data)).toList(),
);
}
Widget _buildListitem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot((data));
bool _values = false;
void _onChanged(bool newValue) {
setState(() {
_values = newValue;
});
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 9.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
child: new ListTile(
onTap: () {
_onChanged(!_values);
},
leading: CircleAvatar(child: Text(record.name[0])),
title: new Column(
children: <Widget>[
new CheckboxListTile(
title: Text(record.name),
value: _values,
onChanged: _onChanged,
)
],
),
),
),
);
}
It's good idea if you create new stateful widget class:
class CustomListItemWidget extends StatefulWidget {
CustomListItemWidget({Key key, #required this.record}) : super(key: key);
final record;
#override
State createState() => _CustomListItemWidgetState();
}
class _CustomListItemWidgetState extends State<CustomListItemWidget> {
bool _values = false;
void _onChanged(bool newValue) {
setState(() {
_values = newValue;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 9.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
child: new ListTile(
onTap: () {
_onChanged(!_values);
},
leading: CircleAvatar(child: Text(widget.record.name[0])),
title: new Column(
children: <Widget>[
new CheckboxListTile(
title: Text(widget.record.name[0]),
value: _values,
onChanged: _onChanged,
)
],
),
),
),
);
}
}
Next, you can pass value from your method _buildListitem:
Widget _buildListitem(BuildContext context, DocumentSnapshot data) {
return CustomListItemWidget(
record: Record.fromSnapshot((data)),
);
}

display loading indicator while list is being populating from an api

am new to flutter ... and am calling an api in my app and i want to display a loading indicator while the api function is finished and every thing is retrieved from the api .. how to achieve this?
class _CompaniesPageState extends State<CompaniesPage> {
getcompanies() async {
var url = 'my link';
http
.post(url, body: json.encode({ 'token': globals.token }))
.then((response) {
// print("Response body: ${response.body}");
Map data = json.decode(response.body);
final companies =
(data['Companies'] as List).map((i) => new Company.fromJson(i));
setState(() {
for (final company in companies) {
if (!company.category.contains("الراعي") &&
!company.category.contains("الشريك") &&
!company.category.contains("الداعم")) {
if (company.logo != "") {
names.add(company.name);
logos.add(company.logo);
}
}
}
});
});
}
#override
Widget build(BuildContext context) {
getcompanies();
if (globals.isguest) {
return new Directionality(
.....
);
}
}
List<Widget> createCompaniesChildren() {
List<Widget> children = List<Widget>();
for (int i = 0; i < names.length; i++) {
children.add(
new Padding(
padding: new EdgeInsets.only(right: 15.0, left: 15.0),
child: new Image.network(
logos[i],
width: 346.0,
height: 180.0,
),
),
);
children.add(
new Padding(
padding: new EdgeInsets.only(right: 15.0, left: 15.0),
child: new Center(
child: new Text(
names[i],
style: new TextStyle(color: Colors.black, fontSize: 17.0),
textAlign: TextAlign.center,
),
),
),
);
children.add(new Divider());
}
return children;
}
how can i display a loading indicator while the list is being populating and then dismiss it once it's finished??
Tried this:
body: new Padding(
padding: new EdgeInsets.only(top: 15.0),
child: new Center(
child: new FutureBuilder(
future:
getcompanies(), // your async method that returns a future
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// if data is loaded
if (snapshot.data != null) {
return new ListView.builder(
padding: new EdgeInsets.all(8.0),
itemExtent: 20.0,
itemBuilder: (BuildContext context, int i) {
return new Center(
child: new Text(
snapshot.data[i].name,
style: new TextStyle(
color: Colors.black, fontSize: 17.0),
textAlign: TextAlign.center,
),
);
},
);
} else {
// if data not loaded yet
return new CircularProgressIndicator();
}
}
},
),
),
),
but got this error: I/flutter (31276): Another exception was thrown: A build function returned null.
You could use the FutureBuilder class for that purpose.
Also instead of manually creating the companies list you could use a ListView.
Something like this:
new FutureBuilder(
future: getcompanies(), // your async method that returns a future
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// if data is loaded
return new ListView.builder(
padding: new EdgeInsets.all(8.0),
itemExtent: 20.0,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int i) {
return new Center(
child: new Text(
snapshot.data[i].name,
style: new TextStyle(color: Colors.black, fontSize: 17.0),
textAlign: TextAlign.center,
),
);
},
).build(context);
} else {
// if data not loaded yet
return new CircularProgressIndicator();
}
},
)
Although you have already solved or got the answer. I will show you 2 Ways.
1. Using any builder like FutureBuilder, StreamBuilder....
FutureBuilder(
future: getcompanies(), // your async method that returns a future
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.active:
case ConnectionState.waiting:
return Padding(
padding: const EdgeInsets.only(top: 20),
child: Center(
child: CircularProgressIndicator()
),
);
case ConnectionState.none:
return Center(child: Text("Unable to connect right now"));
case ConnectionState.done:
if (snapshot.hasError) {
print("Error: ${snapshot.error}");
}
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int i) {
return new Center(
child: new Text(
snapshot.data[i].name,
style: new TextStyle(color: Colors.black, fontSize: 17.0),
textAlign: TextAlign.center,
),
);
},
);
}
},
),
2. Without using builder
Put this code before API call, Like
AlertDialog alert = AlertDialog(
content: Row(children: [
CircularProgressIndicator(
backgroundColor: Colors.red,
),
Container(margin: EdgeInsets.only(left: 10), child: Text("Loading...")),
]),
);
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return alert;
},
);
api.getcompanies();
And after getting API response
var response = api.getcompanies();
Navigator.pop(context);
Note : You can put the code into a method and then call it here

Resources