#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;
});
Related
I'm learning and trying to add parameters when calling parameters in functions when getting data from the API, but I'm a bit confused about how I call them in widgets.
static Future<Map<String, DataKuliahModel>> getDataKuliah(String smt) async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
await Future.delayed(const Duration(milliseconds: 1000));
// String responseJson = await rootBundle.loadString('assets/1.json');
Map<String, DataKuliahModel> finalResult = {};
final response = await http.get(
Uri.parse(
'$url/auth/mhs_siakad/perwalian/get_paket',
),
headers: {
'Authorization': 'Bearer $token',
},
);
final result = jsonDecode(response.body)['data'] as Map<String, dynamic>;
result.forEach((key, value) {
DataKuliahModel dataKuliah = DataKuliahModel.fromMap(value);
finalResult.addAll({
key: dataKuliah,
});
});
return finalResult;
}
and I want to call him here
When you declare a function with positional parameters you need to provide those parameters when you call that function.
import 'package:flutter/material.dart';
class Services {
static Future<String> greeting(String name) async {
/// this function doesn't need to be Future
/// but when you call API to get some data it should be a Future
return 'Hello $name';
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context) {
return FutureBuilder(
/// pass positional parameter to [greeting] here
future: Services.greeting('Dash'),
builder: (context, AsyncSnapshot<String> snapshot) {
return Center(
child: Text(snapshot.data ?? 'default'),
);
},
);
}
}
Result: Hello Dash
In your case smt seems to be an int not a String
and you have to pass it as query parameter to http request as follows
static Future<Map<String, DataKuliahModel>> getDataKuliah(int smt) async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
await Future.delayed(const Duration(milliseconds: 1000));
// String responseJson = await rootBundle.loadString('assets/1.json');
Map<String, DataKuliahModel> finalResult = {};
final response = await http.get(
// Uri.parse(
// '$url/auth/mhs_siakad/perwalian/get_paket',
// ),
Uri.http(url, '/auth/mhs_siakad/perwalian/get_paket',
{'smt':smt}),
headers: {
'Authorization': 'Bearer $token',
},
);
final result = jsonDecode(response.body)['data'] as Map<String, dynamic>;
result.forEach((key, value) {
DataKuliahModel dataKuliah = DataKuliahModel.fromMap(value);
finalResult.addAll({
key: dataKuliah,
});
});
return finalResult;
}
Have you looked at the Uri replace method?
You can do the following:
Uri.parse('$url/auth/mhs_siakad/perwalian/get_paket').replace(queryParameters:{ "smt":"$smt"});
Update on FutureBuilder:
// Put this outside your build function
Future<Map<String, DataKuliahModel>> DK ;
// Put this in your initState if you want the future to run on page load or use it for events like onTap
DK = Service.getDataKuliah(<PARAM>);
// This is in your build method
FutureBuilder(
future:DK,
builder: (context, snapshot) {
// add wigets to display results here
}
)
This is my shared riverpod class that i want to use that on multiple screens, but after navigate to another screen using ref.listen couldn't dispose or cancel and using another ref.listen work twice, how can i cancel each ref.listen on screen which i used that? for example you suppose i have two screen A and B and into A screen i have
A screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
on this ref.listen i navigate to another screen when server return 200 ok? now in B screen which i have ref.listen again:
B screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
without sending any request to server this listener work and listen to previous listener
requestProvider on this class shared between multiple screens and autoDispose don't work for that, because after creating another StateNotifierProvider such as requestProviderA_Screen work fine without problem, for example:
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
final requestProviderA_Screen = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
my request riverpod class:
final requestRepositoryProvider =
Provider.autoDispose<Repository>((ref) => Repository(ref.read));
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
class Repository {
final Reader _reader;
Repository(this._reader);
Future<int?> getResponse(
HTTP method, String endPoint, Map<String, dynamic> parameters) async {
try {
const r = RetryOptions(maxAttempts: 3);
final response = await r.retry(
() => _submit(method, endPoint, parameters),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return response.statusCode;
} on DioError catch (e) {
throw (e.response != null
? e.response!.statusCode
: e.error.osError.errorCode) as Object;
}
}
Future<Response> _submit(
HTTP method, String endPoint, Map<String, dynamic> parameters) {
final Options options = Options(
headers: {'Content-Type': 'application/json'},
);
late Future<Response> _r;
switch (method) {
case HTTP.GET:
_r = _reader(dioProvider).get(
endPoint,
queryParameters: parameters,
options: options,
);
break;
case HTTP.POST:
_r = _reader(dioProvider).post(
endPoint,
queryParameters: parameters,
options: options,
);
break;
}
return _r.timeout(const Duration(seconds: 30));
}
}
class RequestNotifier extends RequestStateNotifier<int?> {
final Repository _repository;
RequestNotifier(this._repository);
Future<NetworkRequestState<int?>> send({
required HTTP method,
required String endPoint,
required Map<String, dynamic> parameters,
}) =>
makeRequest(
() => _repository.getResponse(method, endPoint, parameters));
}
and one of screen which i use this class:
class SignUp extends HookConsumerWidget {
final String mobileNumber;
const SignUp({Key? key, required this.mobileNumber}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final _formKey = useMemoized(() => GlobalKey<FormState>());
final _nameFamily = useTextEditingController();
final future = ref.watch(requestProvider);
useEffect(() {
_nameFamily.dispose();
}, [_nameFamily]);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {
newState.when(
idle: () {},
//...
}
success: (status) {
//...
Routes.seafarer.navigate(
'/complete-register',
params: {
'mobile_number': mobileNumber.trim(),
'name_family': _nameFamily.text.trim()
},
);
},
error: (error, stackTrace) {
//...
});
});
final _onSubmit = useMemoized(
() => () {
if (_nameFamily.text.trim().isEmpty) {
//...
} else {
//..
ref.read(requestProvider.notifier).send(
method: HTTP.GET,
endPoint: Server.$updateNameFamily,
parameters: {
'mobile_number': mobileNumber,
'name_family': _nameFamily.text.trim()
});
}
},
[_formKey],
);
return Scaffold(
//...
);
}
}
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. :)
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.
i wrote an function to login user by rest api in flutter. I want to use response from post but i don't know how to export my variable into another file.
I want use userID, but i dont know how,
can somebody help me?
class LoginScreenState extends State<LoginScreen>{
makeLoginRequest(String email, password) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
Map data = {
'email':email,
'password':password
};
var jsonResponse;
var url = 'http://10.0.2.2:80/user/login';
var response = await http.post(url, body:data);
if(response.statusCode == 200){
jsonResponse = json.decode(response.body);
int userID = jsonResponse['id'];//HERE
if(jsonResponse != null){
setState(() {
_isLoading = false;
});
sharedPreferences.setString("token", jsonResponse['token']);
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => UserPage()), (Route<dynamic> route) => false);
}
}
In your UserPage use a variable to get the ID.
Example:
class UserPage extends StatefulWidget {
final int userId;
UserPage({#required this.userId});
#override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
#override
Widget build(BuildContext context) {
return Container();
}
}
While navigating to UserPage after login pass the userID:
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => UserPage(userId: userID)), (Route<dynamic> route) => false);
When you want to get the value of userId in UserPage, you can use it in following way: widget.userId