With the need for a caffe app established, it's time to build one.
Let's start with how the final app looks like, then I'll go through each page in more detail.
I used Firebase for the backend. For those unfamiliar, Firebase is Google's application development platform, aka Backend-As-A-Service (BAAS). It has all the backend services that an app needs under-the-sun. Glancing at their list of services, which includes authentication and analytic, reminds me of the never-ending list of services available in AWS. AWS has its own BAAS, called AWS Amplify. I'll focus on Firebase in this article.
The Firebase services I used are:
- Authentication - allows a plethora of sign-in options, ranging from basic email address and phone numbers, to Facebook and Twitter by leveraging OAuth
- Cloud Firestore - NoSQL document database for storing user profiles, menus, and store locations. Any information that I don't want to hard code in my application can be stored here
- Cloud Storage - object storage for storing all binary files such as images and videos
- Crashlytics - automatically generates a report when the app crashes which allows for quick fixes
- Google Analytics - collect usage statistics to better understand user behaviour that translates into a better-designed app
The app is into organized into 5 screens:
The News screen shows any announcements that the café wants its customers to see when they first open the app (i.e. app Home screen).
Each news item is on a card that can be tapped for further details, and buttons at the bottom for quick action e.g. add the drink to cart, share to friends.
The Menu screen has two tabs, drinks and food. Tabbing on an item will give you options for customization. Customers can add an item to the cart when ready, at which a "Review Order" button appears that brings the customer to the Orders screen for final review before placing.
There are three tabs, first is the Order tab where customer can view all items that have been added to cart but not yet ordered.
The second tab is the Previous tab where customers can view all the orders that they have placed before.
The last tab is the Favourite tab. This is for viewing all items that have been favourited so that the customer can add these items (along with any customizations that were chosen) to their existing order in one click.
This contains information about all locations for this cafe. Clicking on the phone number and address will take you to the phone and map apps accordingly.
Here the customer can register or log in if it hasn't been done already. Customers can also change the details of the profile here.
Besides the name, phone number and email, it also shows a QR code that contains the customer ID. This is for the store manager to scan (on a different app that I also created) for identifying the customer to add loyalty points.
State management is always a hot topic in mobile and web development. This is because these apps are heavily UI driven (compare this to a script or a calculation process that works on a database). Thus an important task for the developer is to make sure the UI reflects the app's state correctly. Given how apps can have a large number of UI elements (e.g. game), making sure all elements are displaying the correct state can be a daunting task.
Since most popular state management libraries have been ported to Dart, developers who have extensive experience in React or React Native can just continue and use whatever libraries they have been using (e.g. MobX, Redux).
Fortunately, I don't have that prior extensive experience or bias. Thus I explored what is the best solution for Flutter. After trying different methods such as InheritedWidget and providers, I've settled on using Felix Angelov's implementation of the BLoC pattern.
I won't be going into detail on how the library works as the official docs are well written and clear, but I'll try to give a summary here.
The central theme of this pattern is the idea of a bloc component (Business Logic Component). It sits between the UI and the data. The Bloc component handles any events that come from the UI and makes asynchronous requests to the data component, which can be an interface that talks to the database. Once the data is ready, the bloc component will receive an asynchronous response from the data component, process the data in any way needed, and send the data back to the UI in the new state. The UI component is notified of the new state and it will update the display to show the new data accordingly.
To better illustrate this, I'll use the example of when a user taps a button to request information on a drink:
- an event is generated by the UI component when the button is tapped
- bloc component sees the event and sends an asynchronous request to the data component
- data component, an interface that talks to a database, makes a request to the database for the requested drinks information
- bloc component sends a "waiting state" to the UI while the database is fetching the data
- UI component responds to the "waiting state" by updating the display to show a "spinning circular arrow" to indicate to the user that the data requested is being fetched
- data component receives the requested drinks information from the database and sends it back to the bloc component in an asynchronous response
- bloc component sees the response and does some light processing on the information
- bloc component sends a new state that includes the requested drink information to the UI component
- UI component updates the screen with a Dialog box to show the drink information to the user
The main idea of this library is to enforce separation of concerns within your application. Each component, whether they belong to the UI, bloc or data layer, will be responsible for only one thing. This will encourage you to write smaller components that make reusing and testing your code easier.
When I find my UI components talk directly to the data components, then I know I haven't implemented this pattern correctly. An easy method that I use to prevent myself from making this mistake is to ensure I only import bloc components in my UI components and data components.
Furthermore, the bloc is an actively maintained repo and Felix has been responsive in answering the communities' questions. I started using the repo when it was still in version 1. It has gone through a few release cycles since and now it's at version 4. The library gets easier to use and the docs get better with each release. Though keep in mind when upgrading as it's common that each major version upgrade brings some breaking changes.
Two payment gateways stand out in the mobile app space, Stripe and Braintree. Although neither has an official implementation in Flutter, I chose the latter because it has a better third-party package (as of the time of development).
Writing an app that satisfies an immediate business need is very motivating. Since much of what the app does falls into a common use case, it made a good entry point into the Flutter ecosystem.