This is the 20th day of my participation in the August More Text Challenge. In preparation for the Nuggets’ August challenge, I’m bringing you my dart-related tips from development, the second season of # DART 7 Tips

8. Use named constructors and initialization lists for a more ergonomic API.

Suppose you want to declare a class that represents a temperature value.

You can have your class API explicitly support two constructors named Celsius and Fahrenheit:

class Temperature { Temperature.celsius(this.celsius); Temperature. Fahrenheit (double Fahrenheit) : Celsius = (Fahrenheit - 32) / 1.8; double celsius; }Copy the code

This class requires only one storage variable to represent the temperature and uses an initialization list to convert Fahrenheit to Celsius.

This means you can declare a temperature value like this:

final temp1 = Temperature.celsius(30);
final temp2 = Temperature.fahrenheit(90);
Copy the code

9. Getter and setter

In the class above Temperature, Celsius is declared as a storage variable.

But users may prefer to get or set the temperature in degrees Fahrenheit.

This can be easily done using getters and setters, which allow you to define computed variables. Here’s the updated lesson:

class Temperature { Temperature.celsius(this.celsius); Temperature. Fahrenheit (double Fahrenheit) : Celsius = (Fahrenheit - 32) / 1.8; double celsius; Double get Fahrenheit => Celsius * 1.8 + 32; Set Fahrenheit (double Fahrenheit) => Celsius = (Fahrenheit) / 1.8; }Copy the code

This makes it easy to get or set the temperature using Fahrenheit or Celsius:

final temp1 = Temperature.celsius(30);
print(temp1.fahrenheit);
final temp2 = Temperature.fahrenheit(90);
temp2.celsius = 28;
Copy the code

Bottom line: Use named constructors, getters, and setters to improve class design.

10. Underline unused function arguments

In Flutter, we often use widgets with function parameters. A common example is listView.builder:

class MyListView extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( itemBuilder: (context, index) => ListTile( title: Text('all the same'), ), itemCount: 10, ); }}Copy the code

In this case, we do not use the itemBuilder argument (context, index). So we can replace them with underscores:

ListView.builder(
  itemBuilder: (_, __) => ListTile(
    title: Text('all the same'),
  ),
  itemCount: 10,
)
Copy the code

Note: These two parameters are different (_and__) because they areA separate identifier.

11. Need a class that can only be instantiated once? Use static instance variables with private constructors.

The most important feature of a singleton is that there can be only one instance of it in the entire program. This is useful for modeling things like file systems.

// file_system.dart
class FileSystem {
  FileSystem._();
  static final instance = FileSystem._();
}
Copy the code

To create a singleton in Dart, you can declare a named constructor and make it private using the _ syntax.

You can then use it to create a static final instance of the class.

Therefore, any code in other files can only access this class through the instance variable:

// some_other_file.dart
final fs = FileSystem.instance;
// do something with fs
Copy the code

Note: Final can cause many problems if you are not careful. Make sure you understand their disadvantages before using them.

12. Need to collect unique sets? Use collections instead of lists.

The most common collection type in Dart is List.

But lists can have duplicate items, and sometimes that’s not what we want:

const citiesList = [
  'London',
  'Paris',
  'Rome',
  'London',
];
Copy the code

We can Set using a when we need a unique Set of values (note the use of final) :

// set is final, compiles
final citiesSet = {
  'London',
  'Paris',
  'Rome',
  'London', // Two elements in a set literal shouldn't be equal
};
Copy the code

The code above generates a warning because London is included twice. If we tried to do the same for constSet, we would get an error and our code would not compile:

// set is const, doesn't compile
const citiesSet = {
  'London',
  'Paris',
  'Rome',
  'London', // Two elements in a constant set literal can't be equal
};
Copy the code

When we work with Taiwan, we can get useful apis like Union, difference, and intersection:

citiesSet.union({'Delhi', 'Moscow'});
citiesSet.difference({'London', 'Madrid'});
citiesSet.intersection({'London', 'Berlin'});
Copy the code

Bottom line: When you create a collection, ask yourself if you want its items to be unique, and consider using a collection.

13. How to use try, on, catch, rethrow, finally

Try and catch is ideal when using Future-based apis, which may throw an exception if something goes wrong.

Here’s a complete example of how to make the most of them:

Future<void> printWeather() async { try { final api = WeatherApiClient(); final weather = await api.getWeather('London'); print(weather); } on SocketException catch (_) { print('Could not fetch data. Check your connection.'); } on WeatherApiException catch (e) { print(e.message); } catch (e, st) { print('Error: $e\nStack trace: $st'); rethrow; } finally { print('Done'); }}Copy the code

Some considerations:

  • You can add more than oneonClause to handle different types of exceptions.
  • You can use rollbackcatchClause to handle all exceptions that do not match any of the above types.
  • You can userethrowStatement throws the current exception up the call stack,Keep the stack trace.
  • You can usefinallyinFutureRun some code when you’re done, whether it succeeds or fails.

If you are using or designing future-based apis, be sure to handle exceptions as needed.

14. Common Future constructors

DartFuture class with some convenient factory constructor: Future., of the Future. The value and Future of the error.

We can future.delayed to create a Future that waits for a certain amount of delay. The second argument is an (optional) anonymous function that you can use to complete a value or throw an error:

await Future.delayed(Duration(seconds: 2), () => 'Latte');
Copy the code

But sometimes we want to create a Future that’s done immediately:

await Future.value('Cappuccino');
await Future.error(Exception('Out of milk'));
Copy the code

We can do this successfully with future. value as a value, or future. error as an error.

You can use these constructors to simulate responses from future-based apis. This is useful when writing mock classes in your test code.

15. Generic flow constructor

The Stream class also comes with some handy constructors. Here are the most common:

Stream.fromIterable([1, 2, 3]);
Stream.value(10);
Stream.empty();
Stream.error(Exception('something went wrong'));
Stream.fromFuture(Future.delayed(Duration(seconds: 1), () => 42));
Stream.periodic(Duration(seconds: 1), (index) => index);
Copy the code
  • Used for a list of slave valuesStream.fromIterableTo create aStream.
  • useStream.valueIf you only have one value.
  • Used forStream.emptyCreate an empty stream.
  • Used forStream.errorCreate a stream that contains the error value.
  • Used forStream.fromFutureCreate a flow that contains only one value that will be available on future completion.
  • Used forStream.periodicCreate a periodic stream of events. You can specify aDurationIs the time interval between events, and specifies an anonymous function to generate each value given its index in the stream.

16. Synchronous and asynchronous generators

In Dart, we can define the synchro generator as a function Iterable that returns:

Iterable<int> count(int n) sync* { for (var i = 1; i <= n; i++) { yield i; }}Copy the code

This uses sync* syntax. Inside a function, we can “generate” or yield multiple values. These will be returned by Iterable when the function completes.


An asynchronous generator, on the other hand, is a function Stream that returns a:

Stream<int> countStream(int n) async* { for (var i = 1; i <= n; i++) { yield i; }}Copy the code

This uses this async* syntax. Inside the function, we can yield just as we would in the synchronous case.

But we can use await future-based API if we want, because this is an asynchronous generator:

Stream<int> countStream(int n) async* { for (var i = 1; i <= n; i++) { // dummy delay - this could be a network request await Future.delayed(Duration(seconds: 1)); yield i; }}Copy the code