Custom script for generating environment-specific code for different platforms
Features
The flutter_env.dart file appears to be a custom script for generating environment-specific code for different platforms (Dart, Objective-C, and Gradle for Android). It reads from an environment file (.env by default), and generates code based on the key-value pairs in the environment file.
Here's a basic usage guide:
- Create an environment file: Create a .env file in your project root (or specify a different file using the envfile argument). This file should contain key-value pairs, one per line, like this:
API_KEY=123456
BASE_URL=https://api.example.com
2.Run the script: You can run the DotENV class with the command-line arguments. For example:
void main(List<String> args) {
DotENV(args);
}
You can pass arguments to specify the platform (platform), the environment file (envfile), and the directory name (dirname). If not specified, it will use default values.
3.Generated code: The script will generate a Dart file (lib/env.dart by default) with a class ENV that contains static string properties for each key-value pair in the environment file. For example:
class ENV {
static String API_KEY = "123456";
static String BASE_URL = "https://api.example.com";
}
You can then import this file in your Flutter code and access the environment variables like this: ENV.API_KEY.
Please note that this is a basic guide and the actual usage may vary depending on your project setup and requirements. Also, remember to exclude your environment files from version control to avoid committing sensitive data.
Unit Test Command
At the root of the project, run the following command:
flutter test test/dotenv_test.dart
Generate the env.dart file
At the root of the example project, run the following command:
like: /Users/danli/Desktop/2024/packages/package_flutter_env/example
Because test is the development environment, the generated file is in the lib folder.
flutter test test/env_test.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:color_cards/color_common.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
// Generated directories file (created by widgetbook_generator).
// Run: `flutter pub run build_runner build --delete-conflicting-outputs`
// to generate `lib/main.directories.g.dart` which exposes `directories`.
import 'main.directories.g.dart' as generated;
void main() => runApp(const WidgetbookApp());
@widgetbook.App()
class WidgetbookApp extends StatelessWidget {
const WidgetbookApp({super.key});
@override
Widget build(BuildContext context) {
// Use generated directories from `main.directories.g.dart`.
return Widgetbook.material(directories: generated.directories);
}
}
@widgetbook.UseCase(name: 'Blue', type: ColorGrid)
Widget buildBlueAll(BuildContext context) {
return ColorGrid(
title: 'Blue',
colors: {'blueMedium': BlueColor.blueMedium, 'blueDark': BlueColor.blueDark, 'bluelight1': BlueColor.bluelight1, 'bluelight2': BlueColor.bluelight2},
);
}
@widgetbook.UseCase(name: 'Green', type: ColorGrid)
Widget buildGreenAll(BuildContext context) {
return ColorGrid(
title: 'Green',
colors: {'greenlight1': GreenColor.greenlight1, 'greenlight2': GreenColor.greenlight2, 'greenMedium': GreenColor.greenMedium, 'greenDark': GreenColor.greenDark},
);
}
// Sorting options for the color grid. Note: `luminance` sorts from
// light -> dark (descending luminance) which is typically most
// useful when you want the brightest colors first.
enum ColorSort { none, name, hex, luminance, hue }
// Helper: compute relative luminance (0..1) for sorting by brightness
double _luminance(Color c) {
// Use integer component accessors to avoid ambiguity and ensure
// consistent behavior across SDK versions.
final r = c.red / 255.0;
final g = c.green / 255.0;
final b = c.blue / 255.0;
double chan(double x) => x <= 0.03928 ? x / 12.92 : pow((x + 0.055) / 1.055, 2.4).toDouble();
return 0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b);
}
// Helper: convert rgb -> hsl (h in 0..1)
Map<String, double> _rgbToHsl(Color c) {
final r = c.red / 255.0;
final g = c.green / 255.0;
final b = c.blue / 255.0;
final maxv = [r, g, b].reduce((a, b) => a > b ? a : b);
final minv = [r, g, b].reduce((a, b) => a < b ? a : b);
final l = (maxv + minv) / 2.0;
double h = 0.0, s = 0.0;
final d = maxv - minv;
if (d != 0) {
s = l > 0.5 ? d / (2.0 - maxv - minv) : d / (maxv + minv);
if (maxv == r) {
h = (g - b) / d + (g < b ? 6 : 0);
} else if (maxv == g) {
h = (b - r) / d + 2;
} else {
h = (r - g) / d + 4;
}
h /= 6.0;
}
return {'h': h, 's': s, 'l': l};
}
// Convert Color to 32-bit ARGB int (replacement for deprecated `.value`).
int colorToArgb(Color c) => (c.alpha << 24) | (c.red << 16) | (c.green << 8) | c.blue;
class ColorGrid extends StatefulWidget {
final String title;
final Map<String, Color> colors;
final ColorSort sort;
const ColorGrid({super.key, required this.title, required this.colors, this.sort = ColorSort.luminance});
@override
State<ColorGrid> createState() => _ColorGridState();
}
class _ColorGridState extends State<ColorGrid> {
final TextEditingController _searchController = TextEditingController();
String _query = '';
@override
void initState() {
super.initState();
_searchController.addListener(() {
setState(() => _query = _searchController.text.trim());
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
String _hex(Color c) => '#${colorToArgb(c).toRadixString(16).toUpperCase().padLeft(8, '0')}';
// Normalize user query and hex strings for matching
String _normalizeHexLike(String s) {
var t = s.toUpperCase();
t = t.replaceAll('#', '');
if (t.startsWith('0X')) t = t.substring(2);
// If user provided 6-digit hex, expand to include alpha FF prefix so it matches our ARGB output
if (t.length == 6) t = 'FF$t';
return t;
}
bool _matchesQuery(String name, Color color) {
if (_query.isEmpty) return true;
final q = _query.toUpperCase();
// Match name
if (name.toUpperCase().contains(q)) return true;
// Match hex formats like 0xFF4A90A4, #FF4A90A4, #4A90A4, or 4A90A4
final normalizedQ = _normalizeHexLike(q);
final hex = _hex(color).replaceAll('#', ''); // ARGB uppercased
if (hex.contains(normalizedQ)) return true;
return false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('${widget.title} Colors')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Search field
Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: const InputDecoration(prefixIcon: Icon(Icons.search), hintText: 'Search by name or hex (e.g. 0xFF4A90A4, #4A90A4)'),
),
),
if (_query.isNotEmpty)
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
},
),
],
),
const SizedBox(height: 12),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
// Choose an itemWidth that looks good for your cards; smaller -> more columns
const double itemWidth = 200;
int columns = (constraints.maxWidth / itemWidth).floor();
if (columns < 1) columns = 1;
if (columns > 12) columns = 12; // upper limit to avoid extreme layouts
// Create a sortable list of entries, then apply the selected sort
final entries = widget.colors.entries.where((e) => _matchesQuery(e.key, e.value)).toList();
switch (widget.sort) {
case ColorSort.name:
entries.sort((a, b) => a.key.compareTo(b.key));
break;
case ColorSort.hex:
entries.sort((a, b) => colorToArgb(a.value).compareTo(colorToArgb(b.value)));
break;
case ColorSort.luminance:
// Sort from light -> dark (higher luminance first)
entries.sort((a, b) => _luminance(b.value).compareTo(_luminance(a.value)));
break;
case ColorSort.hue:
entries.sort((a, b) => _rgbToHsl(a.value)['h']!.compareTo(_rgbToHsl(b.value)['h']!));
break;
case ColorSort.none:
break;
}
return GridView.count(
crossAxisCount: columns,
childAspectRatio: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
children: entries.map((e) => _colorTile(e.key, e.value)).toList(),
);
},
),
),
],
),
),
);
}
Widget _colorTile(String name, Color color) {
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300)),
child: Row(
children: [
Container(
width: 72,
height: double.infinity,
decoration: BoxDecoration(color: color, borderRadius: const BorderRadius.only(topLeft: Radius.circular(8), bottomLeft: Radius.circular(8))),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 6),
Text(_hex(color), style: const TextStyle(color: Colors.black54)),
],
),
),
],
),
);
}
}