Flutter Calendar Using BLoC Architecture
Flutter has been an awesome experience from day one. Building good looking UI had never been faster. It’s easy to fall in love with Flutter, whether you’re a beginner or an experienced developer. I was all happy and satisfied, until one day, when I came across something called State Management.
When developing real world apps, things go beyond just UI. There is loads of data involved, which adds to the complexity of the app. As the Widget Tree grows, it becomes difficult to manage the state of app. That’s where the concept of State Management comes into picture.
Any examples?
Sure! Take an example of a social media app. When you hit the like button, the number of likes changes, then the post is stored to another page of liked posts. In this case, both the widgets react differently to the same event, i.e, hitting the like button. Widget is being rebuilt in reaction to data that’s outside the widget class. State Management is the technique/practice of managing this data-widget interaction — which widget has access to what data and how different widgets react to it. That’s it!
Let’s Start Building!
We are going to build an app that’ll use a very effective State Management solution — BLoC Architecture. It’s a pretty basic calendar app that can select and return two different dates, like you see in most ‘booking’ apps.
So, let me first introduce you to BLoC, short for Business Logic Component. It’s the logical part of your application, all the data and functions go inside the bloc class. As said earlier, there are practices and not a hard method to implement BLoC. There are packages like bloc, provider, etc., that make things easier, but we’ll use Stream, Sink and StreamBuilder for implementing BLoC. This method may feel complex, but once you get it, everything else is a piece of cake. It’s a good starting point if you want to have a deep understanding of what’s actually going on.
Imagine a pipe connected to a sink at one end to receive water and other end is letting the flow out. Now, just replace water with a stream of data. You add the data through sink, you receive the data from stream. That’s the whole concept.
Now let’s see the thing in action, shall we?
Forming the base
Go to the lib folder and create the following files:
HomePage.dart
CalendarPage.dart
Calendar.dart
CalendarBloc.dart
Create a StatefulWidget in HomePage and CalendarPage.
We’ll leave them just here, for now.
In Calendar.dart, create some variables that we’ll need for the calendar:
Instantiate them with a constructor:
Now, let’s create some setters and getters to access these variables from our BloC class:
The 7 Great To-Do’s
These 7 steps form the basis of BLoC implementation, and remain mostly the same, no matter what you’re building.
- First things first, do all the necessary imports!
Dart async library provides us with all the stream and future methods and enable asynchronous programming.
2. Then, we create a list of dates, basically check-in and check-out dates. Also a getter for _calendarList
, just like this:
We use the DateTime
class to generate the day, month, year, etc.
3. Now, we finally use the StreamControllers. We build a StreamController
for everything, i.e, _calendarList
, startDate
and endDate
, like this:
broadcast
is used to enable multiple widgets access the data from the same stream. In our case, it’s the CalendarPage and HomePage widgets. By default, only one widget can listen to the changes from one stream.
4. Once again, we create getters for those streams and sinks.
Sink is written as StreamSink
by syntax. So, don’t confuse yourself. ;)
5. Now, we develop the actual logic by creating these methods.
These basically add the date passed through parameter into the _calendarList
.
6. Then, we create a constructor where we’ll sink in the data, i.e, _calendarList
and listen to the changes made by the methods. Essentially, whenever start and end dates in _calendarList
are changed, the constructor puts the new _calendarList
into the data pipe, and at the other end, the UI reacts to this change, displaying the changed dates.
Using _internal
is a part to create singletons which we’ll discuss below. By using listen
, widget gets to know what has changed. We put an event inside those brackets, on which those changes depend on. Like, if we have mentioned _selectStartDate
, it means that the controller only cares about the changes made by this method, and nothing else.
7. Lastly, we create the dispose
method to close all the streams. This is super important for the stability of app as it prevents any kind of data leakage.
Last few things in CalendarBloc
To access CalendarBloc class with a single instance from different classes, we’ll need to create something called as Singleton. If we don’t do it, every time we create a new instance, a separate data pipe is formed and widgets are not able to listen to the data changes.
First, we create the private instance _calendarBloc
.
Then, we use the factory
constructor to return _calendarBloc
.
This may not be the best way, but it’s sufficient for our use case.
Now that our bloc is accessible, let’s create some variables which we’ll use later:
calendarPageIndex
is the current index of PageView
that we’ll use to create months in CalendarPage class. flag
is to toggle between check-in date and check-out date while selection.
Moving towards CalendarPage
Here comes the fun part! Let’s build the UI of our calendar. We’ll start by creating some variables:
First 4 are used to retrieve the current date. _monthSet1
and _monthSet2
are months with 31 and 30 days, respectively. Finally, we create an instance of CalendarBloc class to access the streams, methods, etc.
We’ll be using GridView
to arrange the dates in our calendar.
crossAxisCount
is 7 as per days of week and 42 is the upper limit of number of grid tiles.
I am putting the month name like a heading at the top and then week days below that, arranging the whole thing in a Column
. You are free to customize your calendar however you want. ;)
Flexible is used to place the GridView
comfortably inside the Column
, else it’ll give some nasty errors. primary is made false to make the widget un-scrollable.
Neat and clean!
_date
method is where we retrieve all the dates in the calendar by knowing just the current date.
_firstDay
is the weekday of the first date of the month. Here, index
represents the day.
Below code makes numbered tiles different from blank tiles. It also turns the past dates of current month grey by returning _unavailableDates
method.
Finally, we’ll use the much awaited StreamBuilder. Let’s create the _availableDates
method and return a StreamBuilder
of type <List<Calendar>>
(or simply, a list of Calendar objects).
It has a parameter initialData
which is the data a widget possesses before it gets anything from the stream
. Second is the stream
itself, and that’s calendarListStream
from CalendarBloc
, in our case. Lastly, the builder that takes two parameters, the context (because obviously!) and an AsyncSnapshot
.
Umm, what’s that again? AsyncSnapshot
or simply snapshot
is something that stores the data from the stream for us so that we can use it anywhere in the widget easily. Take it like an actual snapshot, where you click the picture of data from time to time and show it to your widget friends. It’s of the same type of our StreamBuilder
.
Let’s get going! In our app, we should be able to select any two dates, and check-in and check-out dates should be differentiated correctly. For same month, check-in date should be smaller than check-out date. For different months, date with smaller month should be the check-in date. In case of different years, we’ll have to check the difference in years. Huh! Now that means a ton of if-else statements!
_calendarBloc.calendarStartDate.add(data)
basically adds the data to the stream
. Same is the case with endDate
. You can take it as if you’re passing parameters to _selectStartDate()
and _selectEndDate()
methods of CalendarBloc class, respectively. In this case, we are adding Calendar
constructor with the parameters of selected date.
Simply put, it’s the date, we’re adding the date to those methods, which then add that date to _calendarList
and it’s finally selected. Phew!
Time to add some beauty to those tiles! And make them do something.
snapshot.data
let’s us use the data from the snapshot
, as said earlier. The conditions in the gradient mean that if any available date is tapped, it should change color from white to a gradient. Number on the tile should be the index
, which is basically the day.
Now let’s finish CalendarPage with our very own build
method! We’ll start by creating a PageController
.
Return a Scaffold
, as always (or mostly). Let’s place the selected dates in the appBar
, shall we? Add the following code.
For the body, we build a PageView
with different pages for different months.
controller
property takes _controller
that we created earlier ;) children
consist of _calendar
widgets. For current month, we add _day
, _month
and _year
as parameters. For subsequent months, we keep day as 1 as all dates in future months are available. We increment month by 1 and keep the year same here. We change it in the _calendar
method itself.
To add some more feedback for the user, we use arrow buttons in the bottomNavigationBar
that changes the page in our PageView
. For this exact reason, we created a controller
.
_controller.animateToPage(…)
moves to the page with specific index in PageView
that, in this case, is _calendarBloc.calendarPageIndex
which we created earlier. If you wish, you can play with the transition by changing duration and curve of animation.
HomePage (at last!)
Again, start by creating an instance of CalendarBloc
.
Then call the dispose
method to be on the safer side.
Coming to the build method, we would like to have a button that directs to CalendarPage
. We use a Container
, wrapped within an InkWell
to create a custom one. Now, just put the whole thing inside a StreamBuilder
and we can easily access the selected dates.
We did it!
Here’s what the complete app feels like:
Now, you might be wondering if the effort we put in for such a small app was worth it or not. Well, the app you see here is robust, easy to integrate and highly scalable. You can try on your own and understand what I exactly mean.
That’s all folks! This was the mighty BLoC pattern. You are now officially qualified to build your own robust apps using BLoC.
Check out the whole code here: https://github.com/imaachman/Flutter-Calendar-Package
Hope you know how to star a repo ;)
Wanna use this in your app? Here’s the link to package: https://pub.dev/packages/calendar_package
Any questions or suggestions? Put them all in the comments section :D
Liked the article? Press and hold on the 👏 button! That’ll motivate me to write more and more!
You can find me on LinkedIn, follow me on Twitter or email me at imaachman@gmail.com for any kind of tech discussion.