Introduction to Mixins in Dart

Joshua Hall

Some of the best reasons for using object-oriented programming (OOP) are its techniques for helping to keep our code clean and DRY. We’re going to explore some of Dart’s strategies for making your code reusable over separate classes.

The Problem

Imagine we have two creature classes, each of them with their own set of behaviors. The obvious solution would be to just directly outline the methods for each class as we need them, but many classes aren’t going to be entirely unique and will share a lot of commonality with each other. We obviously want to find the most efficient structure for writing these classes in the most reusable way.

Our animals will have a few specific actions they can perform and a larger behavior composed out of them. As you can see, their diet may be different, but the methods are mostly the same and should ideally be broken out into something more reusable.

main.dart

class Alligator {
  void swim() => print('Swimming');
  void bite() => print('Chomp');
  void crawl() => print('Crawling');
  void hunt() {
    print('Alligator -------');
    swim();
    crawl();
    bite();
    print('Eat Fish');
  }
}

class Crocodile {
  void swim() => print('Swimming');
  void bite() => print('Chomp');
  void crawl() => print('Crawling');
  void hunt() {
    print('Crocodile -------');
    swim();
    crawl();
    bite();
    print('Eat Zebra');
  }
}

main() {
  Crocodile().hunt();
  Alligator().hunt();
}

// Output should be 
// Crocodile -------
// Swimming
// Crawling
// Chomp
// Eat Zebra
// Alligator -------
// Swimming
// Crawling
// Chomp
// Eat Fish

Extensions

The most common option we have is extensions, where we can take the properties and methods on one class and make them available in another. Since we wouldn’t use Reptile in its own instance, we can set it as an abstract class so it can’t be initialized, just extended.

main.dart

abstract class Reptile {
  void bite() => print('Chomp');
  void swim() => print('Swimming');
  void crawl() => print('Crawling');
  void hunt() {
    print('${this.runtimeType} -------');
    swim();
    crawl();
    bite();
  }
}

class Alligator extends Reptile {
    // Alligator Specific stuff...
}

class Crocodile extends Reptile {
    // Crocodile Specific stuff...
}

main() {
  Crocodile().hunt('Zebra');
  Alligator().hunt('Fish');
}

Mixins

That’s nice for our current example, but as we added more animals it would quickly become evident that many of theses methods aren’t just for Reptiles. If we wanted to create a Fish class with a swim method, instead of just extending Reptile, which is very limiting when we need more functionality from other classes, since we can only use extends one per class. We can use mixins to break our more universal behaviors into smaller, more reusable components that we can add to whatever class we need them in.

We just need to use the mixin type to store our methods and use the with keyword on every class we want it included in. Unlike extends, we can add as many mixins as we want to a class.

main.dart

mixin Swim {
  void swim() => print('Swimming');
}

mixin Bite {
  void bite() => print('Chomp');
}

mixin Crawl {
  void crawl() => print('Crawling');
}

abstract class Reptile with Swim, Crawl, Bite {
  void hunt(food) {
    print('${this.runtimeType} -------');
    swim();
    crawl();
    bite();
    print('Eat $food');
  }
}

class Alligator extends Reptile {
  // Alligator Specific stuff...
}

class Crocodile extends Reptile {
  // Crocodile Specific stuff...
}

class Fish with Swim, Bite {
  void feed() {
    print('Fish --------');
    swim();
    bite();
  }
}

main() {
  Crocodile().hunt('Zebra');
  Alligator().hunt('Fish');
  Fish().feed();
}

On

The last trick we have is the ability to do something that I like to think about as a reverse-extension. We can create a mixin that utilizes the methods from a class, which we can then use with each subclass.

If we wanted to break Hunt into its own mixin, we can use the on keyword to tell it that it will only be used on the Reptile class, which will give it access to all of its functionality, like our Swim, Crawl, and Bite mixins.

This configuration should have the exact same output as our first one.

main.dart

mixin Hunt on Reptile {
  void hunt(food) {
    print('${this.runtimeType} -------');
    swim();
    crawl();
    bite();
    print('Eat $food');
  }
}

abstract class Reptile with Swim, Crawl, Bite {}

class Alligator extends Reptile with Hunt {
  // Alligator Specific stuff...
}

class Crocodile extends Reptile with Hunt {
  // Crocodile Specific stuff...
}

Conclusion

With the growing popularity of Flutter, it’s a good idea to get a good foundational understanding of the basics of the Dart programming language. Hopefully this was a helpful introduction into clarifying some of the mysteries around reusing Dart classes.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#