class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
late Future<Categories> _futureCategories;
late Future<AdBanner> _futureBanners;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
getData() async {
_futureCategories = _adService.getAllCategories();
_futureBanners = _adService.getAds('2');
AdBanner foos;
Categories bars;
await Future.wait<void>([
_futureBanners.then((result) => foos = result),
_futureCategories.then((result) => bars = result),
]);
}
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> shot) {
// (BuildContext context, AsyncSnapshot<List<dynamic>> shot) {
if (shot.hasData) {
return ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
// return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else if (shot.hasError) {
return NewsError(
errorMessage: '${shot.hasError}',
);
} else {
return const NewsLoading(
text: 'loading...',
);
}
});
}
}
I am trying to combine two different API and fetch the results but in this structure I cannot get any data and run only ProgressBarIndicator.
If I am use regular FutureBuilder the JSON calls works without any problem. My goal is get data from two different API's like shot.data[0].value and shot.data[1].value
you made mistake in defining the getData() function.
remove getData from build method and put outside build method because the build is itself a method, you cant define a method inside a method in Dart.
class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
late Future<Categories> _futureCategories;
late Future<AdBanner> _futureBanners;
#override
void initState() {
super.initState();
}
Future getData() async {
_futureCategories = _adService.getAllCategories();
_futureBanners = _adService.getAds('2');
AdBanner foos;
Categories bars;
await Future.wait<void>([
_futureBanners.then((result) => foos = result),
_futureCategories.then((result) => bars = result),
]);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> shot) {
// (BuildContext context, AsyncSnapshot<List<dynamic>> shot) {
if (shot.hasData) {
return ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
// return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else if (shot.hasError) {
return NewsError(
errorMessage: '${shot.hasError}',
);
} else {
return const NewsLoading(
text: 'loading...',
);
}
});
}
}
Related
I can't get the length of the list in the widget although I wrote everything correct
this is my cubit
class AppCubit extends Cubit<NewsStates> {
AppCubit() : super(NewsInit());
static AppCubit get(context) => BlocProvider.of(context);
MianTeams? mianTeams;
void getTimes() {
emit(NewsLoding());
DioHelper.getData(
Query: {"action": "get_teams", "league_id": "141", "APIkey": api})
.then((value) {
mianTeams = MianTeams.fromJson(value.data);
////////////////+++++ i can get the length here in cubit
print(mianTeams!.team.length);
emit(NewsSucsess());
}).catchError((onError) {
print(onError.toString());
emit(NewsErorr(onError.toString()));
});
}
this is my model
class MianTeams {
List<TeamsModel> team = [];
MianTeams.fromJson(List<dynamic> json) {
json.forEach((e) {
team.add(TeamsModel.fromJson(e));
});
}
}
class TeamsModel {
String? teamKey;
String? teamName;
String? teamBadge;
List<Players> players = [];
TeamsModel.fromJson(Map<String, dynamic> json) {
teamKey = json['team_key'];
teamName = json['team_name'];
teamBadge = json['team_badge'];
json['players'].forEach((e) {
players.add(Players.fromJson(e));
});
}
}
but in Widget I can't get the length to set the itemCount of List View separated
this is my statelessWidget
class Teams extends StatelessWidget {
const Teams({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, NewsStates>(
listener: (context, state) {},
builder: (context, state) {
// print(AppCubit.get(context).mianTeams!.team.length);
return Scaffold(
appBar: AppBar(),
body: ListView.separated(
itemBuilder: (context, index) => Divider(),
separatorBuilder: (context, index) => Divider(),
itemCount: AppCubit.get(context).mianTeams!.team.length),
);
});
}
}
I don't know what's wrong and I think I wrote everything correctly
The problem is that mianTeams is null until after getTimes() is called and the response is received from your API. So when the ListView tries to build initially, it's trying to get the length of a null list.
Instead of storing mianTeams directly as a property of the Cubit, you should make it a property of one of your State classes. Probably NewsSuccess - then the list will always be available in that state. And if the Cubit is in a different state, you can display a loading indicator or something.
Your Cubit might then look something like this:
class AppCubit extends Cubit<NewsStates> {
AppCubit() : super(NewsInit());
static AppCubit get(context) => BlocProvider.of(context);
void getTimes() {
emit(NewsLoding());
DioHelper.getData(
Query: {"action": "get_teams", "league_id": "141", "APIkey": api})
.then((value) {
final mianTeams = MianTeams.fromJson(value.data);
emit(NewsSucsess(mianTeams: mianTeams));
}).catchError((onError) {
print(onError.toString());
emit(NewsErorr(onError.toString()));
});
}
}
And your widget might be something like:
class Teams extends StatelessWidget {
const Teams({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, NewsStates>(
listener: (context, state) {},
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: _buildBody(state),
});
}
Widget _buildBody(NewsStates state) {
if (state is NewsError) {
// For example
return Text("Error: ${state.message}");
} else if (state is NewsLoding) {
// For example
return const CircularProgressIndicator();
} else if (state is NewsSuccess) {
return ListView.separated(
itemBuilder: (context, index) => Divider(),
separatorBuilder: (context, index) => Divider(),
itemCount: state.mianTeams.team.length),
);
} else {
// Handle any other possible states you have
}
}
}
You can use the state from builder
itemCount: state.mianTeams?.team.length,
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());
}
},
),
);
}
}
I am confused that will Nested ProviderScope and all Providers be romoved from memory? And is following usecase good practice or bad practice?
I have idsProvider
final idsProvider = Provider((_) => List.generate(50, (i) => i));
and have itemIdProvider for every id of idsProvider
final itemIdProvider = Provider.autoDispose((_) => 0);
UI as follows:
class BuildListView extends ConsumerWidget {
const BuildListView({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final ids = ref.watch(idsProvider);
return ListView.builder(
itemCount: ids.length,
itemBuilder: (context, index) {
return ProviderScope(
overrides: [
itemIdProvider.overrideWithValue(ids[index]),
],
child: const BuildItem(),
);
},
);
}
}
class BuildItem extends ConsumerWidget {
const BuildItem({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final itemState = ref.watch(itemProvider);
return itemState.when(
data: (id, data) => ListTile(
title: Text("ID: $id"),
subtitle: Text(data),
),
loading: () => const CircularProgressIndicator(),
error: (error) => Text(error.toString()),
);
}
}
Then I have stateNotifierProvider to manipulate the state of every item of the ListView:
final itemProvider = StateNotifierProvider.autoDispose<ItemNotifier, ItemState>(
(ref) => ItemNotifier(ref.watch(itemIdProvider)),
dependencies: [itemIdProvider],
);
class ItemNotifier extends StateNotifier<ItemState> {
ItemNotifier(this.id) : super(const ItemState.loading()) {
fetchData();
}
final int id;
Future<void> fetchData() async {
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
state = ItemState.data(id: id, data: "Data for $id");
}
}
// A lot of methods to change the state
// ...
// ...
}
#freezed
class ItemState with _$ItemState {
const factory ItemState.data({required int id, required String data}) = Data;
const factory ItemState.loading() = Loading;
const factory ItemState.error([String? message]) = Error;
}
I think it's perfectly acceptable. In addition, you may not have an initial value:
final itemIdProvider = Provider.autoDispose((_) => throw UnimplementedError());
This way it will be seen that the value will be implemented later.
About memory. ProviderScope is a StatefulWidget and has the following lines of code under the 'hood':
#override
void dispose() {
container.dispose();
super.dispose();
}
So you don't have to worry too much :)
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();
},
),
);
}
}
class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
Future<AdBanner?> futureAdd() async {
_adService.getAds('2');
}
Future<Categories?> futureCatergoriess() async {
_adService.getAllCategories();
}
#override
void initState() {
futureAdd();
futureCatergoriess();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.grey[200],
body: FutureBuilder(
future: Future.wait([futureCatergoriess(), futureAdd()]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
final advertisements = snapshot.data![0];
return ListView.builder(
itemCount: advertisements!.length,
itemBuilder: (BuildContext context, int index) {
//return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else {
if (snapshot.hasError) {
return NewsError(
errorMessage: '${snapshot.hasError}',
);
}
return const NewsLoading(
text: 'Loading...',
);
}
},
),
);
}
}
snapshot.data![0]; returning null value. I tried already many versions ([1] or snapshot.data.data but I cannot call the results.
I am using future.wait first time. There is no problem if I use any of API with traditional Future.builder.
any help?
after the advice of #ChristopherMoore I modified the code but the main problem is still continue. This code gives as output:
index
index
modified code
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([futureCatergoriess(), futureAdd()]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
final advertisements = snapshot.data!;
return ListView.builder(
itemCount: advertisements.length,
itemBuilder: (BuildContext context, int index) {
//return bannerListTile(advertisements, index, context);
return const Text('index');
});
This original line gives this error:
final advertisements = snapshot.data![0];
The getter 'length' was called on null. Receiver: null Tried calling: length The relevant error-causing widget was FutureBuilder<List<Object?>> lib/view/ad_view.dart:37