Beyond the hurdles: Addressing challenges in Android, iOS, and Web development

BTLA Tech Team
7 min readApr 4, 2024

--

This is the third blog of this series of blog posts highlighting our experiences with flutter and what’s next for mobile apps.

Overcoming Incremental Feature Rollouts with Flutter Views and Flutter Engine group:

During the initial phases of migration, we faced the daunting task of integrating Flutter features into our existing native apps without disrupting our release timelines. To tackle this, we adopted Flutter Views, allowing us to gradually migrate features while ensuring compatibility with our native codebase. This approach enabled us to maintain a steady release cadence while progressively introducing Flutter components into the app.

But this posed its own set of challenges:

  • Limited Flexibility: Flutter Views offer limited flexibility in terms of customization and UI interactions.
  • Performance Overhead: Integrating Flutter Views within native apps may introduce performance overhead, especially during navigation and layout rendering.
  • Tighter Coupling: Flutter Views tightly coupled Flutter components with native code, making it challenging to maintain and update.

So once a significant user flow of the app was migrated to Flutter, we moved to Flutter Engines.

AAR (Android Archive) and Pod methods are popular approaches for integrating Flutter code into native Android and iOS apps, respectively. AAR involves packaging Flutter code as an Android library for seamless inclusion in Android projects, while Pod method enables incorporating Flutter modules as CocoaPods in iOS projects, ensuring smooth integration and interoperability with native codebases.

As we transitioned to utilising Flutter Engine, we encountered significant delays during the initialisation process, impacting app performance. To address this challenge, we optimised engine initialisation by running it in the background of preceding screens. This allowed us to maintain a smooth user experience while ensuring the seamless integration of Flutter components within our native app.

Overcoming App Size: Major Multi platform Hurdle

App size reduction holds paramount importance, particularly in the realm of cross-platform app development. In our pursuit of optimising the size of our applications, we identified two key strategies:

Asset Resolver

The Asset Resolver strategy involves leveraging the capabilities of AWS S3 Bucket to store our assets on AWS servers. Rather than bundling assets directly with the application, we store them remotely in the cloud. At runtime, we dynamically retrieve these assets from the cloud whenever the application requires access to them. This approach effectively optimizes the size of the application assets, as they are fetched on-demand during runtime instead of being bundled with the initial download package.

Deferred Components

In our comparative analysis, we embarked on creating two distinct projects to explore the impact of deferred components within Flutter applications. Each project was equipped with assets totalling ~4 MB in size. Upon thorough examination, we observed a notable difference in download sizes between the project incorporating deferred components and the one without such optimisation.

The project integrated with deferred components exhibited a download size approximately 5.3 MB smaller than its counterpart lacking this feature.

This significant reduction in download size underscores the effectiveness of employing deferred components in mitigating the overall footprint of Flutter applications, thus enhancing user accessibility and improving the user experience, particularly for users with limited bandwidth or storage constraints.

To learn more about our journey of using these techniques to combat App Size issues checkout our detailed blog on Strategies for Streamlining Cross-Platform App Size

Controlled Rollouts and Feature Migrations

Implementing controlled rollouts and feature migrations is a best practice to manage user exposure to significant changes in an application.

This allows us to analyze the impact and handle issues early without impacting a significant number of users’ experience.

SDUI:

Leveraging Neon: our Server Driven UI (SDUI), we implemented a controlled rollout strategy based on cohort-level layouts. This enabled us to selectively expose a predetermined percentage of users to our Flutter experience while maintaining flexibility in adjusting rollout percentages. By harnessing the power of SDUI, we could efficiently manage and control the deployment of Flutter features, ensuring a seamless transition for our users.

AB Experiment:

We began with AB Experiment-based rollouts, allowing us to selectively expose users to newly migrated features. AB experiments involved exposing a fraction of users to the Flutter experience of specific features, enabling controlled testing and feedback collection.This approach enabled data-driven decision-making and iterative improvements.

Phase Wise Rollout:

However, as we progressed with migrating a substantial portion of the app, using AB experiments became impractical. Incorporating both old Native and new Flutter code in app bundles would inflate the app size and potentially impact business metrics. To address this challenge, we transitioned to Phase-wise rollouts.

Phase-wise rollouts involve gradually releasing updates or changes to the entire user base over multiple phases. This method allows for controlled testing and monitoring of the impact on app performance and user experience.

Both Google Play Store and Apple App Store provide mechanisms for phase-wise rollouts. Google Play allows developers to roll out updates to a percentage of users initially, gradually increasing the rollout percentage. Similarly, Apple’s TestFlight platform enables phased distribution of beta builds to testers before releasing them to the public via the App Store

Integrating Authentication SDKs into Flutter

One of our significant challenges was integrating our in-house Authentication SDKs, written in platform-specific frameworks (Kotlin for Android, Swift for iOS, and JavaScript for Web), into our Flutter codebase. Migrating these SDKs posed complexities due to third-party dependencies and platform-specific requirements. To bridge this gap, we opted to integrate the native SDKs into our Flutter codebase using Flutter plugins.

What is a Flutter Plugin?

A Flutter plugin is a package containing code written in platform-specific languages that enables communication between Flutter and native code. It allows developers to access platform-specific features and functionalities seamlessly within Flutter applications.

Creating a Flutter Plugin Module:

To create a Flutter plugin module, follow these steps:

  1. Create a new Flutter plugin project using the below command
flutter create - template=plugin
  1. Add platform-specific code in the respective platform folders (android, iOS, web).
  2. Define method channels to communicate between Flutter and native code.
  3. Implement platform-specific functionalities in native code.
  4. Invoke native functionalities from Flutter using method channels.
// Flutter Plugin Module: auth_plugin.dart
import 'package:flutter/services.dart';

class AuthPlugin {
static const MethodChannel _channel = MethodChannel('auth_plugin');

static Future<bool> authenticate() async {
try {
final bool isAuthenticated = await _channel.invokeMethod('authenticate');
return isAuthenticated;
} on PlatformException catch (e) {
print("Failed to authenticate: ${e.message}");
return false;
}
}
}

Benefits of Using Flutter Plugins:

  • Seamless Integration: Enables seamless integration of platform-specific functionalities into Flutter applications.
  • Code Reusability: Maximizes code reusability by leveraging existing native codebases.
  • Access to Native Features: Provides access to platform-specific features and functionalities not available in Flutter.
  • Cross-Platform Compatibility: Ensures cross-platform compatibility by facilitating communication between Flutter and native codebases.

By leveraging Flutter plugins, we successfully integrated Authentication SDKs into our Flutter codebase, overcoming challenges associated with third-party dependencies and platform-specific requirements.

Addressing Compatibility Issues with Flutter on x86 Architecture:

Compatibility issues with Flutter on x86 architecture presented a significant hurdle in our migration journey. Despite the challenges, we proceeded with migration after thorough assessment from product and business teams. Our decision to prioritize other platforms over x86 architecture was based on its rarity among our user base. This strategic approach ensured forward progress in our migration efforts while actively monitoring and addressing compatibility issues as needed.

Persistence Solutions for Web

One challenge we encountered was the lack of robust local persistence solutions for web platforms compared to Isar Db for managing user-level data locally. Fortunately, our architecture enabled us to create separate repositories leveraging Web Local Storage for the web platform.

  • Web Local Storage provides a simple key-value storage mechanism available in web browsers.
  • Web Local Storage is synchronous and can only store string-based data, making it suitable for storing user preferences, settings, and other small data sets.
  • Despite its limitations, Web Local Storage serves as a convenient solution for web-based applications requiring client-side data storage.

Integrating this solution into our codebase was seamless, thanks to the Dependency Injection (DI) approach we employed throughout our architecture. To learn more about our architecture checkout our blog on Behind the scenes: Architecting robust flutter apps

--

--

No responses yet