Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 677e562

Browse files
gaaclarkevashworth
andauthored
[ios]: Warning for FlutterAppDelegate.window.rootViewController in launch functions (#169166)
fixes flutter/flutter#169218 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Victoria Ashworth <[email protected]>
1 parent bcfb871 commit 677e562

5 files changed

Lines changed: 241 additions & 1 deletion

File tree

packages/flutter_tools/bin/xcode_backend.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ class Context {
128128
}
129129
errorOutput.write(resultStderr);
130130
echoError(errorOutput.toString());
131+
132+
// Stream stderr to the Flutter build process.
133+
// When in verbose mode, `echoError` above will show the logs. So only
134+
// stream if not in verbose mode to avoid duplicate logs.
135+
// Also, only stream if exitCode is 0 since errors are handled separately
136+
// by the tool on failure.
137+
if (!verbose && exitCode == 0) {
138+
streamOutput(errorOutput.toString());
139+
}
131140
}
132141
if (!allowFail && result.exitCode != 0) {
133142
throw Exception('Command "$bin ${args.join(' ')}" exited with code ${result.exitCode}');

packages/flutter_tools/lib/src/build_system/targets/ios.dart

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../base/build.dart';
1010
import '../../base/common.dart';
1111
import '../../base/file_system.dart';
1212
import '../../base/io.dart';
13+
import '../../base/logger.dart' show Logger;
1314
import '../../base/process.dart';
1415
import '../../base/version.dart';
1516
import '../../build_info.dart';
@@ -365,6 +366,126 @@ class DebugUnpackIOS extends UnpackIOS {
365366
BuildMode get buildMode => BuildMode.debug;
366367
}
367368

369+
// TODO(gaaclarke): Remove this after a reasonable amount of time where the
370+
// UISceneDelegate migration being on stable. This incurs a minor build time
371+
// cost.
372+
Future<void> _checkForLaunchRootViewControllerAccessDeprecation(
373+
Logger logger,
374+
File file,
375+
Pattern usage,
376+
Pattern terminator,
377+
) async {
378+
final List<String> lines = file.readAsLinesSync();
379+
380+
bool inDidFinishLaunchingWithOptions = false;
381+
int lineNumber = 0;
382+
for (final String line in lines) {
383+
lineNumber += 1;
384+
if (!inDidFinishLaunchingWithOptions) {
385+
if (line.contains('didFinishLaunchingWithOptions')) {
386+
inDidFinishLaunchingWithOptions = true;
387+
}
388+
} else {
389+
if (line.startsWith(terminator)) {
390+
inDidFinishLaunchingWithOptions = false;
391+
} else if (line.contains(usage)) {
392+
_printWarning(
393+
logger,
394+
file.path,
395+
lineNumber,
396+
// TODO(gaaclarke): Add a link to the migration guide when it's written.
397+
'Flutter deprecation: Accessing rootViewController in `application:didFinishLaunchingWithOptions:` [flutter-launch-rootvc].\n'
398+
'\tnote: \n' // The space after `note:` is meaningful, it is required associate the note with the warning in Xcode.
399+
'\tAfter the UISceneDelegate migration the `UIApplicationDelegate.window` and '
400+
'`UIWindow.rootViewController` properties will not be set in '
401+
'`application:didFinishLaunchingWithOptions:`. If you are relying on that '
402+
'in order to register platform channels at application launch use the '
403+
'`FlutterPluginRegistry` API instead. Other setup can be moved to a '
404+
'FlutterViewController subclass (ex: `awakeFromNib`).',
405+
);
406+
}
407+
}
408+
}
409+
}
410+
411+
/// Checks [file] representing objc code for deprecated usage of the
412+
/// rootViewController and writes it to [logger].
413+
@visibleForTesting
414+
Future<void> checkForLaunchRootViewControllerAccessDeprecationObjc(Logger logger, File file) async {
415+
try {
416+
await _checkForLaunchRootViewControllerAccessDeprecation(
417+
logger,
418+
file,
419+
RegExp('self.*?window.*?rootViewController'),
420+
RegExp('^}'),
421+
);
422+
// ignore: avoid_catches_without_on_clauses
423+
} catch (_) {}
424+
}
425+
426+
/// Checks [file] representing swift code for deprecated usage of the
427+
/// rootViewController and writes it to [logger].
428+
@visibleForTesting
429+
Future<void> checkForLaunchRootViewControllerAccessDeprecationSwift(
430+
Logger logger,
431+
File file,
432+
) async {
433+
try {
434+
await _checkForLaunchRootViewControllerAccessDeprecation(
435+
logger,
436+
file,
437+
'window?.rootViewController',
438+
RegExp(r'^.*?func\s*?\S*?\('),
439+
);
440+
// ignore: avoid_catches_without_on_clauses
441+
} catch (_) {}
442+
}
443+
444+
void _printWarning(Logger logger, String path, int line, String warning) {
445+
logger.printWarning('$path:$line: warning: $warning');
446+
}
447+
448+
class _IssueLaunchRootViewControllerAccess extends Target {
449+
const _IssueLaunchRootViewControllerAccess();
450+
451+
@override
452+
Future<void> build(Environment environment) async {
453+
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
454+
if (flutterProject.ios.appDelegateSwift.existsSync()) {
455+
await checkForLaunchRootViewControllerAccessDeprecationSwift(
456+
environment.logger,
457+
flutterProject.ios.appDelegateSwift,
458+
);
459+
}
460+
if (flutterProject.ios.appDelegateObjc.existsSync()) {
461+
await checkForLaunchRootViewControllerAccessDeprecationObjc(
462+
environment.logger,
463+
flutterProject.ios.appDelegateObjc,
464+
);
465+
}
466+
}
467+
468+
@override
469+
List<Target> get dependencies => <Target>[];
470+
471+
@override
472+
List<Source> get inputs {
473+
return <Source>[
474+
const Source.pattern(
475+
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart',
476+
),
477+
Source.fromProject((FlutterProject project) => project.ios.appDelegateObjc, optional: true),
478+
Source.fromProject((FlutterProject project) => project.ios.appDelegateSwift, optional: true),
479+
];
480+
}
481+
482+
@override
483+
String get name => 'IssueLaunchRootViewControllerAccess';
484+
485+
@override
486+
List<Source> get outputs => <Source>[];
487+
}
488+
368489
abstract class IosLLDBInit extends Target {
369490
const IosLLDBInit();
370491

@@ -495,7 +616,11 @@ abstract class IosAssetBundle extends Target {
495616
const IosAssetBundle();
496617

497618
@override
498-
List<Target> get dependencies => const <Target>[KernelSnapshot(), InstallCodeAssets()];
619+
List<Target> get dependencies => const <Target>[
620+
KernelSnapshot(),
621+
InstallCodeAssets(),
622+
_IssueLaunchRootViewControllerAccess(),
623+
];
499624

500625
@override
501626
List<Source> get inputs => const <Source>[

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ Future<XcodeBuildResult> buildXcodeProject({
418418

419419
Future<void> listenToScriptOutputLine() async {
420420
final List<String> lines = await scriptOutputPipeFile!.readAsLines();
421+
bool inWarningBlock = false;
422+
bool inNoteBlock = false;
421423
for (final String line in lines) {
422424
if (line == 'done' || line == 'all done') {
423425
buildSubStatus?.stop();
@@ -426,6 +428,25 @@ Future<XcodeBuildResult> buildXcodeProject({
426428
return;
427429
}
428430
} else {
431+
if (!globals.logger.isVerbose) {
432+
if (line.contains('error:')) {
433+
globals.printError(line);
434+
} else if (line.contains('warning:')) {
435+
globals.printWarning(line);
436+
inWarningBlock = true;
437+
} else if (inNoteBlock) {
438+
globals.printWarning(line);
439+
inNoteBlock = false;
440+
} else if (inWarningBlock) {
441+
if (line.startsWith(RegExp(r'\s+?note[:]'))) {
442+
// Xcode doesn't echo this, so we don't.
443+
inNoteBlock = true;
444+
}
445+
inWarningBlock = false;
446+
}
447+
continue;
448+
}
449+
429450
initialBuildStatus?.cancel();
430451
initialBuildStatus = null;
431452
buildSubStatus = globals.logger.startProgress(

packages/flutter_tools/lib/src/xcode_project.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,10 @@ def __lldb_init_module(debugger: lldb.SBDebugger, _):
436436
File get appDelegateSwift =>
437437
_editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift');
438438

439+
/// The 'AppDelegate.m' file of the host app. This file might not exist if the app project uses Swift.
440+
File get appDelegateObjc =>
441+
_editableDirectory.childDirectory('Runner').childFile('AppDelegate.m');
442+
439443
File get infoPlist => _editableDirectory.childDirectory('Runner').childFile('Info.plist');
440444

441445
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');

packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,87 @@ void main() {
955955
);
956956
});
957957

958+
group('CheckForLaunchRootViewControllerAccessDeprecation', () {
959+
testWithoutContext('Swift Positive', () async {
960+
final File file = fileSystem.file('AppDelegate.swift');
961+
file.writeAsStringSync('''
962+
@objc class AppDelegate: FlutterAppDelegate {
963+
override func application(
964+
_ application: UIApplication,
965+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
966+
967+
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
968+
}
969+
}
970+
''');
971+
await checkForLaunchRootViewControllerAccessDeprecationSwift(logger, file);
972+
expect(
973+
logger.warningText,
974+
startsWith(
975+
'AppDelegate.swift:6: warning: Flutter deprecation: Accessing rootViewController',
976+
),
977+
);
978+
});
979+
980+
testWithoutContext('Swift Negative', () async {
981+
final File file = fileSystem.file('AppDelegate.swift');
982+
file.writeAsStringSync('''
983+
@objc class AppDelegate: FlutterAppDelegate {
984+
override func application(
985+
_ application: UIApplication,
986+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
987+
}
988+
989+
func doIt() {
990+
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
991+
}
992+
}
993+
''');
994+
await checkForLaunchRootViewControllerAccessDeprecationSwift(logger, file);
995+
expect(logger.warningText, equals(''));
996+
});
997+
998+
testWithoutContext('Objc Positive', () async {
999+
final File file = fileSystem.file('AppDelegate.m');
1000+
file.writeAsStringSync('''
1001+
@implementation AppDelegate
1002+
1003+
- (BOOL)application:(UIApplication*)application
1004+
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
1005+
FlutterViewController* controller =
1006+
(FlutterViewController*)self.window.rootViewController;
1007+
}
1008+
1009+
@end
1010+
''');
1011+
await checkForLaunchRootViewControllerAccessDeprecationObjc(logger, file);
1012+
expect(
1013+
logger.warningText,
1014+
startsWith('AppDelegate.m:6: warning: Flutter deprecation: Accessing rootViewController'),
1015+
);
1016+
});
1017+
1018+
testWithoutContext('Objc Negative', () async {
1019+
final File file = fileSystem.file('AppDelegate.m');
1020+
file.writeAsStringSync('''
1021+
@implementation AppDelegate
1022+
1023+
- (BOOL)application:(UIApplication*)application
1024+
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
1025+
}
1026+
1027+
- (void)doIt {
1028+
FlutterViewController* controller =
1029+
(FlutterViewController*)self.window.rootViewController;
1030+
}
1031+
1032+
@end
1033+
''');
1034+
await checkForLaunchRootViewControllerAccessDeprecationObjc(logger, file);
1035+
expect(logger.warningText, equals(''));
1036+
});
1037+
});
1038+
9581039
testWithoutContext('skips thin framework', () async {
9591040
binary.createSync(recursive: true);
9601041

0 commit comments

Comments
 (0)