Sealed Classes

If your like me and you come from a Kotlin/Android background, you will probably be on a similar wave length in missing all the great language features that we have in Kotlin. One of my favorite and probably most used language features is Sealed Classes. I use them a lot. Mainly due to the architecture pattern MVI. MVI is described in my other post and relies on Sealed Classes to wrap Events and Effects, the core foundation of the communication between business logic containers and views. As I am currently in the process of writing a Flutter app, I thought I would try and replicate this type of design pattern as it works great, extremely easy to understand and makes things so obvious to work with. As I tried to write out my first MVI class, I realised one massive issues… we are missing Sealed Classes in Dart! FFS!

via GIPHY

Once I realised that we were missing Sealed Classes my research found a number of solutions, Some of them are pretty great, and some are pretty more. Not to mention a lot of them mean we need to import a library just to get a mimic a popular language feature. As I am not a fan of libraries everywhere (Yes, some are necessary, some are just overkill and leave your apps in the state of depreciated code owners) I thought I would list out some of the top libraries first, then go into how I think is best to approach mimicking Sealed Classes.

We can achieve similar to Sealed Classes with the above libraries, but again, a dependency on a library isn’t always great. Let’s have a look at something we can do in pure Dart. Here is one approach:-

abstract class LoginResponse {
  // Not necessary when case classes are public, 
  // but I usually put factories and constants on the base class.
  
  factory LoginResponse.success(authToken) = LoginSuccess;
  static const invalidCredentials = const InvalidLoginCredentials();
  static const noNetwork = const NoNetworkForLogin();
  factory LoginResponse.unexpectedException(Exception exception) = UnexpectedLoginException;
}

class LoginSuccess implements LoginResponse {
  final authToken;
  const LoginSuccess(this.authToken);
}

class InvalidLoginCredentials implements LoginResponse {
  const InvalidLoginCredentials();
}

class NoNetworkForLogin implements LoginResponse {
  const NoNetworkForLogin();
}

class UnexpectedLoginException implements LoginResponse {
  final Exception exception;
  const UnexpectedLoginException(this.exception);
}

With calling code looking something like:-

void on(LoginResponse loginResponse) {
  if (loginResponse is LoginSuccess) {
    // Dart has smart casting too, so you can use authToken w/o a cast
    loginResponse.authToken;
  } else if (loginResponse is InvalidLoginCredentials) {
    
  } else if (loginResponse is NoNetworkForLogin) {
    
  } else if (loginResponse is UnexpectedLoginException) {
    // Dart has smart casting too, so you can use exception w/o a cast
    loginResponse.exception;
  } else {
    // Dart doesn't have sealed classes
    throw new ArgumentError.value(loginResponse, 'loginResponse', 'Unexpected subtype of LoginResponse');
  }
}

Big shout out to @avilladsen for the above. More can be found here – https://github.com/dart-lang/language/issues/349.

So the above works quite well, it’s not as pretty but works great, another approach is something like this:-

class MyState {
  MyState._();

  factory MyState.success(String foo) = MySuccessState;
  factory MyState.error(String foo) = MyErrorState;
}

class MyErrorState extends MyState {
  MyErrorState(this.msg): super._();

  final String msg;
}

class MySuccessState extends MyState {
  MySuccessState(this.value): super._();

  final String value;
}

Again, we call it in the same manner, and still misses the when statement. Bug shout out to Rémi Rousselet for this one.

Either way we have a couple of ways to replicate the Sealed Classes we love in Kotlin, but it still does raise the question as to wether were going to get the Dart Team to come up with their own approach. Let me know if you have anything better than the above solutions!

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *