When I am using the provider package in Flutter to load data from an API into a list it repeatedly calls the API, how do I fix it? - flutter

I am trying to lode data from an api call that retrieves a map, I am able to get the map from the api to display how I want it to, however it repeatedly calls the api meaning the list keeps on refreshing. Even though I have tried setting the listener to false, it works but I have to manually refresh the app for it to work?
Additional Info: Assigning and Retrieving Data
import 'package:http/http.dart' as http;
class Stores with ChangeNotifier {
var s_length;
Future<List<Store>> getStores(String storeCatName) async {
final queryParameters = {
"store_category_name": storeCatName,
};
try {
//TODO this is the issue - must fix.
final uri = Uri.http("url", 'url', queryParameters);
//print(uri);
final response = await http.get(uri);
//print(response.statusCode);
//print(response.body);
if (response.statusCode == 200) {
final List<Store> stores = storeFromJson(response.body);
_stores = stores;
//print(_stores);
print("lenght: ${_stores.length}");
Store store;
for(store in _stores) {
store.products = Products().products(store.storeId);
}
//check if this is correct
notifyListeners();
//return stores;
} else {
print("error1");
return List<Store>();
}
} catch (e) {
print(e.toString());
return List<Store>();
}
//notifyListeners();
print(_stores);
}
List<Store> get favoriteItems {
//return _stores.where((storeItem) => storeItem.isFavorite).toList();
}
bool isNotFull(){
if (_stores.isEmpty){
return true;
} else {
return false;
}
}
int get numberOfStores{
return s_length;
}
List<Store> _stores = [];
List<Store> stores (String storeCatName){
getStores(storeCatName);
//print("cpp; + $s_length");
//notifyListeners();
return _stores;
}
}
final storesProvider = Provider.of<Stores>(
context, listen: false
);
storesProvider.getStores(categoryName);
final providerStoreList = storesProvider.stores(category.storeCategoryName);
Additional Info: Builder for List:
child: ListView.builder(
itemCount: providerStoreList.length,
itemBuilder: (context, index) => ChangeNotifierProvider.value(
value: providerStoreList[index],
child: StoreItem(),
)));
If any additional information is required just let me know. Any help would be greatly appreciated.
Thanks

Use
listen: false;
var ourClient = Provider.of<CartBlock>(context, listen: false);

Setting the listener to false means that your widget won't build again when notifyListeners() is called.
So, that might not be the issue.
The only reason I can think of is calling the API again from the build method,
which might happen if you are using a ListView builder.
So, every time you might be scrolling the ListView your API would call again.

Related

The argument type 'Stream<List<ObjectModel>>' can't be assigned to the parameter type 'List<ObjectModel>'

I am using BLoC pattern (with rxdart package) to read a list of "EmpresaDatosModel" and when trying to include the sink it throws me the following error:
The argument type 'Stream <List < EmpresaDatosModel >>' can't be
assigned to the parameter type 'List < EmpresaDatosModel >'.
In the BLoC pattern I am using the following code:
class EmpresaDatosBloc {
final _empresaDatosController = new BehaviorSubject<List<EmpresaDatosModel>>();
Stream <List<EmpresaDatosModel>> get empresaDatosStream => _empresaDatosController.stream;
Stream<List<EmpresaDatosModel>> cargarEmpresasStream() {
final empresasList = _empresaDatosProvider.cargarEmpresasStream();
_empresaDatosController.sink.add(empresasList); //THE ERROR THROWS HERE
return empresasList;
}
dispose() {
_empresaDatosController?.close();
}
}
The provider where the query is made from Firebase RTDB has the following:
Stream<List<EmpresaDatosModel>> cargarEmpresasStream() {
Query resp = db.child('admon');
final empStream = resp.onValue;
final publicarStream = empStream.map((event) {
final empMap = Map<String, dynamic>.from(event.snapshot.value);
final empList = empMap.entries.map((e) {
return EmpresaDatosModel.fromJson(Map<String,dynamic>.from(e.value));
}).toList();
return empList;
});
return publicarStream;
}
And the display widget looks like this:
final empresaDatosBloc = Provider.empresaDatosBloc(context);
empresaDatosBloc.cargarEmpresasStream();
//---
return StreamBuilder(
stream: empresaDatosBloc.empresaDatosStream,
builder: (BuildContext context, snapshot){
final empresasList = [];
if (snapshot.hasData) {
final myList = snapshot.data as List<EmpresaDatosModel>;
myList.forEach((element) {
empresasList.add(element);
});
}
How can I assign a List<EmpresaDatosModel> to the sink in order to fix the error?
You can call .addStream on the _empresaDatosController BehaviorSubject .
This forwards data and error events to the controller's stream.
_empresaDatosController.addStream(empresasList);
Hope can help you.
Just keep a reference to StreamSubscription and use Stream.listen() method
class EmpresaDatosBloc {
StreamSubscription<void>? _subscription;
final _empresaDatosController = new BehaviorSubject<List<EmpresaDatosModel>>();
Stream <List<EmpresaDatosModel>> get empresaDatosStream => _empresaDatosController.stream;
void cargarEmpresasStream() {
_subscription?.cancel();
_subscription = _empresaDatosProvider.cargarEmpresasStream()
.listen(_empresaDatosController.add, onError: _empresaDatosController.addError);
}
dispose() {
_subscription?.cancel();
_empresaDatosController?.close();
}
}

Load new data form server on scrolling | Pagination | Flutter/Dart

I'm trying to implement pagination on my flutter app. The task is to load/fetch new data on scrolling. I'm able to fetch data from the server on every scroll but the thing is every time the same data is fetched.
Here's my code for your review:
Future<Map<String, dynamic>> fetchEvents(String accessToken,int vehicleId,) async {
String url = '${AppConstants.baseUrl}v2/event/paginated?size=10&offset=${offset++}&eventsId=${widget.eventsId}';
final response = await get(
Uri.parse('$url'),
headers: {
'Authorization': "bearer $accessToken",
},
);
Map<String, dynamic> responseBody = json.decode(response.body);
fetchedEvents = [...responseBody['eventResponses']];
for (var event in fetchedEvents) {
setState(() {
events.add(event);
});
}
return responseBody;
}
On every scroll the offset gets updated yet returns the same data.
But on Postman, if I change the offset new set of data gets fetched. Where am I going wrong?
Here's my initState()'s code:
#override
initState() {
super.initState();
loadMoreData(); //initial data is fetched
_controller.addListener(() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) { //data fetched on scroll
loadMoreData();
}
});
}
Here's laodMoreData():
loadMoreData() {
fetchEvents(widget.accessToken, widget.eventId);
}
Please help me overcome this. Thanks in advance.
You dont have to use controller for this
you can add 1 to the itemCount to load next Page
ListView.builder(
itemCount: list.length+1,
itemBuilder: (context, i) {
if(i<list.length)
{
return listItem()
}
else{
loadMoreData()//this function will be called with the last listitem
return Loader() //loader for pagination
}
},
)

A value of type "Future<Null>" can't be assigned to a variable of type "Data"

I'm trying figured out how to work with future for fetching data over internet. I was trying to write a simple code where I convert Future into "average" data but it doesn't work and I can't get why that's happend.
Here's my code
class Fetch {
Data getData () {
Data data;
data = fetch().then((value) {data = value;}); //here's I'm getting error
}
Future<Data> fetch() async {
int number = await Future.delayed(Duration(seconds: 1), () => 3);
return(Data.fromIt(number));
}
}
class Data{
int date;
Data({this.date});
factory Data.fromIt(int num) {
return Data(
date: num,
);
}
}
After I corrected this part of code error has gone but now getData() returns null instead of value:
Data getData () {
Data data;
fetch().then((value) {data = value;});
return data; //null
}
Thanks
You can make getData an async function, and use the await keyword. It is much easier to understand if you are new to async programming and Futures
Future<Data> getData () async {
final data = await fetch();
// Whatever you want to do with data
return data; // Dont forget to return it back to the caller
}
The data is defined as a Data object, while fetch() returns a Future<Data>, causing the error.
Data data;
data = fetch().then((value) {
data = value;
});
You can not transform a Future object to a synchronous object without awaiting it. You can do this at the UI to get the Future value:
FutureBuilder<Data>(
future: Fetch().fetch(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
Data data = snapshot.data;
return Text(data);
},
)

Flutter: How to Access Data & NotifyListeners Outside a Stream

I have a list of items and a stream within a class. The stream triggers a future where then notifylisteners is called to update the list of items. It works, but it only shows updates within the stream. How do I notifylistners outside the stream as well?
Where, if I were to call Provider.of(context).items it won't return as empty.
Here is the following code structure.
class Mans with ChangeNotifier {
List<Man> _items = [];
List<Man> get items {
return [..._items];
}
Stream<List<Man>> stream;
bool hasMore;
bool _isLoading;
List<Man> _data;
StreamController<List<Man>> _controller;
Mans({page = 1}) {
_data = List<Man>();
_controller = StreamController<List<Man>>.broadcast();
_isLoading = false;
// Test if list prints #1
items.forEach((list) {
print("nono: ${list.id}");
});
stream = _controller.stream.map((List<Man> mansData) {
// Test if list prints #2
items.forEach((list) {
print("nono: ${list.id}");
});
return mansData;
});
// Test if list prints #3
items.forEach((list) {
print("nono2: ${list.id}");
});
hasMore = true;
refresh();
}
Future<void> refresh() {
return loadMore(
page: 1,
clearCachedData: true,
);
}
Future<void> loadMore(
{bool clearCachedData = false,
page = 1}) async {
if (clearCachedData) {
_data = List<Man>();
hasMore = true;
}
if (_isLoading || !hasMore) {
return Future.value();
}
_isLoading = true;
return await fetchAndSetMans(page)
.then((mansData) {
_isLoading = false;
_data.addAll(mansData);
hasMore = (mansData.isNotEmpty);
_controller.add(_data);
});
}
Future<List<Man>> fetchAndSetMans(page) async {
var cookie = '';
try {
print('Called_API_Mans');
var response = await SiteApi(serverConfig["url"]).getAsync("api_link?view_id=$page");
List<Man> list = [];
for (var item in response) {
//This just adds an instance of Man to the list from a Model not added to this Stack Question. It works.
list.add(Man.fromJson(item));
}
_items = list;
notifyListeners();
return _items;
} catch (error) {
return [];
}
}
As you can see, I placed three different instances where I can print the items after notifyListeners() is called in the Future 'fetchAndSetMans'.
Unfortunately, only in the one where the comment says "Test if list prints #2" does it show that the list has been updated. Basically, within the stream data.
#1 and #3 are empty.
So, anything outside of the stream, notifyListeners() doesn't update the items list.
I wish to know how I can update the value outside the stream when the future is called.
So, if I call a Provider.... like, Provider.of(context).items... I can actually get results.
Thanks, I'd appreciate any help.

Flutter Riverpod : How to Implement FutureProvider?

I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.