Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate create command to null safety #104484

Merged
merged 1 commit into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 53 additions & 58 deletions packages/flutter_tools/lib/src/commands/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @dart = 2.8

import '../android/gradle_utils.dart' as gradle;
import '../base/common.dart';
import '../base/context.dart';
Expand Down Expand Up @@ -32,8 +30,8 @@ const String kPlatformHelp =

class CreateCommand extends CreateBase {
CreateCommand({
bool verboseHelp = false,
}) : super(verboseHelp: verboseHelp) {
super.verboseHelp = false,
}) {
addPlatformsOptions(customHelp: kPlatformHelp);
argParser.addOption(
'template',
Expand All @@ -57,7 +55,6 @@ class CreateCommand extends CreateBase {
flutterProjectTypeToString(FlutterProjectType.module): 'Generate a project to add a Flutter module to an '
'existing Android or iOS application.',
},
defaultsTo: null,
);
argParser.addOption(
'sample',
Expand All @@ -66,7 +63,6 @@ class CreateCommand extends CreateBase {
'"--template=app". The value should be the sample ID of the desired sample from the API '
'documentation website (http://docs.flutter.dev/). An example can be found at: '
'https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html',
defaultsTo: null,
valueHelp: 'id',
);
argParser.addOption(
Expand All @@ -88,7 +84,7 @@ class CreateCommand extends CreateBase {
String get category => FlutterCommandCategory.project;

@override
String get invocation => '${runner.executableName} $name <output directory>';
String get invocation => '${runner?.executableName} $name <output directory>';

@override
Future<CustomDimensions> get usageValues async {
Expand All @@ -100,8 +96,7 @@ class CreateCommand extends CreateBase {
}

// Lazy-initialize the net utilities with values from the context.
Net _cachedNet;
Net get _net => _cachedNet ??= Net(
late final Net _net = Net(
httpClientFactory: context.get<HttpClientFactory>(),
logger: globals.logger,
platform: globals.platform,
Expand All @@ -112,25 +107,25 @@ class CreateCommand extends CreateBase {
? 'api.flutter.dev'
: 'master-api.flutter.dev';

Future<String> _fetchSampleFromServer(String sampleId) async {
Future<String?> _fetchSampleFromServer(String sampleId) async {
// Sanity check the sampleId
if (sampleId.contains(RegExp(r'[^-\w\.]'))) {
throwToolExit('Sample ID "$sampleId" contains invalid characters. Check the ID in the '
'documentation and try again.');
}

final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/$sampleId.dart');
final List<int> data = await _net.fetchUrl(snippetsUri);
final List<int>? data = await _net.fetchUrl(snippetsUri);
if (data == null || data.isEmpty) {
return null;
}
return utf8.decode(data);
}

/// Fetches the samples index file from the Flutter docs website.
Future<String> _fetchSamplesIndexFromServer() async {
Future<String?> _fetchSamplesIndexFromServer() async {
final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/index.json');
final List<int> data = await _net.fetchUrl(snippetsUri, maxAttempts: 2);
final List<int>? data = await _net.fetchUrl(snippetsUri, maxAttempts: 2);
if (data == null || data.isEmpty) {
return null;
}
Expand All @@ -145,7 +140,7 @@ class CreateCommand extends CreateBase {
if (outputFile.existsSync()) {
throwToolExit('File "$outputFilePath" already exists', exitCode: 1);
}
final String samplesJson = await _fetchSamplesIndexFromServer();
final String? samplesJson = await _fetchSamplesIndexFromServer();
if (samplesJson == null) {
throwToolExit('Unable to download samples', exitCode: 2);
} else {
Expand All @@ -158,11 +153,12 @@ class CreateCommand extends CreateBase {
}

FlutterProjectType _getProjectType(Directory projectDir) {
FlutterProjectType template;
FlutterProjectType detectedProjectType;
FlutterProjectType? template;
FlutterProjectType? detectedProjectType;
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
if (argResults['template'] != null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this wasn't safe... #101595

template = stringToProjectType(stringArgDeprecated('template'));
final String? templateArgument = stringArg('template');
if (templateArgument != null) {
template = stringToProjectType(templateArgument);
}
// If the project directory exists and isn't empty, then try to determine the template
// type from the project directory.
Expand All @@ -188,23 +184,25 @@ class CreateCommand extends CreateBase {

@override
Future<FlutterCommandResult> runCommand() async {
if (argResults['list-samples'] != null) {
final String? listSamples = stringArg('list-samples');
if (listSamples != null) {
// _writeSamplesJson can potentially be long-lived.
await _writeSamplesJson(stringArgDeprecated('list-samples'));
await _writeSamplesJson(listSamples);
return FlutterCommandResult.success();
}

validateOutputDirectoryArg();

String sampleCode;
if (argResults['sample'] != null) {
if (argResults['template'] != null &&
stringToProjectType(stringArgDeprecated('template') ?? 'app') != FlutterProjectType.app) {
String? sampleCode;
final String? sampleArgument = stringArg('sample');
if (sampleArgument != null) {
final String? templateArgument = stringArg('template');
if (templateArgument != null && stringToProjectType(templateArgument) != FlutterProjectType.app) {
throwToolExit('Cannot specify --sample with a project type other than '
'"${flutterProjectTypeToString(FlutterProjectType.app)}"');
}
// Fetch the sample from the server.
sampleCode = await _fetchSampleFromServer(stringArgDeprecated('sample'));
sampleCode = await _fetchSampleFromServer(sampleArgument);
}

final FlutterProjectType template = _getProjectType(projectDir);
Expand All @@ -215,7 +213,7 @@ class CreateCommand extends CreateBase {

final List<String> platforms = stringsArg('platforms');
// `--platforms` does not support module or package.
if (argResults.wasParsed('platforms') && (generateModule || generatePackage)) {
if (argResults!.wasParsed('platforms') && (generateModule || generatePackage)) {
final String template = generateModule ? 'module' : 'package';
throwToolExit(
'The "--platforms" argument is not supported in $template template.',
Expand All @@ -224,18 +222,18 @@ class CreateCommand extends CreateBase {
} else if (platforms == null || platforms.isEmpty) {
throwToolExit('Must specify at least one platform using --platforms',
exitCode: 2);
} else if (generateFfiPlugin && argResults.wasParsed('platforms') && platforms.contains('web')) {
} else if (generateFfiPlugin && argResults!.wasParsed('platforms') && platforms.contains('web')) {
throwToolExit(
'The web platform is not supported in plugin_ffi template.',
exitCode: 2,
);
} else if (generateFfiPlugin && argResults.wasParsed('ios-language')) {
} else if (generateFfiPlugin && argResults!.wasParsed('ios-language')) {
throwToolExit(
'The "ios-language" option is not supported with the plugin_ffi '
'template: the language will always be C or C++.',
exitCode: 2,
);
} else if (generateFfiPlugin && argResults.wasParsed('android-language')) {
} else if (generateFfiPlugin && argResults!.wasParsed('android-language')) {
throwToolExit(
'The "android-language" option is not supported with the plugin_ffi '
'template: the language will always be C or C++.',
Expand All @@ -258,7 +256,7 @@ class CreateCommand extends CreateBase {

final String dartSdk = globals.cache.dartSdkBuild;
final bool includeIos = featureFlags.isIOSEnabled && platforms.contains('ios');
String developmentTeam;
String? developmentTeam;
if (includeIos) {
developmentTeam = await getCodeSigningIdentityDevelopmentTeam(
processManager: globals.processManager,
Expand All @@ -272,7 +270,7 @@ class CreateCommand extends CreateBase {
// The dart project_name is in snake_case, this variable is the Title Case of the Project Name.
final String titleCaseProjectName = snakeCaseToTitleCase(projectName);

final Map<String, Object> templateContext = createTemplateContext(
final Map<String, Object?> templateContext = createTemplateContext(
organization: organization,
projectName: projectName,
titleCaseProjectName: titleCaseProjectName,
Expand Down Expand Up @@ -432,12 +430,12 @@ Your $application code is in $relativeAppMain.

Future<int> _generateModule(
Directory directory,
Map<String, dynamic> templateContext, {
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
}) async {
int generatedCount = 0;
final String description = argResults.wasParsed('description')
final String? description = argResults!.wasParsed('description')
? stringArgDeprecated('description')
: 'A new Flutter module project.';
templateContext['description'] = description;
Expand All @@ -453,7 +451,6 @@ Your $application code is in $relativeAppMain.
context: PubContext.create,
directory: directory.path,
offline: boolArgDeprecated('offline'),
generateSyntheticPackage: false,
);
final FlutterProject project = FlutterProject.fromDirectory(directory);
await project.ensureReadyForPlatformSpecificTooling(
Expand All @@ -466,12 +463,12 @@ Your $application code is in $relativeAppMain.

Future<int> _generatePackage(
Directory directory,
Map<String, dynamic> templateContext, {
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
}) async {
int generatedCount = 0;
final String description = argResults.wasParsed('description')
final String? description = argResults!.wasParsed('description')
? stringArgDeprecated('description')
: 'A new Flutter package project.';
templateContext['description'] = description;
Expand All @@ -487,21 +484,20 @@ Your $application code is in $relativeAppMain.
context: PubContext.createPackage,
directory: directory.path,
offline: boolArgDeprecated('offline'),
generateSyntheticPackage: false,
);
}
return generatedCount;
}

Future<int> _generateMethodChannelPlugin(
Directory directory,
Map<String, dynamic> templateContext, {
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
FlutterProjectType projectType,
required FlutterProjectType projectType,
}) async {
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults.wasParsed('platforms')) {
if (!argResults!.wasParsed('platforms')) {
for (final String platform in kAllCreatePlatforms) {
templateContext[platform] = false;
}
Expand All @@ -517,7 +513,7 @@ Your $application code is in $relativeAppMain.
final bool willAddPlatforms = platformsToAdd.isNotEmpty;
templateContext['no_platforms'] = !willAddPlatforms;
int generatedCount = 0;
final String description = argResults.wasParsed('description')
final String? description = argResults!.wasParsed('description')
? stringArgDeprecated('description')
: 'A new Flutter plugin project.';
templateContext['description'] = description;
Expand All @@ -534,7 +530,6 @@ Your $application code is in $relativeAppMain.
context: PubContext.createPlugin,
directory: directory.path,
offline: boolArgDeprecated('offline'),
generateSyntheticPackage: false,
);
}

Expand All @@ -545,9 +540,9 @@ Your $application code is in $relativeAppMain.
project: project, requireAndroidSdk: false);
}

final String projectName = templateContext['projectName'] as String;
final String organization = templateContext['organization'] as String;
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String;
final String? projectName = templateContext['projectName'] as String?;
final String organization = templateContext['organization']! as String; // Required to make the context.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is icky but that's what we get serializing and deserializing into maps.

final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?;
final String exampleProjectName = '${projectName}_example';
templateContext['projectName'] = exampleProjectName;
templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName);
Expand All @@ -572,13 +567,13 @@ Your $application code is in $relativeAppMain.

Future<int> _generateFfiPlugin(
Directory directory,
Map<String, dynamic> templateContext, {
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
FlutterProjectType projectType,
required FlutterProjectType projectType,
}) async {
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults.wasParsed('platforms')) {
if (!argResults!.wasParsed('platforms')) {
for (final String platform in kAllCreatePlatforms) {
templateContext[platform] = false;
}
Expand All @@ -596,7 +591,7 @@ Your $application code is in $relativeAppMain.
final bool willAddPlatforms = platformsToAdd.isNotEmpty;
templateContext['no_platforms'] = !willAddPlatforms;
int generatedCount = 0;
final String description = argResults.wasParsed('description')
final String? description = argResults!.wasParsed('description')
? stringArgDeprecated('description')
: 'A new Flutter FFI plugin project.';
templateContext['description'] = description;
Expand All @@ -613,7 +608,6 @@ Your $application code is in $relativeAppMain.
context: PubContext.createPlugin,
directory: directory.path,
offline: boolArgDeprecated('offline'),
generateSyntheticPackage: false,
);
}

Expand All @@ -623,9 +617,9 @@ Your $application code is in $relativeAppMain.
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
}

final String projectName = templateContext['projectName'] as String;
final String organization = templateContext['organization'] as String;
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String;
final String? projectName = templateContext['projectName'] as String?;
final String organization = templateContext['organization']! as String; // Required to make the context.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?;
final String exampleProjectName = '${projectName}_example';
templateContext['projectName'] = exampleProjectName;
templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName);
Expand Down Expand Up @@ -662,7 +656,7 @@ Your $application code is in $relativeAppMain.
return -files.length;
}

List<String> _getSupportedPlatformsFromTemplateContext(Map<String, dynamic> templateContext) {
List<String> _getSupportedPlatformsFromTemplateContext(Map<String, Object?> templateContext) {
return <String>[
for (String platform in kAllCreatePlatforms)
if (templateContext[platform] == true) platform,
Expand All @@ -671,7 +665,7 @@ Your $application code is in $relativeAppMain.

// Returns a list of platforms that are explicitly requested by user via `--platforms`.
List<String> _getUserRequestedPlatforms() {
if (!argResults.wasParsed('platforms')) {
if (!argResults!.wasParsed('platforms')) {
return <String>[];
}
return stringsArg('platforms');
Expand All @@ -682,10 +676,11 @@ Your $application code is in $relativeAppMain.
// Determine what platforms are supported based on generated files.
List<String> _getSupportedPlatformsInPlugin(Directory projectDir) {
final String pubspecPath = globals.fs.path.join(projectDir.absolute.path, 'pubspec.yaml');
final FlutterManifest manifest = FlutterManifest.createFromPath(pubspecPath, fileSystem: globals.fs, logger: globals.logger);
final List<String> platforms = manifest.validSupportedPlatforms == null
final FlutterManifest? manifest = FlutterManifest.createFromPath(pubspecPath, fileSystem: globals.fs, logger: globals.logger);
final Map<String, Object?>? validSupportedPlatforms = manifest?.validSupportedPlatforms;
final List<String> platforms = validSupportedPlatforms == null
? <String>[]
: manifest.validSupportedPlatforms.keys.toList();
: validSupportedPlatforms.keys.toList();
return platforms;
}

Expand Down