Published on

Swypex Flutter Build (Part 1)

Authors

Introduction

About Swypex

The all-in-one financial platform empowering businesses with control, visibility, and efficiency. Swypex simplifies business finances, freeing you to focus on growth.

About the Author

Mostafa, a Senior Flutter Engineer at Swypex, stands at the forefront of our mobile application development efforts. As the principal architect behind the Swypex mobile application, he has helped create a robust, user-friendly platform that serves as the cornerstone of our mobile strategy.

The Evolution of Build Processes

After completing the critical stages of coding, designing, and local testing, the next crucial step in software development is deployment – transitioning the application to a new environment. Once it successfully passes these tests, the app is ready for production and can be released to all users.

Deployment is often a multi-stage process, each stage having its own unique requirements and challenges. In this article, we'll explore the limitations of traditional build processes, identify areas we wanted to improve, and showcase how Swypex has set a new standard in elevating these processes.

This discussion is divided into two main sections: Environment Setup and Build & Deployment. Today we will focus mostly on Environment Setup.


Typically, building and deploying an app involves managing a variety of critical variables, including:

  • Endpoint settings
  • App Icon / App Name
  • Version Number
  • Version Code
  • Third party setup files

Proper Environments!

Managing environment specific variables can be challenging, especially as your application grows and requires different configurations for various stages like development, staging, and production. Traditionally, developers often hard-coded these values directly into the application, resulting in several issues:

  • Code Duplication: Hardcoding environment variables meant repeating the same code across multiple files for different environments, making the codebase difficult to maintain.
  • Error-Prone Process: Manual changes to URLs, API keys, or other configuration values for different environments increase the risk of errors. A misplaced key or URL could lead to time-consuming debugging sessions or unintended data leaks.
  • Inefficient Workflow: Switching between environments required manually altering code and configuration files, slowing down the development and deployment processes. This inefficiency often led to bottlenecks, especially in teams with frequent deployments.
  • Security Risks: Storing sensitive information like API keys directly in the code posed a security risk, especially if the code was accidentally committed to a version control without proper safeguards.

To overcome these challenges and streamline our workflow at Swypex, we adopted the .env concept using the flutter_dotenv package. This approach provides a centralized way to manage environment-specific variables.

The .env file allows us to store all environment-specific configurations in a single place, making it easy to switch between different environments (e.g., staging, production) without altering the codebase.

Let’s go through an example of a .env file, similar to what Swypex workflow ended up with:

SERVER=https://app.yourapp.com/
ENDPOINT=api/

BASE_URL=$SERVER/$API

# Env types: staging, production, or dev
ENV_TYPE=staging

# Other keys you may need
MY_INTERESTING_VARIABLE=

Since we use Git for version control, we ensure to add an entry in our .gitignore file to exclude the .env file. This practice is crucial because .env files typically store sensitive information like API keys and environment-specific configurations. If these details are exposed, they can pose significant security risks. By adding the .env file to .gitignore, you prevent it from being tracked by Git, thereby securing your sensitive data. This approach keeps your version control system clean and focused on code, rather than environment-specific configurations, aligning with our goal of maintaining a secure and streamlined deployment process.

Additionally, create a .env.example file and add it to your version control. This file should contain placeholders or example values that mimic the structure of your .env file but without any sensitive data. The .env.example file serves as a guide for other developers, helping them understand the necessary environment variables and how to set up their .env files without risking the exposure of sensitive information.

Next, we can create a class to access these environment variables:

abstract class EndPoints {
  static String server = dotenv.env['SERVER'] ?? '';
  static String endpoint = dotenv.env['ENDPOINT'] ?? '';

  static String baseUrl = dotenv.env['BASE_URL'] ?? '';
}

Another one for determining the environment type:

import 'package:flutter_dotenv/flutter_dotenv.dart';

/// Environments [isDev, isStage, isProd]
abstract class Env {
  static String value = dotenv.env['APP_ENV'] ?? 'dev';

  static bool isDev = value == 'dev';
  static bool isStaging = value == 'staging';
  static bool isProd = value == 'production';
}

With this setup, your variables are now stored efficiently and can be overridden across different deployment environments. For staging, use the staging .env; for production, use the production .env, and so on.

Give your app some Flavor

But managing different environments for an application is not just about configuring different URLs or API keys; it often requires visual and functional distinctions between environments like staging and production. At Swypex, staging and production apps share the same app ID. Without clear visual cues, such as distinct app icons, it became challenging for our team to differentiate between the two environments. Before adopting flavors, developers often had to manually change app icons and names depending on the environment, this is extremely cumbersome as one can imagine.

To address these issues we implemented Flavors in our Flutter application. By creating separate flavors for staging and production, we were able to automate the differentiation process. This approach not only simplifies our development process but also helps prevent critical errors by making each environment visually and functionally distinct.

An Example with Flavors

To create and manage flavors, we followed the Flutter documentation on creating flavors. Here's how we set it up:

productFlavors {
    staging {
        dimension "app"
        versionNameSuffix "-staging"
        applicationId "com.yourapp.app"
    }
    production {
        dimension "app"
        applicationId "com.yourapp.app"
    }
}

By configuring these flavors, we can now easily manage different app icons, names, and settings for staging and production without manually changing files or risking confusion.

This flavors setup, combined with our .env configuration, provides a robust framework for managing multiple environments within the same application, ensuring that each environment is visually distinct and properly configured for its intended use.

What's your version?

For any mobile application, managing versioning is crucial not only for tracking releases but also for complying with the specific requirements of app distribution platforms. Both Android and iOS have their own versioning systems, with Android requiring a unique version code for every release on the Google Play Console and iOS necessitating a unique build number for each version name on the App Store. This is really hard to keep up for us, as we strive to have a very quick release cycle and short testing phase.

Challenges with Manual Versioning

  • Human Error: Manually updating version codes and names for every release increases the likelihood of mistakes. Missing a version code update, for instance, could lead to a rejected build on the Play Console.
  • Inconsistency: With manual processes, maintaining consistency across different environments (e.g., staging, production) becomes challenging. It's easy to forget to increment a version number or mix up values between environments.
  • Deployment Delays: Each version change involves editing configuration files, which, if done manually, can slow down the deployment pipeline. In a fast-paced development environment like Swypex, where quick iteration is essential, these delays can add up.

To streamline this process and eliminate the risks associated with manual versioning, we decided to externalize the control of version names and codes, automating them as part of our build process.

Automating Version Control

Android

On Android, versioning is handled in the build.gradle file. Usually, developers hardcode the version name and code directly in this file, but this approach is prone to the issues mentioned above. To avoid this, we replaced static values with dynamic ones that can be controlled externally:

def getMyVersionCode = {
    -> project.hasProperty('versionCode')
            ? versionCode.toInteger()
            : 1
}

def getMyVersionName = {
    -> project.hasProperty('versionName')
            ? versionName
            : "1.0.0"
}

android {
    defaultConfig {
        versionCode getMyVersionCode()
        versionName getMyVersionName()
    }
}

iOS

For iOS, versioning is managed through the Info.plist file and unlike Android, iOS allows you to use placeholders that can be replaced with specific values during the build process that can get injected using environment variables automatically:

<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>

This setup greatly Reduced Risk of Human Error, added Consistency Across Platforms, and Improved Deployment Efficiency for us.


Wrap up

In the first part of these series, we've outlined the foundational steps in environment setup, which are crucial for building and deploying apps efficiently. Though these steps may seem small, they are the starting point for saving countless hours in the long run. By standardizing and automating these early stages, we gain greater control over the build process, making it not only scalable but also easier to maintain.

This approach lays the groundwork for a more automated and streamlined build pipeline, allowing us to focus on delivering high-quality features rather than getting bogged down by manual tasks. As we move forward into the build and deployment phases, the benefits of this structured setup will become even more evident, helping us achieve faster releases and a more reliable product.

What's Next?

But this is just the beginning. In Part 2, we'll dive deeper into the build and deployment stages, where we'll explore how these initial steps come together to create a seamless, automated, and scalable workflow.


Excited about shaping the future of mobile app development? Come work with us!

At Swypex, we're always looking for talented engineers who are eager to innovate and make an impact. Apply now and join our growing team!