Riverpod FutureProvider keeps on firiging again and again - flutter

I am using Riverpod's FutureProvider with family. The FutureProvider keeps on running again and again. It shows the loading dialog only. Also the hot reload stops working. FutureProvider is working fine without family. Please help in finding what's wrong.
final ephemerisProvider =
Provider((ref) => ApiService("https://localhost"));
final ephemerisFutureProvider = FutureProvider.family
.autoDispose<EpheModel, Map<String, dynamic>>((ref, data) async {
var response = await ref.read(ephemerisProvider).getData(data);
print(EpheModel.fromJSON(response));
return EpheModel.fromJSON(response);
});
class Kundlis extends ConsumerWidget {
static const routeName = "/kundlis";
#override
Widget build(BuildContext context, ScopedReader watch) {
final AsyncValue<EpheModel> kundlis = watch(ephemerisFutureProvider({}));
return Scaffold(
appBar: AppBar(
title: Text("Kundlis"),
),
drawer: AppDrawer(),
body: kundlis.when(
data: (kundli) => Center(child: Text(kundli.toString())),
loading: () => ProgressDialog(message: "Fetching Details..."),
error: (message, st) =>
CustomSnackBar.buildErrorSnackbar(context, '$message')));
}
}
class ApiService {
final String url;
ApiService(this.url);
Future<Map<String, dynamic>> getData(Map<String, dynamic> data) async {
try {
http.Response response = await http.post(url + "/ephe",
headers: <String, String>{'Content-Type': 'application/json'},
body: jsonEncode(data));
if (response.statusCode == 200) {
return data;
} else {
throw Exception("Error Fetching Details");
}
} on SocketException {
throw Exception("No Internet Connection");
} on HttpException {
throw Exception("Error Fetching Details");
}
}
}

{} != {}. Because of .family, you are creating a completely new provider every time you call watch(ephemerisFutureProvider({})). To select a previously-built provider via family, you must pass an identical value. And {} is never identical to {}, guaranteed. :)

Related

How can I use Future<> riverpod example on Flutter?

#riverpod
Future<String> boredSuggestion(BoredSuggestionRef ref) async {
final response = await http.get(
Uri.https('https://www.boredapi.com/api/activity'),
);
final json = jsonDecode(response.body) as Map;
return json['activity']! as String;
}
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final boredSuggestion = ref.watch(boredSuggestionProvider);
// Perform a switch-case on the result to handle loading/error states
return boredSuggestion.when(
loading: () => const Text('loading'),
error: (error, stackTrace) => Text('error: $error'),
data: (data) => Text(data),
);
}
}
I am trying to copy the simple example from Riverpod homepage. However, I get
Undefined class 'BoredSuggestionRef'. Try changing the name to the name of an existing class, or creating a class with the name 'BoredSuggestionRef'.
Error and I am trying to build it.
final testingP = StateProvider<Future<String>>((ref) async {
final response = await http.get(
Uri.https('https://www.boredapi.com/api/activity'),
);
final json = jsonDecode(response.body) as Map;
return json['activity']! as String;
});
#override
Widget build(BuildContext context, WidgetRef ref) {
final testing = ref.watch(testingP);
return testing.when(
loading: () => const Text('loading'),
error: (error, stackTrace) => Text('error: $error'),
data: (data) => Text(data),
);
}
And, in this example, I get The method 'when' isn't defined for the type 'Future'. Try correcting the name to the name of an existing method, or defining a method named 'when' error.
How can I use that example in this case?
Try the following code:
final testingP = FutureProvider.autoDispose<String>((ref) async {
final response = await http.get(
Uri.https('https://www.boredapi.com/api/activity'),
);
final json = jsonDecode(response.body) as Map;
return json['activity']! as String;
});
The method When is not defined for the type StateProvider, try using a Future provider since you're awaiting a future response.The code would look like:
final testingP = FutureProvider<String>((ref) async {
final response = await http.get(
Uri.https('https://www.boredapi.com/api/activity'),
);
final json = jsonDecode(response.body) as Map;
return json['activity']! as String;
});

Riverpod future provider not rebuilding ui

My problem is that when I run the app, the data doesn't show up on the UI. The code below is rendered under a bottom navigation bar format which is a stateful widget. To my knowledge the below code should work (show data on the initial running of app).
The code works but the data is only shown when I press hot reload. I've tried everything that I know but it still doesn't show data when I start the app.
final imageControllerProvider = Provider((ref) {
return ImageController();
});
final mainScreenImages = FutureProvider<List<String>>((ref) async {
List<String> list = [];
list = await ref.watch(imageControllerProvider).getImages();
return list;
});
class ImageController{
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
reference.listAll().then((value) {
for (var element in value.items) {
element.getDownloadURL().then((e) => imageUrls.add(e));
}
});
} catch (e) {
print(e);
}
return imageUrls;
}
}
class GenerateImages extends ConsumerWidget {
const GenerateImages({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final imageList = ref.watch(mainScreenImages);
final double screenwidth = MediaQuery.of(context).size.width;
final double screenheight = MediaQuery.of(context).size.height;
return imageList.when(data: (data) {
return Text('$data');
}, error: (_, __) {
return const Scaffold(
body: Center(
child: Text("OOPS"),
),
);
}, loading: () {
return const Center(child: const CircularProgressIndicator());
});
}
}
I think the problem is because in getImages() you are not awaiting the results instead you are using the then() handler to register callbacks. Replace your getImages() function with this and try.
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
final value = await reference.listAll();
for (var element in value.items) {
final url = await element.getDownloadURL();
imageUrls.add(url);
}
} catch (e) {
print(e);
}
return imageUrls;
}
}

Flutter Refresh List From API

i have a GET function in my flutter code and everytime i add a new item to the list. the list doesn't refresh and won't display the newly added item unless i refresh the whole page.
this is my POST method :
Future<http.Response> ajoutFournisseur(
String numeroFournisseur,
String addressFournisseur,
String matriculeFiscaleFournisseur,
String raisonSocialeFournisseur,
String paysFournisseur,
String villeFournisseur,
double timberFiscaleFournisseur) async {
List fournisseurs = [];
final response = await http.post(
Uri.parse('http://127.0.0.1:8000/api/fournisseur/'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, dynamic>{
'tel': numeroFournisseur,
'adresse': addressFournisseur,
'mf': matriculeFiscaleFournisseur,
'raisonSociale': raisonSocialeFournisseur,
'pays': paysFournisseur,
'ville': villeFournisseur,
'timberFiscale': timberFiscaleFournisseur,
}),
);
if (response.statusCode == 200) {
return fournisseurs = jsonDecode(response.body);
} else {
throw Exception('Erreur base de données!');
}
}
Future<dynamic> future;
and this is code of the button to confirm :
ElevatedButton(
onPressed: (() {
if (_formKey.currentState.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
setState(() {
future = ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
double.parse(timberFiscaleFournisseur.text));
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ajout en cours')),
);
}
}), ...
and this is my GET methos to fetch the items from the list :
fetchFournisseurs() async {
final response =
await http.get(Uri.parse('http://127.0.0.1:8000/api/fournisseur'));
if (response.statusCode == 200) {
var items = jsonDecode(response.body);
setState(() {
fournisseurs = items;
print(fournisseurs[0]['raisonSociale']);
});
} else {
throw Exception('Error!');
}
}
.
.
.
for (var i = 0; i < fournisseurs.length; i++)
Card(
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
ListTile(
title: Text(fournisseurs[i]['raisonSociale']), ...
how can i refresh the list everytime i add a new item without refreshing the whole page ?
I think you first of all need to learn some Flutter good practices.
For example, don't put your logic into the ElevatedButton, set it into a separate Widget function like below :
class Test extends StatefulWidget {
const Test({ Key? key }) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<void> _handleAjout() async {
if (_formKey.currentState.validate()) {
// First, check your request succeed
try {
var fournisseurs = await ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
double.parse(timberFiscaleFournisseur.text));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ajout en cours')),
);
// Will only update state if no error occured
setState(() => future = fournisseurs);
}
on Exception catch(e) {
// Always make sure the request went well
print("error");
}
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (() {
_handleAjout();
}),
)
}
}
And by awaiting POST result, then you can tell your Widget to fetch data and refresh the list by making :
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (() {
_handleAjout().then(() => fetchFournisseurs());
}),
)
}
The .then function tells Flutter to execute the code contained in the () => myCallbackFunction() only if the previous asynchronous function went well.
By the way, you should always check if your content looks like what you expected before calling setState and set data to your variables :)

SnackBar is not showing in flutter

AS I am new to flutter, I can't find why the SnackBar is not showing on my UI while I am calling different function for API call! In one case it is showing but not in other cases.
I have to show a Snackbar on success of each API call (like in my project it is on success of generateOtp and on success of verifyOtp).
Below is my code.
snackbar.dart
showInSnackBar(String message, key){
key.currentState.showSnackBar(
SnackBar(
content:Text(message),
backgroundColor: Colors.blueAccent[700],
)
);
}
api_service.dart
class ApiService {
bool isVerified = false;
BaseOptions options = BaseOptions(
baseUrl: "http://...",
);
generateOtp(String mobileNo, key) async {
Dio dio = new Dio(options);
FormData formData = FormData.fromMap({'mobile_no': mobileNo});
try {
Response response = await dio.post("generate_otp/", data: formData);
if (response.statusCode == 200) {
// on success of generate otp I have to show a message on SnackBar. But it is not working.
showInSnackBar(response.data["msg"], key);
print(response.data);
}
} on DioError catch (e) {
showInSnackBar(e.message, key);
}
}
Future<bool> verifyOtp(String mobileNo, String otp, key) async {
Dio dio = new Dio(options);
FormData formData = FormData.fromMap(
//.....);
try {
Response response = await dio.post("verify_otp/", data: formData);
if (response.statusCode == 200) {
// here also it is not working.
showInSnackBar(response.data["msg"], key);
// Otp verified
isVerified = true;
}
} on DioError catch (e) {
showInSnackBar(e.message, key);
}
return isVerified;
}
}
register.dart
class _RegisterPageState extends State<RegisterPage> {
var _key = new GlobalKey<ScaffoldState>();
//...........
service.generateOtp(_data.mobileNo, _key); /* here I am calling generateOtp() */
} else {
print('invalid credentials');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
body: SingleChildScrollView(
//..........
otp.dart
submit() async {
_formKey.currentState.save();
bool verify =
await service.verifyOtp(widget.mobNumber, pinController.text, _key); /* here I am calling
verifyOtp() */
if (verify) {
SharedPreferences preferences = await SharedPreferences.getInstance();
String userInfo = preferences.getString('user_data');
// Decoding String data to map
Map map = json.decode(userInfo);
service.registerUser(map);
} else {
showInSnackBar('Invalid otp', _key); /* here SnackBar is showing on my UI*/
}
}
Can anybody please help me to solve this!
Lack of context, (Context).
docs : https://api.flutter.dev/flutter/widgets/BuildContext-class.html
try this(work for me):
void _showSnackBar(BuildContext context, String text) {
Scaffold.of(context).showSnackBar(SnackBar(content: Text(text)));
}
If You want to use snackbar without context u can use this package get: ^3.13.2
and call snackbar like this any where you want:
Get.snackbar(
"title",
"content",
);

implementing simple rxDart with Bloc don't get result

from this link on my web server as
http://instamaker.ir/api/v1/getPersons
i'm trying to get result and printing avatar from that result, unfortunately my implementation with rxDart and Bloc don't get result from this response and i don't get any error
server response this simplified result:
{
"active": 1,
"name": "my name",
"email": " 3 ",
"loginType": " 3 ",
"mobile_number": " 3 ",
...
"api_token": "1yK3PvAsBA6r",
"created_at": "2019-02-12 19:06:34",
"updated_at": "2019-02-12 19:06:34"
}
main.dart file: (click on button to get result from server)
StreamBuilder(
stream: bloc.login,
builder: (context,
AsyncSnapshot<UserInfo>
snapshot) {
if (snapshot.hasData) {
parseResponse(snapshot);
}
},
);
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.avatar);
}
LoginBlock class:
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async{
UserInfo userInfo = await _repository.userInfo();
_login_fetcher.sink.add(userInfo);
}
dispose(){
_login_fetcher.close();
}
}
final bloc = LoginBlock();
Repository class:
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin();
}
my model:
class UserInfo {
int _active;
String _name;
...
UserInfo.fromJsonMap(Map<String, dynamic> map)
: _active = map["active"],
_name = map["name"],
...
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['active'] = _active;
data['name'] = _name;
...
return data;
}
//GETTERS
}
BaseUrl class:
class BaseUrl {
static const url = 'http://instamaker.ir';
}
and then InstagramApiProviders class:
class InstagramApiProviders {
Client client = Client();
Future<UserInfo> checkUserLogin() async {
final response = await client.get(BaseUrl.url+'/api/v1/getPersons');
print("entered "+BaseUrl.url+'/api/v1/getPersons');
if (response.statusCode == 200) {
return UserInfo.fromJsonMap(json.decode(response.body));
} else
throw Exception('Failed to load');
}
}
Well the answer here is part of the test that I make to get this done. I can put my all test here but I think that the problem cause was because as StreamBuilder is a widget his builder method callback is only called when the widget is in flutter widget tree. As in your sample you're just creating a StreamBuilder the builder method will never be called bacause this widget isn't in widget tree.
As advice first test your code changing only UI layer... do somenthing like:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
Here we're putting the StreamBuilder in widget tree so the builder callback is called and maybe you will see the results. If it fails, please comment that I update my answer with my full test code with this working.
Updating the answer with sources that I made tests.
Basic model
class UserInfo {
int _active;
String name;
UserInfo.fromJsonMap(Map<String, dynamic> map) {
_active = map["active"];
name = map["name"];
}
Map<String, dynamic> toJson() => {
'active' : _active,
'name' : name,
};
}
The provider class
class InstagramApiProviders {
Future<UserInfo> checkUserLogin() async {
UserInfo info;
try {
http.Response resp = await http.get("http://instamaker.ir/api/v1/getPersons");
if (resp.statusCode == 200){
print('get response');
print( resp.body );
info = UserInfo.fromJsonMap( Map.from( json.decode(resp.body ) ));
}
}
catch (ex) {
throw ex;
}
print('returning $info');
return info;
}
}
Repository
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin().then((user) => user);
}
BLoC class
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async {
UserInfo info = await _repository.userInfo();
_login_fetcher.sink.add(info);
}
dispose(){
_login_fetcher.close();
}
}
Widget UI
This starts showing There is no data message but when you hit appBar button wait a little and then the data is fetched and updates the UI.
class WidgetToShowData extends StatelessWidget {
final LoginBlock bloc = LoginBlock();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
}
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.name);
}
}