Skip to content

Commit aa498cd

Browse files
authored
Add API to read flavor from framework at run time (#134179)
Resolves #128046. Adds a services API that allows flutter app developers to write app code that determines `--flavor` the app was built with. This is implemented by having the tool adding the value of `--flavor` to its list of dart environment declarations, which will be available to the app at run time. Specifically,`FLUTTER_APP_FLAVOR` is set. I chose this implementation for its simplicity. There is some precedent for this, but only for web ([example](https://github.com/flutter/flutter/blob/cd2f3f5e78409027d3c9014172708e4dec7f2185/packages/flutter_tools/lib/src/runner/flutter_command.dart#L1231)).
1 parent c35515a commit aa498cd

File tree

5 files changed

+109
-3
lines changed

5 files changed

+109
-3
lines changed

dev/integration_tests/flavors/integration_test/integration_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:flavors/main.dart' as app;
6+
import 'package:flutter/services.dart';
67
import 'package:flutter_test/flutter_test.dart';
78
import 'package:integration_test/integration_test.dart';
89

@@ -16,6 +17,7 @@ void main() {
1617
await tester.pumpAndSettle();
1718

1819
expect(find.text('paid'), findsOneWidget);
20+
expect(appFlavor, 'paid');
1921
});
2022
});
2123
}

packages/flutter/lib/services.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export 'src/services/browser_context_menu.dart';
1919
export 'src/services/clipboard.dart';
2020
export 'src/services/debug.dart';
2121
export 'src/services/deferred_component.dart';
22+
export 'src/services/flavor.dart';
2223
export 'src/services/font_loader.dart';
2324
export 'src/services/haptic_feedback.dart';
2425
export 'src/services/hardware_keyboard.dart';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// The flavor this app was built with.
6+
///
7+
/// This is equivalent to the value argued to the `--flavor` option at build time.
8+
/// This will be `null` if the `--flavor` option was not provided.
9+
const String? appFlavor = String.fromEnvironment('FLUTTER_APP_FLAVOR') != '' ?
10+
String.fromEnvironment('FLUTTER_APP_FLAVOR') : null;

packages/flutter_tools/lib/src/runner/flutter_command.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,10 +1247,20 @@ abstract class FlutterCommand extends Command<void> {
12471247
}
12481248
}
12491249

1250+
final String? flavor = argParser.options.containsKey('flavor') ? stringArg('flavor') : null;
1251+
if (flavor != null) {
1252+
if (globals.platform.environment['FLUTTER_APP_FLAVOR'] != null) {
1253+
throwToolExit('FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.');
1254+
}
1255+
if (dartDefines.any((String define) => define.startsWith('FLUTTER_APP_FLAVOR'))) {
1256+
throwToolExit('FLUTTER_APP_FLAVOR is used by the framework and cannot be '
1257+
'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}');
1258+
}
1259+
dartDefines.add('FLUTTER_APP_FLAVOR=$flavor');
1260+
}
1261+
12501262
return BuildInfo(buildMode,
1251-
argParser.options.containsKey('flavor')
1252-
? stringArg('flavor')
1253-
: null,
1263+
flavor,
12541264
trackWidgetCreation: trackWidgetCreation,
12551265
frontendServerStarterPath: argParser.options
12561266
.containsKey(FlutterOptions.kFrontendServerStarterPath)

packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import 'package:flutter_tools/src/base/common.dart';
1111
import 'package:flutter_tools/src/base/error_handling_io.dart';
1212
import 'package:flutter_tools/src/base/file_system.dart';
1313
import 'package:flutter_tools/src/base/io.dart';
14+
import 'package:flutter_tools/src/base/logger.dart';
15+
import 'package:flutter_tools/src/base/platform.dart';
1416
import 'package:flutter_tools/src/base/signals.dart';
1517
import 'package:flutter_tools/src/base/time.dart';
1618
import 'package:flutter_tools/src/base/user_messages.dart';
1719
import 'package:flutter_tools/src/build_info.dart';
1820
import 'package:flutter_tools/src/cache.dart';
21+
import 'package:flutter_tools/src/commands/run.dart';
1922
import 'package:flutter_tools/src/dart/pub.dart';
2023
import 'package:flutter_tools/src/device.dart';
2124
import 'package:flutter_tools/src/globals.dart' as globals;
@@ -700,6 +703,67 @@ void main() {
700703
expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDevice));
701704
});
702705
});
706+
707+
group('--flavor', () {
708+
late _TestDeviceManager testDeviceManager;
709+
late Logger logger;
710+
late FileSystem fileSystem;
711+
712+
setUp(() {
713+
logger = BufferLogger.test();
714+
testDeviceManager = _TestDeviceManager(logger: logger);
715+
fileSystem = MemoryFileSystem.test();
716+
});
717+
718+
testUsingContext("tool exits when FLUTTER_APP_FLAVOR is already set in user's environment", () async {
719+
fileSystem.file('lib/main.dart').createSync(recursive: true);
720+
fileSystem.file('pubspec.yaml').createSync();
721+
fileSystem.file('.packages').createSync();
722+
723+
final FakeDevice device = FakeDevice('name', 'id');
724+
testDeviceManager.devices = <Device>[device];
725+
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
726+
final CommandRunner<void> runner = createTestCommandRunner(command);
727+
728+
expect(runner.run(<String>['run', '--no-pub', '--no-hot', '--flavor=strawberry']),
729+
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.'));
730+
731+
}, overrides: <Type, Generator>{
732+
DeviceManager: () => testDeviceManager,
733+
Platform: () => FakePlatform(
734+
environment: <String, String>{
735+
'FLUTTER_APP_FLAVOR': 'I was already set'
736+
}
737+
),
738+
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
739+
FileSystem: () => fileSystem,
740+
ProcessManager: () => FakeProcessManager.any(),
741+
});
742+
743+
testUsingContext('tool exits when FLUTTER_APP_FLAVOR is set in --dart-define or --dart-define-from-file', () async {
744+
fileSystem.file('lib/main.dart').createSync(recursive: true);
745+
fileSystem.file('pubspec.yaml').createSync();
746+
fileSystem.file('.packages').createSync();
747+
fileSystem.file('config.json')..createSync()..writeAsStringSync('{"FLUTTER_APP_FLAVOR": "strawberry"}');
748+
749+
final FakeDevice device = FakeDevice('name', 'id');
750+
testDeviceManager.devices = <Device>[device];
751+
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
752+
final CommandRunner<void> runner = createTestCommandRunner(command);
753+
754+
expect(runner.run(<String>['run', '--dart-define=FLUTTER_APP_FLAVOR=strawberry', '--no-pub', '--no-hot', '--flavor=strawberry']),
755+
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file'));
756+
757+
expect(runner.run(<String>['run', '--dart-define-from-file=config.json', '--no-pub', '--no-hot', '--flavor=strawberry']),
758+
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file'));
759+
}, overrides: <Type, Generator>{
760+
DeviceManager: () => testDeviceManager,
761+
Platform: () => FakePlatform(),
762+
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
763+
FileSystem: () => fileSystem,
764+
ProcessManager: () => FakeProcessManager.any(),
765+
});
766+
});
703767
});
704768
}
705769

@@ -853,3 +917,22 @@ class FakePub extends Fake implements Pub {
853917
PubOutputMode outputMode = PubOutputMode.all,
854918
}) async { }
855919
}
920+
921+
class _TestDeviceManager extends DeviceManager {
922+
_TestDeviceManager({required super.logger});
923+
List<Device> devices = <Device>[];
924+
925+
@override
926+
List<DeviceDiscovery> get deviceDiscoverers {
927+
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
928+
devices.forEach(discoverer.addDevice);
929+
return <DeviceDiscovery>[discoverer];
930+
}
931+
}
932+
933+
class _TestRunCommandThatOnlyValidates extends RunCommand {
934+
@override
935+
Future<FlutterCommandResult> runCommand() async {
936+
return FlutterCommandResult.success();
937+
}
938+
}

0 commit comments

Comments
 (0)