Features

Coding Guidelines

Coding guidelines for UK and EU app teams

The following guidelines are for the UK and EU app teams, that work on different apps but have a shared codebase

What is the problem we are facing?

While the app shares a single codebase and different regions can benefit from the development of other regions, the requirements of some of the features or designing is not always 100% aligned.

That introduces a risk that some of the code might be merged and change the functionality of some regions while it’s not intended.

Proposed workflow

Starting with the code architecture, we must ensure that a functionality we are not 100% sure that needs change, needs to remain intact from all the changes that are introduced for other regions.

An example would be the introduction of the Bloomreach CMS and Banners on the Home page.

Previously all the regions were using Firebase Remote Config as the service providing the banners. Now the UK region migrated from Remote Config to Bloomreach CMS, which was a great opportunity to start using the code architecture that is proposed.

To be able to have both services while maintaining a simpler code in higher levels of the code (UI/Widgets/Pages, Bloc - Business Logic Component), some levels of abstraction were introduced.

Different services (3rd party services) code architecture

Common data models that abstract differences between different services, provide a single way for the app to consume banners data without needing to worry about how these are received and transformed.

Code example

// App model for a single Banner
class Banner {}

// App model for home page banners config
class HomePageConfig {
    List<Banner> carouselBanners;
    List<Banner> lftBanners;
}

Services need to abstract the core functionality into a shared interface that only describes a contract between all the services that implement the contract.

Code example

// The contract
abstract class BannersRepository {
  Stream<HomePageConfig> get stream;  void loadBanners();
}

All the services that can provide banners must meet this contract.

In the case of the banners we have 2 services that can provide such banners, Bloomreach CMS and RemoteConfig.

For these to work we need to separate the implementation of the services into their own

Code example

class BloomreachCmsBannersRepository implements BannersRepository {
    Stream<HomePageConfig> get stream =>;             
    void loadBanners() { … }
}

Code example

class RemoteConfigBannersRepository implements BannersRepository {
    Stream<HomePageConfig> get stream =>;             
    void loadBanners() { … }
}

Now while the code has 2 ways of getting banners, we keep this information to the lowest level of the code only and provide a cleaner way for different parts of the code to access the banners through the interface only.

This adds the ability to inject the service we need per region based on a configuration like Feature Flags or a specific region needs.

Code example

// initialization code
BannersRepository bannersRepository;
String bannersImplementionFlag = featureFlags[‘banners_implementation’];
if (bannersImplementionFlag == ‘bloomreach_cms’) {
    // inject bloomreach cms repository
} else {
    // inject remote config
}

Now the app can consume the “bannersRepository” without any knowledge about the actual implementation.

UI code architecture

UI code is more flexible from the perspective of what we can do to cover different problems, but as we have multiple regions we need to make sure our code is easy to read and review and safe to change.

A way to achieve this is to use region aware values and widget builders. A callback proxy system that you have only to provide for a region and the region is automatically selected

Code example

// a simple callback that provides a generic value T
typedef ValueProvider<T> = T Function();


// an extension on the Region enum that provides named arguments for all the regions and a default value that is shared across the regions

extension RegionAwareValueProvider on Region {
  T provideValueForRegion<T>({
    ValueProvider<T>? uk,
    ValueProvider<T>? nl,
    ValueProvider<T>? be,
    ValueProvider<T>? fr,
    required ValueProvider<T> $default,
  }) {
    switch (this) {
      case Region.UK:
        return uk?.call() ?? $default();
      case Region.NL:
        return nl?.call() ?? $default();
      case Region.BE:
        return be?.call() ?? $default();
      case Region.FR:
        return fr?.call() ?? $default();
    }
  }
}

Code example

// A proxy widget that provides named arguments for all the regions and a default widget for all the regions

class RegionAwareBuilder extends Stateless {
    final ValueProvider<Widget>? uk;
    final ValueProvider<Widget>? nl;
    final ValueProvider<Widget>? be;
    final ValueProvider<Widget>? fr;
    final ValueProvider<Widget> $default;

    Widget builder(BuildContext context) {
      return context.region.provideValueForRegion<Widget>(
        uk: uk,
        nl: nl,
        be: be,
        fr: fr,
        $default: $default,
      );
    }
}

Code example

Example usage of the extension on the region (provideValueForRegion) and RegionAwareBuilder

class SomeButton extends StatefulWidget {
  Widget builder(BuildContext context) {
    return MaterialButton(
      color: context.region.provideValueFor<Color>(
        uk: () => Colors.black,
        nl: () => Colors.white,
        $default: () => Colors.red,
      ),

      child: RegionAwareBuilder(
        uk: () {
          return Row(children: [
            Icon(Icons.random),
            Text('UK text')
          ]);
        },
       $default: () => Text('Common text')
      ),
    );
  }
}

With this approach, we can achieve the following

  • The code is easier to read
  • The code is easier to review.
  • The code can handle differences between regions in a more secure way (from colors and texts, to widgets or even screens)
    • Security in this context means, that if the code needs to change for a specific region without having information about other regions, this proxy system can ensure that the code will function the same way for all the regions and the new change will only affect the specific region

Code review

In code review, besides ensuring that the code is functioning correctly, we also need to ensure it doesn’t affect the functionality of different regions (without being explicitly approved).

  • Good things to do in code review
    • At least 1 reviewer from each region

Copyright © 2026