Cannot use data from GraphQl query after getting data using Flutter and Ferry - flutter

I currently have a function in a bloc which is calling my repository to grab data from my server using GraphQL. I am able to get the data back. However I am not able to return it to my bloc as the function exits out after grabbing the data.
Here is the code in my bloc....
Either<AuthFailure, GLoginData_login> failureOrSuccess;
failureOrSuccess = await loginWithUsernameAndPassword(username: state.username, password: state.password)
.whenComplete(() { <---------- This line is never triggered (why not?)
failureOrSuccess.fold((l) => null, (r) {
print('You have the data $r');
});
});
As you can see I am awaiting for the response from the forwardedCall.
Here is the code in my repository which the bloc calls....
abstract class LoginRepository<TData, TVars, TRequest extends OperationRequest<TData, dynamic>> {
Future<Either<AuthFailure, GLoginData_login>> loginWithUsernameAndPassword({
#required Username username,
#required Password password,
});
}
This repository is an abstract class where the method is implemented in the following class...
class LoginUser extends LoginRepository {
#override
Future<Either<AuthFailure, GLoginData_login>> loginWithUsernameAndPassword(
{#required Username username, #required Password password}) =>
_runQuery(username: username, password: password);
Future<Either<AuthFailure, GLoginData_login>> _runQuery(
{#required Username username, #required Password password}) async {
final loginReq = GLoginReq(
(b) => b
..vars.LoginInput.username = usernameStr
..vars.LoginInput.password = passwordStr,
);
try {
return await GetIt.instance<Client>().request(loginReq).listen((response) {
if (!response.loading && response.dataSource == DataSource.Link &&
response.data != null) {
GLoginData data = response.data;
GLoginData_login login = data.login;
return login;
}
if (response.linkException != null) {
return response.graphqlErrors;
}
}).asFuture(); <------ I am marking it as a future so that it can be passed back
} on Exception catch (e) {
// Will need a created AuthException depending on our API and requirements
if (e.toString() == 'ERROR_NO_ACCOUNT_MATCHES_DETAILS_GIVEN') {
return left(const AuthFailure.invalidUsernameAndPaswordCombination());
} else {
return left(const AuthFailure.serverError());
}
}
}
}
I do receive the data from the serverising the client response. However in my bloc I cannot do anything with it as it just jumps out of the bloc after the loginWithUsernameAndPassword function completes.
How can I use the return value in my bloc, the whenComplete value line isn't working.
Thanks for any help you can provide.

just do:
either.fold(
(l) => Something(),
(r) => Something(),
);
If you have a function that returns the values then put the l or r into the function.

Related

How to throw error inside riverpod future provider and catch it on error flutter

final loginProvider =
FutureProvider.family<bool, LoginParam>((ref, param) async {
if (param.sgId == '' || param.password == '') {
return false;
}
final http.Response response =
await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity =
await ref.watch(userDefaultsProvider(param.sgId).future);
//ref.state = AsyncValue.data(true);
return true;
} else {
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
});
void login(String userName, String password) async {
state = AsyncValue.loading();
AsyncValue<bool> result;
try {
result = await ref.refresh(loginProvider(LoginParam(userName, password)));
state = result;
} catch (e) {
state = AsyncError(e);
}
}
I'm trying to throw an custom exception inside riverpod future provider and catch the exception in other state notifier classes, but the catch block is not triggered.
Is there any other way to handle exceptions that future provider throw.
First of all, you won't have to manually catch errors inside a FutureProvider, it will do that for you. Refer this example.
Generally, the operations that happen after certain "user interaction" like a button click (in this case, login operation), are not meant to be written in FutureProvider. Scenarios where you'd be using FutureProvider are as follows:
Fetching some data over HTTP/HTTPS.
Performing operations like reading a file or a local database.
So your use case of login can be achieved using a StateNotifier.
// auth_provider.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
// Always prefer some strongly typed object to
// know current status of authentication.
enum AuthState {
unauthenticated,
authenticated,
authenticating,
failed,
}
// StateNotifier is recommended to encapsulate all your business
// logic into a single class and use it from there.
class AuthStateNotifier extends StateNotifier<AuthState> {
// Initialize with the default state of "unauthenticated".
const AuthStateNotifier() : super(AuthState.unauthenticated);
Future<void> login(LoginParam params) async {
if (param.sgId.isEmpty || param.password.isEmpty) {
state = AuthState.failed;
return;
}
final http.Response response = await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity = await ref.watch(userDefaultsProvider(param.sgId).future);
state = AuthState.authenticated;
return;
} else {
state = AuthState.failed;
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
}
}
// Finally, create a provider that can be consumed in the presentation layer (UI).
final authProvider = StateNotifierProvider<AuthStateNotifier, AuthState>((ref) => const AuthStateNotifier());
Then, in your UI part, usually in the onTap / onPressed event handler of button, you can use it as follows. Please note that, we have created a button widget that extends the ConsumerWidget to access the ref.
// login.dart
import 'auth_provider.dart';
class LoginButton extends ConsumerWidget {
final LoginParam params;
const LoginButton({
Key? key,
required this.params,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
void login() {
try {
await ref.read(authProvider.notifier).login(params);
} catch (e) {
// Handle error here.
}
}
return ElevatedButton(
child: Text('Login'),
// Call the handler here.
onPressed: login,
);
}
}

flutter return future list in a var to use outside the loop

Hello I'm trying to recuperate the list value of a database.
i can but what i want is to export the result in a var so i can use in all my code just by calling "print(myList);"
this is my code :
static const URL =
'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(
URL,
));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw <Employee>[];
}
} catch (e) {
return <Employee>[];
}
}
and my classe Employee
class Employee {
String id;
String firstName;
String lastName;
Employee({required this.id, required this.firstName, required this.lastName});
factory Employee.fromJson(Map<String, dynamic> json) {
return Employee(
id: json['id'] as String,
firstName: json['lat'] as String,
lastName: json['lng'] as String,
);
}
}
can i have help please ?
There are two ways to access async data in most modern languages, including dart, they are:
1. By providing a callback then
2. By using the function in an async context and awaiting the result
I've wrapped the code above in a class called API so the examples below are easier to follow,
class API {
static const URL = 'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(URL));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw("${response.statusCode} Failed to parse photos");
}
} catch (e) {
throw e;
}
}
}
Method 1: Providing a callback to .then, this method will allow you to work with async actions in a synchronous context, but be aware it will not halt the execution flow.
void main() {
API.getEmployees().then((resp) => print(resp)).catchError(e) => print(e);
}
Method 2: Async/Await, this method will allow you to access the data inline, that is var x = await myAsyncFunc() remember the await keyword requires the function to be called within an async context. And the await keyword will halt the execution flow till the future completes.
void main() async {
try {
final list = await API.getEmployees();
print(list);
} catch (e) {
print(e);
}
}
Using either one of the two methods outlined above will allow you to access the data of the list later on.
Additional Reading:
Async programming in dart
Futures and error handling

Flutter : New error after dart migrate and null safety

After running dart migrate I got a ton of errors, most of them I believe I have fixed, but the last one does not make sense to me.
Stream<UserData> get user{
return _auth.authStateChanges()
.asyncMap(_userFromFireBaseUser as FutureOr<UserData> Function(User?));
}
The problematic part is the .asyncMap... The part "as FutureOr<UserData> Function(User?));" Was added by the dart migrate.
The errors I am getting are
error: Undefined class 'FutureOr'. (undefined_class) and error: The
argument type 'dynamic Function(User?)' can't be assigned to the
parameter type 'FutureOr Function(User?)'.
Posting rest of the code as well, it is used for Authentication.
class AuthService extends ChangeNotifier
{
// UserData _currentUser = AuthService();
String? _uid;
String? _email;
String? get getUid => _uid;
String? get getEmail => _email;
final FirebaseAuth _auth = FirebaseAuth.instance;
//Create user ojb based on firebaseuser
UserData? _userFromFireBaseUser(User? user) {
return user != null ? UserData(uid: user.uid) : null;
}
Stream<UserData> get user{
return _auth.authStateChanges()
.asyncMap(_userFromFireBaseUser as FutureOr<UserData> Function(User?));
}
//Get UID
Future getCurrentUID() async
{
final currentUid = await _auth.currentUser!.uid;
return currentUid;
}
//Sign in(anon)
Future signInAnon() async
{
try
{
UserCredential result = await _auth.signInAnonymously();
User? user = result.user;
return _userFromFireBaseUser(user);
}
catch(e) {
print(e.toString());
return null;
}
}
What is causing the error?
You can simply remove type cast to FutureOr<UserData> Function(User?) because _userFromFireBaseUser is already the function that gets User? and returns UserData?. To avoid returning null values from the stream just filter them with where. Use this code:
Stream<UserData> get user {
return _auth.authStateChanges()
.asyncMap(_userFromFireBaseUser)
.where((user) => user != null)
.map((user) => user!);
}

Flutter api login using riverpod

I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}
Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})
Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }

Singleton class for http requests

Can you give some advice how to design class for api requests in flutter? I'm ios developer and I used singleton classes with alamofire. If you provide some code it would be great!
class Client: ApiBase {
static let shared = Client()
private override init() {}
func login(phoneNumber: String, password: String, completion: #escaping(_ error: String?) -> Void) {
let params: [String : String] = [
"userId" : phoneNumber,
"password" : password,
]
baseRequest(route: ApiRouter.login(), params: params) { (response) in
if let json = response.json {
Session.current.sessionId = json["sessionId"].string
}
completion(response.error)
}
}
}
How login method called:
#IBAction func singin(_ sender: TransitionButton) {
Client.shared.login(phoneNumber: "12312", password: "123") { (error) in
guard error == nil else {
// show error
return
}
// navigate to home page
}
}
In flutter you don't have to deal with the relative nastiness of IBActions, protocols as callback, or retain cycles, and you have async and await to help out.
There's a few ways you could do the API calls - one would be to simply put them right in the same code as your UI. That has downsides, but it is certainly readable.
class WhateverMyComponentIsState extends State<WateverMyComponentIs> {
Future<String> _doLogin({#required String phoneNumber, #required String password}) async {
final response = await http.post(LOGIN_URL, body: {'userId': phoneNumber, 'password': password})
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(body);
return jsonResponse['sessionId'];
} else {
... error handling
}
}
String phoneNumber;
String password;
#override
Widget build(BuildContext context) {
return ...(
child: FlatButton(
onPressed: () async {
final sessionId = await _doLogin(phoneNumber: phoneNumber, password: password);
... do whatever - setState(() => loggedIn = true), or Navigator.push ...
}
),
)
}
}
If you wanted, you could extract all of the api calls into a different class - they could be static methods, but that makes it so that it's harder to write good tests if you ever decide to do that.
My personal recommendation is to use a form of more or less 'dependency injection', by utilizing InheritedWidget to provide an implementation of a class that actually performs the login (and could hold the sessionId). Rather than implementing all of that yourself, though, you could use the ScopedModel plugin which I personally like very much as it greatly reduces the amount of boilerplate needed.
If you use ScopedModel properly (which I'll leave as an exercise for you - I'm pretty sure there's other questions about that), you can use it or a class it provides to do the http request, and then have the sessionId stored in the ScopedModel.
The beauty of that is that if you were to ever get to writing tests (or have to deal with two slightly servers, etc), you could then replace the ScopedModel with a different ScopedModel which implemented the same interface but doesn't actually perform http requests or performs them differently.
In flutter you should create a class something like this
class User {
String name;
String pass;
User({
this.name,
this.pass,
});
User.fromJson(Map<String, dynamic> json) {
name = json['name'];
pass= json['pass'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['pass'] = this.pass;
return data;
}
}
Now create the list of type User class something like this
final List<User> user;
Now call the URL (API) for user Auth
Future<void> validateUsr() async {
var client = new http.Client();
try {
var response = await client.get(
'https://xxxxxxxx/wp-json/jwt-auth/v1/token?username=xxxxx2&password=xxxxxx');
if (response.statusCode == 200) {
var data = json.decode(response.body);
var list = data as List;
setState(() {
user=list.map<User>((i) => User.fromJson(i)).toList();
});
} else {
print('Somthing went wrong');
}
} catch (e) {
print(e);
} finally {
client.close();
}
}
Hope this helped you