One of the cons that I read most when reading about Flutter is the use of the Dart programming language. It is not as mature as Kotlin is one of the most mentioned arguments I read. In my opinion (and I admit that it can be controversial), Dart is a great language, I wouldn't change it for any other when creating apps with Flutter, and I say it after working professionally creating android apps in Kotlin, which is also an elegant and beautiful language, by the way.
In this article I intend to showcase 4 of my favorites features of the Dart programming language, in no particular order; let's see how can we take advantage of this modern tool:
Null safety
Recently added in version 2.12 (included in Flutter 2.0). Null safety is a must in any modern language that pretends to be solid and productive. Having runtime errors because a variable is null when it isn't intended to be null should be something of the past, that's why the Dart team has been working on implementing sound null safety, which means that we can have types that can be nullable and types that can't be nullable, if we try to perform an unsafe operation on the later we will get a compilation error before the app is built:
String? nullVar;
String nonNullVar = 'I am not null';
late String lateNonNullVar;
if (nullVar != null) {
nonNullVar.toLowerCase();
}
nullVar?.toLowerCase();
nonNullVar.toLowerCase();
lateNonNullVar = 'some value';
lateNonNullVar.toLowerCase();
Async / await
Just like in Javascript we have Promises , in Dart we have Futures ; where the async/await are the main keywords, giving us the developers an easy and powerful way to handle asynchronous operations:
With Futures we can easily stop the flow of the current operation, wait for some asynchronous operation to complete, and then resume the work.
asyncFunction() async {
}
await asyncFunction();
main() async {
await asyncFunction();
}
Future<NetworkResult> doNetworkCall() async {
}
final result = await doNetworkCall();
late NetworkResult result;
try {
result = await doNetworkCall();
} on NetworkException catch (e) {
}
It is important to mention that even using the async/await keywords everything is executed in the same thread, if we need specific requirements for performance we can spawn alternative threads with dart isolates.
Multiple ways to define function parameters
In Dart we have multiple options when defining function parameters:
functionWithMandatoryParameters(String someString, int someNumber) {
}
functionWithMandatoryParameters('some_string', 46);
functionWithOptionalParams(
{String? optionalString, int? optionalNumber}) {
}
functionWithOptionalParams();
functionWithOptionalParams(optionalString: 'some_string');
functionWithOptionalParams(
optionalString: 'some_string', optionalNumber: 46);
functionWithDefaultValue({String someString = 'default'}) {
}
functionWithDefaultValue();
functionWithDefaultValue(someString: 'some_string');
createUser(
{required String username,
required String name,
required String surname,
required String address,
required String city,
required String country}) {
}
createUser(
username: 'Ghost',
name: 'John',
surname: 'Doe',
address: '3590 Mill Street',
city: 'Beaver',
country: 'US');
Composition with mixins
One of the least trendings in software development is composition over inheritance, which means adding functionality to a class using component-like elements instead of inheriting from a parent. This method allows us to easily add encapsulated functionality without dealing with a complex hierarchy of inheritance.
For instance, imagine that you have a login logic that you might want to use in different places within your application. You could create a component (a mixin) with this logic and then reuse it whenever you need it:
abstract class AuthUtils {
Future<User> login() async {
}
}
class LoginPage extends StatefulWidget {
LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> with AuthUtils {
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: login(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
},
);
}
}
The advantage here is that we can add as many mixins as we want, opposing to inheriting from just one parent by using inheritance.
Here is a great post explaining with a lot more details what mixins are about.
Conclusion
These are only 4 of the multiple useful features that Dart offers to developers. If you want to learn more I recommend you to visit the Dart language tour, which explains every detail of the language in a very friendly way.
Happy coding!