Set profile photo with Flutter bloc or how to bloc backward
What you will learn from this post?
Image a typical real-life application where the user is required to set a profile picture.
What preliminary knowledge is needed?
A basic understanding of Flutter classes and navigation.
A photo can be selected or taken, and also edited, but how to send data back from the edit page to the home page to display a profile photo? The answer is Bloc — a state management tool.
💡 NOTE: I will only briefly cover how to upload photos, edit them without going into UI implementation details for now.
Step 1: Plan
The first step is to build a code structure and to collect dependencies.
- ui package: Home, Edit pages
- routes package: routes names and destinations generation
- bloc package: state management classes
main.dart class — a starting point of the application:
Necessary dependencies in pubspec.yaml :
Step 2: UI
For Step 2 and 3 I left comments inside the code as a small help for you to understand it better.
The asset I used as a default image you can download from Flaticon.
This class can be further optimized, for example, SimpleDialogOption can be extracted to a separate widget to avoid boilerplate, and the hardcoded values can be saved as constants. However, to keep everything simple, I will leave this up to you.
Please don’t be overwhelmed by the _cropImage() method. It is a simplified version based on the image_cropper library implementation example. There is no need to understand it, especially when learning bloc.
There are only a couple of things you should keep in mind:
- in the init() method, we’re receiving an image coming from the HomePage and after calling the _cropImage() method to crop it.
- once the image is cropped, we navigate back to the HomePage:
Step 3: Routing
You may see a lot of warning related to routes, so for setting the routing, create two files:
As you noticed for the EditPage we are passing an argument, as when the user selects a photo on the HomePage we should send this photo to the EditPage, so it can be cropped.
Replacing our TODO in the main.dart class in the code from Step 1:
Let’s test our app so far:
We can select/take, crop a photo, but the cropped picture does not appear on the HomePage yet, only the original one.
It’s time to bloc!
What is state management ❔
You’ll find lots of nice explanations on the Internet, but let’s forget about the “state management” expression for a minute and imagine any food delivery app today. You are notified at each stage of the process by seeing the UI updates, so does it mean that there is someone who’s watching over your order? That would be overwhelming. Luckily, we have state management, such as bloc that keeps an eye on the data and notifies us through the UI changes, such as loading indicators, fancy animations, etc.
Or another typical example would be YouTube. Bloc knows that if your Internet is slow, then the data is treated as not loaded and in this case, you can see a loading indicator. Once the data got to the loaded state, you can enjoy your video.
What is bloc good for ❔
We know it tracks the state (state management), but there are plenty of other benefits, as we separate business logic from the UI, which means that it’s convenient, easier, faster, and more efficient to
- implement changes
- work in a team
After all, it makes your code look clean and professional.
What do I mean by business logic, though❔
It’s all about working with data. UI displays data visually and we may be able to modify it visually, but saving, uploading, downloading data is all business logic, which should be separated from the presentation logic (UI). Bloc is a businesswoman or businessman. It’s talking only business.
Step 4: Adding bloc
I use a plugin called Bloc that generates all the necessary classes for me. Whether you’re using Android Studio or Vscode it’s available on both.
Thus, bloc consists of three classes: bloc.dart, event.dart, state.dart.
MoodTracker app example
Now, imagine an app that could identify your mood and notify you of what do you need at the moment to support it. For example, if you feel sad you get advice to make a hot beverage
If you’re happy you’ll be prompted to share this nice mood with others:) How the bloc classes would work in this case?
Condition: you already clicked the button “What is my mood”:
The diagram is simplified to have one event, though further, the app can consist of the following events: click the button, identify the mood, make a coffee, rate app, and states: button is not clicked, the mood is loading, the mood is good/bad, coffee is preparing, coffee is ready, the app is rated, etc. It can grow complex based on the application goals, but I hope this small example can help you to form some general understanding of bloc.
Going back to our app
Equatable is necessary for comparing objects with each other. For example, you had a bad mood, but once you met your friend it improved, now bloc should know whether your mood changed, it takes a current mood state and compares it with a new one. We extend from Equatable to avoid manually overriding
List propsis a list of properties that are used by the Equatable to compare whether two “equatables” are equal (in our case these two “equatables” are the current and new photos)
In our app, we have two states:
initial app state when the default image is used
after a photo is edited it is compared with a current image and if there is an update it will be set
mapEventToState() method is pretty straightforward, it takes an event and maps it with a certain app state. In our case, the only event we are interested in is to get the edited photo: GetPhoto.
Step 5: Bloc and UI
For the UI to access our bloc add a BlocProvider by wrapping it around the MaterialApp. It provides a global instance of the bloc, that can be used by any widget within the app. It is possible that you set BlocProvider locally, but since our screens are using the same bloc and tightly connected in interactions we need to have it globally.
💡 NOTE: You could hear that bloc should be disposed of after use, it is true when it is used only at a specific portion of the application. In our case, it is declared globally and will be disposed of when the application is closed. More info on StackOverflow.
💡 NOTE: bloc.dispose() was renamed to bloc.close().
The next step is to set a BlocBuilder. Inside the HomePage builder() method:
BlocBuilder is needed for widgets to respond to the state changes. In our example, we want to swap the current image to the one we selected and edited. In the code above, I’m checking if the state is equal to PhotoInitial then we can use the default image, otherwise, the state is PhotoSet and we can set a cropped photo.
💡 NOTE: Why don’t we use BlocListener? Because we only redraw the widget, but not performing any action. Action can mean navigation, pop up windows, dialogs, etc. For example, once the photo is set a pop-up window appears to rate the application from 1 to 5, then we would need a BlocListener as well.
As you remember, in the EditPage, after cropping an image we pop of the stack to return back to the HomePage. Now our task is to listen to the image updates before navigating.
That can be easily done using
context.bloc<PhotoBloc>().add(GetPhoto(imageFile)); Here, we are accessing the global bloc
context.bloc<PhotoBloc>()instance and referring to the GetPhoto state.
💡 NOTE: bloc.dispatch() was renamed to bloc.add().
As you noticed the image is getting updated, but we still have a text saying “Please select your profile photo”. A challenge for you is to change the text to “Nice photo:)” once it is set.
You will find the solution gist here, but I encourage you to try it first!
The full source code is available on my Github.
For those who completed the challenge 🏆, you can share your solution on social media with the #blocbackwards or #blocwards tags.
I’m curious to hear you feedback 🙂
If you feel like the taking & cropping photo part needs more explanation, let me know and I will create another tutorial focusing only on the UI side this time.