Using Impurities in Flutter Applications





I work in a game development company, but as a home hobby, I have recently become interested in developing mobile applications. Therefore, when a friend invited me to a meeting dedicated to the development of mobile applications using the Flutter framework, I agreed with pleasure. Having tried Flutter in action there, I decided to definitely study this technology. Since Dart, necessary for development, was unfamiliar to me, language learning was also included in the compulsory program. After sitting a bit over the code examples, I found Dart an easy-to-understand and concise language that I really liked. One of the features of Dart that I liked is the impurities.



What are impurities?



For an initial acquaintance, I will give an excerpt from Wikipedia .

Admixture (English mix in) is an element of a programming language (usually a class or module) that implements some clearly defined behavior. It is used to clarify the behavior of other classes; it is not intended to generate independently used objects.
In Dart, such constructs are defined by the word mixin before the name.



The above definition means that we get the functionality of logically isolated behaviors that can be added to other classes.



Does it remind you of the possibility of multiple inheritance? Yes, but it seems to me that the impurity approach is better. And why, let's look at an example.



Suppose we have an abstract class Animal.



abstract class Animal { void voice(); }
      
      





And also the Cat and Dog classes that implement the Animal class.



 class Cat extends Animal { void voice() { print(“Meow”); } } class Dog extends Animal { void voice() { print(“Woof”); } }
      
      





And then we suddenly needed ...
catdog



Yes, yes, I personally do not have such things during development.



And in the case of multiple inheritance, we would do the same.



 class CatDog extends Cat, Dog { }
      
      





But as soon as we voice our voice command to our pet, we get a very unpleasant situation - it is not clear what exactly he should answer, because the voice method is implemented in both classes. This situation is widely known and is called the diamond problem or Deadly Diamond of Death .



Deadly diamond of death






In the case of sales through impurities, we will not encounter it.



 lass Animal { void voice() { print(“Hakuna Matata!”); } } mixin Cat { void voice() { print(“Meow”); } } mixin Dog { void voice() { print(“Woof”); } } class CatDog extends Animal with Cat, Dog { }
      
      





And what will we hear if we now give a voice command? In this case - Woof, and as you already understood, it depends on the order of addition of impurities. This happens because adding them is not parallel, but sequential.



I implemented the Animal class specifically to mark a feature introduced in Dart 2.1 . Before it, impurities could only be added to classes inheriting from Object . Starting with version 2.1, the addition of any classes to the heirs is implemented.



This mechanism makes it very convenient to make and use common parts of the functional, which solves the problem of code duplication. Consider an example.



 abstract class Sportsman { void readySteadyGo(); } mixin SkiRunner { void run() { print(“Ski, ski, ski”); } } mixin RifleShooter { void shot() { print(“Pew, pew, pew”); } } class Shooter() extends Sportsman with RifleShooter { void readySteadyGo() { shot(); } } class Skier() extends Sportsman with SkiRunner { void readySteadyGo() { run(); } } class Biathlete() extends Sportsman with SkiRunner, RifleShooter { void readySteadyGo() { run(); shot(); } }
      
      





As you can see, we distributed all the duplicate code by impurities and only used the necessary ones in each of the implementations.



During development, a situation may arise when the functionality of any of the impurities should not be publicly available for inclusion by all classes. And a mechanism that will allow us to impose these restrictions is also available. This is the on keyword in the admixture declaration along with the class name. So we will limit the use of impurities only to classes that implement the specified or inherited from it.



For example:



 class A { } abstract class B { } mixin M1 on A { } mixin M2 on B { }
      
      





Then we can declare similar classes:



 class C extends A with M1 { } class D implements B with M2 { }
      
      





But we get an error trying to declare something like this:

 class E with M1, M2 { }
      
      





Usage in Flutter Application



As I mentioned above, impurities allow you to get rid of duplication of code and make separate logical parts that can be reused. But how is this generally applicable to Flutter, where everything is atomic and so divided into widgets that are responsible for a certain functionality? As an example, I immediately imagined a situation in which the project uses many widgets, the display of which changes depending on a certain internal state. I will consider this example in the BLoC architecture and use entities from the rxDart library.



We need an interface to close the flow controller.



 /// Interface for disposable objects abstract class Disposable { void dispose(); }
      
      





An admixture with which we implement state support.



 /// Mixin for object which support state mixin StateProvider implements Disposable { static const NONE_STATE = "None"; final _stateController = BehaviorSubject<String>(seedValue: NONE_STATE); Observable<String> get stateOut => _stateController.stream; String get currentState => _stateController.value; void setState(String state) { _stateController.sink.add(state); } @override void dispose() { _stateController.close(); } }
      
      





The logical part that will control the state of the widget. Let her set a state by calling the method and after 3 seconds change it to another.



 /// Example BLoC class ExampleBloc implements Disposable with StateProvider { static const EXAMPLE_STATE_1 = "EX1"; static const EXAMPLE_STATE_2 = "EX2"; Timer _timer; void init() { setState(EXAMPLE_STATE_1); _timer = new Timer(const Duration(seconds: 3), () { _timer = null; setState(EXAMPLE_STATE_2); }); } @override void dispose() { if (_timer != null) { _timer.cancel(); _timer = null; } } }
      
      





And the widget itself that will respond to a state change. Imagine obtaining the desired logical component using Dependency Injection.



 class ExampleWidget extends StatelessWidget { final bloc = di<HomePageBloc>(); @override Widget build(BuildContext context) { return StreamBuilder( stream: bloc.stateOut, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // build widget by state }, ); } }
      
      





If desired, we can even add to the mix what is written and only require the implementation of the builder method using the interface, but it seems to me that this is already unnecessary, because it is unlikely that there will be many such simple widgets in the project, because it was just an example. Nevertheless, the logical part of the state support functional will be easily added to any of the BLoCs using this admixture.



Conclusion



The mechanism of using impurities seemed to me a rather interesting and flexible development tool, which makes it possible to build a simple, understandable and convenient architecture. Personally, I decided that this tool in my kit obviously will not be superfluous, I hope that it will be useful to you too.



Resources:

A tour of the dart language

Wikipedia



All Articles