3

I have an async function signIn in a Dart program that takes username and password string arguments. The function calls a remote server and the server responds with a session token or validation messages in the case of missing or incorrect username and/or password.

Currently I have this implemented with callbacks. The appropriate callback is called after the server responds. No need for await.

signIn(username, password, onSuccess, onFailure);

The more I read about Dart, I feel like the above isn't really the Dart way of doing things. Should I be using await combined with try and catch? Something like the following?

try {
    sessionToken = await signIn(username, password);
    // navigate from the sign in screen to the home screen.
} on InvalidParams catch (e) {
    // e contains the server's validation messages
    // show them to the user.
}

Invalid sign in credentials are likely. Handling them is normal program flow. I was taught never to use try/catch for regular, expected program flow. It seems that the Dart language is encouraging using exception handling for this especially in combination with await.

From Error class documentation [Emphasis mine.]

If the conditions are not detectable before calling a function, the called function should not throw an Error. It may still throw a value, but the caller will have to catch the thrown value, effectively making it an alternative result rather than an error. The thrown object can choose to implement Exception to document that it represents an exceptional, but not erroneous, occurrence, but it has no other effect than documentation.

What's the best most Dart way to implement this?

Ted Henry
  • 1,442
  • 1
  • 14
  • 34
  • Why can't you just a check before calling the method that requests the server, for the username and password are not empty and with required length – Lakhwinder Singh Jul 12 '19 at 06:54
  • I cannot check that the username exists in the server database or that the password is the correct password for the username. Some things cannot be validated in a client. – Ted Henry Jul 12 '19 at 15:14

1 Answers1

6

Error vs. Exception

The documentation you linked essentially says that the Error class should not be used for what you define as "regular, expected program flow" but Exception should. This also means that using try-catch for addressing these kinds of cases is encouraged in Dart.

From the documentation, Error's should be used for "a program failure that the programmer should have avoided", i.e. unexpected program flow. However, Exception's are "intended to convey information to the user about a failure, so that the error can be addressed programmatically", i.e. expected program flow.

In order to implement exceptions, you will have to extend Exception and create your own exception class. Dart enforces this by not providing access to the message passed to a regular Exception.

Creating instances of Exception directly with new Exception("message") is discouraged, and only included as a temporary measure during development, until the actual exceptions used by a library are done.

Example

enum InvalidCredentials { username, password }

class InvalidCredentialsException implements Exception {
  final InvalidCredentials message;

  const InvalidCredentialsException({this.message});
}

function() {
  try {
    await signIn(username, password);
  } on InvalidCredentialsException catch (e) {
    switch (e.message) {
      case InvalidCredential.username:
        // Handle invalid username.
        break;
      case InvalidCredential.password:
        // Handle invalid password.
        break;
    }
  } on Error catch (e) {
    print(e);
  } catch (e) {
    // Handle all other exceptions.
  }
}

I created InvalidCredentialsException to handle invalid credentials passed to signIn. For the message (you can call it whatever you want), I simply used an enum to distinguish between an invalid username and an invalid password (probably not what you would want to do, it should just explain the concept).

When handling it using try-catch, you can create different catch-blocks for different types. In my example, I use on InvalidCredentialsException for responding to the expected exception in your program flow and another one on Error for unexpected failures.

When using on for catch-statements, you run the risk of not catching other types of exceptions, which will then be thrown. If you want to prevent that, you can either have another block for generic exceptions, i.e. on Exception or just have a generic catch-block at the end (catch (e)).

You might want to override toString in your exception class if you want to be able to print out your exception.

Additionally, you can obviously transport as much information with your exception as you like, e.g. (modified from the above code):

// ...
throw InvalidCredentialsException(cause: InvalidCredentials.password, message: password);
// ...

class InvalidCredentialsException implements Exception {
  final InvalidCredentials cause;
  final String message;

  const InvalidCredentialsException({this.cause, this.message});
}
creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
  • Thanks. I tried refactoring my signIn code and associated widget similar to what you've written. It is not less code. It is not really more readable code. It is possibly more robust code. For some reason, it definitely feels much more Dart-like. I don't know if that is justification enough. It is difficult to accept the idea of catch being part of expected program flow. – Ted Henry Jul 12 '19 at 20:32
  • 1
    Instead of `catch (e) {if (e is InvalidCredentialsException) {...} else if (e is Error) {...}}` it looks like the most Dart way to write it would be `on InvalidCredentialsException catch (e) {...} catch (e) {...}` – Ted Henry Jul 12 '19 at 20:33
  • 1
    @TedHenry Yes, thanks. I will include that in the answer. Obviously both ways work, however, what you said **is** the *most Dart way* of doing it. – creativecreatorormaybenot Jul 12 '19 at 20:38
  • 1
    @TedHenry I updated the answer and also explained the risk of not catching some exceptions if they are not include in your `on` statements, however, from what you posted it seems like you handle that correctly. – creativecreatorormaybenot Jul 12 '19 at 20:46
  • 1
    One thing I like about enums and switch in Dart is that Dart can check at compile time if the switch is exhaustive. So `if (e.message == InvalidCredential.username) {...} else if (e.message == InvalidCredential.password) {...}` can be written as `switch (e.message) {case InvalidCredential.username: ... break; case InvalidCredential.password: ... break;}` If `InvalidCredential` is changed to `SignInCredential` then there is no reason to expect that more enumerations will be added in the future. This adds nice compile time checks that all validation cases have been handled. – Ted Henry Jul 12 '19 at 21:18
  • I think `on Error catch (e)` should possibly be `on Exception catch (e)` so it catches all other types of Exceptions as well. Programs are supposed to catch Exception objects. Programs are not necessarily supposed to catch Error objects, according to the documentation. Since I wouldn't want my Flutter app to die when something unexpected happens, I'd use generic `catch (e)` to catch anything at all. – Ted Henry Jul 12 '19 at 21:22
  • 2
    @TedHenry I thought about including it in the original answer, however, the Dart `switch`-syntax is bulky and not too nice for two cases. I agree with you that it would be the *most Dart* way, so I will include it in the answer. – creativecreatorormaybenot Jul 12 '19 at 21:22
  • @TedHenry Just keep in mind that you can `throw` any kind of object in Dart, which means that `on Exception` and `on Error` together **should** catch all failures, however, there might be an exception that does not extend `Exception` even though that is the Dart way of handling it. – creativecreatorormaybenot Jul 12 '19 at 21:26