From 3c0e5759313afe07fd0d22cf0485fcd34c152cb3 Mon Sep 17 00:00:00 2001 From: Ortes Date: Tue, 29 Sep 2026 10:52:43 +0200 Subject: [PATCH 1/2] fix(package_info_plus): prefer compile-time build name and number on web --- .../package_info_plus_web_test.dart | 62 +++++++++++++------ .../lib/src/package_info_plus_web.dart | 40 ++++++++++-- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart index 1ba5fa7b48..67a8315b37 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart @@ -48,7 +48,7 @@ void main() { group('Package Info Web', () { setUp(() { client = MockClient(); - plugin = PackageInfoPlusWebPlugin(client); + plugin = PackageInfoPlusWebPlugin(client, null, null, null); }); testWidgets('Get correct values when response status is 200', ( @@ -67,12 +67,41 @@ void main() { expect(versionMap.buildSignature, VERSION_JSON['build_signature']); }); + testWidgets('Prefer the compile-time version over version.json', ( + tester, + ) async { + when(client.get(any)).thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_2_JSON), 200)), + ); + plugin = PackageInfoPlusWebPlugin(client, null, '1.0', '1'); + + final versionMap = await plugin.getAll(); + + expect(versionMap.appName, VERSION_2_JSON['app_name']); + expect(versionMap.packageName, VERSION_2_JSON['package_name']); + expect(versionMap.version, '1.0'); + expect(versionMap.buildNumber, '1'); + }); + + testWidgets('Keep the compile-time version when version.json fails', ( + tester, + ) async { + when(client.get(any)) + .thenAnswer((_) => Future.value(http.Response('', 404))); + plugin = PackageInfoPlusWebPlugin(client, null, '1.0', '1'); + + final versionMap = await plugin.getAll(); + + expect(versionMap.appName, isEmpty); + expect(versionMap.version, '1.0'); + expect(versionMap.buildNumber, '1'); + }); + testWidgets('Get empty values when response status is not 200', ( tester, ) async { - when( - client.get(any), - ).thenAnswer((_) => Future.value(http.Response('', 404))); + when(client.get(any)) + .thenAnswer((_) => Future.value(http.Response('', 404))); final versionMap = await plugin.getAll(); @@ -93,11 +122,10 @@ void main() { await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch; - when( - client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache')), - ).thenAnswer( - (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), - ); + when(client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache'))) + .thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), + ); final versionMap = await plugin.getAll(baseUrl: baseUrl); @@ -240,7 +268,7 @@ void main() { setUp(() { client = MockClient(); assetManagerMock = MockAssetManager(); - plugin = PackageInfoPlusWebPlugin(client, assetManagerMock); + plugin = PackageInfoPlusWebPlugin(client, assetManagerMock, null, null); }); testWidgets('Get correct values when using the AssetManager baseUrl', ( @@ -257,11 +285,10 @@ void main() { await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch; - when( - client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache')), - ).thenAnswer( - (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), - ); + when(client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache'))) + .thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), + ); final versionMap = await plugin.getAll(); @@ -283,9 +310,8 @@ void main() { final Clock fakeClock = Clock(() => now); when(assetManagerMock.assetsDir).thenReturn(assetsDir); - when( - assetManagerMock.getAssetUrl(''), - ).thenReturn('$managerBaseUrl$assetsDir/'); + when(assetManagerMock.getAssetUrl('')) + .thenReturn('$managerBaseUrl$assetsDir/'); await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch; diff --git a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart index 3e884cae36..cc3a63288c 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart @@ -8,16 +8,39 @@ import 'package:package_info_plus_platform_interface/package_info_data.dart'; import 'package:package_info_plus_platform_interface/package_info_platform_interface.dart'; import 'package:web/web.dart' as web; +/// The build name compiled into the running bundle. +/// +/// Injected by `flutter_tools` since Flutter 3.47 from the pubspec `version` +/// (or `--build-name`), and exposed by Flutter as `appBuildName`. Read through +/// the environment so older Flutter versions keep compiling. +const String? _compileTimeBuildName = bool.hasEnvironment('FLUTTER_BUILD_NAME') + ? String.fromEnvironment('FLUTTER_BUILD_NAME') + : null; + +/// The build number compiled into the running bundle, see +/// [_compileTimeBuildName]. +const String? _compileTimeBuildNumber = + bool.hasEnvironment('FLUTTER_BUILD_NUMBER') + ? String.fromEnvironment('FLUTTER_BUILD_NUMBER') + : null; + /// The web implementation of [PackageInfoPlatform]. /// /// This class implements the `package:package_info_plus` functionality for the web. class PackageInfoPlusWebPlugin extends PackageInfoPlatform { final Client? _client; final AssetManager _assetManager; - - /// Create plugin with http client and asset manager for testing purposes. - PackageInfoPlusWebPlugin([this._client, AssetManager? assetManagerMock]) - : _assetManager = assetManagerMock ?? assetManager; + final String? _buildName; + final String? _buildNumber; + + /// Create plugin with http client, asset manager and compile-time build + /// name and number for testing purposes. + PackageInfoPlusWebPlugin([ + this._client, + AssetManager? assetManagerMock, + this._buildName = _compileTimeBuildName, + this._buildNumber = _compileTimeBuildNumber, + ]) : _assetManager = assetManagerMock ?? assetManager; /// Registers this class as the default instance of [PackageInfoPlatform]. static void registerWith(Registrar registrar) { @@ -64,8 +87,13 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { return PackageInfoData( appName: versionMap['app_name'] ?? '', - version: versionMap['version'] ?? '', - buildNumber: versionMap['build_number'] ?? '', + // `version.json` describes the deployed bundle, which differs from the + // running one when the client is stale, so prefer the compile-time + // version embedded in the running bundle. + version: _buildName ?? versionMap['version'] ?? '', + buildNumber: _buildName != null + ? _buildNumber ?? '' + : versionMap['build_number'] ?? '', packageName: versionMap['package_name'] ?? '', // will remain empty on web buildSignature: '', From ff8181858ef46996c2c5f8076e9d96c2c1c12c8b Mon Sep 17 00:00:00 2001 From: Ortes Date: Tue, 29 Sep 2026 11:07:09 +0200 Subject: [PATCH 2/2] style(package_info_plus): format web integration test --- .../package_info_plus_web_test.dart | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart index 67a8315b37..f2d69285cc 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart @@ -86,8 +86,9 @@ void main() { testWidgets('Keep the compile-time version when version.json fails', ( tester, ) async { - when(client.get(any)) - .thenAnswer((_) => Future.value(http.Response('', 404))); + when( + client.get(any), + ).thenAnswer((_) => Future.value(http.Response('', 404))); plugin = PackageInfoPlusWebPlugin(client, null, '1.0', '1'); final versionMap = await plugin.getAll(); @@ -100,8 +101,9 @@ void main() { testWidgets('Get empty values when response status is not 200', ( tester, ) async { - when(client.get(any)) - .thenAnswer((_) => Future.value(http.Response('', 404))); + when( + client.get(any), + ).thenAnswer((_) => Future.value(http.Response('', 404))); final versionMap = await plugin.getAll(); @@ -122,10 +124,11 @@ void main() { await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch; - when(client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache'))) - .thenAnswer( - (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), - ); + when( + client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache')), + ).thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), + ); final versionMap = await plugin.getAll(baseUrl: baseUrl); @@ -285,10 +288,11 @@ void main() { await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch; - when(client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache'))) - .thenAnswer( - (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), - ); + when( + client.get(Uri.parse('${baseUrl}version.json?cachebuster=$cache')), + ).thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), + ); final versionMap = await plugin.getAll(); @@ -310,8 +314,9 @@ void main() { final Clock fakeClock = Clock(() => now); when(assetManagerMock.assetsDir).thenReturn(assetsDir); - when(assetManagerMock.getAssetUrl('')) - .thenReturn('$managerBaseUrl$assetsDir/'); + when( + assetManagerMock.getAssetUrl(''), + ).thenReturn('$managerBaseUrl$assetsDir/'); await withClock(fakeClock, () async { final int cache = now.millisecondsSinceEpoch;