Dart: Asynchronous Programming and Generics

In this post, we’ll explore two important advanced features of the Dart language: asynchronous programming and generics. Asynchronous programming, in particular, is a crucial concept in modern application development, so it’s important to first understand how it differs from synchronous programming.

Synchronous vs Asynchronous Programming

Synchronous Programming

In synchronous programming, code is executed sequentially. Each task waits for the previous task to complete before executing. While this is simple and intuitive, it can lead to poor user experience when dealing with time-consuming operations (e.g., file reading, network requests) as the entire program execution gets blocked.

Example:

void main() {
  print('Task 1 starts');
  longRunningTask();  // Program waits here until this task completes
  print('Task 2 starts');
}

void longRunningTask() {
  // Time-consuming operation
}

Asynchronous Programming

In asynchronous programming, time-consuming tasks are sent to the “background”, allowing other code to continue executing. When the task completes, its result is processed. This approach improves program responsiveness and allows for more efficient resource utilization.

Example:

void main() async {
  print('Task 1 starts');
  longRunningTask().then((_) => print('Long task completed'));
  print('Task 2 starts');  // This runs before longRunningTask completes
}

Future<void> longRunningTask() async {
  // Time-consuming operation
}

Benefits of asynchronous programming:

  1. Better performance: Multiple tasks can be processed concurrently.
  2. Improved responsiveness: The user interface doesn’t get blocked.
  3. Efficient resource utilization: CPU and I/O resources can be used more efficiently.

Dart provides keywords like Future, async, and await to easily implement asynchronous programming. Let’s now dive into these concepts in detail.

1. Asynchronous Programming

Future

A Future represents the result of an asynchronous operation.

Future<String> fetchUserOrder() {
  return Future.delayed(Duration(seconds: 2), () => 'Large Latte');
}

void main() {
  print('Fetching user order...');
  fetchUserOrder().then((order) => print('Your order is: $order'));
  print('This runs before the order is fetched!');
}

async and await

The async and await keywords make asynchronous code easier to write.

Future<void> main() async {
  print('Fetching user order...');
  String order = await fetchUserOrder();
  print('Your order is: $order');
}

Stream

A Stream represents a sequence of asynchronous events.

Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}

void main() async {
  Stream<int> stream = countStream(5);
  await for (int number in stream) {
    print(number);
  }
}

2. Generics

Generics allow you to write code that can work with different types.

Generic Classes

class Box<T> {
  T value;

  Box(this.value);

  T getValue() => value;
  void setValue(T value) => this.value = value;
}

void main() {
  var intBox = Box<int>(42);
  var stringBox = Box<String>('Hello, Dart!');

  print(intBox.getValue());     // Output: 42
  print(stringBox.getValue());  // Output: Hello, Dart!
}

Generic Functions

T first<T>(List<T> list) {
  if (list.isEmpty) throw Exception('List is empty');
  return list[0];
}

void main() {
  var numbers = [1, 2, 3, 4, 5];
  var strings = ['one', 'two', 'three'];

  print(first(numbers));  // Output: 1
  print(first(strings));  // Output: one
}

Generic Constraints

You can restrict generic types to a specific type or its subtypes.

class Animal {
  void makeSound() => print('Some generic animal sound');
}

class Dog extends Animal {
  @override
  void makeSound() => print('Woof!');
}

void makeAnimalSound<T extends Animal>(T animal) {
  animal.makeSound();
}

void main() {
  var animal = Animal();
  var dog = Dog();

  makeAnimalSound(animal);  // Output: Some generic animal sound
  makeAnimalSound(dog);     // Output: Woof!
}

Conclusion

In this post, we’ve covered Dart’s advanced features: asynchronous programming and generics. These features greatly enhance code efficiency and reusability when developing complex applications.

In our next post, we’ll explore how to start mobile app development with Flutter using Dart.

If you have any questions about Dart, feel free to leave a comment below!

Related Resources

For more detailed information, check out these official websites:

Leave a Reply

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