How to put JSON data from server with GridView --Flutter - flutter

I had referred to the cookbook.
https://flutter.dev/docs/cookbook/networking/fetch-data
The sample code is to get single JSON data.
I'm trying to get following multiple JSON data from StatefulWidget.
And I would like to show season data in each grid by GridView.
[
{"id":1,"season_end":"1999/01","season_name":"First","season_start":"1999/08"},
{"id":2,"season_end":"1999/07","season_name":"Second","season_start":"1999/02"},
{"id":3,"season_end":"2000/01","season_name":"Third","season_start":"1999/08"},
{"id":4,"season_end":"2000/07","season_name":"Forth","season_start":"2000/02"}
]
However I have no idea to write better code like below.
class _HomePageState extends State<HomePage> {
Future<List<Season>> seasons;
#override
void initState(){
super.initState();
seasons = fetchSeasons();
}
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
...
),
itemBuilder: (context, index){
return seasons[index].toString();
}
)
)
}
}
I should have used FutureBuilder<List<Season>>, But I don't know how to use with GridView.
Do you have any advice? Please.
Future<List<Season>> fetchSeasons() async {
final response =
await http.get('http://10.0.2.2:4000/api/seasons');
if(response.statusCode == 200){
Iterable list = json.decode(response.body);
var seasons = list.map((season) => Season.fromJson(season)).toList();
return seasons;
}else{
print('Error!!');
throw Exception('Failed to Load Post');
}
}
class Season {
final int id;
final String season_name;
final String season_start;
final String season_end;
Season({this.id, this.season_name, this.season_start, this.season_end});
factory Season.fromJson(Map<String, dynamic> json){
return Season(
id: json['id'],
season_name: json['season_name'],
season_start: json['season_start'],
season_end: json['season_end']
);
}
}

The problem is that seasons is a Future, not a List, that's why you can't use it like a list.
If you want to access the list of that Future, you need to use FutureBuilder, like this:
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Season>>(
future: seasons,
builder: (context, snapshot) {
if (snapshot.hasData) {
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
...
),
itemBuilder: (context, index) {
return Text("${snapshot.data[index].season_name}");
}
);
} else if (snapshot.hasError) {
return Text("Error");
}
return Text("Loading...");
},
),
);
}

There are couple of ways to do that with FutureBuilder you can do like this,in this case you dont need to use initstate or a Stateful widget the futurebuilder automatically calls the method fetchSeasons() as it gets rendered on screen and the result is received as a snapshot which can be accessed as below.
FutureBuilder<List<seasons>>(
future:fetchSeasons(),
builder:(BuildContext context,AsyncSnapshot <List<seasons>>snapshot){
snapshot.hasData?
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
...
),
itemCount:snapshot.data.length,
itemBuilder: (context, index){
return Column(
children: <Widget>[
Text('${snapshot.data[index]['id']}'),
Text('${snapshot.data[index]['season_name']}'),
Text('${snapshot.data[index]['season_end']}'),
Text('${snapshot.data[index]['season_start']}'),
]
):Center(child:CircularProgressIndicator());
}
});

Related

Correct way to load ListView data source initially

I have a stateful widget whose state builds a ListView. The ListView gets its data from an http API. I am using a Future<void> method called getData to retrieve this data and populate a List<> with it before calling setState.
My question is where should I call getData when this screen first launches? If I call it in initState(), I get the following error in the debug console:
[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedTheme>() or dependOnInheritedElement() was called before _EventListState.initState() completed.
If I wrap the call to getData in a delayed Future, I do not see the error. Here's my code:
class _EventListState extends State<EventList> {
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
setState(() {});
}
List<Event> events = [];
#override
initState() {
super.initState();
getData(); // this cause the error
// Future.delayed(Duration(seconds: 1), getData); // this works
}
#override
build(context) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
}
}
Forcing a delay to retrieve the data does not feel right, so is there a better way?
Use FutureBuilder.
List<Event> events = [];
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return Center(child: Text('Please wait its loading...'));
}
},
future: getData(),
);
}
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
}

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.

How can i use a Future Int using a Provider?

I am trying to show a live count of total documents in an Appbar. I get the right information in my console, but when i try to pass it with an Provider it returns an Instance of 'Future'. Can someone tell why i am getting still an Instence even if i await the result and the result is printed correctly in my console?
this is where i get the Future int and print the result to my console.
class AuthenticationService extends ChangeNotifier {
Future<int> totalJumps(jumpDict) async {
var respectsQuery = _db.collection(jumpDict);
var querySnapshot = await respectsQuery.get();
var result = querySnapshot.docs.length;
print(result);
// notifyListeners();
return result;
}
}
This is were it should show the result as a int in the title of the appBar
class LazyListOnline extends StatefulWidget {
static const String id = 'Lazy_list_online';
#override
_LazyListOnlineState createState() => _LazyListOnlineState();
}
class _LazyListOnlineState extends State<LazyListOnline> {
#override
Widget build(BuildContext context) {
String userDict = Provider.of<AuthenticationService>(context).findJumpDict;
var _firestoreDb =
FirebaseFirestore.instance.collection(userDict).snapshots();
var totalJump = Provider.of<AuthenticationService>(context)
.totalJumps(userDict)
.toString();
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.popAndPushNamed(context, HomeDrawer.id);
}),
title: Text('totalJumps'),
body: Stack(children: [
Padding(
padding: const EdgeInsets.all(18.0),
child: Container(
decoration: BoxDecoration(),
),
StreamBuilder<QuerySnapshot>(
stream: _firestoreDb,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return JumpItem(
snapshot: snapshot.data,
index: index,
);
});
}),
]),
);
}
}
I think you just need to store only the amount of documents as an int in Provider like.
class DocumentData extends ChangeNotifier {
int documentLength;
void setCurrentLengthOfDocuments(int length) {
this. documentLength = length;
notifyListeners();
}
}
Then in StreamBuilder. Every time data has been changed. You just need to update. Regrading to your example be something like.
StreamBuilder<QuerySnapshot>(
stream: _firestoreDb,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
// Update amount of documents length
Provider.of<DocumentData>(context, listen: false)
.setCurrentLengthOfDocuments(lengthOfCurrentDocuments);
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return JumpItem(
snapshot: snapshot.data,
index: index,
);
});
}),
]),
Then when you can get the length of documents every where on this page by just use Consumer widget. Or get the value directly from Provider.

flutter Read stream after bloc call inside for loop

** UPDATE THE QUESTION **
Now all work correctly !
I make my first backend that return to me all images in base64 string format inside a json format like that :
[
{
"base64Img":"/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAAqACAAQAAAABAAAAyKADAAQAAAABAAAAlgAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAA....."
}]
I take this pointing to this path : 192.168.1.20:8888/myserver/immagini/onserver/mydevicename/{name of the img}
On my pc where backend run i have several images and i want to return all of this.
Now in flutter i create a bloc :
class ImmagineBloc {
Repository _repository = Repository();
Observable <List<ImmagineCompleta>> get immagini => _immagini.stream;
** UPDATE WORKING MODE **
getImmagini(String deviceName, String immagineName) async {
List<ImmagineCompleta> Immagini = await _repository.getImmagini(deviceName, immagineName);
return Immagini;
}
I want to read the stream of every request, create the image from base 64 string ( try with one request of one image and it work, image display correctly), so create this image and put inside the list of widget for make it visible inside a grid :
** UPDATE WORKING MODE **
#override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: nameOnServer.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: _immagineBloc.getImmagini(
_deviceName, nameOnServer[index]),
builder: (context, snapshotData) {
return Container(
height: 200,
width: 200,
child: Image.memory(
base64Decode(snapshotData.data[0].base64img),
fit: BoxFit.cover,
));
});
}
);
}
}
How can i do that ? Read every stream before do another request and save image create from base 64 string inside a list of widget .
Inside _mediaList i wanna store all the images create from base64 conversion.
My code might not perfect, but I think this is what you want to achieve.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Example"),
automaticallyImplyLeading: false,
),
body: GridView.builder(
itemCount: nameOnServer.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: _immagineBloc.getImmagini(
_deviceName, nameOnServer[index]),
builder: (context, snapshotData) {
return Container(
height: 200,
width: 200,
child: Image.memory(
base64Decode(snapshotData.data[0].base64img),
fit: BoxFit.cover,
));
});
}));
}
Edit
The correct answer as below:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Example"),
automaticallyImplyLeading: false,
),
body: GridView.builder(
itemCount: nameOnServer.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: _immagineBloc.getImmagini(
_deviceName, nameOnServer[index]),
builder: (context, snapshotData) {
return Container(
height: 200,
width: 200,
child: Image.memory(
base64Decode(snapshotData.data[0].base64img),
fit: BoxFit.cover,
));
});
}));
}
You may use single StreamBuilder which listen the stream Stream<<List<Uint8List>> which is accumulated in bloc. Below scratch code.
bloc.dart
class Bloc {
final imageController =
StreamController<List<Uint8List>>.broadcast();
Stream<List<Uint8List>> get images => imageController.stream;
// Here you get your all images in loop
void getImages() async {
final imageList = <Uint8List>[];
for (int i = 0; i < nameOnServer.length; i++) {
final imageBase64 = await getImage(...);
final imageDecoded = base64decode(imageBase64);
// Decode image and accumulate in list
imageList.add(imageDecoded);
// which sent to sink
imageController.add(imageList);
}
}
widget.dart
final bloc = Bloc();
#override
void iniState() {
super.initState();
bloc.getImages();
}
#override
Widget build(BuildContext context) {
// Build `GridView` basis on stream.
// As list contains all images so they will displayed
// one by one
return StreamBuilder<List<Uint8List>>(
stream: bloc.images;
builder: (context, snapshot) {
// check error
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final images = snapshot.data;
return GridView.builder(
itemCount: images.length,
itemBuilder: (_, index) {
return ImageMemory(images[index]);
}
);
},
);
}

Flutter BLoC implementation with streamBuilder

I have a problem with my BLoC implementation, I have this code in synchronize.dart:
...
class _SynchronizeState extends State<Synchronize> {
UserBloc userBloc;
//final dbRef = FirebaseDatabase.instance.reference();
#override
Widget build(BuildContext context) {
userBloc = BlocProvider.of(context);
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Container(
...
),
child: StreamBuilder(
stream: dbRef.child('info_tekax').limitToLast(10).onValue,
builder: (context, snapshot) {
if(snapshot.hasData && !snapshot.hasError){
Map data = snapshot.data.snapshot.value;
List keys = [];
data.forEach( (index, data) => keys.add(index) );
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) => SynchronizeItem(title: keys[index], bottom: 10, onPressed: (){ print(keys[index]); })
);
}else{
return Container(
child: Center(
child: Text('Loading...'),
),
);
}
}
),
),
);
}
}
The previos code, works correctly, but i want implemente bloc Pattern, i have userBloc then i want to put this
userBloc.getDevicesForSinchronized()
instead of
dbRef.child('info_tekax').limitToLast(10).onValue,
my problem is this:
void getDevicesForSynchronized() {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}
i get this error **A vaue of type 'Stream' can't be returned from method 'getDevicesForSynchronized' because it has a return type of 'void'
The error is very clear, but i don't know what is type that i need return, try:
Furure<void> getDevicesForSynchronized() async {
return await dbRef.child(DEVICES).limitToLast(10).onValue;
}
or
Furure<void> getDevicesForSynchronized() async {
dynamic result = await dbRef.child(DEVICES).limitToLast(10).onValue;
}
and another solutions, but I don't know how return correctly value for use in the StreamBuilder
From the error message you can see that the return type is Stream. Change your method like:
Future<Stream> getDevicesForSynchronized() async {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}