Achieve mobile release independence in Flutter with flutter_eval

A free and open-source CodePush alternative

Achieve mobile release independence in Flutter with flutter_eval

While Flutter mobile developers, in the eyes of many, enjoy superior tooling and a more predictable programming environment, there is one feature from web development that we often wish we had. This article will explore this coveted feature, focusing on how it can improve our Flutter development experience.

Achieve mobile release independence in Flutter with flutter_eval

Our mobile-exclusive DX parts include stateful hot-reload, a tool that mobile Flutter developers frequently use but is missed in non-Flutter projects. The absence of this feature can lead to significant time loss daily, as developers struggle to regain the state of the app section they are currently working on. Moreover, the stability and robustness of our platforms are commendable, freeing us from concerns about ad blockers interfering with network requests or add-ons disrupting our application layout, which can lead to unexpected bugs. However, there is one aspect of the web developer's experience that I, and perhaps many of you, cannot help but envy – the capability to release new software versions on demand. This article will delve into this feature, exploring its potential to revolutionize our Flutter development process.

Web vs. mobile development: smooth sailing vs. rough waters

In the realm of web development, addressing bugs and deploying fixes is a seamless procedure. When a bug is encountered, a developer can promptly address it, deploy the solution, and instruct the user to refresh the page to resolve the issue simply. The deployment process can even be automated after a commit to the main branch. This capability also extends to rollbacks; if a previous version was more stable, developers could easily revert to it, offering a high degree of flexibility and control.

Achieve mobile release independence in Flutter with flutter_eval

In contrast, the mobile development journey is a more complex and unpredictable one. When a broken feature is reported, it is promptly fixed, but the process becomes uncertain when the release is sent for review. The procedure can be a gamble, with approval times ranging from under an hour to several days, depending on the reviewer's findings. If a compliance issue is discovered, negotiations for a later fix can become political, with the primary concern being the user's smooth experience with the app.

Even after approval, the rollout process is still uncertain. Not all users may receive the fix, as updates depend on device usage and charging patterns. Some users may never update to the newest version, leaving unresolved issues in the error tracking system. While there are mitigation options like feature flags and forced updates, one aspect remains elusive: store reviews. Automating and enhancing the reliability of this process would be a welcome solution in the mobile development journey.

Flutter_eval: Streamlining Dart development with remote execution

I would like to introduce you to a fascinating tool called flutter_eval. It is part of a larger open-source project known as dart_eval, which is a portable virtual machine designed for executing Dart code. Think of it as an alternative runtime, much like Firefox's "Spidermonkey" JavaScript engine is to Chrome's V8. However, flutter_eval stands out with its unique capability to run inside your native Dart code. It uses the analyzer package to parse the code, ensuring full compatibility with standard Dart tooling.

Achieve mobile release independence in Flutter with flutter_eval

The most intriguing aspect of flutter_eval is its potential to allow developers to switch their app's implementation, including logic and UI, remotely, bypassing the entire review process. This tool, written in Dart, can be used in any environment where Dart is supported, including Android, iOS, desktop, and even the web, which adds an interesting twist to the context of this article.

When it comes to code safety, dart_eval prioritizes it with a robust and built-in security layer to protect your projects. By default, remote code can only interact with the compiled Dart code and its memory. However, the mechanism enables you to define which resources are accessible by the code running in the virtual machine. We will get into the details later in the discussion.

The magical trio

Let's explore the architecture needed to achieve remote code delivery.

To enable the ability to switch widgets at runtime, we require three key components:

Achieve mobile release independence in Flutter with flutter_eval
  1. dart_eval: This component plays dual roles in the process. It acts as the virtual machine that runs our remote code, and it also serves as a command-line interface on the development machine for compiling the code into an .evc file.

  2. eval_annotation: This annotation is used to mark the parts of the code that we intend to switch at runtime.

  3. flutter_eval: This component provides specific features for the Flutter framework. It includes a widget that downloads the remote code package and configures it. Flutter_eval also includes helper widgets that switch the implementation at the appropriate time.

It's important to note that dart_eval does not replace your existing app implementation. Instead, it works with your current code to provide remote updates. However, these updates should be applied to specific parts of the app. We will discuss this in more detail later.

Setting up Flutter_eval: integrating remote code execution in your Flutter project

To begin the setup, open any existing Flutter project and add flutter_eval as a dependency. This will give you access to HotSwapLoader and HowSwap widgets. HotSwapLoader is a widget that loads and caches your remote code on the target device. Simply point to the HTTPS endpoint containing your .evc file (which we’ll generate in a bit), and HotSwapLoader will handle the rest.

Achieve mobile release independence in Flutter with flutter_eval

Once loaded, HotSwap widgets will ensure that the remote code replaces sections of the Dart code shipped with the application based on the hot-swap id. In the basic setup, we’ll use raw strings, but it’s recommended to use enums to avoid typos. For now, take note of the id selected here: #myHomePage. The childBuilder function makes sure there’s always something meaningful to render for the app. If the .evc code cannot be accessed for any reason, childBuilder will simply render the code provided with the original app’s release.

If you have any additional arguments for the remote widget, use the args array. It must always be populated with at least the current widget context, as it’s necessary for Flutter to correctly render the widgets, but will accept any number of arguments. As you can see, the BuildContext uses flutter_eval’s $BuildContext helper class, which wraps the context generated via shipped code to work with the runtime dart_eval VM. More details can be found in the documentation, but we’ll skip this complexity for brevity.

Achieve mobile release independence in Flutter with flutter_eval

Next up, the cool part, preparing the hot swap package. Runtime code replacements are shipped independently from the app. Therefore, in the recommended setup, we store the remote code in a separate package. 

Start by creating the package, just like you would create any other package project. Then, get the flutter_eval.json file from the flutter_eval GitHub release matching the version number of your current flutter_eval version. The flutter_eval.json file contains bindings for many widgets that are shipped with the Flutter framework, so you can use them in the remotely delivered code. 

Then, create a .dart_eval/bindings directory in your new project and store the file there. It’ll be used in the compilation step.

Achieve mobile release independence in Flutter with flutter_eval

While still in the new package project, make sure to add the eval_annotation dependency. It’ll give you access to the @RuntimeOverride annotation, allowing you to later replace the tag during runtime. Each RuntimeOverride annotation should decorate a function that returns a Widget. The args list you provide for the matching HotSwap widget will be converted to arguments of the function declared here. 

Once your implementation is ready, compile the code with the dart_eval CLI. If you haven’t already, activate it first, then call dart_eval compile with the desired output filename.

Achieve mobile release independence in Flutter with flutter_eval

Now, the grand finale – publishing your code. My only recommendation is to pick a reliable hosting for your .evc files. Once live, the new .evc contents will replace the current implementation in the target apps. It’s also a good idea to include the application version number with the .evc file request. At some point, as you might have multiple app versions using .evc files, it’s advisable to be able to swap contents per app version to avoid interlocks.

Managing risks with Flutter_eval

Let's talk about the security aspect of the tool as promised. If the .evc hosting is compromised, an attacker could potentially cause significant harm to the end-users' devices. Although limiting the number of widgets that can be remotely updated may help reduce the risk, it does not eliminate the possibility of turning your app into ransomware or a cryptocurrency miner.

Achieve mobile release independence in Flutter with flutter_eval

This potential security risk was identified early on in the development process, and as a result, by default, your remote code does not have access to any resources on the disk or the network. However, if your use case requires granting access to potentially risky resources, you can still specify which directories on the disk and which domains on the web can be accessed, providing a more granular level of control over the security of your application.

Best practices

Before integrating flutter_eval into your app, there are several considerations to keep in mind. Some of these have been mentioned earlier, but here is a condensed set of guidelines to follow:

Do's:

  • Ensure that you provide some basic functionality with the code shipped with the app, in case of poor network conditions or your hosting is inaccessible. Having loading indicators everywhere will not provide a good user experience.

  • Identify overrides using consts instead of raw strings to avoid typos or name conflicts.

  • Send version information with the .evc request to stay agile with matching .evc files to app versions, should some native dependency cause conflicts.

Don'ts:

  • Avoid building your entire app with flutter_eval. While it is technically possible, it is not recommended as network access is not guaranteed on mobile devices. If your user is in a state where they have no network access on the first launch, your app will be useless.

  • Do not perform major overhauls via remote updates. Store policies are vague, but they do state that unreviewed functionality can result in a ban. While there are different levels to this, switching your app from a music player to a crypto exchange will definitely get you banned. Unsupervised rebranding might get you nexted, while fixes to the existing functionality and offering user-customized content is definitely fine. Exploring the store policies’ boundaries at your own risk is advisable, but it is recommended to stick to bug fixes and feature enhancements.

A peek at store regulations

Please note that this summary is for clarification purposes only and should not be considered legal advice. It is important to thoroughly read your agreements.

An app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. Likewise, an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play. This restriction does not apply to code that runs in a virtual machine or an interpreter that either that provides indirect access to Android APIs (such as JavaScript in a webview or browser)

~ Play Console Help

[...] interpreted code may be downloaded to an Application but only if the code:

a) does not change the primary purpose of the Application by providing

features or functionality that are inconsistent with the intended and

advertised purpose of the Application as submitted to the App Store,

b) does not create a store or storefront for other code or applications, and

c) does not bypass signing, sandbox, or other security features of the OS.

~ Apple Developer Program License Agreement

Both app stores generally allow the downloading and running of interpreted code, provided it does not directly access system APIs or bypass OS security measures. Additionally, the app cannot change its purpose through these updates, and it cannot offer an internal app gallery that is updated without allowing store reviewers to examine it before it is made available to users.

Stay agile

Remember, flutter_eval is all about helping you with the review process while your production environment is in crisis. If you have any questions or need assistance with implementing flutter_eval in your project, or any other solution for that matter, please do not hesitate to contact Monterail. We would be more than happy to help.