LY Corporation Tech Blog

We are promoting the technology and development culture that supports the services of LY Corporation and LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam).

This post is also available in the following languages. Korean

Improving the product development environment using Flutter Web

(The original post was published on September 6, 2024)

Hello! I'm Jong Sic Kim, and I work on developing the Demae-can (出前館) app at ABC Studio. Demae-can is one of Japan's largest food delivery services, which started back in 2000. ABC Studio has been involved in improving the product since the spring of 2021.

In a previous article, The end of the Flutter journey - The third recode of one of Japan's largest delivery apps, I talked about how we transitioned the Demae-can consumer app (hereafter ConsumerApp) to Flutter technology. Flutter is a powerful cross-platform framework that lets you develop applications for various platforms using a single code base. Since its debut, it's been increasingly used not just for mobile apps but also for PC and web environments, and it keeps getting better.

For the MerchantApp and ManagerApp, which are apps for business owners, we were already using the web version with Flutter during the product improvement phase. The ManagerApp, in particular, was distributed internally with a web version for testing right from the start. We got feedback that it was a huge help throughout the product development process, from planning to design and QA. Additionally, during the development of the RetailerApp for franchisees, we experimented with using the web version to determine the completion of work by generating a PR (pull request) and building and deploying the web version.

On the flip side, the ConsumerApp didn't use the web version during development. We wrapped up the UI/UX improvement task last March without it. Since ConsumerApp is a service with many stakeholders and the task required meticulous verification of the actual operation screen, we felt some pain points during the process. We realized we needed a development environment that could improve the service faster and more efficiently. In this article, I'll share how we improved the development environment to enable the ConsumerApp to run on the web, as part of our efforts to create such an environment. 

image

Why we need ConsumerApp support in the web environment

We conduct ConsumerApp product improvement activities in six development environments, as shown in the figure below. In each environment, we verify whether tasks proceeding in parallel or regular deployments are progressing, allowing us to flexibly adjust the release schedule. 

image

Currently, for internal testing or sharing, we use DeployGate for Android and TestFlight for iOS to check work on actual devices. Since many people are working together, checking work on actual devices requires preparing accounts and devices individually and in teams, which can unnecessarily waste the budget. Additionally, to maintain security, you must connect to the company's VPN to use the app service, which is quite cumbersome to prepare. For these reasons, it is not easy for product makers to communicate while checking the actual app operation during the project.

Flutter technology has been steadily improving in a multi-platform environment since its release. All service apps operated by Demae-can are developed with Flutter, allowing them to run in a web environment. To improve the various inconveniences mentioned earlier, there was a suggestion that it would be good to have a development environment where ConsumerApp can also be checked for operation on the web, and it was expected to help in the future app product improvement process. Therefore, the development team took the lead in configuring the ConsumerApp web operation environment. 

How we built a ConsumerApp web development environment using Flutter web

We followed these steps to set up the ConsumerApp web operation environment using Flutter Web: 

  1. Conduct PoC to confirm feasibility
  2. Derive task list and set task direction
  3. Update reference packages causing build errors to web-supported versions
  4. Refactor code using non-web-supported features and packages
  5. Address CORS issues in the local development environment
  6. Display in-app web view on the web
  7. Display map view on the web

Let's dive into each step. 

1. Conduct PoC to confirm feasibility

First, we conducted a PoC (Proof of Concept) to work up to the login screen in the local development environment as shown below. Since ConsumerApp provides many features and is implemented by referencing various packages, preparing only the initial operation environment is considered half the success (for reference, ConsumerApp uses Flutter 3.19.5).

image

2. Derive task list and set task direction

Based on the work done in the PoC stage, we organized the parts that need code modification and refactoring to enable web execution into a task unit list as shown in the figure below. Thanks to deriving tasks in this way, we were able to proceed with development divided into parts that minimized side effects and did not burden code reviews.

image

The direction set for the ConsumerApp Flutter Web version based on the PoC results is as follows.

  • The goal is not to provide the ConsumerApp Flutter Web version to end users. Demae-can already provides services on the web, and Flutter Web has some disappointing aspects, such as slow initial startup speed and a lack of tools that can be used at a commercial service level in the web environment, so it was not considered for end users from the preparation stage (reference: Flutter Web or React Native Web: Who Will Win the Battle?).
  • The goal is not to make all functions that operate on actual devices work perfectly the same on mobile and web. There were packages that did not support the web environment among the packages we used, and there were cases where the functions provided by the app were not suitable for providing on the web. Therefore, instead of making them work the same, we decided to share the fact that there are restrictions on using some functions such as in-app web view and payment flow and to utilize them.
  • The goal is not to configure the web execution environment as the optimal or suitable environment for the team. Currently, there are six development environments for ConsumerApp. We aimed to prepare it to a level where ConsumerApp can be executed and utilized in a web environment in one of those environments. If there is a growing need to utilize the web environment in the future, we decided to improve the web build and deployment environment according to the situation.

3. Update reference packages causing build errors to web-supported versions

There were cases where we had to inevitably raise the version because errors occurred in the packages we were using when building in the web environment. For example, the following error occurred in the newrelic_mobile package.

Launching lib/main.dart on Chrome in debug mode...
main.dart:1
: Error: Dart library 'dart:ffi' is not available on this platform.
newrelic_mobile.dart:7
import 'dart:ffi';
       ^
Context: The unavailable library 'dart:ffi' is imported through these packages:
 
web_entrypoint.dart => package:consumer_app => package:newrelic_mobile => dart:ffi
 
Detailed import paths for (some of) the these imports:
... 
 
Failed to compile application.
Exited

It is known that you can use C and Objective-C & Swift APIs using Dart's FFI (Foreign Function Interface) package, but when executing a web build, an error occurred in the import statement of the FFI package. The reason was that although the ConsumerApp project does not use the FFI package, the package referenced by ConsumerApp referenced the FFI package. This issue occurred in version 1.0.1 of the newrelic_mobile package used during the Recode project, and we resolved the issue by updating the version of the newrelic_mobile package referenced by ConsumerApp to 1.0.6.

Errors also occurred in Firebase packages as shown below, and we had to update the versions to resolve them.

image

Package NameBefore UpdateAfter Update
firebase_core2.27.22.32.0
firebase_auth4.18.04.19.4
firebase_analytics10.9.010.10.4
firebase_crashlytics3.4.203.5.4
firebase_messaging14.7.2114.9.1
firebase_remote_config4.3.194.4.7

4. Refactor code using non-web-supported features and packages

Some features implemented using Flutter packages were difficult to implement from a functional perspective because they did not provide a web environment. Examples include displaying app badge icons, logging in with external services, and linking accounts with external services. It would be nice if the packages used to implement these features supported the web environment, but since they did not, we could not provide the features. 

The code below is an example of wrapping the interface of the flutter_app_badger package used to display app badges. In cases where the feature is not supported in the web environment, we created it in a form that wraps the API provided by each package on a package-by-package basis.

// app_badger.dart
// note) _AppBadgerWeb, _AppBadgerImpl are an implementation of AppBadger.
AppBadger get $appBadger => PlatformHelper.isWeb ? _AppBadgerWeb() : _AppBadgerImpl();

abstract class AppBadger {
  Future<void> updateBadgeCount(int count);
  Future<void> removeBadge();
}

// AS-IS 
FlutterAppBadger.updateBadgeCount(count);

// TO-BE
$appBadger.updateBadgeCount(count);

In the case of Firebase, it also supports the web environment (reference), but there may be differences in operation between the web and the app for push notifications, and it requires registering a web app in the project, which increases the management scope. Therefore, we decided not to support Firebase features in the web environment.

Below is a list of refactored package usage codes.

Package NameBefore RefactoringAfter Refactoring
adjust_sdkAdjust.***$adjustUtil.***
flutter_inappwebChromeSafariBrowser()$chromeSafariBrowser
flutter_app_badgerFlutterAppBadger.***$appBadger
firebase_coreFirebase.***FirebaseUtil.***
firebase_analyticsFirebaseAnalytics.instance.***$firebaseAnalytics.***
firebase_authFirebaseAuth.instance.***$firebaseAuth.***
firebase_crashlyticsFirebaseCrashlytics.instance.***$firebaseCrashlytics.***
firebase_messagingFirebaseMessaging.instance.***$firebaseMessaging.***
firebase_remote_configFirebaseRemoteConfig.instance.***$firebaseRemoteConfig.***
...

We refactored the package usage parts as shown above, and we also worked on custom lint to prevent confusion when others work on it later. For more details on handling platform-specific operation branching and utilizing custom lint in Flutter, please refer to my previous article Utilizing Custom Lint in Flutter.

5. Address CORS issues in the local development environment

Now, it is necessary to check whether the basic functions work properly when running the web in the local development environment. After running ConsumerApp in the web environment and entering the main page, an error message was displayed, and it was the same for each tab (main, search, order details, my page) screen. The cause was CORS (cross-origin resource sharing), a troublesome issue that you inevitably encounter when developing for the web, and it was unavoidable in the Flutter Web execution environment.

The main situations where CORS issues occur are as follows (in the deployment environment, we solved this problem with the help of the DevOps team). 

  • When calling BFF (backend for frontend) APIs for each domain
  • When displaying images as URLs on the screen

The way to solve this is as follows. When you build Flutter locally and run the web, it usually runs in the Chrome browser. At this time, you can check how the Chrome browser is specifically launched in {flutter_sdk_install_path}/packages/flutter_tools/lib/src/web/chrome.dart. You can solve the issue by adding the '--disable-web-security' parameter to this chrome.dart or by passing this option additionally when executing the flutter run command (reference).

image

A tool that allows you to check CORS more easily in the local environment is flutter_cors. If it is cumbersome to configure custom settings for web execution in the IDE, or if you are developing using multiple versions of the Flutter SDK, I recommend using this tool. The installation and usage methods are as follows. 

/// Install
dart pub global activate flutter cors

// To disabling CORS checks 
fluttercors -d -p {flutter_sdk_install_path}

// To enabling CORS checks
fluttercors -e -p {flutter_sdk_install_path}

6. Display in-app web view on the web

ConsumerApp uses the flutter_inappweb package to implement in-app web views. This package supports the web environment from version 6.0.0 (reference), and internally uses an iframe to display the URL requested for screen display.

As shown in the figure below, the in-app web view was well displayed in the web environment. Although usability such as zoom in/out and long press events was slightly inferior to existing web services, we were satisfied enough with the in-app web view being expressed on the web to confirm the operation of the app within the app.

image

However, it worked fine in the local development environment, but an error occurred when the build result was deployed. When building ConsumerApp, the --base-href option is used to set the path of the actual deployment environment. The cause was that when this option is set, the path of web_support.js in the index.html generated after the build does not match.

This problem was solved by modifying the content of the index.html file generated after the build in the web build and deployment script. Below is the error response code (the build and deployment script in ConsumerApp is written in Deno).

// Work-around: `flutter_inappwebview_web` doesn't support `--base-href` option.
// Therefore, the script replaces a javascript path in `index.html`.
// If the library supports that, we can remove below work-around.
const htmlPath = './build/web/index.html'
const originalHtml = Deno.readTextFileSync(htmlPath)
const replacedHtml = originalHtml.replaceAll(
  '/assets/packages/flutter_inappwebview_web/assets/web/web_support.js',
  'assets/packages/flutter_inappwebview_web/assets/web/web_support.js',
)
Deno.writeTextFileSync(htmlPath, replacedHtml)

7. Display map view on the web 

ConsumerApp uses maps for setting delivery addresses, checking location information before ordering, and viewing delivery status screens. As shown in the figure below, there was an issue where the map was not displayed in the web environment. 

image

On iOS, AppleMapView is required, and on Android, GoogleMapView is required. To achieve this, we use the platform_maps_flutter package in Flutter, which can express map views according to each platform. However, an error occurred in the web environment because Platform.*** is used for platform-specific branching within this package. 

Since map display is an important feature of ConsumerApp, we decided to make it expressible in the web environment as well, and we proceeded with refactoring to display map views using the platform_maps_flutter package in the mobile app environment and the google_maps_flutter package in the web environment, as shown in the figure below.

image

Fortunately, there was little difference in the usage of interfaces such as Controller, Location, Marker, and Camera for expressing map views, so the refactoring was not very difficult. By doing this, there is also the advantage that if we need to change to using Google MapView for both iOS and Android in the future, or if there is a task to replace it with another package, we can easily replace the package (it is a part I hope for because using AppleMapView in Flutter is not easy 🙂).

While setting the header of index.html (reference), it was expected that the usage of the Flutter Web version of ConsumerApp for internal development confirmation would not be high, so we used the API key already in use. For reference, the API key for the map view used in the app and web products cannot use localhost due to system abuse, so we checked whether the map was displayed well after uploading it to the actual web server. 

As a result of testing in the actual web environment, there were some regrets in using features such as user interaction and rotation in the map view. This is also a part that google_maps_flutter_web preliminarily informs as limitations. Since it works well when initially entering all screens where the map is displayed, we decided to utilize it as it is, considering that there are some restrictions on using the map view, as the purpose of checking the app's operation on the web was achieved.

Conclusion

As it became possible to check the operation of ConsumerApp in the web environment, I felt that the communication efficiency of product makers improved significantly. In online meetings, we can now communicate by sharing the web environment screen when checking app operation, and screen capture has become much more convenient. By utilizing the device_preview package, which allows you to check how the app operates on different devices or environments, you can set the execution environment in more detail and check it. I felt that the number of packages available in the Flutter Web development environment is gradually increasing while working on this task. Thanks to this, I think we were able to meet the existing requirements without much difficulty.

As shown in the figure below, it has become easier to work by running two browsers on one monitor to check the differences in operation between the web and the app, or when conducting linkage tests between ConsumerApp, MerchantApp, and DeliveryApp.

image

The faster the cycle of checking the progress of tasks and receiving feedback on areas that need improvement to supplement them, the higher the completeness of the task, and the faster the improved product can be delivered to users. Currently, there is an increasing number of web deployment requests to check whether functions and designs are well implemented on a task basis, and I think this is increasing the efficiency of product improvement.

This task was a meaningful attempt to expand the users to include colleagues who make the product together. Personally, I felt rewarded when the Japanese planners were very pleased when I shared this work content. 🙂 If you are using Flutter, I recommend considering how to effectively use Flutter Web in the product improvement process, and I will conclude here.