Refactoring and Flutter’s BLoC Pattern

Sarah
Pilar 2020
Published in
6 min readNov 17, 2020

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.

— Martin Fowler on Refactoring.com

When we do refactoring, we are improving the design of the code that we refactor. Refactoring can turn a bad design into a better one. Refactoring is quite similar to cleaning up codes, however, refactoring can go beyond that as it provides a more efficient manner. Our code will be easier to understand and less expensive to modify with refactoring.

Why Refactoring?

Based on Martin Fowler’s Refactoring — Improving the Design of Existing Code book, refactoring improves the software design, makes our software easier to understand, helps us find bugs and program faster.

When do we need to refactor?

The term “Rules of Three” by Don Robert applies when we want to do refactoring.

  1. When you’re doing something for the first time, just get it done.
  2. When you’re doing something similar for the second time, cringe at having to repeat but do the same thing anyway.
  3. When you’re doing something for the third time, start refactoring.

Refactoring comes in handy when we want to add a new feature. Sometimes we need to change our existing code first before implementing the feature. For instance, we might find a similar code but we should not justify duplications. Therefore, we need to do some refactoring. Other than that, we can increase the comprehension of our code by refactoring. This can be done by using more descriptive variable names or fixing functions that execute their job badly. Refactoring can also happen during a code review. Our code may seem clear enough to us, but not for our teammates. Others can give suggestions on how to improve our code by refactoring.

Some common code smells that we often encounter are:

  • Mysterious names: use good names when naming variables, classes and functions.
  • Duplicated code: remove duplication from our code.
  • Long parameter list: replace parameters with an object.
  • Large class: Extract classes as classes with too many instance variables can introduce duplications and chaos.

How do our team refactor our code

One of the refactoring methods that we implemented is “remove assignments to parameter”. Here is our code before:

Future<void> createGoodsDonation(String program, String quantity, String description, String address, String methodOfDelivery) async {
methodOfDelivery == 'DLV' ? address = null : address = address;
final formData = FormData.fromMap({
'program': program,
'donation_type': 'GDS',
'goods_quantity': quantity,
'goods_description': description,
'delivery_method': methodOfDelivery,
'delivery_address': address
});
await _dio.post(url, data: formData);
}

It is not the best practice to re-assign parameters, hence a new variable is needed. The deliveryAddress variable stores value from parameter address based on the condition.

Future<void> createGoodsDonation(String program, String quantity, String description, String address, String methodOfDelivery) async {
String deliveryAddress;
methodOfDelivery == 'DLV' ? deliveryAddress = null : deliveryAddress = address;
final formData = FormData.fromMap({
'program': program,
'donation_type': 'GDS',
'goods_quantity': quantity,
'goods_description': description,
'delivery_method': methodOfDelivery,
'delivery_address': deliveryAddress
});
await _dio.post(url, data: formData);
}

When doing my first Flutter project, I wasn’t familiar with Flutter’s state management and design pattern. I used to store logic and other API calls alongside codes for the interface. However, because the previous development team implemented the BLoC state management, I realized that the BLoC pattern has helped me a lot when I want to add new features and functionalities to our PILAR application.

What is BLoC?

BLoC is short for Business Logic Component which is firstly introduced at Google I/O ’18. The BLoC design pattern separates the presentation from business logic. BLoC is designed to meet developers’ needs, such as knowing the state of our application every time, record user interactions to make data-driven decisions, easily test every case and also to work as efficiently as possible. BLoC relies on events to trigger event changes rather than functions to then convert these events to outgoing states. The three core values of BLoC are,

  • Simple: Easy to comprehend and can be used by developers with different skill levels.
  • Powerful: Help to create complex applications composed of smaller components.
  • Testable: Every aspect can be tested easily.
source: https://pub.dev/packages/bloc

BLoC separates our application into three layers, our UI stands for the presentation, BLoC of course is the business logic and last but not least we have the data layer where it consists of repository and data provider. In our project, our backend acts as the data provider.

  • Data Layer: retrieve or manipulate data from sources. Data provider is supposed to provide raw data and repository acts as a wrapper of one or more data providers.
  • Business Logic Layer: respond to input from the presentation layer and emit new states.
  • Presentation Layer: figure out how to render itself based on one or more bloc states. Besides, it should handle user input and application lifecycle events.

Implementing BLoC In Our Project

To use BLoC pattern in Flutter, you have to add a new dependency to your pubspec.yaml file. You can the bloc package here and flutter_bloc package here.

dependencies:
bloc: <latest version of bloc>
flutter_bloc: <latest version of flutter_bloc>

Then, run pub get or flutter pub get. Finally, you can use Flutter in your code by importing it.

import 'package:bloc/bloc.dart';

If you’re using Android Studio, you can use the Bloc plug in to speed up your development. With the Bloc plugin, you can auto-generate BLoCs that will consist of three files, the BLoC itself, the state and event. Here’s an example of the BLoC implementation for our Progress Program PBI:

Repository

Our repository contains a function that will make a GET HTTP request to our API service.

BLoC

This is where our input or events are transformed into states. When the FetchProgressProgram event is invoked, it will invoke the fetchProgressProgram function in our repository. If the request is successful, it will yield a new state, whereas it will yield a different state if an error occurred.

Events

Events can be considered as the input for our BLoC. Events are usually invoked by user actions. Additional data can be passed to the event. In our Progress Program event file, we have an event called FetchProgressProgram. This event will be called when a user clicks a program card, then the details of the program will be showed. When the UI renders, the FetchProgressProgram event will be yield.

State

State is the output of our BLoC. A BLoC always has an initial state. The ProgressProgramLoaded yields when the request succeeds whereas ProgressProgramError yields when an error occurred. (refer to the BLoC code snippet for better visualization).

User Interface

So, how do we handle the state changes in our program? We implemented the BlocProvider widget. It provides a bloc to its children and acts as a dependency injection (DI) widget so that a single instance of a bloc can be provided to a number of widgets within a subtree. When the UI renders, it will instantly call the FetchProgressProgram event, then the BlocBuilder widget will respond to the state changes.

Here’s an example if the FetchProgressProgram event succeeds:

To sum up, the BLoC pattern makes it easier for us to add new functionalities to our application. It is also helps us a lot to track bugs and errors on our code. If you ask, do we recommend the BLoC pattern? Absolutely!

Thanks for reading!

--

--