You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter升级器报错:PlayStoreSearchAPI.lookupById返回null的解决方法

Flutter启动时Crashlytics报错:PlayStoreSearchAPI.lookupById返回null的修复方案

问题描述

每次启动Flutter应用都会触发Crashlytics错误,报错位置在app_update_controller.dart,错误原因是PlayStoreSearchAPI.lookupById返回null。

环境信息

  • Flutter (Channel stable, 3.19.1, on macOS 13.5.2 22G91 darwin-arm64, locale ru-AE)
  • Android toolchain - develop for Android devices (Android SDK version 34.0.0)
  • Xcode - develop for iOS and macOS (Xcode 14.3.1)
  • Chrome - develop for the web
  • Android Studio (version 2023.1)
  • VS Code (version 1.87.0)
  • Connected device (3 available)
  • Network resources

相关代码(AppUpdateController)

class AppUpdateController extends GetxController {
  final _logger = Get.find<ILogger>();
  String installedVersion = '';
  String packageName = '';
  String appName = '';
  String storeVersion = '';
  String itunesStoreUrl = '';
  final PlayStoreSearchAPI _playStoreSearchAPI = PlayStoreSearchAPI();
  final ITunesSearchAPI _iTunesSearchAPI = ITunesSearchAPI();
  late final FirebaseRemoteConfig remoteConfig;
  static const Duration remindLaterDuration = Duration(days: 1);

  static AppUpdateController get find => Get.find();

  Future<void> init(FirebaseRemoteConfig remoteConfig) async {
    this.remoteConfig = remoteConfig;
    await _checkAppUpdate(true);
  }

  void getPackageInfo() => _getPackageInfo();

  Future<void> _getPackageInfo() async {
    _logger.verbose('Getting package info...');
    var packageInfo = await PackageInfo.fromPlatform();
    installedVersion = packageInfo.version;
    packageName = packageInfo.packageName;
    appName = packageInfo.appName;
  }

  Future<void> _checkAppUpdate(bool showAlert) async {
    await _getPackageInfo();

    if (Platform.isAndroid) {
      final response = await _playStoreSearchAPI.lookupById(
        packageName,
        country: Get.deviceLocale?.countryCode,
        language: Get.deviceLocale?.languageCode,
      );
      if (response == null) {
        _logger.error('Cant fetch android store version info');
        return;
      }
      final String? version = _playStoreSearchAPI.version(response);
      if (version == null) {
        _logger.error('Failed to parse android store version');
        return;
      }
      storeVersion = version;
    } else if (Platform.isIOS) {
      final response = await _iTunesSearchAPI.lookupByBundleId(packageName,
          country: Get.deviceLocale?.countryCode);
      if (response == null) {
        _logger.error('Cant fetch ios store version info');
        return;
      }
      final String? version = _iTunesSearchAPI.version(response);
      if (version == null) {
        _logger.error('Failed to parse ios store version');
        return;
      }
      storeVersion = version;
      itunesStoreUrl = _iTunesSearchAPI.trackViewUrl(response) ?? '';
    } else {
      _logger
          .info('Updater: Platform ${Platform.operatingSystem} not supported.');
      return;
    }

    final Version parsedCurrentVersion = Version.parse(installedVersion);
    final Version parsedStoreVersion = Version.parse(storeVersion);

    _logger.info(
        'Got version info from store. storeVersion: $parsedStoreVersion, currentVersion: $parsedCurrentVersion');
    if (parsedStoreVersion > parsedCurrentVersion) {
      _logger.info('Store version is newer. Requesting update...');
      final String minVersion =
          remoteConfig.getString(Defaults.remoteConfigMinVersionKey);
      final Version parsedMinVersion =
          minVersion.isEmpty ? Version(0, 0, 0) : Version.parse(minVersion);
      late final bool updateRequired;
      if (parsedMinVersion > parsedCurrentVersion) {
        updateRequired = true;
      } else {
        updateRequired = false;
      }
      final DateTime lastUpdateRequestedString =
          await _getLastUpdateRequestTime();
      final DateTime now = DateTime.now();
      if (!updateRequired &&
          now.difference(lastUpdateRequestedString).inSeconds <
              remindLaterDuration.inSeconds) {
        _logger.info(
            'User suspended update by pressing "install later" button before');
        return;
      }
      RouteManagement.goToAppUpdateView();
    }
  }

  Future<DateTime> _getLastUpdateRequestTime() async {
    final String? lastUpdateRequestedString = await HiveService.get<String>(
        HiveBoxNames.lashUpdateRequested, Defaults.lastUpdateRequestedTimeKey);
    if (lastUpdateRequestedString == null) return DateTime(0);
    return DateTime.parse(lastUpdateRequestedString);
  }

  Future<void> _setLastUpdateRequestTime(DateTime date) {
    return HiveService.put(HiveBoxNames.lashUpdateRequested,
        Defaults.lastUpdateRequestedTimeKey, date.toIso8601String());
  }

  sendLaterLogs() {
    _logger.info('User pressed later update');
  }

  sendUpdateLogs() {
    _logger.info('User pressed update');
  }

  openStore() {
    if (Platform.isAndroid || Platform.isIOS) {
      final url = Uri.parse(
        Platform.isAndroid
            ? AppUrls.appPackageAndroidUrlFormat + packageName
            : itunesStoreUrl,
      );
      launchUrl(
        url,
        mode: LaunchMode.externalApplication,
      );
    }
  }
}

修复方案

1. 增加网络请求重试机制

lookupById返回null大概率是网络请求失败导致的,给请求加重试逻辑,避免单次失败直接触发错误:

// 修改Android分支的请求逻辑
final response = await _retryPlayStoreLookup(packageName);

添加重试方法:

Future<dynamic> _retryPlayStoreLookup(String packageName, {int retries = 2}) async {
  for (int i = 0; i <= retries; i++) {
    try {
      final result = await _playStoreSearchAPI.lookupById(
        packageName,
        country: Get.deviceLocale?.countryCode,
        language: Get.deviceLocale?.languageCode,
      );
      if (result != null) return result;
    } catch (e) {
      _logger.warning('PlayStore lookup attempt ${i+1} failed: $e');
      if (i < retries) await Future.delayed(const Duration(milliseconds: 500));
    }
  }
  return null;
}

2. 处理设备Locale为null的情况

如果Get.deviceLocale返回null,传递country和language参数可能导致API请求异常,给默认值:

final response = await _playStoreSearchAPI.lookupById(
  packageName,
  country: Get.deviceLocale?.countryCode ?? 'US',
  language: Get.deviceLocale?.languageCode ?? 'en',
);

3. 捕获API请求异常

原代码未捕获lookupById可能抛出的异常,直接导致null返回,添加try-catch包裹请求:

if (Platform.isAndroid) {
  try {
    final response = await _playStoreSearchAPI.lookupById(
      packageName,
      country: Get.deviceLocale?.countryCode ?? 'US',
      language: Get.deviceLocale?.languageCode ?? 'en',
    );
    if (response == null) {
      _logger.error('Cant fetch android store version info');
      return;
    }
    // 后续版本解析逻辑...
  } catch (e, stackTrace) {
    _logger.error('PlayStore lookup failed with exception: $e', stackTrace);
    return;
  }
}

4. 延迟检查更新时机

避免在启动时立即执行更新检查,等待应用完全初始化后再执行,减少网络请求冲突:

修改init方法:

Future<void> init(FirebaseRemoteConfig remoteConfig) async {
  this.remoteConfig = remoteConfig;
  // 延迟1秒执行更新检查
  await Future.delayed(const Duration(seconds: 1));
  await _checkAppUpdate(true);
}

5. 验证PackageName正确性

确保获取的packageName是Google Play上的正确包名,避免因包名错误导致API返回null。在_getPackageInfo中添加日志验证:

Future<void> _getPackageInfo() async {
  _logger.verbose('Getting package info...');
  var packageInfo = await PackageInfo.fromPlatform();
  installedVersion = packageInfo.version;
  packageName = packageInfo.packageName;
  appName = packageInfo.appName;
  _logger.info('Fetched packageName: $packageName'); // 添加日志验证
}

内容的提问来源于stack exchange,提问作者user19886724

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:53:31