Sunday, May 26, 2019

Flutter - how to architect projects with BLOC pattern.

Today, whenever we start a new project. We have to focus on building a solid structure or architecture of the project. An application leads readable, maintainable and testable if you have made a clean and good architecture of an application.
Flutter framework provides us a lot of flexibility to organize and architect applications code-base. So, if you not following the proper and good architecture of the project. It may be lead to applications with large classes, inconsistent naming schemes, scary architectures.

In this post, we going to explain all the aspects of the BLOC architecture with a Flutter application example. So, you'll be able to create a maintainable, scalable and easily testable Flutter project. To demonstrate the implementation of BlOC, we'll make one network API request to get some country list and we'll display it in the ListView. The final output of example will look like below:





So, before start development of this example. Let's try to understand all important aspect of BLOC pattern.


Introduction of BLoC pattern
The Business Logic Components (BLOC) is a design pattern that is presented by Google developers Paolo Soares and Cong Hui at
Dart Conference 2018. 
The above diagram shows, how the data flow from user interface to the Data layer and vice versa. The BLOC will never have any reference of the widgets in the user interface Screen. The user interface screen will only observe changes coming from BLOC class. In general terms, data will be flowing from the BLOC to the UI or from UI to the BLOC in the form of streams. The BLoC pattern allows for a separation of the business logic of an application from the user interface by using streams and sinks.
  • Streams:- A stream is a continuous flow or succession of asynchronous events. Let’s take an example of pipe has two ends that containing some liquid. We can pass (input) from one end and then it is processed. It gets out from another end (output). such as a value, an event, an object, a collection, a map, an error or even another Stream.
  • Sink:-The property which takes input. We can update values of the stream and put multiple data values into a sink and when no more data is available.
We build all of our business logic with these streams and sink which allows us to use reactive libraries like RxDart. The streams and sink are provided by a StreamController.
  • StreamController:- We use properties of the widget as a Stream which will change in run time using StreamController. When you define a listener, you receive a StreamSubscription object. This is via that StreamSubscription object that you will be notified that something happens at the level of the Stream. As soon as there is at least one active listener, the Stream starts generating events to notify the active StreamSubscription object.
  • StreamBuilder:- acts as an observable eye which keeps on listening for these changes and updates the screen accordingly. It’s a method that takes stream as an input and provides us with a builder which rebuilds every time there is a new value of a stream 
  • StreamTransformer:- To control the processing of the data inside a Stream, we have to use StreamTransformer. It may be used to do any type of processing, such as:
    •  filtering: to filter the data based on any type of condition.
    •  modification: to apply any type of modification to the data.
    •  inject data to other streams,
So, that all to implement BLoC architecture in the Flutter. Let's create a small application to understand the flow of the stream.

Creating a new Project

1. Create a new project from File New Flutter Project with your development IDE.

2. We’re going to use the following package to create BLoC architecture based application. You have to add it as a dependency to your pubspec.yaml file as follows:
 rxdart: ^0.18.0
 http: ^0.12.0+1
 flutter_svg: ^0.5.0


now create few new packages blocs, models, resources and ui as shown in the below diagram:


blocs package will hold our BLOC implementation related files. 
models package will hold the PODO class or the model class of the JSON response that we'll be getting from the server. 
resources package will hold the repository class and the network call implemented the class. 
ui package will hold our screens that will be visible to the user.
3. After that open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc_architechture/src/ui/country_list.dart'; void main() { runApp(App()); } class App extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primaryColor: Color(0xFF02BB9F), primaryColorDark: Color(0xFF167F67), accentColor: Color(0xFF167F67), ), home: Scaffold( body: CountryList(), ), ); } }
4. Now create another country_list.dart file for display list of countries.   

country_list.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc_architechture/src/models/country_response.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../blocs/country_bloc.dart'; class CountryList extends StatefulWidget { @override State<StatefulWidget> createState() { return CountryListState(); } } class CountryListState extends State<CountryList> { @override void initState() { super.initState(); bloc.fetchCountries(); } @override void dispose() { bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('BLOC Architechture',style: TextStyle(color: Colors.white),), ), body: StreamBuilder( stream: bloc.allCountries, builder: (context, AsyncSnapshot<CountryResponse> snapshot) { if (snapshot.hasData) { return buildList(snapshot); } else if (snapshot.hasError) { return Text(snapshot.error.toString()); } return Center(child: CircularProgressIndicator()); }, ), ); } Widget buildList(AsyncSnapshot<CountryResponse> snapshot) { return GridView.builder( itemCount: snapshot.data.countries.length, gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2), itemBuilder: (BuildContext context, int index) { return getStructuredGridCell(snapshot.data.countries[index]); }); } Card getStructuredGridCell(Country country) { return Card( elevation: 1.0, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, verticalDirection: VerticalDirection.down, children: <Widget>[ SvgPicture.network( country.flag.replaceAll('https', 'http'), height: 130.0, width: 100.0, placeholderBuilder: (BuildContext context) => Container( child: CircularProgressIndicator(), padding: EdgeInsets.all(70), ), ), Padding( padding: EdgeInsets.only(left: 10.0), child: Text(country.name), ) ], )); } }
to get the country list from API, we have called bloc.fetchCountries() method in this initState of the widget and we have implemented bloc.allCountries method for display it as shown above.  


5.
Here, We going to write some complicated part of this example. We going to implement bloc logic. Let’s create a new file inside the blocs package and name it as country_bloc.dart

country_bloc.dart
import 'package:flutter_bloc_architechture/src/models/country_response.dart'; import 'package:rxdart/rxdart.dart'; import '../resources/repository.dart'; class CountryBloc { final _repository = Repository(); final _countriesFetcher = PublishSubject<CountryResponse>(); Observable<CountryResponse> get allCountries => _countriesFetcher.stream; fetchCountries() async { CountryResponse itemModel = await _repository.fetchCountries(); _countriesFetcher.sink.add(itemModel); } dispose() { _countriesFetcher.close(); } } final bloc = CountryBloc();
We have imported a package import package:rxdart/rxdart.dart which will eventually import all the RxDart related methods and classes in this file. Inside the above class, we are creating the repository class object which will be used to access the fetchCountries() method. We are creating a PublishSubject object whose responsibility is to add the data which it got from the server in the form of model object and pass it to the user interface screen as a stream. To pass the model object as stream we have created another method allCountries() whose return type is Observable. We have created a single instance of the CountryBloc class to the UI screen.  

6. Now, lets write some code for get list of countries from the server. Create a file inside the resources package and name it as counrty_api_provider.dart:
country_api_provider.dart
import 'dart:async'; import 'dart:convert'; import 'package:flutter_bloc_architechture/src/models/country_response.dart'; import 'package:http/http.dart' show Client; class CountryApiProvider { Client client = Client(); Future<CountryResponse> fetchCountryList() async { final response = await client.get("http://restcountries.eu/rest/v2/all"); if (response.statusCode == 200) { // If the call to the server was successful, parse the JSON return CountryResponse.fromJson(json.decode(response.body)); } else { // If that call was not successful, throw an error. throw Exception('Failed to load post'); } } }
we have created fetchCountryList() method to perform network process.  


7.
Next, we are going to create a new file inside the resources package and name it as repository.dart.

repository.dart
import 'dart:async'; import 'package:flutter_bloc_architechture/src/models/country_response.dart'; import 'country_api_provider.dart'; class Repository { final moviesApiProvider = CountryApiProvider(); Future<CountryResponse> fetchCountries() => moviesApiProvider.fetchCountryList(); }
above file contains methods that we calling in the country_bloc.dart.   
8. Let’s build a PODO class for the network response. Create a new file inside the models package and name it as country_response.dart.

country_response.dart
class CountryResponse { List<Country> countries; CountryResponse({this.countries}); CountryResponse.fromJson(dynamic json) { countries = new List<Country>(); json.forEach((v) { countries.add(new Country.fromJson(v)); }); } } class Country { String name; String flag; Country({ this.name, this.flag, }); Country.fromJson(Map<String, dynamic> json) { name = json['name']; flag = json['flag']; } }
  
As I told you, our CountryBloc class is passing the new data as a stream. So to deal with streams we have a inbuilt class StreamBuilder which will listen to the incoming streams and update the UI accordingly. The StreamBuilder is expecting a stream parameter where we are passing the CountryBloc’s allCountries() method as it is returning a stream. So the moment there is a stream of data coming, StreamBuilder will re-render the widget with the latest data. Here the snapshot data is holding the model object. We using a GridView to display all the countries that are there in the results list.

Listview custom filter example source code               

If you have followed the article carefully, you can see the app running very smoothly. But if you are facing any problem, please feel free to ask from comments.

 
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...