Our iOS pipeline was green.
That was the problem.
After upgrading to Flutter 3.50, every local build printed a warning about Swift Package Manager. The CI job printed no warning, installed pods from cache and produced a valid archive.
Nothing had failed, but the pipeline had not tested the dependency path Flutter was moving toward.
It had tested the fallback.
The Build That Hid the Migration
The relevant part of the CI workflow looked familiar:
- name: Install CocoaPods
run: sudo gem install cocoapods
- name: Restore Pods cache
uses: actions/cache@v4
with:
path: |
ios/Pods
~/.cocoapods
key: pods-${{ hashFiles('ios/Podfile.lock') }}
- name: Install pods
run: |
cd ios
pod install
- name: Build iOS
run: flutter build ios --release --no-codesign
This pipeline was dependable because it made CocoaPods available before Flutter needed it.
It also meant we could not answer a more important question:
Can this application build when Swift Package Manager is the primary dependency path?
A successful CocoaPods build provided no evidence.
What Flutter Actually Changed
Flutter 3.44 made Swift Package Manager the default dependency manager for iOS and macOS projects.
When a project runs or builds, the Flutter CLI can update its Xcode configuration automatically. Plugins that support SwiftPM are resolved as Swift packages. Plugins without support can temporarily fall back to CocoaPods.
Flutter 3.50 makes the transition harder to ignore. Disabling SwiftPM now produces a warning on every Flutter CLI command. Flutter plans to remove the opt-out option and eventually remove its CocoaPods fallback.
There is also a deadline behind that warning. The CocoaPods registry becomes permanently read-only on December 2, 2026.
That does not mean every existing CocoaPods application stops building on December 2. Existing packages remain available. However, new pod versions will no longer be added to the trunk, while Flutter continues moving toward Apple’s supported package manager.
Waiting for the first failed release is a poor migration test.
Why Local Builds Can Mislead You
Flutter’s automatic migration can modify files inside the Xcode project, including:
ios/Runner.xcodeproj/project.pbxproj
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
A developer may run the application locally, accept those changes and get a successful SwiftPM build.
CI can behave differently when:
- the updated Xcode files were not committed
- SwiftPM was disabled globally on the runner
- cached Pods hide an unsupported plugin
- the pipeline opens or builds the old workspace configuration
- only the default flavor received the required scheme pre-action
- a script still runs
pod installbefore every build
That final case is easy to miss. Keeping pod install in the pipeline does not prove it is still required.
Make CI Prove SwiftPM Readiness
Start by enabling SwiftPM explicitly in the migration branch:
flutter config --enable-swift-package-manager
flutter clean
flutter pub get
flutter build ios --release --no-codesign
Do this in a clean job without restoring the Pods cache.
The Flutter build output may identify plugins that lack SwiftPM support and trigger the CocoaPods fallback. Capture that output so the job can expose the fallback rather than quietly accepting it.
set -o pipefail
flutter build ios \
--release \
--no-codesign 2>&1 | tee flutter-ios-build.log
if grep -Eqi \
"falling back to CocoaPods|does not support Swift Package Manager" \
flutter-ios-build.log; then
echo "The iOS build still depends on CocoaPods fallback."
exit 1
fi
Flutter’s exact warning text can change, so match the patterns produced by the SDK version pinned in the project.
This job should be separate from the existing release pipeline at first. Its purpose is to reveal the real migration gap without blocking urgent releases immediately.
Check the Xcode Configuration Too
A successful command-line build is useful, but the generated Xcode integration still needs review.
According to Flutter’s Swift Package Manager migration guide, the project should include:
-
FlutterGeneratedPluginSwiftPackageas a package and target dependency -
Run Prepare Flutter Framework Scriptas a build pre-action - the preparation command using Flutter’s
xcode_backend.sh prepare
Check every shared scheme used by CI. A project with development, staging and production flavors may have separate schemes, and an automatically migrated default scheme does not guarantee the others are correct.
Run at least one clean build for each release flavor.
flutter build ios \
--flavor staging \
--release \
--no-codesign
flutter build ios \
--flavor production \
--release \
--no-codesign
Do Not Delete the Podfile First
Removing CocoaPods should be the final cleanup step, not the first migration step.
Flutter still falls back to CocoaPods when a dependency lacks SwiftPM support. Deleting the Podfile before auditing plugins can turn a manageable compatibility warning into a broken build.
First, classify the dependencies:
- plugins that support SwiftPM
- plugins waiting for maintainer updates
- internal plugins the team must migrate
- manually added pods that need Swift package replacements
- dependencies that should be replaced or removed
Only remove CocoaPods integration after all required dependencies build through SwiftPM.
At that point, the official migration process includes running pod deintegrate, deleting CocoaPods files and removing Pod-related .xcconfig includes.
A Green Build Needs a Better Definition
The old definition was simple:
The iOS archive was produced.
The new definition needs one more condition:
The archive was produced through the intended dependency manager.
For teams working with a hybrid app development company, this is a useful delivery question: does the CI pipeline prove SwiftPM compatibility, or does it still succeed because CocoaPods fallback is available?
Flutter is giving teams time to migrate.
The warning is not the failure. It is the chance to discover the failure before the fallback disappears.
Top comments (0)