Convert Flutter Mobile Application to Web With BLoC
It was introduced as a pattern that allows for sharing up to 50% of code between Flutter and AngularDart applications. In this blog post, I will share the experience of converting my Flutter to the web app and I will compare this solution to Flutter For Web.
Share code with mobile and web
For this blog post, I will be using the application that I have created as part of a BLoC tutorial. If you are not familiar with the BLoC pattern, or if you want to have a better understanding of the app that I will be converting, I advise you to take a look at it first. The finished shared code application can be found in this repository.
Shared application code can only be applied if you want to have the same behavior on both mobile and web. You can, of course, create different states, events, and BLoC just for one platform, but creating many of them makes shared code less profitable. Your application also has to be built based on the BLoC pattern. It would be best if it had been created based on the BLoC library, which is supported both on Flutter and AngularDart.
Since both mobile and web applications will behave in the same way and have the same states and events, they could share everything except the UI classes. Keeping that in mind, a diagram of a standard shared BLoC application might look like this:
As you can see on the diagram above, all three layers that provide data and application states are shared between mobile and web UI.
Converting Flutter app to shared code
The first thing I had to do to start working with shared code is to move all of the shared classes to a new folder, which should be created in the same place that mobile and web applications will be. So in the end, a folder containing the application should look like this:
Classes that could be shared between web and mobile could be all classes that provide data and manage the application state. So all BLoC, Models, Repositories, and data providers can be moved to common_bloc_lyrics.
Afterward, I had to add a pubspec.yaml file, so that it could provide the necessary dependencies and allow importing this package to web and mobile projects.
name: common_bloc_lyrics
description: Lyrics app shared Code between AngularDart and Flutter
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
meta: ^1.1.6
equatable: ^0.2.0
http: ^0.12.0
bloc: ^2.0.0
Then, I had to create a file common_bloc_lyrics.dart, which would export all the dependencies that this new package will provide.
export 'src/model/song_base.dart';
export 'src/model/api/search_result_error.dart';
export 'src/model/api/search_result.dart';
export 'src/model/api/song_result.dart';
export 'src/model/api/artist.dart';
export 'src/repository/local_client.dart';
export 'src/repository/lyrics_repository.dart';
export 'src/repository/lyrics_client.dart';
export 'src/bloc/song/search/songs_search.dart';
export 'src/bloc/song/add_edit/song_add_edit.dart';
Now I had to change all of the imports in this package to the import of the newly created file.
import 'package:common_bloc_lyrics/common_bloc_lyrics.dart';
And that was it. Thanks to these changes, the shared library could be imported both by Flutter and AngularDart projects.
Changes to the Flutter app
Since all the shared dependencies were moved from the mobile app project, I had to modify the pubspec.yaml file of my Flutter project, so that it would import these classes as if it were just another plugin.
name: flutter_mobile_bloc_lyrics
description: Flutter BLoC shared code example
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
...
common_bloc_lyrics:
path: ../common_bloc_lyrics
After that, I just had to change all of the imports of classes that will be shared to the export file that was created in the common package, similarly to what I did before. Every class that will use BLoC or models should have this import:
import 'package:common_bloc_lyrics/common_bloc_lyrics.dart';
And that was it. Thanks to these changes, I have managed to make my mobile application work with the shared code package.
Angular Dart application
To create an AngularDart project I used the recommended Dart project generator, called Stagehand. After downloading it, I also had to update my system path so that it would point to the required tools.
Creating an AngularDart application with Stagehand is as simple as generating a new Flutter project, you just need to type this command:
stagehand web-angular
As I stated before, I don't have a lot of experience in working with web applications. The one that I wrote was based on the Github search example from the BLoC library documentation.
Since this code is very similar to the one from the example, I won't describe the web project itself. But if you are curious about what it is like, you can check out its repository.
To run AngularDart projects in your browser you need to execute this command:
webdev serve
Working with AngularDart differs a lot from writing an application in Flutter. The views are written in HTML, and Dart files provide data and class instances to them.
So, for example, the project’s search bar Dart class looks like this:
import 'package:angular/angular.dart';
import 'package:common_bloc_lyrics/common_bloc_lyrics.dart';
@Component(
selector: 'search-bar',
templateUrl: 'song_search_bar_component.html',
)
class SearchBarComponent {
@Input()
SongsSearchBloc songsSearchBloc;
void onTextChanged(String text) {
songsSearchBloc.add(TextChanged(query: text));
}
}
It looks similar to the mobile app version of this component. SongSearchBloC is injected into this class thanks to the @Input() annotation. Similarly to the mobile version, the method onTextChanged should be called each time the input in the search bar changes. The templateUrl property tells which view layout should be used for the creation of this component. The search bar HTML file looks like this:
<label class="clip" for="term">Enter song title</label>
<input
id="term"
placeholder="Song title"
class="input-reset outline-transparent glow o-50 bg-near-black near-white w-100 pv2 border-box b--white-50 br-0 bl-0 bt-0 bb-ridge mb3"
autofocus
(keyup)="onTextChanged($event.target.value)"
/>
Thanks to the keyup keyword, on every character typed by the user, the function onTextChanges from the Dart class will be called.
The dependency for the BLoC injection is created in the main search component, which, similarly to the mobile version, creates view instances for both the search bar and the list.
@Component(
selector: 'search-form',
templateUrl: 'search_song_component.html',
directives: [
SearchBarComponent,
SearchBodyComponent
],
pipes: [
BlocPipe
])
class SearchSongComponent implements OnInit, OnDestroy {
@Input()
LyricsRepository lyricsRepository;
SongsSearchBloc songsSearchBloc;
@override
void ngOnInit() {
songsSearchBloc = SongsSearchBloc(
lyricsRepository: lyricsRepository,
);
}
@override
void ngOnDestroy() {
songsSearchBloc.close();
}
}
Initialising BLoC in ngOnInit makes sure that its instance would be created at the initialisation of component and would be accessible for all of the views created inside this class. Each time the view is destroyed, the method ngOnDestroy is called. So it is the best place to close BLoC, since it won't be needed anymore.
Since web applications have special security restrictions, it is worth pointing out is that you cannot make a request to a web server that doesn’t set CORS headers that accept it from the domain that your app is on. The API that I have worked with didn't have this feature, so to make my web app running I had to run my browser without CORS. When you are working with a custom API, it isn't hard to add CORS headers to it, but you need to keep in mind that you can't do this with most of external APIs.
Converting the app to Flutter web
Creating a shared web and mobile application takes time. It also requires some knowledge of Dart, Flutter, and AngularDart. It might not be the best solution for most mobile developers. But if you want to convert your Flutter app to be working with web, there is a simpler way.
Flutter For Web lets you easily convert any application to a working web application, regardless of which architecture you use. Since it is still in preview, it is not advised to be used in a production-ready application.
If you want to learn more about this, I would recommend this great article. By following the steps described in the article, I managed to convert and run my application locally on my browser.
To make my app work, I had to make some workarounds in the Java code. You can see how I did that in the commits in the repository.
But these workarounds still didn't make my app work and all I saw was a white screen. The next thing that I had to do was to remove the library that I used for app translations, since it was not supported on the web.
These fixes made my application work, but not everything behaved as it should. In the mobile app version, when you swiped an element on the list, it was removed. But similarly to the library for translations, the one used for swipe and delete is not supported on web projects.
What was a pleasant surprise to me was that the BLoC libraries worked as they should. Nothing behaved differently in the web application.
Summary
For now, there is no easy way for mobile developers to convert their mobile apps to production-ready web applications. Working with shared code requires a lot of knowledge, and it is not so greatly supported, since AngularDart is not as popular as other web technologies. But for now, the best way to go would be to create a production-ready web application with Flutter. This makes it another reason to keep your fingers crossed for the success of Flutter web.
Photo by Yogas Design on Unsplash