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.
Related
I'm a beginner developer and I have problem with implementation of BloC framework. Let's assume that I have this code (Model, NetworkService, Repository, Cubit, State, Widget):
class NetworkService {
Future getData(Uri uri) async {
try {
http.Response httpsResponse = await http.get(
uri,
headers: {
// some headers //
},
);
if (httpsResponse.statusCode == 200) {
return httpsResponse.body;
} else {
throw 'Request failed with status: ${httpsResponse.statusCode}';
}
} catch (e) {
// What I shloud return here?
return e.toString();
}
}
Future<List<dynamic>> fetchData() async {
final uri = Uri.parse('some url');
var data = await getData(uri);
return = jsonDecode(data) as List;
}
}
class Repository {
final NetworkService networkService = NetworkService();
Future<List<SomeObject>> fetchDataList() async {
final dataRaw =
await networkService.fetchDataList();
return dataRaw.map((e) => SomeObject.fromJson(e)).toList();
}
}
class SomeCubit extends Cubit<CubitState> {
final Repository repository;
SomeCubit(this.repository) : super(LoadingState()) {
fetchDataList();
}
void fetchDataList() {
try {
repository
.fetchDataList()
.then((dataList) => emit(LoadedState(dataList)));
} catch (e) {
// What I shloud return here?
emit(ErrorState(e.toString()));
}
}
}
How to make this code "bullet proof" because I don't know how to "pass" error from NetworkService to Cubit? It works fine till I have dynamic responses in functions but in Repository class I want to return List of specific objects and when function fail I will return null. If I write try/catch I have to provide return statement in catch block - and I can't return List. I want to return some kind of Error...
I suggest that you use the excellent class named Either from the dartz package. It will allow you to return X if things went bad, and return Y if all is well, as such: Future<Either<X, Y>>
Then you can check on your variable (e.g. result) as follows: result.isLeft() for error, or do result.fold( ... ) to easily handle the return type (error or success).
In your particular case you could do as follows when returning from the repository to the cubit:
Future<Either<RepositoryError, List<SomeObject>>> fetchDataList() async { ... }
Where RepositoryError could be a class containing information about the type of error.
So in the cubit you do:
final result = await repository.fetchDataList();
emit(
result.fold(
(error) => ErrorState(error),
(dataList) => LoadedState(dataList)
)
);
Then you continue with this pattern all the way to NetworkService getData(). Either with the same common "error class" in the Repository and the NetworkService, or separate ones in the different layers and you "translate" between different "error classes". Perhaps it makes sense to have a NetworkServiceError that is returned there..
In your NetworkService you could do as follows:
Future<Either<NetworkServiceError, String>> getData(Uri uri) async { ... }
Future<Either<NetworkServiceError, List<dynamic>>> fetchData() async { ... }
This will give you great flexibility and passing of information from the service, to the repository and to the cubit.
You can let exceptions propagate through Futures from NetworkService up to the cubit, by removing the try/catch from getData.
I'm a new flutter user here, so this time I have a problem when I login with API SERVICE, the login says
_CastError (Null check operator used on a null value)
the error is in the use of the source as below
Future<void> login(BuildContext context) async {
AppFocus.unfocus(context);
if (loginFormKey.currentState!.validate()) {
final LoginResponse? res = await apiRepository.login(
LoginRequest(
email: loginEmailController.text,
password: loginPasswordController.text,
),
);
final SharedPreferences prefs = Get.find<SharedPreferences>();
if (res!.accessToken.isNotEmpty) {
prefs.setString(StorageConstants.token, res.accessToken);
Get.toNamed(Routes.HOME);
Get.snackbar("Berhasil", "Selamat Berhasil Login");
} else {
Get.snackbar("title", "coab");
}
}
}
exactly the error appears in this section
if (res!.accessToken.isNotEmpty)
final LoginResponse? res
you declare with ? which means res is nullable value.
but if (res!.accessToken.isNotEmpty) you use ! which means, res is non-null value. thats throw error
try to remove the ! mark.
if (res?.accessToken.isNotEmpty) {
... rest of your code
or add new contidion
if( res != null) {
if (res!.accessToken.isNotEmpty){
... rest of your code
}
}
hope it solve your issue. feel free to comment if any error occurred
Try this code and test it.
Future<void> login(BuildContext context) async {
if (loginFormKey.currentState?.validate() ?? false) {
/*
your API code
*/
if (res.accessToken?.isNotEmpty ?? false) {
/*
if accessToken found
*/
} else {
/*
if accessToken not found
*/
}
}
return Future.value();
}
Here's my program (you can run it as a dart file):
/// Fetches data from TestRepository and shows result
main () async {
final repository = TestRepository();
final resource = await repository.fetchString();
if (resource.isError) {
print("| Error: ${resource.error}");
} else {
print("| Success: ${resource.data}");
}
}
/// A class to get Success or Error responses
class Resource<S> {
final S? _data;
final String? _message;
Resource.success(S data)
: _data = data,
_message = null;
Resource.error(String message)
: _data = null,
_message = message;
bool get isError => _message != null;
S get data => _data!;
String get error => _message!;
}
/// A sample repository class to use API and handle errors
class TestRepository {
Future<Resource<String>> fetchString() {
return _dummyApiCall().useErrorHandler();
}
/// Dummy API Call mimicking
Future<Resource<String>> _dummyApiCall() async {
await Future.delayed(const Duration(seconds: 1));
if (true) {
// to mimic an exception so that we can catch it
throw Exception("Error Occurred");
}
return Resource.success("Hello World");
}
}
/// An extension to wrap Resource Futures with error handler
extension FutureResourceExt<T extends Resource> on Future<T> {
Future<T> useErrorHandler() {
return onError((error, stacktrace) => handleError(error));
}
}
/// A function to handle errors thrown by Future.onError
T handleError<T extends Resource>(dynamic error) {
return Resource.error("Error: $error");
}
I have a code error on following line:
return Resource.error("Error: $error");
The error says:
A value of type 'Resource<dynamic>' can't be returned from the function 'handleError' because it has a return type of 'T'
If I change the implementation to add as T to above statement, code error disappears and gets thrown on runtime.
Unhandled Exception: type 'Resource<dynamic>' is not a subtype of type 'Resource<SomeDataType>' in type cast
I don't know why can I not assign Resource to T extends Resource return type.
How should I implement Resource class such that I can do this without knowing S type of Resource?
Here's the line with error code:
return Resource.error("Error: $error");
Here's the solution:
return Resource<Never>.error("Error: $error") as T;
I received a hint from this nice answer. Never knew something like Never existed.
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 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.