Unable to show Circular progress indicator when FutureBuilder is loading data - flutter

I am trying to show Circular Progress Indicator while my data in Future Builder loads, I tried two methods to add it however both of them didn't work. How can I achieve the desired result?
My code
class _MyHomePageState extends State<MyHomePage>{
#override MyHomePage get widget => super.widget;
#override
Widget build(BuildContext context){
//To show the ListView inside the Future Builder
Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty ? Ink(
.....
) : new CircularProgressIndicator(); //TRIED TO ADD CIRCULAR INDICATOR HERE
},
);
}
//Future Builder widget
Column cardsView = Column(
children: <Widget>[...
Expanded(
child: FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
return createTasksListView(context, snapshot);
}),
),
],
);
return Scaffold(
...);
}
}

Try with:
FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
else
return createTasksListView(context, snapshot);
}),
),
When the value of the future is an empty list, which is your initialData, you are rendering a ListView with 0 items, so you cannot render a CircularProgressIndicator in its itemBuilder.

Related

Flutter how to refresh appBar Title after FutureBuilder

Is it possible to refresh appBar title after widget FutureBuilder ?
I'd like to set title after FutureBuilder is done
class _SimpleBarChart extends State<SimpleBarChartPage> {
String _appBarTitle = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_appBarTitle)),
body: Center(child: futureCAGraph()));
}
futureCAGraph() {
return FutureBuilder(
future: BddAMKGraphCA().getDataGraphCA(_caAnnee),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, int currentIndex) {
return affGraphCA(context);
});
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
//return a circular progress indicator.
return new CircularProgressIndicator();
});
}
You Just Have to update Text(_appBarTitle) with Text(snapshot.data[Index].title)
you can easily update it with setstate() after you get data from future. just assign _appBarTitle in setstate() as shown below,
setState(() {
_appBarTitle=//assign your snapshot data
});
It's not a good practice to call setState during the build method. Instead you can move Scaffold inside the builder like this:
Widget build(BuildContext context) {
return FutureBuilder(
future: BddAMKGraphCA().getDataGraphCA(_caAnnee),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot)
=> Scaffold(
appBar: AppBar(title: Text(_appBarTitle)),
body: Center(child: (snapshot.hasData)
? ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, int currentIndex) {
return affGraphCA(context);
})
: (snapshot.hasError)
? Text('${snapshot.error}')
: //return a circular progress indicator.
CircularProgressIndicator(),
),
)
);
}
Sorry, after testing appBar's title don't print anything
Widget build(BuildContext context) {
return FutureBuilder(
future: BddAMKGraphCA().getDataGraphCA(_caAnnee),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot)
=> Scaffold(
appBar: AppBar(title: Text(_appBarTitle)),
body: Center(child: (snapshot.hasData)
? ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, int currentIndex) {
**print('appBar something');**
return affGraphCA(context);
})
: (snapshot.hasError)
? Text('${snapshot.error}')
: //return a circular progress indicator.
CircularProgressIndicator(),
),
)
);
}

Display Data as GridView instead of ListView

I've successfully displayed my Firestore Data as a ListView() unfortunately I can't get it to display as a GridView()
I've tried many different methods but can't seem to find the right fit - I'd like a two columns GridView
Here is my ListView code :
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['num']),
subtitle: Text(data['num']),
);
}).toList(),
);
},
);
Turns out it was easier than I thought :
I just had to change ListView to GridView.count for it to display as a GridView & set crossAxisCount: 2 for two columns
class UserGrid extends StatefulWidget {
#override
_UserGridState createState() => _UserGridState();
}
class _UserGridState extends State<UserGrid> {
final Stream<QuerySnapshot> _usersStream =
FirebaseFirestore.instance.collection('User Data').snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: [
...snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['num']),
subtitle: Text(data['num']),
);
}).toList(),
ElevatedButton(onPressed: () {}, child: Text("Hello")),
],
);
});
}
}
SliverGrid must also be a part of CustomScrollView
class SliverGridWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
///no.of items in the horizontal axis
crossAxisCount: 4,
),
///Lazy building of list
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
/// To convert this infinite list to a list with "n" no of items,
/// uncomment the following line:
/// if (index > n) return null;
return listItem(Utils.getRandomColor(), "Sliver Grid item:\n$index");
},
/// Set childCount to limit no.of items
/// childCount: 100,
),
)
],
),
);
}

Error trying to build a ListView in a Flutter FutureBuilder

I am new to Flutter and building a small app to record my expenses and learn a bit.
I am using Hive to store data. Now I am building a page which targets to show all the previously saved entries. I do this by creating a List with all the data and then trying to use a FutureBuilder to show the data in a ListView.
This is the code so far:
class LogScreen extends StatefulWidget {
const LogScreen({Key? key}) : super(key: key);
#override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
get futureEntries => getEntries();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: futureEntries.length,
itemBuilder: (context, index) {
Entry currentEntry = Hive.box<Entry>('entriesBox').getAt(index);
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
}
I am getting the following error though:
The following _TypeError was thrown building LogScreen(dirty, state: _LogScreenState#75644):
type 'Future<List<dynamic>>' is not a subtype of type 'Future<Widget>?'
The relevant error-causing widget was:
LogScreen file:///home/javier/StudioProjects/finanzas/lib/main.dart:55:14
When the exception was thrown, this was the stack:
#0 _LogScreenState.build (package:finanzas/log_screen.dart:29:17)
Could someone please tell me what I am doing wrong and suggest a solution? I come from Python and am having a though time with all these types :-P
Thanks in advance.
The generic type of FutureBuilder<T>() should correspond to the data type your Future will return, not what the builder is building. In your case you have FutureBuilder<Widget> so it expects a Future<Widget>, but your getEntries returns a Future<List<dynamic>>. So this is what the error is hinting at. Your code should probably look like this:
return FutureBuilder<List<Entry>>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List<Entry>> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data[index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
Also note that i replaced the references in your ListView.builder from directly referencing your future to using the data inside the snapshot
Alright. After some research, here's the code that got to work:
Widget build(BuildContext context) {
return FutureBuilder<List>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data![index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
I don't know yet exactly what the exclamation marks after 'data' do, but they did the trick.

flutter data not retrieved from firestore when using stream

DatabaseService databaseService = new DatabaseService();
Stream questionsSnapshot;
so Im using a stream and and a database service to retrieve data (questions and answers of a quiz) to my listView builder
the called function from database service is
getAquizData(String quizId) async{
return await Firestore.instance
.collection("quiz")
.document(quizId)
.collection("questionReponses")
.snapshots();
}
the init state function
#override
void initState() {
databaseService.getAquizData(widget.quizId).then((value){
questionsSnapshot = value;
setState(() {});
});
super.initState();
}
my listViewBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
// an appbar
) ,
body: Container(
child: Column(children: <Widget>[
StreamBuilder(
stream: questionsSnapshot,
builder: (context, snapshot) {
return snapshot.data == null
? Container(child: Text("empty"),) : ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: snapshot.data.documents.length,
itemBuilder: (context,index){
return QuizPlayTile(
questionModel: getQuestionModelFromDatasnapshot(
snapshot.data.documents[index]),
index: index,
);
});
},
)
],)
)
);
}
}
when running it just show the word empty for a second and then it shows questions without answers
[1]: https://i.stack.imgur.com/E4CS4.jpg
We can use stream builder without calling it in initState method. Following code works for me. user quizStreamer in build method.
quizStreamer(){
return StreamBuilder(
stream: Firestore.instance
.collection('quiz')
.document(quizId)
.collection('questionReponses')
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
//loadWidgets method takes snapshot and render defined widgets
return loadWidgets(snapshot);
},
);
}

FutureBuilder only works in Debug

I have a FutureBuilder with a ListView to display custom items (Widgets) with values which are read from .txt files.
The problem is that these items are only displayed if I launch the app in Debug-mode or run-mode. When I try to open the app with the AppLauncher (like a "normal" user would do it) the listView is empty. I tried this on an AVD and on a "real" device.
the Future "listFuture" is used to read the values from the files and return a list of Widgets
class Home extends StatefulWidget {
final Future listFuture = setupList();
#protected
#mustCallSuper
void initState() {
print("init complete");
}
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
If the FutureBuilder gets the data correctly a listView with the list of my widgets should be displayed
child: FutureBuilder<List<SubListItem>>(
future: widget.listFuture,
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text("None");
case ConnectionState.waiting:
return new Text("loading");
default:
if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),
Widget subListView(BuildContext context, AsyncSnapshot snapshot) {
List<Widget> items = snapshot.data;
//This ScrollConfiguration is used to remove any animations while scrolling
return ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
child: new ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[items[index]],
);
},
),
),
);
}
Thanks for helping!
Ok, I solved the problem. You just have to call "setState" when your Widget is built.
#protected
#mustCallSuper
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
//This setState is necessary because it refreshes the listView
setState(() {});
});
}
It's looks like a async data issue, try these changes:
Remove listFuture from your StatefulWidget.
Add the listFuture var inside your State.
Move the setupList() method inside your State.
And finally call directly like this:
child: FutureBuilder<List<SubListItem>>(
future: setupList(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(!snapshot.hasData) {
return new Text("loading");
}
else if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),