I have a simple functionality for user authentication. When user clicks Login buttonm than callback calls login method from SecurityBloc which in its turn calls execute method of ApiProvider.
If user enter wrong password than method _handleResponse throws ApiException with error description which I am expecting to be catched in method login of SecurityBloc. And it works as expected when I run project under the web. I see snackbar with error message.
The problem occurred when I run project under Android. When user enter wrong password than VS Code (I use it) stops on line with throw ApiException('invalid authentication response');, i.e. debugger thinks that this exception is unhandled! But it is catched and handled (see code). When I click button continue on debugger control panel, the highlighted row jumps over the code and at the end I see the error message in snackbar.
So is it possible to skip (fix) this situation? Maybe is it knowing bug and there is a workaround?
P.S. If I checked off the "Uncaught exception" checkbox looks fine but it is not the case because now I may pass really uncaucht exceptions.
Any ideas?
class ApiProvider {
/// Executes HTTP request
Map<String, dynamic> execute(url, query, ...) async {
final response = await http.post(url,query:query);
return _handleResponse(response);
}
/// Parses HTTP response
Map<String, dynamic> _handleResponse(Response response) {
if (!response.contains('user')) {
throw ApiException('invalid password');
}
... // other statements
}
}
class SecurityBloc {
Future<AuthEvent> login(String user, String password) async {
try {
final data = api.execute()
if (data == null) {
throw ApiException('invalid authentication response');
}
final token = _parseData(data); // Can throws FormatException
return AuthEvent.ok(token);
} on ClientException catch(e) {
return AuthEvent.error(e.message);
} on FormatException catch(e) {
return AuthEvent.error(e.message);
} on ApiException catch(e) {
return AuthEvent.error(e.message);
}
}
}
class _LoginState extends State<Login> {
final securityBloc = SecutiryBloc();
#override
Widget build(BuildContext context) {
return
...
FlatButton(
child: Text('Login'),
onPressed: () async {
final authEvent = await securityBloc.login(...);
if (authEvent.failed) {
ScaffoldMessenger.of(context).showSnackbar(...); // Show authentication error
} else {
// access granted
}
},
),
...
}
Please see this https://github.com/FirebaseExtended/flutterfire/issues/3475 that I raised...if you follow the thread and the link at the end it would appear a fix was posted to master in early October....don't know when it will make it through the releases to stable. Basically though, this is an issue that impacts the IDE and won't manifest itself in the released app on a device.
Related
I am implementing a password recovery function based on the url sent to the email. Opening the app based on that url was successful. But instead of directly opening the required page in the app that is in the background, it duplicates the app. Although it still leads me to the password recovery page, now there will be 2 same apps running side by side
Procedure
Enter your email to send the password reset link
Click submit
Open the email containing the recovery link
Duplicate the app and open a recovery password page
Things what happen
Splash screen, first page open in the app, I am trying to do as instructed from uni_links package but still no success. Currently the function getInitialLink has the effect of opening the app based on the recovery link
class SplashController extends GetxController {
final SharedPreferencesHelper _helper = Get.find<SharedPreferencesHelper>();
late StreamSubscription sub;
#override
void onReady() async {
super.onReady();
await checkToken();
}
Future<void> checkToken() async {
await Future.delayed(Duration(seconds: 3));
var token = _helper.getToken();
if (token == null) {
Get.offNamed(Routes.LOGIN);
} else {
Get.offNamed(Routes.MAIN);
}
}
#override
void onInit() {
super.onInit();
initUniLinks();
}
Future<Null> initUniLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String? initialLink = await getInitialLink();
if (initialLink != null) {
print("okay man");
Get.toNamed(Routes.RECOVERY);
}
sub = getLinksStream().listen((link) {
}, onError: (err) {
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
}
}
}
I found the solution, actually this answer is already on Stackoverflow, and it's really simple.
In the AndroidManifest.xml file of the app. Find "android:launchMode" and change its old value to singleTask. And here is the result
android:launchMode="singleTask"
I have set up Push Notification with Firebase.
A failure is occuring with unclear error message
FormatException: Unexpected end of input (at character 1)
strong text
I use Bloc
class PushNotificationsBloc
extends Bloc<PushNotificationsEvent, PushNotificationsState> {
final UserDataRepository userDataRepository;
PushNotificationsBloc({this.userDataRepository})
: super(PushNotificationsInitial());
#override
Stream<PushNotificationsState> mapEventToState(
PushNotificationsEvent event,
) async* {
if (event is SendNewNotification) {
yield* mapSendNewNotificationToState(event.map);
}
}
Stream<PushNotificationsState> mapSendNewNotificationToState(Map map) async* {
yield SendNewNotificationInProgressState();
try {
String res = await userDataRepository.sendNewNotification(map);
if (res != null) {
yield SendNewNotificationCompletedState(res);
} else {
yield SendNewNotificationFailedState();
}
} catch (e) {
print(e);
yield SendNewNotificationFailedState();
}
}
}
This error is shown when you expect A but got B. Then the flutter tries to convert it to object A but it is not possible because you got something else. For example if I expect a JSON that looks like this:
{"username": "userName", "password": "password"}
I convert this to an object:
class User {
String username;
String password;
}
It works fine. But if something goes wrong and it throws back an exception like:
{"Exception": "Internal Server Error"}
Then it will show your error because if it tries to convert the exception JSON into a user, it fails.
What is the right way to pass exception/failure from services or other parts to your widget.
I would like to pass error code from register to onSignIn. Whats the right way. Is it ok to do it the way I am doing or should I
again throw exception from register and catch it in onSignIn.
don't catch in register but catch in onSignIn
File A
void onSignIn() async {
dynamic result = await _auth.register();
if (result.runtimeType == String) {
print(result);
}
}
File B
Future register() async {
try {
AuthResult result = await _auth.createUser();
return _user(result.user);
} catch (e) {
return e.code;
}
}
For error handling I usually use a wrapper object to help me out better handling, errors or even "loading". This is a patter from one of Google's Android examples and I've used on flutter with success. So basically you create a generic class, in my case I call it "Response" that has the structure of:
class Response<T> {
final ApiError error;
final T data;
final ResponseType type;
Response({
this.error,
this.data,
this.type,
});
bool get isOk {
return type == ResponseType.ok;
}
bool get isLoading {
return type == ResponseType.loading;
}
bool get hasError {
return type == ResponseType.error;
}
factory Response.loading() {
return Response<T>(type: ResponseType.loading);
}
factory Response.ok(T data) {
return Response<T>(type: ResponseType.ok, data: data);
}
factory Response.empty() {
return Response<T>.ok(null);
}
factory Response.error(ApiError data) {
return Response<T>(type: ResponseType.error, error: data);
}
}
enum ResponseType { loading, error, ok }
ApiError is my custom implementation that handles errors for a specific project, it can be replaced with a generic Error.
This approach is super helpful when you're using Streams, because it won't close a stream in case of error, you have the possibility to mark the beginning of the task by replying with a loading and it will handle succes/error properly.
This approach would also allow you to send a more well defined error to the UI, let's say you need the error code, error message, and maybe you get an input field name where you want to show that error on.
Altho, in your case it might be a bit of overkill, I would still use it like this:
Future<Response<AuthResult>> register() async {
try {
AuthResult result = await _auth.createUser();
return Response.ok(_user(result.user));
} catch (e) {
return Response.error(e);
}
}
void onSignIn() async {
Response<AuthResult> result = await _auth.register();
if (result.isOk) {
//TODO: all good let's use data with result.data
}
else if (result.isLoading) {
// TOOD: well...show a loading indicator
}
else {
//TODO: we got error handle it using result.error
}
}
In flutters' documentation, they almost always caught the error in the parent function(take a look at here as an example). Also using dynamic may be dangerous since it accepts all kinds of objects, a better approach would be using the final keyword. so the preferred way would be:
Future<AuthResult> register() async {
AuthResult result = await _auth.createUser();
return _user(result.user);
}
and then:
void onSignIn() async {
try{
final result = await _auth.register();
print(result);
} on Exception catch(ex){
// do something with error
}
}
I usually like to use Provider for such kind of things.
class Auth with ChangeNotifier{
AuthResult _authResult;
AuthResult get authResult => _authResult;
}
enum AuthResult{
Successful,
Error,
OTPError,
}
Then, I will use the provider package to get the data wherever needed.
I get into this weird exception which keeps on coming and freezing my app.
I am try to handle SocketException and TimeOutException in http package with provider package.
class Auth with ChangeNotifier{
..........
Future<User> getUser(String id) async {
try {
final user = (await http.post("${Domain.ADDRESS}/user",
headers: {"auth-token": _token}, body: {"userId": id}))
.body;
} on SocketException {
throw HttpException("DISCONNECTED");
} on TimeoutException {
throw HttpException("TIMEOUT");
} catch (error) {
print(error);
throw HttpException("SOMETHING_WENT_WRONG");
}
notifyListeners();
return userdata;
}
......
}
when internet in not connected application freezing at
on SocketException {
throw HttpException("DISCONNECTED"); <------------ Exception has occurred.
HttpException (DISCONNECTED)
But I handle this on next screen
#override
Future<void> didChangeDependencies() async {
......
setState(() {
isLoading = true;
});
try{
user= await Provider.of<Auth>(context)
.getUser(Provider.of<Auth>(context).userId);
if (this.mounted) {
setState(() {
isLoading = false;
});
}
}on HttpException catch(error){
if(error.toString().contains("DISCONNECTED")){
Scaffold.of(context).showSnackBar(SnackBar(content: Text("Please check your internet
connection"),));
}
}catch(error){
print(error);
}
super.didChangeDependencies();
}
Custom HttpException.dart
class HttpException implements Exception {
final String message;
HttpException(this.message);
#override
String toString() {
return message;
}
}
So if I understand you right, your IDE pauses when the exception is thrown, even though you catch it correctly.
Can you tell me what happens after resuming / unpausing the IDE that you're using, does it behave as expected and print this?
if(error.toString().contains("DISCONNECTED")){
Scaffold.of(context).showSnackBar(SnackBar(content: Text("Please check your internet
connection"),));
Because if it does, that means that you probably have the Breakpoints setting on All Exceptions and not only Uncaught Exceptions.
I also had this problem and I found out that it was due to a bugged flutter beta/dev version that I had.
Solution
I fixed it by changing the channel to stable and upgrading to the newest version.
flutter channel stable
flutter upgrade
If you want, you can also change the channel to master to have the newest features of flutter. But I would really suggest using the stable channel, since it is more stable and beacause a new stable build was just recently released.
Here's a link to the issue that you're encountering:
https://github.com/flutter/flutter/issues/66488
<--- UPDATE --->
FOUND THE SOLUTION. CHECK ANSWER BELOW
<--- UPDATE --->
Any ideas why this is happening, what causes the bug, and how do I fix it?
I was following TheNetNinja's tutorial on YouTube and I've seen people had a similar problem, but not quite the same.
An exception is thrown when the app is closed suddenly (using the stop button in Android Studio) without logging out and then restarted, only when using e-mail and pass login. For Google login, it seems to work fine. Also, this doesn't happen if I manually create a new user in Firebase Console and login for the first time.
THE EXCEPTION:
════════ Exception caught by provider ══════════════════════════════════════════════════════════════
The following assertion was thrown:
An exception was throw by _MapStream<FirebaseUser, User> listened by
StreamProvider<User>, but no `catchError` was provided.
Exception:
RangeError (index): Invalid value: Only valid value is 0: 1
════════════════════════════════════════════════════════════════════════════════════════════════════
I'm using firebase_auth: ^0.16.0 in my pubspec.yaml and I'm thinking it might have something to do with the version.
When I log in, I get this output
I/BiChannelGoogleApi(17821): [FirebaseAuth: ] getGoogleApiForMethod() returned Gms: com.google.firebase.auth.api.internal.zzaq#cd1fa83
D/FirebaseAuth(17821): Notifying id token listeners about user ( 1EMwk8483hQWnSQwJeQYm9AcpiD2 ).
which looks like the account gets verified and logged in, but then the screens don't change, so I guess it gets stuck somewhere in the verification process.
And the weird thing is, if I log in normally with the exception and restart the app, it works just fine. If I keep restarting it, it works just fine, but if I suddenly stop it, I get the exception again.
This is my authentication code
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:common_ui_module/Utils.dart';
import 'package:user_module/Role.dart';
import 'package:user_module/User.dart';
import 'package:localization_module/AppLocalizations.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
return user != null
? User(uid: user.uid)
: null;
}
Stream<User> get user {
// map FireBaseUsers to User objects
return _auth.onAuthStateChanged.map(_userFromFireBaseUser);
}
// sign in with email and pass
Future<User> signInWithEmailAndPassword(String email, String pass) async {
try {
AuthResult result =
await _auth.signInWithEmailAndPassword(email: email, password: pass);
FirebaseUser user = result.user;
assert(await user.getIdToken() != null);
print(
"Signed in: ${user.email} , pass: $pass, phone: ${user.phoneNumber}");
/// return the mapped user
return _userFromFireBaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
}
This is the Wrapper that switches between Login and main screen
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final User user = Provider.of<User>(context);
if (user != null) {
return RestaurantOrdersScreen();
} else {
return LoginScreen();
}
}
}
And I've wrapped the main application in a MultiProvider:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
/// PROVIDER FOR AUTHENTICATION
StreamProvider<User>.value(
value: AuthService().user,
),
//...
// some other providers
],
child: MaterialApp(
home: SplashScreen(),
),
);
}
FINALLY FOUND A FIX FOR IT!
For me, it was this split when I was trying to manipulate the displayName. That's why I was getting the index out of range error.
List<String> name = ["", ""];
if (user != null && user.displayName != null) {
name = user.displayName.split(" ");
}
return user != null
? User(
id: 0,
token: user.uid,
firstName: name[0],
lastName: name[1],
...
So, if you get this kind of error, carefully check your User attributes manipulation when trying to convert FirebaseUser into your own custom User object.