ENVied: the best way to handle your environment variables in Flutter

ENVied: the best way to handle your environment variables in Flutter

ยท

3 min read

Hi everyone, in this post, I want to show you the best way to store environment variables in a Flutter project that I started to use some months ago in all of my projects after seeing it in this blog post from CodewithAndrea: How to Store API Keys in Flutter: --dart-define vs .env files.

In an application that communicates with external services, that needs to authenticate via API key or to configure particular settings we must set environment variables that must be kept as hidden as possible, especially those related to API keys, but rather conveniently to be used in our Flutter applications.

For all this, we are helped by the valid ENVied package that lets you create a convenient Dart class that maps the .env file. To install it we can add it to the pubspec.yaml file along with envied_generator and build_runner for the build step and generation of the Dart class:

dependencies:
  ...
  envied: ^0.3.0

dev_dependencies:
  ...
  envied_generator: ^0.3.0
  build_runner: ^2.3.3

Now we can create an .env file inside our project where we can add all our environment variables like this:

GREETINGS="Hello world!"
MY_NAME="Alberto"

Now we have to create a env.dart file where we can define our constants:

import 'package:envied/envied.dart';

part 'env.g.dart';

//Here you can specify the path to your .env file
//  relative to the project root
@Envied(path: '.env')
abstract class Env {
  //The ofuscate flag lets you make your variables more
  // secure through obfuscation
  @EnviedField(varName: 'GREETINGS', obfuscate: true)
  static final greetings = _Env.greetings;
  @EnviedField(varName: 'MY_NAME', obfuscate: true)
  static final myName = _Env.myName;
}

Now we can do the build step and let ENVied generate the class for us, so let's run:

flutter pub run build_runner build --delete-conflicting-outputs

ENVied will generate a file named env.g.dart alongside env.dart with pretty much this content:

// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'env.dart';
// *****************************************************
// EnviedGenerator
// *****************************************************
class _Env {
  static const List<int> _enviedkeygreetings = [
    3271495377, 3997315090, 436058207, 3553787292, 3538777736,
    955590460, 3819871908, 4039201348, 2424541315, 3686169803,
    1299522498, 1263434759
  ];
  static const List<int> _envieddatagreetings = [
    3271495321, 3997315191, 436058163, 3553787376, 3538777831,
    955590428, 3819871955, 4039201323, 2424541425, 3686169767,
    1299522470, 1263434790
  ];
  static final greetings = String.fromCharCodes(
    List.generate(_envieddatagreetings.length, (i) => i, growable: false)
        .map((i) => _envieddatagreetings[i] ^ _enviedkeygreetings[i])
        .toList(growable: false),
  );
  static const List<int> _enviedkeymyName = [
    3026637516, 1908999005, 3526437322, 2478128297, 2753058997,
    855764648, 1111676782
  ];
  static const List<int> _envieddatamyName = [
    3026637453, 1908998961, 3526437288, 2478128332, 2753059015,
    855764700, 1111676673
  ];
  static final myName = String.fromCharCodes(
    List.generate(_envieddatamyName.length, (i) => i, growable: false)
        .map((i) => _envieddatamyName[i] ^ _enviedkeymyName[i])
        .toList(growable: false),
  );
}

Before proceeding it is good to exclude from version control the .env and env.g.dart files that contain all our variables, to do this simply add to the .gitignore file:

#Envied
*.env
env.g.dart

App Code

At this point, we can create a small example app that shows how to use our environment variables and call them within the code, so create a main.dart file with the following code:

import 'package:flutter/material.dart';

import 'env.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'ENVied Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text("ENVied Demo"),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                Env.greetings,
              ),
              Text(
                "Hi, I'm ${Env.myName}",
              ),
            ],
          ),
        ),
      ),
    );
  }
}

As you can see we can call within the code of our app the environment variables we created simply with:

//Import the env class
import 'env.dart';

//Use variables
Env.greetings
Env.myName

The result is something like this:

Conclusions

In this post, we saw how to secure our environment variables through obfuscation, how to exclude them from version control so they are not exposed in our repositories, and how to generate a Dart class that we can access throughout the code to conveniently use our variables. So we have all the requirements to start developing an app with a good degree of security.

Code strong, Alberto

Did you find this article valuable?

Support Flutter and Other Experiments by becoming a sponsor. Any amount is appreciated!