Skip to content

Conversation

@garrettjavalia
Copy link
Contributor

@garrettjavalia garrettjavalia commented Oct 10, 2025

(Please be generous for some typos and grammar error in sentences below. English is not my mother tongue)

TRDR : This patch fixes flutter run(and might be flutter build too) failure on iOS flutter apps with companion watchOS app in case the WKCompanionAppBundleIdentifier value is defined by xcode project configuration and uses different app bundle id by scheme(which happens when you do something like com.myapp.app1.dev things for scheme bundle id)

This change checks default scheme (the scheme with corresponding name for the debug/release mode and selected build flavor.) in the build settings check step of containsWatchCompanion function.

current code of containsWatchCompanion function works like below

  1. check all default Info.plist file's content of all targets to determine if the project has watchOS companion apps.
    (this doesn't work well in mordern xcode settings when they use multi plist files which are selected on build time by build configuration. Sometimes, the default Info.plist file doesn't even exist in the project.)

  2. check if "WKCompanionAppBundleIdentifier" is included in the xcode project info file(the ios/Runner.xcodeproj/project.pbxproj file in case of iOS project generated by flutter)

  3. If "WKCompanionAppBundleIdentifier" has found in the project info file,
    check build configuration variables of every single scheme but the current target scheme if they have config value with key "INFOPLIST_KEY_WKCompanionAppBundleIdentifier" identical to current build configuration's bundle identifier returned by productBundleIdentifier@xcode_project.dart function.

I believe The third step causes many problems reported in some issue and pr, saying iOS app with companion watchOS app fails on build or run.
#160622
#172436

In my case, My iOS project had multiple schemes which are linked to different build configurations, but using single Runner setting per app.
because we exclude current scheme for the check, we get all the INFOPLIST_KEY_WKCompanionAppBundleIdentifier values except the one which should be same with the identifier returned by productBundleIdentifier@xcode_project.dart function.
that is because companion watch apps doesn't have to have same WKCompanionAppBundleIdentifier with other scheme's main app. We have to check the default scheme to see watchOS companion app`s WKCompanionAppBundleIdentifier, not other scheme. (other schemes can have slightly different or totally different bundle id. that was to support different environment systems like dev, prod in my case)
as a result, flutter run gave me WatchOS app built for device target iOS/iPad, not Watch. which caused problem during installation of iOS app to a simulator because the embeded Watch App was in invalid format. I found this by inspecting Info.plist of generated watchOS app.

below is part of the flutter run verbose log with failure caused by old code.
[+1154 ms] An error was encountered processing the command (domain=IXUserPresentableErrorDomain, code=1):
App installation failed: ‘Bora Debug-dev’을(를) 설치할 수 없음 (which says "cannot install" in korean)
나중에 다시 시도하십시오. (which says "try again later" in korean)
Found WatchKit 2.0 app at /Users/javalia/Library/Developer/CoreSimulator/Devices/071691B3-7901-47E5-9B38-4D5B799F3530/data/Library/Caches/com.apple.mobile.installd.staging/temp.8HZIEG/extracted/Payload/Runner.app/Watch/bora Watch App.app but it does not have a WKWatchKitApp or WKApplication key set to true in its Info.plist
Underlying error (domain=IXUserPresentableErrorDomain, code=1):
‘Bora Debug-dev’을(를) 설치할 수 없음 (which says "cannot install" in korean)
나중에 다시 시도하십시오. (which says "try it later" in korean)

I suggest this change with two reasons :

  1. I think forcing watchOS app makers split their runner configurations to support watch companion app is something not recommendable/not good to force as a convention.
    (Which is implied in the comment which is removed by this pr, and could be one of the valid detouring.
    some people in this issue Unable To Run Flutter App With Apple Watch Extension After Upgrading 3.27.0 #160622 said that a detouring is adding of dummy WKCompanionAppBundleIdentifier configuration in iOS app, which should be in only watchOS apps in normal.)

  2. The function should work as it's name implies, so I guess it should not omit default scheme during check. Matching function's behavior with it's name will be likely to reduce potential errors caused by this change while fixing problems.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Previously, the loop skipped the default scheme when iterating over project schemes. This commit removes that check, ensuring all schemes, including the default, are processed.
@garrettjavalia garrettjavalia requested a review from a team as a code owner October 10, 2025 14:24
@github-actions github-actions bot added tool Affects the "flutter" command-line tool. See also t: labels. team-ios Owned by iOS platform team labels Oct 10, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the containsWatchCompanion function in packages/flutter_tools/lib/src/xcode_project.dart. The change removes a condition that was skipping the defaultScheme when searching for a watchOS companion app. This allows the function to check all schemes, including the default one, which addresses an issue where companion apps defined in the default scheme were not being detected. The change is logical and well-explained in the pull request description.

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 10, 2025

I changed the code to check only the scheme of current flavor, so that the slow build config generation is only executed for current flavor. the build config generation is slow as much as real build, so we must remove useless generation.

Refactors the logic to evaluate only the default scheme's build settings when verifying the watch companion app bundle identifier, instead of iterating through all schemes.
@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 10, 2025

Removed unrequired loop

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@LongCatIsLooong
Copy link
Contributor

from triage: @vashworth could you take a look since you also reviewed #172436?

@vashworth vashworth self-requested a review October 20, 2025 18:23
@vashworth
Copy link
Contributor

It appears that this PR checks for WKCompanionAppBundleIdentifier within the build settings of the default scheme. However, this does not makes sense as WKCompanionAppBundleIdentifier should be set in the companion Watch app's settings, not the iOS app target settings: https://developer.apple.com/documentation/bundleresources/information-property-list/wkcompanionappbundleidentifier?language=objc#Discussion

@garrettjavalia Did this solution not work for you? #172436

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 23, 2025

I did not test solution in #172436. My solution was made separately from that, and I see that solution is currently closed.

please let me explain about this more.

first of all, checking default(currently targeting) scheme is a valid operation in my opinion.
schemes can include multiple build targets(the apps) and build configurations to use on each operation(debug run, build, etc.)
Imagine a valid Xcode scheme which includes two targets(a host iOS app and a companion watchOS app.) for archive.(which flutter build command should handle, in real world senarios.)
(we surely can omit watchOS app in the scheme we want to use in flutter run command, but not for the flutter build command.)

the second part, which you might feel this code is wrong is, How it can work if it just processes xcode build configuration without specifying correct watchOS app as target. (buildSettingsForBuildInfo function and it`s call chain can specify a target to test, but current code doesn't specify one.)
by running flutter run -v, we can see that if no target is given, it generates config for all targets in the scheme at once.
and it surely includes INFOPLIST_KEY_WKCompanionAppBundleIdentifier if the scheme has watchOS app which is correctly configured by xcode project's target build settings.

now we can think that the valid way to handle this problem might be by making it check only watchOS App's build configuration, and yes that is a valid point. but that is for some cases when a scheme has two or more watchOS app(like one for companion app and one for independent app, or just two companion watchOS apps in wrong configuration.)

with above inspections, I suggest current PR a pretty good way to solve current situation. well, maybe I can improve the code to handle more complicated situations I mentioned above, but it would be optional for now. + I found that checking multiple schemes slow down the flutter run command a lot due to slow build config generation which is also mentioned in some code comments.

      // showBuildSettings is reported to occasionally timeout. Here, we give it
      // a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
      // When there is a timeout, we retry once.

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 23, 2025

I tried the #172436 and it worked but it checked different scheme.(my xcode project has a watchApp only scheme which is just used to run watchos app in simulator, So it has same INFOPLIST_KEY_WKCompanionAppBundleIdentifier value with the iOS app which is used by flutter run.)
So I think this is false positive.

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 23, 2025

Adding screenshot of valid xcode targer configurations of my project for your information.
스크린샷 2025-10-24 오전 5 26 57
스크린샷 2025-10-24 오전 5 28 27

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 25, 2025

Following screenshot shows how a scheme can have multiple apps in it's scheme configuration of xcode.
스크린샷 2025-10-25 오후 6 45 32

And this shows how a host iOS app handles it's companion watchOS app during the build phases.
스크린샷 2025-10-25 오후 6 47 15

So in short, Xcode project scheme != Xcode build target(app)

@vashworth
Copy link
Contributor

vashworth commented Oct 28, 2025

Do you not have a scheme for just the Watch app too?

Nevermind, I just saw this comment:

I tried the #172436 and it worked but it checked different scheme.(my xcode project has a watchApp only scheme which is just used to run watchos app in simulator, So it has same INFOPLIST_KEY_WKCompanionAppBundleIdentifier value with the iOS app which is used by flutter run.)
So I think this is false positive.

The purpose of loop through of schemes that you're trying to change is to find the watchApp only scheme to find the INFOPLIST_KEY_WKCompanionAppBundleIdentifier, so I think it's an accurate positive?

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 28, 2025

The target dependency and copying steps added on the host iOS app are not something i've invented, it's default behavior of Xcode when you add a watch companion app to a flutter generated iOS app project. isn't it better following convention of Xcode and other iOS apps in the world so that developers can use their contextual information for the similar works?

People on #160622 and myself did not make it to fix our project in the way that you've intended. instead, I had to spent whole night to find out what is happening. current code is blocking intended scheme pair solution. I got confused of it, sorry.

Furthermore, forcing watchOS app to be built by it's own scheme will force developers somehow tweak their host iOS app's build command to rely on another scheme's build output, which will be not so good in my opinion. (running xcode build command inside a xcode build script or in a comprehensive and independent flutter-build.sh script?) This is also invalid statement, for the host iOS app can be built correctly with the double scheme solution without additional tweak.

@vashworth
Copy link
Contributor

Sorry, I guess maybe I'm misunderstanding something...

If we look at the current logic, it gets all the schemes and then gets the build settings for each scheme (except the default). If the scheme has INFOPLIST_KEY_WKCompanionAppBundleIdentifier, it returns true if it matches the bundleIdentifier.

final String? defaultScheme = projectInfo.schemeFor(buildInfo);
if (defaultScheme == null) {
projectInfo.reportFlavorNotFoundAndExit();
}
for (final String scheme in projectInfo.schemes) {
// the default scheme should not be a watch scheme, so skip it
if (scheme == defaultScheme) {
continue;
}
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(
buildInfo,
deviceId: deviceId,
scheme: scheme,
isWatch: true,
);
if (allBuildSettings != null) {
final String? fromBuild = allBuildSettings['INFOPLIST_KEY_WKCompanionAppBundleIdentifier'];
if (bundleIdentifier == fromBuild) {
return true;
}
if (fromBuild != null && fromBuild.contains(r'$')) {
final String substitutedVariable = substituteXcodeVariables(fromBuild, allBuildSettings);
if (substitutedVariable == bundleIdentifier) {
return true;
}
}
}
}
return false;
}

If I understand correctly, you think it should check the default scheme because the default scheme may be building multiple targets, one of which may be the watch target. Correct?

However, one of the schemes should also be the watchApp only scheme. So it should be finding that scheme and returning true. It's not building that scheme - it's only using the scheme to determine if there is a watch companion app. I think the problem people are having is that it's failing when it tries to get the watch scheme's build settings as I explain here.

Does that makes sense or am I stilling missing something?

@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Oct 29, 2025

Thank you for considering my opinion/comments many times even it doesn't seems quite right at the first look. I really appreciate your effort. I tried to fix my own confusions and clear out the two possible solutions below.

"If I understand correctly, you think it should check the default scheme because the default scheme may be building multiple targets, one of which may be the watch target. Correct?"

  • Yes, that is my opinion and most important point of this PR.

I checked deviceId solution again throughly. and found that it solves slow startup problem.(which is not existing in my solution, too. it exists only in current flutter build chain.) xcode's slow build config generation was fixed by not specifying deviceId.
and I found that deviceId solution doesn't need a tweak on host iOS app to make a correct archive. Sorry for invalid information.

You mentioned that a Xcode project with companion watchOS app should have a dedicated scheme only having that watchOS app as target. The answer could be "almost certain.", not always.

  • When you add a companion watchOS app, Xcode generates dedicated scheme, while editing host iOS app's target build phases to support bundling of watchOS companion app within it
    (We have a documentation about the structure. https://developer.apple.com/documentation/bundleresources/placing-content-in-a-bundle as a support that we need to bundle watchOS app in the host iOS app. you can check the last part of the table.)
  • So, yes it is almost certain that we will have/keep dedicated scheme but we have to assume that developers are having dedicated watchOS app scheme which is matching with iOS host app bundle id variation. (I have all the dedicated schemes for all variations, but this is something good to do for development, not mandatory.)

things can be wrapped up like below :

  1. use solution "remove deviceId on check but rely on dedicated scheme" : it requires pair schemes of host iOS and watchOS app are correctly configured. If not, host iOS app will fail to run or build because it forces embedded watchOS app to be built for iOS, not watchOS. (check original PR of mine for description)
  2. use solution "check only current scheme(default scheme) if they have watch companion app with them." : correctly configuring current scheme is all you need.

what I think is option 2 is better.

@vashworth
Copy link
Contributor

vashworth commented Oct 29, 2025

How about as a comprise we do both? We change it to so that it will check the default scheme but it will also check the other schemes for the watch-only scheme?

final String? defaultScheme = projectInfo.schemeFor(buildInfo); 
   if (defaultScheme == null) { 
     projectInfo.reportFlavorNotFoundAndExit(); 
   } 
   for (final String scheme in projectInfo.schemes) { 
-     // the default scheme should not be a watch scheme, so skip it 
-     if (scheme == defaultScheme) { 
-       continue; 
-     } 
     final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo( 
       buildInfo, 
-       deviceId: deviceId, 
       scheme: scheme, 
       isWatch: true, 
     ); 
     if (allBuildSettings != null) { 
       final String? fromBuild = allBuildSettings['INFOPLIST_KEY_WKCompanionAppBundleIdentifier']; 
       if (bundleIdentifier == fromBuild) { 
         return true; 
       } 
       if (fromBuild != null && fromBuild.contains(r'$')) { 
         final String substitutedVariable = substituteXcodeVariables(fromBuild, allBuildSettings); 
         if (substitutedVariable == bundleIdentifier) { 
           return true; 
         } 
       } 
     } 
   } 
   return false; 
 } 

@garrettjavalia
Copy link
Contributor Author

Ok, I considered benefits of your merged approach, it seems it can handle some miss configured projects which can be produced in following steps.

  1. add multiple watch companion app targets to default scheme as build targets.
  2. some of those watch companion apps have wrong WKCompanionAppBundleIdentifier.
  3. because "parseXcodeBuildSettings" can handle only the latest value on the config output string, flutter can possibly fail on detecting correctly configured watch companion app with matching WKCompanionAppBundleIdentifier.
  4. the app build output still can work fine if the companion app with wrong WKCompanionAppBundleIdentifier is not included in the host iOS app's "embed watch content" build step.

(I have no idea what will happen if we actually include the wrong watch companion app in the iOS app bundle. neither for the case that multiple watch companion app is actually included in the iOS app.)

So, I guess it will be good to write a defensive and robust logic.

I agree with your solution and I will edit this PR to have latest flutter branch file and update it.

Previously, only the default scheme was checked for the WKCompanionAppBundleIdentifier. This update iterates through all available schemes in the project to ensure the correct bundle identifier is found, improving robustness against scheme with multiple watchOS apps.
@vashworth
Copy link
Contributor

Great! I'm glad you agree!

Can you ensure all tests in test/general.shard/project_test.dart pass and add an additional test that uses the default scheme?

@garrettjavalia
Copy link
Contributor Author

Updated existing tests and added new tests as a separated test group.

Adds mock build settings for XcodeProjectBuildContext in two test cases to ensure correct variable substitution during plist parsing.
@garrettjavalia
Copy link
Contributor Author

found out that 'watch companion' test group's test codes are getting host iOS app bundle name configuration with XcodeProjectBuildContext(scheme: 'Runner')
while it requires XcodeProjectBuildContext(scheme: 'Runner', deviceId: '123' ) for substituteXcodeVariables calls used to parse PList file.

refactored test codes in following ways.

  1. Removed ineffective configurations related to substituteXcodeVariables in test cases those are not interacting with plist xcode variable.
  2. unified code that sets bundle id into the 'watch companion' test group, which were divided into sub testgroups in my previous commit.

@garrettjavalia garrettjavalia marked this pull request as draft November 4, 2025 19:49
@garrettjavalia
Copy link
Contributor Author

I found that multi target schemes are Ok in xcode, but not in flutter code.
productBundleIdentifier function in xcode_project.dart is weak to multiple targets so that it can detect wrong bundle identifier.
It was pure luck that my xcode project was working fine in flutter run and flutter build command.
I will think of improvement to this. until then, I guess this PR should be draft

…roject

Detection of watch companion app without dedicated watch app target scheme requires watch companion app target included as a explicit build target of the default scheme, which causes malfunctioning of bundle identifier detection logic of flutter ios build chain which assumes default scheme will have only one explicit build target which generates it's build configuration for xcodebuild command.
@garrettjavalia
Copy link
Contributor Author

garrettjavalia commented Nov 5, 2025

I had to rollback code to something identical to #172436

Detection of watch companion app without dedicated watch app target scheme requires watch companion app target included as a explicit build target of the default scheme.
however, it causes malfunctioning of bundle identifier detection logic of flutter ios build chain.
because the existing logics of flutter ios build chain assumes default scheme will have only one explicit build target which generates it's build configuration output for xcodebuild command.

I don't think changing the entire flow just for watch companion app will give us any visible benefit for now.
It should be some high level redesign of flutter/ios integration.

@garrettjavalia garrettjavalia marked this pull request as ready for review November 5, 2025 08:12
@vashworth vashworth requested a review from LouiseHsu November 10, 2025 19:55
@vashworth
Copy link
Contributor

@LouiseHsu Can you do a second review?

Copy link
Contributor

@LouiseHsu LouiseHsu left a comment

Choose a reason for hiding this comment

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

tiny typo nit, lgtm!

@vashworth vashworth added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 18, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Nov 18, 2025
Merged via the queue into flutter:master with commit 3a28b6e Nov 18, 2025
140 of 141 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Nov 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Nov 18, 2025
…ct companion watch apps defined by only the project info file. (flutter/flutter#176832)
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Nov 18, 2025
…ct companion watch apps defined by only the project info file. (flutter/flutter#176832)
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Nov 18, 2025
flutter/flutter@cc14ef5...cb7b7df

2025-11-18 [email protected] Enable UIScene Migration and update create templates (flutter/flutter#178700)
2025-11-18 Minsuk Jung Fix #160622: change containsWatchConpanion function to detect companion watch apps defined by only the project info file. (flutter/flutter#176832)
2025-11-18 [email protected] Roll Skia from 614e71550fc3 to ca906091e199 (2 revisions) (flutter/flutter#178716)
2025-11-18 [email protected] Revert "[ Tool ] Don't delete `.dart_tool/widget_preview_scaffold` during `flutter clean` (#175664)" (flutter/flutter#178672)
2025-11-18 [email protected] Add missing flutter_lints dev dependencies (flutter/flutter#178105)
2025-11-18 [email protected] Roll Skia from ec2f626cdcad to 614e71550fc3 (3 revisions) (flutter/flutter#178708)
2025-11-18 [email protected] Roll Dart SDK from a8ad764281e3 to 312845b06afc (1 revision) (flutter/flutter#178704)
2025-11-18 [email protected] Roll Skia from d7268f8245f2 to ec2f626cdcad (1 revision) (flutter/flutter#178703)
2025-11-18 [email protected] Refactor SnackBar behavior selection example to use `RadioGroup` (flutter/flutter#178618)
2025-11-18 [email protected] Add framework-side hitTestBehavior support for Semantics widget and apply to ModalRoute (flutter/flutter#177570)
2025-11-18 [email protected] Fix deprecation warning in some API examples using RadioListTile (flutter/flutter#178635)
2025-11-18 [email protected] Roll Skia from 47fd0d9b1044 to d7268f8245f2 (6 revisions) (flutter/flutter#178695)
2025-11-18 [email protected] Roll Dart SDK from cf94632d94a1 to a8ad764281e3 (1 revision) (flutter/flutter#178694)
2025-11-18 [email protected] [fuchsia] Add wrapper for zx_iob_writev (flutter/flutter#178626)
2025-11-17 [email protected] Make a11y `computeChildGeometry` slightly faster (flutter/flutter#177477)
2025-11-17 [email protected] Fix DropdownMenu width when decorationBuilder provides label (flutter/flutter#178465)
2025-11-17 [email protected] Add DropdownMenuFormField.decorationBuilder (flutter/flutter#178640)
2025-11-17 [email protected] Roll Skia from 84c83c0dfb4a to 47fd0d9b1044 (4 revisions) (flutter/flutter#178673)
2025-11-17 [email protected] Small cleanup in `AndroidTouchProcessor.java‎` (flutter/flutter#178574)
2025-11-17 [email protected] Remove unnecessary `final` modifier in `StandardMessageCodec.java‎` (flutter/flutter#178598)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
IvoneDjaja pushed a commit to IvoneDjaja/flutter that referenced this pull request Nov 22, 2025
…companion watch apps defined by only the project info file. (flutter#176832)

(Please be generous for some typos and grammar error in sentences below.
English is not my mother tongue)

TRDR : This patch fixes flutter run(and might be flutter build too)
failure on iOS flutter apps with companion watchOS app in case the
WKCompanionAppBundleIdentifier value is defined by xcode project
configuration and uses different app bundle id by scheme(which happens
when you do something like com.myapp.app1.dev things for scheme bundle
id)

This change checks default scheme (the scheme with corresponding name
for the debug/release mode and selected build flavor.) in the build
settings check step of containsWatchCompanion function.


current code of containsWatchCompanion function works like below

1. check all default Info.plist file's content of all targets to
determine if the project has watchOS companion apps.
(this doesn't work well in mordern xcode settings when they use multi
plist files which are selected on build time by build configuration.
Sometimes, the default Info.plist file doesn't even exist in the
project.)

2. check if "WKCompanionAppBundleIdentifier" is included in the xcode
project info file(the ios/Runner.xcodeproj/project.pbxproj file in case
of iOS project generated by flutter)

3. If "WKCompanionAppBundleIdentifier" has found in the project info
file,
check build configuration variables of every single scheme but the
current target scheme if they have config value with key
"INFOPLIST_KEY_WKCompanionAppBundleIdentifier" identical to current
build configuration's bundle identifier returned by
productBundleIdentifier@xcode_project.dart function.


I believe The third step causes many problems reported in some issue and
pr, saying iOS app with companion watchOS app fails on build or run.
flutter#160622
flutter#172436

In my case, My iOS project had multiple schemes which are linked to
different build configurations, but using single Runner setting per app.
because we exclude current scheme for the check, we get all the
INFOPLIST_KEY_WKCompanionAppBundleIdentifier values except the one which
should be same with the identifier returned by
productBundleIdentifier@xcode_project.dart function.
that is because companion watch apps doesn't have to have same
WKCompanionAppBundleIdentifier with other scheme's main app. We have to
check the default scheme to see watchOS companion app`s
WKCompanionAppBundleIdentifier, not other scheme. (other schemes can
have slightly different or totally different bundle id. that was to
support different environment systems like dev, prod in my case)
as a result, flutter run gave me WatchOS app built for device target
iOS/iPad, not Watch. which caused problem during installation of iOS app
to a simulator because the embeded Watch App was in invalid format. I
found this by inspecting Info.plist of generated watchOS app.

below is part of the flutter run verbose log with failure caused by old
code.
[+1154 ms] An error was encountered processing the command
(domain=IXUserPresentableErrorDomain, code=1):
App installation failed: ‘Bora Debug-dev’을(를) 설치할 수 없음 (which says
"cannot install" in korean)
           나중에 다시 시도하십시오. (which says "try again later" in korean)
Found WatchKit 2.0 app at
/Users/javalia/Library/Developer/CoreSimulator/Devices/071691B3-7901-47E5-9B38-4D5B799F3530/data/Library/Caches/com.apple.mobile.installd.staging/temp.8HZIEG/extracted/Payload/Runner.app/Watch/bora
Watch App.app but it does not have a WKWatchKitApp or WKApplication key
set to true in its Info.plist
Underlying error (domain=IXUserPresentableErrorDomain, code=1):
‘Bora Debug-dev’을(를) 설치할 수 없음 (which says "cannot install" in korean)
           	나중에 다시 시도하십시오. (which says "try it later" in korean)


I suggest this change with two reasons : 

1. I think forcing watchOS app makers split their runner configurations
to support watch companion app is something not recommendable/not good
to force as a convention.
(Which is implied in the comment which is removed by this pr, and could
be one of the valid detouring.
some people in this issue
flutter#160622 said that a detouring
is adding of dummy WKCompanionAppBundleIdentifier configuration in iOS
app, which should be in only watchOS apps in normal.)

2. The function should work as it's name implies, so I guess it should
not omit default scheme during check. Matching function's behavior with
it's name will be likely to reduce potential errors caused by this
change while fixing problems.

## 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.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- 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: hellohuanlin <[email protected]>
@RCVZ
Copy link

RCVZ commented Nov 24, 2025

@vashworth could you cherry-pick this for a hotfix on stable?

mboetger pushed a commit to mboetger/flutter that referenced this pull request Dec 2, 2025
…companion watch apps defined by only the project info file. (flutter#176832)

(Please be generous for some typos and grammar error in sentences below.
English is not my mother tongue)

TRDR : This patch fixes flutter run(and might be flutter build too)
failure on iOS flutter apps with companion watchOS app in case the
WKCompanionAppBundleIdentifier value is defined by xcode project
configuration and uses different app bundle id by scheme(which happens
when you do something like com.myapp.app1.dev things for scheme bundle
id)

This change checks default scheme (the scheme with corresponding name
for the debug/release mode and selected build flavor.) in the build
settings check step of containsWatchCompanion function.


current code of containsWatchCompanion function works like below

1. check all default Info.plist file's content of all targets to
determine if the project has watchOS companion apps.
(this doesn't work well in mordern xcode settings when they use multi
plist files which are selected on build time by build configuration.
Sometimes, the default Info.plist file doesn't even exist in the
project.)

2. check if "WKCompanionAppBundleIdentifier" is included in the xcode
project info file(the ios/Runner.xcodeproj/project.pbxproj file in case
of iOS project generated by flutter)

3. If "WKCompanionAppBundleIdentifier" has found in the project info
file,
check build configuration variables of every single scheme but the
current target scheme if they have config value with key
"INFOPLIST_KEY_WKCompanionAppBundleIdentifier" identical to current
build configuration's bundle identifier returned by
productBundleIdentifier@xcode_project.dart function.


I believe The third step causes many problems reported in some issue and
pr, saying iOS app with companion watchOS app fails on build or run.
flutter#160622
flutter#172436

In my case, My iOS project had multiple schemes which are linked to
different build configurations, but using single Runner setting per app.
because we exclude current scheme for the check, we get all the
INFOPLIST_KEY_WKCompanionAppBundleIdentifier values except the one which
should be same with the identifier returned by
productBundleIdentifier@xcode_project.dart function.
that is because companion watch apps doesn't have to have same
WKCompanionAppBundleIdentifier with other scheme's main app. We have to
check the default scheme to see watchOS companion app`s
WKCompanionAppBundleIdentifier, not other scheme. (other schemes can
have slightly different or totally different bundle id. that was to
support different environment systems like dev, prod in my case)
as a result, flutter run gave me WatchOS app built for device target
iOS/iPad, not Watch. which caused problem during installation of iOS app
to a simulator because the embeded Watch App was in invalid format. I
found this by inspecting Info.plist of generated watchOS app.

below is part of the flutter run verbose log with failure caused by old
code.
[+1154 ms] An error was encountered processing the command
(domain=IXUserPresentableErrorDomain, code=1):
App installation failed: ‘Bora Debug-dev’을(를) 설치할 수 없음 (which says
"cannot install" in korean)
           나중에 다시 시도하십시오. (which says "try again later" in korean)
Found WatchKit 2.0 app at
/Users/javalia/Library/Developer/CoreSimulator/Devices/071691B3-7901-47E5-9B38-4D5B799F3530/data/Library/Caches/com.apple.mobile.installd.staging/temp.8HZIEG/extracted/Payload/Runner.app/Watch/bora
Watch App.app but it does not have a WKWatchKitApp or WKApplication key
set to true in its Info.plist
Underlying error (domain=IXUserPresentableErrorDomain, code=1):
‘Bora Debug-dev’을(를) 설치할 수 없음 (which says "cannot install" in korean)
           	나중에 다시 시도하십시오. (which says "try it later" in korean)


I suggest this change with two reasons : 

1. I think forcing watchOS app makers split their runner configurations
to support watch companion app is something not recommendable/not good
to force as a convention.
(Which is implied in the comment which is removed by this pr, and could
be one of the valid detouring.
some people in this issue
flutter#160622 said that a detouring
is adding of dummy WKCompanionAppBundleIdentifier configuration in iOS
app, which should be in only watchOS apps in normal.)

2. The function should work as it's name implies, so I guess it should
not omit default scheme during check. Matching function's behavior with
it's name will be likely to reduce potential errors caused by this
change while fixing problems.

## 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.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- 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: hellohuanlin <[email protected]>
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…companion watch apps defined by only the project info file. (flutter#176832)

(Please be generous for some typos and grammar error in sentences below.
English is not my mother tongue)

TRDR : This patch fixes flutter run(and might be flutter build too)
failure on iOS flutter apps with companion watchOS app in case the
WKCompanionAppBundleIdentifier value is defined by xcode project
configuration and uses different app bundle id by scheme(which happens
when you do something like com.myapp.app1.dev things for scheme bundle
id)

This change checks default scheme (the scheme with corresponding name
for the debug/release mode and selected build flavor.) in the build
settings check step of containsWatchCompanion function.


current code of containsWatchCompanion function works like below

1. check all default Info.plist file's content of all targets to
determine if the project has watchOS companion apps.
(this doesn't work well in mordern xcode settings when they use multi
plist files which are selected on build time by build configuration.
Sometimes, the default Info.plist file doesn't even exist in the
project.)

2. check if "WKCompanionAppBundleIdentifier" is included in the xcode
project info file(the ios/Runner.xcodeproj/project.pbxproj file in case
of iOS project generated by flutter)

3. If "WKCompanionAppBundleIdentifier" has found in the project info
file,
check build configuration variables of every single scheme but the
current target scheme if they have config value with key
"INFOPLIST_KEY_WKCompanionAppBundleIdentifier" identical to current
build configuration's bundle identifier returned by
productBundleIdentifier@xcode_project.dart function.


I believe The third step causes many problems reported in some issue and
pr, saying iOS app with companion watchOS app fails on build or run.
flutter#160622
flutter#172436

In my case, My iOS project had multiple schemes which are linked to
different build configurations, but using single Runner setting per app.
because we exclude current scheme for the check, we get all the
INFOPLIST_KEY_WKCompanionAppBundleIdentifier values except the one which
should be same with the identifier returned by
productBundleIdentifier@xcode_project.dart function.
that is because companion watch apps doesn't have to have same
WKCompanionAppBundleIdentifier with other scheme's main app. We have to
check the default scheme to see watchOS companion app`s
WKCompanionAppBundleIdentifier, not other scheme. (other schemes can
have slightly different or totally different bundle id. that was to
support different environment systems like dev, prod in my case)
as a result, flutter run gave me WatchOS app built for device target
iOS/iPad, not Watch. which caused problem during installation of iOS app
to a simulator because the embeded Watch App was in invalid format. I
found this by inspecting Info.plist of generated watchOS app.

below is part of the flutter run verbose log with failure caused by old
code.
[+1154 ms] An error was encountered processing the command
(domain=IXUserPresentableErrorDomain, code=1):
App installation failed: ‘Bora Debug-dev’을(를) 설치할 수 없음 (which says
"cannot install" in korean)
           나중에 다시 시도하십시오. (which says "try again later" in korean)
Found WatchKit 2.0 app at
/Users/javalia/Library/Developer/CoreSimulator/Devices/071691B3-7901-47E5-9B38-4D5B799F3530/data/Library/Caches/com.apple.mobile.installd.staging/temp.8HZIEG/extracted/Payload/Runner.app/Watch/bora
Watch App.app but it does not have a WKWatchKitApp or WKApplication key
set to true in its Info.plist
Underlying error (domain=IXUserPresentableErrorDomain, code=1):
‘Bora Debug-dev’을(를) 설치할 수 없음 (which says "cannot install" in korean)
           	나중에 다시 시도하십시오. (which says "try it later" in korean)


I suggest this change with two reasons : 

1. I think forcing watchOS app makers split their runner configurations
to support watch companion app is something not recommendable/not good
to force as a convention.
(Which is implied in the comment which is removed by this pr, and could
be one of the valid detouring.
some people in this issue
flutter#160622 said that a detouring
is adding of dummy WKCompanionAppBundleIdentifier configuration in iOS
app, which should be in only watchOS apps in normal.)

2. The function should work as it's name implies, so I guess it should
not omit default scheme during check. Matching function's behavior with
it's name will be likely to reduce potential errors caused by this
change while fixing problems.

## 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.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- 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: hellohuanlin <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants