Implementation

If you have read the last article, you should already be familiar with the problem that could be resolved by using the factory design pattern. If not, here is a short overview of the problem we will fix:

Even though you are using the same code base with Flutter, usually there is a demand that UI components should look different on different platforms. The simplest imaginable use case in Flutter context — showing the Material or Cupertino style widgets based on whether you are using accordingly an Android or iOS device.

Last time, we have resolved this issue by introducing the Factory Method design pattern to our code and providing a separate factory for each platform-specific component which creates the required widget dynamically after checking the current platform, hence separating the business logic from the representation layer (UI). However, this approach becomes a tremendous headache when multiple components exist which should look different on distinct platforms. Based on the Factory Method design pattern, every component should have a dedicated factory class to it and e.g. if you are creating an application for Android, iOS and Web, every new component would also require to add a new abstract class and 3 extra derived classes for the implementation in each specific platform.

Having the said problems in mind, the Abstract Factory design pattern is a better option than the Factory Method since only a single factory is needed per platform, a family of components are created and used together.

Class diagram

The class diagram below shows the implementation of the Abstract Factory design pattern.

Class Diagram — Implementation of the Abstract Factory design pattern

IWidgetsFactory is an abstract class which is used as an interface for all the specific widget factories:

getTitle() — an abstract method which returns the title of the factory. Used in the UI;

createActivityIndicator() — an abstract method which returns the specific implementation (UI component/widget) of the activity (process) indicator implementing the IActivityIndicator interface;

createSlider() — an abstract method which returns the specific implementation (UI component/widget) of the slider implementing the ISlider interface;

createSwitch() — an abstract method which returns the specific implementation (UI component/widget) of the switch button implementing the ISwitch interface.

MaterialWidgetsFactory and CupertinoWidgetsFactory are concrete classes which implement the IWidgetsFactory class and its methods. MaterialWidgetsFactory creates Material style components (widgets) while the CupertinoWidgetsFactory creates Cupertino style widgets.

IActivityIndicator, ISlider and ISwitch are abstract classes which define the render() method for each component. These classes are implemented by both — Material and Cupertino — widgets.

AndroidActivityIndicator, AndroidSlider and AndroidSwitch are concrete implementations of the Material widgets implementing the render() method of corresponding interfaces.

IosActivityIndicator, IosSlider and IosSwitch are concrete implementations of the Cupertino widgets implementing the render() method of corresponding interfaces.

AbstractFactoryExample contains a list of factories implementing the IWidgetsFactory interface. After selecting the specific factory, the example widget uses its methods to create the corresponding widgets/UI components.

IWidgetsFactory

An interface which defines methods to be implemented by the specific factory classes. These methods are used to create components (widgets) of the specific type defined by the concrete factory. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.

Widget factories

MaterialWidgetsFactory — a concrete factory class which implements the IWidgetsFactory interface and its methods creating the Material style widgets.

CupertinoWidgetsFactory — a concrete factory class which implements the IWidgetsFactory interface and its methods creating the Cupertino style widgets.

IActivityIndicator

An interface which defines the render() method to render the activity indicator component (widget).

Activity indicator widgets

AndroidActivityIndicator — a specific implementation of the activity indicator component returning the Material style widget CircularProgressIndicator.

IosActivityIndicator — a specific implementation of the activity indicator component returning the Cupertino style widget CupertinoActivityIndicator.

ISlider

An interface which defines the render() method to render the slider component (widget).

Slider widgets

AndroidSlider — a specific implementation of the slider component returning the Material style widget Slider.

IosSlider — a specific implementation of the slider component returning the Cupertino style widget CupertinoSlider.

ISwitch

An interface which defines the render() method to render the switch component (widget).

Switch widgets

AndroidSwitch — a specific implementation of the switch button component returning the Material style widget Switch.

IosSwitch — a specific implementation of the switch button component returning the Cupertino style widget CupertinoSwitch.

Example

First of all, a markdown file is prepared and provided as a pattern’s description:

AbstractFactoryExample contains a list of IWidgetsFactory objects (factories). After selecting the specific factory from the list, corresponding widgets are created using the factory methods and provided to the UI.

As you can see in the build() method, the example widget does not care about the selected concrete factory as long as it implements the IWidgetsFactory interface which methods return components implementing the corresponding common interfaces among all the factories and providing the render() methods used in the UI. Also, the implementation of the specific widgets is encapsulated and defined in separate widget classes implementing the render() method. Hence, the UI logic is not tightly coupled to any factory or component class which implementation details could be changed independently without affecting the implementation of the UI itself.

As you can see in the example, by selecting the specific platform widgets option, appropriate widgets are created by the factory methods and provided to the user.

All of the code changes for the Abstract Factory design pattern and its example implementation could be found here.