Flutter #3: I have some async problem in flutter - flutter

I have a piece of code to scan and read device information. I have printed the elements in the list in onScan function, however I don't know how to get that information and put it in a listview.
Can someone help me?
List<Data> listDevice = [];
Future<void> getData() async {
var apiEndpoint = TTAPI.shared;
await apiEndpoint.devideScan(((data) => onScan(data)));
}
Future<void> onScan(dynamic data) async {
var dataResponse = DataResponse.fromJson(data);
print(dataResponse.toJson());
List<dynamic> dt = jsonDecode(jsonEncode(dataResponse.data).toString());
dt.forEach((element) {
var item = Data.fromJson(element);
print(item.modelName);
listDevice.add(item);
});
var connectRequest = {
'serialNumber': 'DEVICE_SERIAL',
'modelName': 'DEVICE_MODEL',
'ipAddr': 'DEVICE_IP'
};
var apiEndpoint = TTAPI.shared;
await apiEndpoint.connectDevice(connectRequest);
}
Future<List<Data>> getList() async {
return listDevice;
}
You can see more of my code here: https://docs.google.com/document/d/1ntxaDpyNCLD1MyzJOTmZsrh7-Jfim8cm0Va86IQZGww/edit?usp=sharing

As for the current code structure, listDevice is populated inside Future. So you can call setState to update the UI after getting the list at the end of onScan.
Future<void> getData() async {
var apiEndpoint = TTAPI.shared;
await apiEndpoint.devideScan(((data) => onScan(data)));
setState((){});
}
But it would be great to use FutureBuilder and return list from getData.
Current question pattern example
class TextFW extends StatefulWidget {
const TextFW({super.key});
#override
State<TextFW> createState() => _TextFWState();
}
class _TextFWState extends State<TextFW> {
//for current question way
List<int> listDevice = [];
Future<void> getData() async {
await Future.delayed(Duration(seconds: 2));
/// others async method
listDevice = List.generate(10, (index) => index);
setState(() {}); //here or `getData().then()`
}
#override
void initState() {
super.initState();
getData();
// or this getData().then((value) => setState((){}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: listDevice.length,
itemBuilder: (context, index) => ListTile(
title: Text("${listDevice[index]}"),
),
),
);
}
}
Using FutureBuilder
class TextFW extends StatefulWidget {
const TextFW({super.key});
#override
State<TextFW> createState() => _TextFWState();
}
class _TextFWState extends State<TextFW> {
/// method will provide data by scanning
Future<List<int>> getData() async {
await Future.delayed(Duration(seconds: 2));
return List.generate(10, (index) => index);
}
late final fututre = getData();
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<int>>(
future: fututre,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("${snapshot.error}");
}
if (snapshot.hasData) {
final listDevice = snapshot.data;
return ListView.builder(
itemCount: listDevice?.length,
itemBuilder: (context, index) => ListTile(
title: Text("${listDevice![index]}"),
),
);
}
return CircularProgressIndicator();
},
),
);
}
}

Related

How to use querySnapshot in a listview builder? (flutter)

I'm trying to fetch documents from my firebase DB and use them to create a social media feed. Here I'm trying to get the length of the fetched collection but I cannot manage to call the variable. Any help would be appreciated. Example code
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
#override
void initState() {
super.initState();
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('fish');
Future<void> getData() async {
// Get docs from collection reference
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
}
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: querySnapshot.docs.length,
itemBuilder: (BuildContext context, int index) {
return _postView();
},
),
);
}
}
First of all it is not ok to call future function in initstate, you need to use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
late CollectionReference _collectionRef;
#override
void initState() {
super.initState();
_collectionRef = FirebaseFirestore.instance.collection('fish');
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<QuerySnapshot>(
future: _collectionRef.get(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
QuerySnapshot? querySnapshot = snapshot.data;
return ListView.builder(
itemCount: querySnapshot?.docs?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var data = querySnapshot?.docs?[index].data();
print("data = $data");
return _postView();
},
);
}
}
},
),
);
}
}
inside listview's builder you can use data to parse your data and use it.
You can use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
const LoadDataFromFirestore({super.key});
#override
State<LoadDataFromFirestore> createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
//TODO change Map<String, dynamic> with your data type with fromJson for example
Future<List<Map<String, dynamic>>> _getData() async {
final querySnapshot = await FirebaseFirestore.instance.collection('fish').get();
return querySnapshot.docs.map((doc) => doc.data()).toList();
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return _postView(/* Ithink you have to pass here your item like snapshot.data[index]*/);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
}

can't see circularprogressindicator while getting data from api in flutter

I am trying to show data from api and while loading data , there should be shown a circularprogressindicator,
but when I start app..it directly showing data instead of circularprogressindicator
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist ;
Future<void> fetchdata() async {
setState(() {
isloading=true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading==true ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
It's actually working perfectly fine, it shows too fast because it is receiving data quickly(+ could be cache case).
If you like to have more delay you can add, future.delay which is unnecessary
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
// get more delay
await Future.delayed(Duration(seconds: 2));
setState(() {
isloading = false;
});
}
A better of way of handling future method with FutureBuilder
Try the following code:
class _HomeScreenState extends State<HomeScreen> {
var maplist;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
setState(() {
maplist = json.decode(resp.body);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return FutureBuilder(
future: fetchdata(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
return ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
}
You need to use FutureBuilder, it is not good to use async function in initState, try this:
FutureBuilder<List<Map<String,dynamic>>>(
future: fetchdata(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Map<String,dynamic>> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Text(data[index]['title']));
});
}
}
},
),
also you need to change your fetchdata to this:
Future<List<Map<String,dynamic>>> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
return json.decode(resp.body);
}
Try this one,set isloading default true
class _HomeScreenState extends State<HomeScreen> {
bool isloading = true;
var maplist ;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
You can use like that
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist;
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: isloading ? const CircularProgressIndicator() : const MyBody(),
);
}
}
class MyBody extends StatelessWidget {
const MyBody({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
//Write your code here
);
}
}

convert future builder to listview builder

i want to fetch data withour using future, can someone help me to convert it ? direct using listview.builder without using future builder. and how can i post it ? i already try it for a couple days and stuck here. please explain it too
thank you
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:latihan_dio/src/features/home/domain/user.dart';
import '../../../../dio_client.dart';
class myHomepage extends StatefulWidget {
const myHomepage({Key? key}) : super(key: key);
#override
State<myHomepage> createState() => _myHomepageState();
}
class _myHomepageState extends State<myHomepage> {
// List<User> users = [];
var selectedIndex = 0;
#override
void initState() {
super.initState();
// fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<User>>(
future: fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
// if (snapshot.hasError) {
// return Text('Error: ${snapshot.error}');
// } else {
List<User>? data = snapshot.data;
return ListView.builder(
itemBuilder: (context, index) {
return Column(children: [
Text(data![index].firstName!),
]);
},
itemCount: data?.length,
);
}
}
// },
),
));
}
Future<List<User>> fetchData() async {
var Response = await DioClient().apiCall(
url: 'https://reqres.in/api/users?page=2',
requestType: RequestType.GET,
// queryParameters: {},
);
if (Response.statusCode == 200) {
List<dynamic> listUser = Response.data['data'];
List<User> users = listUser.map((e) => User.fromJson(e)).toList();
return users;
} else {
return [];
}
}
}
// Future<void> fetchData() async {
// var Response = await DioClient().apiCall(
// url: 'https://reqres.in/api/users?page=2',
// requestType: RequestType.GET,
// // queryParameters: {},
// );
// // List<dynamic> listUser = Response.data;
// // OR
// List<dynamic> listUser =
// Response.data['data']; // if you want to access data inside it
// List<User> users = listUser.map((e) => User.fromJson(e)).toList();
// }
as u can see here is my homepage. i make a freeze class and using dio client here.
Try this
class _myHomepageState extends State<myHomepage> {
List<User> user = [];
bool isLoading = false;
#override
void initState() {
initFunction();
super.initState();
}
void initFunction() async {
setState((){
isLoading= true;
})
user = await fetchData();
setState((){
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) {
return Column(children: [
Text(user[index].firstName!),
]);
},
itemCount: user.length,
);
),
));
}
Future<List<User>> fetchData() async {
var Response = await DioClient().apiCall(
url: 'https://reqres.in/api/users?page=2',
requestType: RequestType.GET,
// queryParameters: {},
);
if (Response.statusCode == 200) {
List<dynamic> listUser = Response.data['data'];
List<User> users = listUser.map((e) => User.fromJson(e)).toList();
return users;
} else {
return [];
}
}
}

Unable to get data from the List to build the ListView.builder()

I'm trying to fetch data from the jsonplaceholder todos API, Once I retrieve the data I'm storing it into a List and notifying all the listeners. But something weird is happening.
class Todos with ChangeNotifier {
List<Todo> _items = [];
List<Todo> get item {
return [..._items];
}
Future fetchAndSetData() async {
try {
const url = 'https://jsonplaceholder.typicode.com/todos';
final List<dynamic> response =
json.decode((await http.get(Uri.parse(url))).body);
List<Todo> extractedTodo =
response.map((dynamic item) => Todo.fromJson(item)).toList();
_items = extractedTodo;
print(_items.length); // Getting 200 which is exact length I'm expecting
notifyListeners();
} catch (err) {
print(err);
}
}
}
The above code is where I'm making a get request and storing the data into the List. The following code is where I'm calling the fetchAndSetData with the help of Provider.
class _HomeScreenState extends State<HomeScreen> {
var _isLoading = true;
#override
void didChangeDependencies() {
Provider.of<Todos>(context, listen: false).fetchAndSetData().then((_) {
setState(() {
_isLoading = false;
});
});
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: TodoList(),
);
}
}
The following is where I'm trying to get the todos from the items list.
#override
Widget build(BuildContext context) {
final todos = Provider.of<Todos>(context, listen: false).item;
print(todos.length);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (ctx, index) {
return Card(
child: ListTile(
title: Text(todos[index].title),
),
);
},
);
}
Once didChangeDependencies, it will call the fetchAndSetData and will set the List, so the print statement on the Todos class will print 200 as the length of items I'm expecting but in the TodoList class where I'm calling the getter, the length I'm receiving is 0.
Now the weird part is when I removed the listen: false in the didChangeDependencies, the print statement on the fetchAndSetData getting called again and again. With that I mean the length for the todos is 200 but the print goes beyond 200. As, there is no way that the data gets updated, so I mark those as listen: false
Please help me
Please fetch your data in
Future<List<dynamic>> _post;
#override
void initState() {
super.initState();
_post = fetchAndSetData();
}
and then use a FutureBuilder like this
return FutureBuilder(
future: _post,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {

How to search for items in an already generated ListView.builder with Getx

At the moment, I am displaying a list of items received by the query and the database. How can I use the search bar to rebuild ListView.builder. I only use StatelessWidget since I'm trying to learn the getX package. How will my SearchBar link to the list of items?
Given that in the Search bar I need to join the onChanged property
P.S: I'm sorry for my English, it's terrible)
My builder code:
Widget build(BuildContext context) {
return GetX<ProductController>(
init: ProductController(),
builder: (controller) {
return FutureBuilder<List<ProductsCombined>>(
future: controller.productList.value,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: snapshot.data.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) =>
ProductCards(
index: index,
snapshot: snapshot,
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
);
},
);
}
My controller code:
class ProductController extends GetxController {
final productList = Future.value(<ProductsCombined>[]).obs;
#override
void onInit() {
fetchProducts();
super.onInit();
}
Future<List<ProductsCombined>> callDb() async {
return await DBHelper.db.getProducts();
}
void fetchProducts() async {
productList.value = callDb();
}
}
EDIT:
I tried to do this, but without success
Changed the controller code:
class ProductController extends GetxController {
final productList = Future.value(<ProductsCombined>[]).obs;
Rx<Future <List<ProductsCombined>>> filterProduct = Future.value(<ProductsCombined>[]).obs;//Переписать productList в копию и везде указывать уже копию
Future<void> searchChanged(String value) async{
if(value != null && value.isNotEmpty){
List<ProductsCombined> s = await productList.value;
filterProduct = Future.value(s.where((element) => (element.productName.toLowerCase().contains(value.toLowerCase()))).toList()).obs;
}
filterProduct = productList;
}
#override
void onInit() {
fetchProducts();
filterProduct = productList;
super.onInit();
}
Future<List<ProductsCombined>> callDb() async {
return await DBHelper.db.getProducts();
}
void fetchProducts() async {
productList.value = callDb();
}
}
Calling a method in TextField:
TextField(
onChanged: (string) {
controller.searchChanged(string);
}
Also in FutureBuilder, I changed the controller.ProductList.value on the controller.filterProduct.value