flutter Read stream after bloc call inside for loop - flutter

** 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]);
}
);
},
);
}

Related

Unable to load data from API when changing min SDK to 2.12.0 from 2.7.0. what am I doing wrong? (Flutter)

I am making a practice mobile application using flutter, where I load data by calling a third-party API. I want to use late keyword to integrate a feature for which i need to change min SDK version to 2.12.0 from 2.7.0. But after doing that i am unable to load data from API. I am using future builder and ill share it's code before and after changes.
before changing min sdk to 2.12.0:
Widget build(BuildContext context) {
final ApiService client = ApiService(pageIndex);
return FutureBuilder(
future: client.getArticle(),
builder: (BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
List article = snapshot.data;
if (snapshot.hasData) {
return ListView.separated(
separatorBuilder: (context, index) {
return SizedBox(
height: 10,
);
},
padding: EdgeInsets.all(15),
itemCount: article.length,
itemBuilder: (context, i) {
return NewsScreenWidget(
author: article[i].author,
title: article[i].title.toString(),
description: article[i].description.toString(),
url: article[i].url.toString(),
urlToImage: article[i].urlToImage,
publishedAt: DateTime.parse(article[i].publishedAt),
content: article[i].content.toString(),
);
});
}
return Center(
child: CircularProgressIndicator(
color: Colors.black,
strokeWidth: 5,
),
);
},
);
}
After changing min SDK to 2.12.0
Widget build(BuildContext context) {
final ApiService client = ApiService(widget.pageIndex);
return FutureBuilder(
future: client.getArticle(),
builder: (BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
var article = snapshot.data;
if (article == null) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
var datalength = article.length;
if (datalength == 0) {
return const Center(
child: Text('No data found'),
);
} else {
return ListView.separated(
separatorBuilder: (context, index) {
return SizedBox(
height: 10,
);
},
padding: EdgeInsets.all(15),
itemCount: datalength,
itemBuilder: (context, i) {
return NewsScreenWidget(
author: article[i].author,
title: article[i].title.toString(),
description: article[i].description.toString(),
url: article[i].url.toString(),
urlToImage: article[i].urlToImage,
publishedAt: DateTime.parse(article[i].publishedAt),
content: article[i].content.toString(),
);
},
);
}
}
},
);
}
Currently, I am just seeing CircularProgressIndicator using the above code, but when I switch back to the branch where min SDK is 2.7.0 everything works (but can't use the late keyword).
Any help would be great :) Thanks.
These are best practices when making a future builder:
FutureBuilder<YOUR CLASS>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
// do something with logic
return WIDGET;
}
if(snapshot.hasError) {
return Text(snapshot.error);
}
return const Center(child: CircularProgressIndicator());
},
),
for more info: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

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.

Refresh swipe up to refresh widget flutter

I have an app that gets some data from firebase and than calls a class to display a widget based on the data from firebase. I tried adding a swipe up refresh but i have no idea where to put it and what to to call on refresh. I was trying it using the RefreshIndicator.
Here i will put my code in which it calls the database(firebase) and than creates an widget for each event in the database.
If you need any more information, please feel free to comment. Thank you so much for the help!
FutureBuilder(
future: databaseReference.once(),
builder: (context, AsyncSnapshot<DataSnapshot> snapshot) {
List lists = [];
if (snapshot.hasData) {
lists.clear();
Map<dynamic, dynamic> values = snapshot.data.value;
values.forEach((key, values) {
lists.add(values);
});
return new ListView.builder(
primary: false,
padding: EdgeInsets.only(left:12.0,right:12,bottom: 15,),
shrinkWrap: true,
itemCount: lists.length,
itemBuilder: (BuildContext context, int index) {
if(lists[index]["Status"]== "Active"){;
return Container(
child:EvendWidget(lists[index]["EventImage"],
Text(lists[index]["EventName"]).data,
Text(lists[index]["EventLocation"]+ ", "+lists[index]["EventCounty"] ).data,
Text(lists[index]["Date"]+ ", "+lists[index]["Time"]+ " - "+lists[index]["Time"]).data,
Text(lists[index]["Duration"]+ " H").data,
Text(lists[index]["Genre"]).data,
Text(lists[index]["Price"]).data,false));}else{return SizedBox.shrink(); };
});
}
return Container(
margin: const EdgeInsets.only(top: 300),
child:CircularProgressIndicator());
}),
Do something like this..
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
//write your code here to update the list*********
},
child: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Text('Line $index');
}
)
),
);
}
}
You can try with below lines may be it will work for you
return RefreshIndicator(
color: Colors.blue,
onRefresh: () {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => HomePage()));
},
child: ListView.builder(
....
));

how to load data from firestore to flutter app using stream Builder

I'm trying to load data from Firestore to my flutter app , but I'm stuck in ' loading... ' text , I feel I'm missing something !
Here is the code :
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kMainColor,
body: StreamBuilder<QuerySnapshot>(
stream:FirebaseFirestore.instance.collection(kProductsCollection).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
List<Product> products = [];
for (var doc in snapshot.data.docs) {
var data = doc.data();
products.add(Product(
pPrice: data[kProductName],
pName: data[kProductPrice],
pDescription: data[kProductDescription],
pImage: data[kProductImage],
pCategory: data[kProductCategory]));
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: .8,
),
itemBuilder: (context, index) => Text(products[index].pName),
itemCount: products.length,
);
}
else {
// I'm stuck in here
return Center(child: Text('Loading...'));
}
},
),
);
}

How to put JSON data from server with GridView --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());
}
});