timtraversy / new_version

Check if your user has the most recent version of your Flutter app.
BSD 3-Clause "New" or "Revised" License
125 stars 153 forks source link

Play store changes => StateError: Bad state: No element #107

Open peterhijma opened 2 years ago

peterhijma commented 2 years ago

Google Play store looks very different since yesterday or today?

I've got some error messages popping up now.

StateError: Bad state: No element
  File "list.dart", line 167, in ListMixin.firstWhere
  File "new_version.dart", line 157, in NewVersion._getAndroidStoreVersion
  File "<asynchronous suspension>"
  File "new_version.dart", line 94, in NewVersion.showAlertIfNecessary

So I think changes are necessary. You can find the version of the app in a modal @ "About this app ->"

timtraversy commented 2 years ago

Yes looks like it, thanks for reporting. This will probably break the plugin until I determine the new HTML elements.

peterhijma commented 2 years ago

Looks like they are rolling it out gradually? Right now I see the old play store in my browser again.

yenyichau commented 2 years ago

When is the next update for this package? My users are facing stuck issue because of this error.

JaviCore commented 2 years ago

Hi, any updates regarding this issue? Didn't even know about the store change since said breaking changes haven't reflected in my area till like yesterday/today

mrsoehtet commented 2 years ago

When can I get another update for this package?

angadiumakant commented 2 years ago

Dear author let us know whether this issue is going to fix or not. If not then we will drop this package or let us know the estimation time to fix. thousands of devices are stuck in launcher screen due to this error.

ahmetberber commented 2 years ago

Dear author let us know whether this issue is going to fix or not. If not then we will drop this package or let us know the estimation time to fix. thousands of devices are stuck in launcher screen due to this error.

Agreed. I am waiting the fix too. Please let us know ASAP when you fixed.

yenyichau commented 2 years ago

My client thought I'm the one who cause the issue. I've removed the plugin and upload to AppStore & PlayStore. If the problem fix I I will use It back.

nebiyuelias1 commented 2 years ago

I can confirm I'm experiencing the same problem here as everyone.

shabeenabarde commented 2 years ago

Same here. Dear Author please do let us know ASAP about this fix. Is there an alternative to this package if this won't be fixed anytime soon?

SamarthSerpentCS commented 2 years ago

same issue i faced now. please find the solution by author. plz client is waiting this Error is :

[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Bad state: No element E/flutter ( 8554): #0 ListMixin.firstWhere (dart:collection/list.dart:167:5) E/flutter ( 8554): #1 NewVersion._getAndroidStoreVersion (package:new_version/new_version.dart:153:51) E/flutter ( 8554): E/flutter ( 8554): #2 SplashScreenPageState.checkAppUpdate (package:{pagename}.dart:125:41) E/flutter ( 8554):

peterhijma commented 2 years ago

@timtraversy So apparently the store changed for more users. Any idea how to proceed?

I looked a bit into it myself, but getting the version from the HTML page itself seems more difficult because it doesn't appear in the source code on a page load anymore. It's all javascript-driven it seems.

Also: what if they decide to change a class name anytime soon again? Is there is more robust option to get the latest version of an app?

SamarthSerpentCS commented 2 years ago

@peterhijma From now on, remote configuration needs to be added. But when app is uploaded to Android, you have to do it in Firebase. so this is not proper solution. please find what class is used by javascript? so this is easily to find this. whats version is used in latest playstore.

final additionalInfoElements = document.getElementsByClassName('hAyfc');

This class is not found. beacuse google has changed design of play store.

shabeenabarde commented 2 years ago

Hi. is there going to be a fix for this anytime soon?

timtraversy commented 2 years ago

Unfortunately, there is not an easy fix for this. The new Play Store listing design does not include the app version number. For example: https://play.google.com/store/apps/details?id=com.google.android.apps.cloudconsole

There may be some other site where the version is listed, but someone will have to find it.

Rodrigossff91 commented 2 years ago

actually the designer has changed a little I can't find the app version

sbergmair commented 2 years ago

In section „about this app“ is the version. Does this help?

timtraversy commented 2 years ago

Good catch. That's closer, but that HTML element is only shown after a Javascript function is triggered, so we wouldn't be able to scrape it with a simple HTTP request as we do now.

It looks like the version info is pre-fetched though, and hidden in a huge block of in-line JS. So there might be a way to scrape out of there. But who knows if the format they return it in will be consistent.

timtraversy commented 2 years ago

https://pub.dev/packages/webdriver is a possibility.

Right now, I'm leaning towards turning off Android support and releasing a version without it. But I'll give it another day to see if anything comes up.

timtraversy commented 2 years ago

If anyone is up for a challenge:

  1. Go to random app with no reviews - https://play.google.com/store/apps/details?id=com.aakenya.testapp
  2. Look at page source - view-source:https://play.google.com/store/apps/details?id=com.aakenya.testapp
  3. CTRL-F for the version number - currently 1.0.14
  4. See if there's any possible way to extract that value from that blob of js/json

That's probably the only way.

prakasharyaman commented 2 years ago

JavaScript Code

import { JSOM } from 'jsdom'; 
  import axios from 'axios';

  const res = await axios('https://play.google.com/store/apps/details?id=com.yourapp');
  const dom = new JSDOM(res.data);
  const scripts = Array.from(dom.window.document.querySelectorAll('script'));
  const script = scripts.find(s => s.textContent && s.textContent.includes('/store/apps/developer'));
  const matches = script.textContent.match(versionStringRegex);
  const match = matches[0];
  const version = match.replace(/"/g, '');

  console.log(version); // '1.2.345'

@timtraversy can u check it out ?..

Java code

  class AppVersionParser {

  suspend fun versionForIdentifier(bundleID: String): String? {
   val userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) " +
    "Version/15.3 Safari/605.1.15"

  val document: Document

      try {
             document = Jsoup.connect("https://play.google.com/store/apps/details?id=$bundleID&hl=en")
        .timeout(5000)
        .userAgent(userAgent)
        .referrer("https://www.google.com")
        .get()
         } catch (e: Exception) {
    logger.error("Failed to get Google Play info for $bundleID\n\t${e.toStandardString()}")
    return null
         }

         return parseDocument(document)
     }

     fun parseDocument(document: Document): String? {
         val HTML = document.toString()
         val policyMarker = "" // INSERT YOUR PRIVACY POLICY URL
         val policyUrlIndex = HTML.lastIndexOf(policyMarker)
         val versionStart = HTML.indexOf("\"", startIndex = policyUrlIndex + policyMarker.length + 1) + 1 // "
         val versionEnd = HTML.indexOf("\"", startIndex = versionStart + 1)
         return HTML.substring(startIndex = versionStart, endIndex = versionEnd)
     }
     }`
annkitpanwar commented 2 years ago

For me, some changes in _getAndroidStoreVersion worked.

Future<VersionStatus?> _getAndroidStoreVersion(
      PackageInfo packageInfo) async {
    final id = androidId ?? packageInfo.packageName;
    final uri =
        Uri.https("play.google.com", "/store/apps/details", {"id": "$id"});
    final response = await http.get(uri);
    if (response.statusCode != 200) {
      debugPrint('Can\'t find an app in the Play Store with the id: $id');
      return null;
    }
    final document = parse(response.body);

    String storeVersion = '0.0.0';
    String? releaseNotes;

    final additionalInfoElements = document.getElementsByClassName('hAyfc');
    if (additionalInfoElements.isNotEmpty) {
      final versionElement = additionalInfoElements.firstWhere(
            (elm) => elm.querySelector('.BgcNfc')!.text == 'Current Version',
      );
      storeVersion = versionElement.querySelector('.htlgb')!.text;

      final sectionElements = document.getElementsByClassName('W4P4ne');
      final releaseNotesElement = sectionElements.firstWhereOrNull(
            (elm) => elm.querySelector('.wSaTQd')!.text == 'What\'s New',
      );
      releaseNotes = releaseNotesElement
          ?.querySelector('.PHBdkd')
          ?.querySelector('.DWPxHb')
          ?.text;
    } else {
      final scriptElements = document.getElementsByTagName('script');
      final infoScriptElement = scriptElements.firstWhere(
            (elm) => elm.text.contains('key: \'ds:4\''),
      );

      final param = infoScriptElement.text.substring(20, infoScriptElement.text.length - 2)
          .replaceAll('key:', '"key":')
          .replaceAll('hash:', '"hash":')
          .replaceAll('data:', '"data":')
          .replaceAll('sideChannel:', '"sideChannel":')
          .replaceAll('\'', '"')
          .replaceAll('owners\"', 'owners');
      final parsed = json.decode(param);
      final data =  parsed['data'];

      storeVersion = data[1][2][140][0][0][0];
      releaseNotes = data[1][2][144][1][1];
    }

    return VersionStatus._(
      localVersion: _getCleanVersion(packageInfo.version),
      storeVersion: _getCleanVersion(forceAppVersion ?? storeVersion),
      appStoreLink: uri.toString(),
      releaseNotes: releaseNotes,
    );
  }

Still not sure if play store HTML will be consistent or not but it worked for now.

Sapnajain01 commented 2 years ago

I'm getting the same error is there any solution because user can't visit to my application it stuck on the splash screen and my application is live Anaaj issue

kldawad commented 2 years ago

I'm getting the same error is there any solution because user can't visit to my application it stuck on the splash screen and my application is live Anaaj issue

same here

timtraversy commented 2 years ago

To those this is breaking: you will have to release a new version of your apps to fix this issue. I would recommend just doing that now, and removing the calls to this plugin. This was an unforseen change in the Play Console, we're working on a fix.

timtraversy commented 2 years ago

Nice concepts above, thank you!

@prakasharyaman - This is a clever idea, but people format there versions different ways. Also, the HTML contains multiple version strings from past versions, so we would have to add some logic to fix that as well.

@ankitpanwar8979 - This is my preferred solution. The nested array doesn't seem like it will remain stable as the Play site changes, but best we can do for now.

glngrizkyy commented 2 years ago

3 days ago

can u explain code _getCleanVersion is what for?

prakasharyaman commented 2 years ago

Try This

Works for now

   public function getAndroidVersion(string $storeUrl): string
   {
   $html = file_get_contents($storeUrl);
       $matches = [];
       preg_match('/\[\[\[\"\d+\.\d+\.\d+/', $html, $matches);
       if (empty($matches) || count($matches) > 1) {
           throw new Exception('Could not fetch Android app version info!');
       }
       return substr(current($matches), 4);
   }
vinayakyengandul commented 2 years ago

@timtraversy same issue is facing ( Bad state: No element ) Now what is the solution for it.

eramitsharma957 commented 2 years ago

Google Play store looks very different since yesterday or today?

I've got some error messages popping up now.

StateError: Bad state: No element
  File "list.dart", line 167, in ListMixin.firstWhere
  File "new_version.dart", line 157, in NewVersion._getAndroidStoreVersion
  File "<asynchronous suspension>"
  File "new_version.dart", line 94, in NewVersion.showAlertIfNecessary

So I think changes are necessary. You can find the version of the app in a modal @ "About this app ->"

Please provide me solution ASAP in "Bad state: No element"

annkitpanwar commented 2 years ago

3 days ago

can u explain code _getCleanVersion is what for?

Check this function https://github.com/timtraversy/new_version/blob/a71a9b8428cb1fd5fe809806f72356d4e1c41bb4/lib/new_version.dart#L117

X-SLAYER commented 2 years ago

I have a similar package and am using regex to get the version from the play store you can check it here flutter_app_version_checker

newVersion = RegExp(r',\[\[\["([0-9,\.]*)"]],')
appstute-sachin commented 2 years ago

Facing same issue since few days and this has broken our force update functionality on android. Can we expect this issue to be resolved in upcoming plugin version?

ghost commented 2 years ago

I have a similar package and am using regex to get the version from the play store you can check it here flutter_app_version_checker

newVersion = RegExp(r',\[\[\["([0-9,\.]*)"]],')

But I am using the same package in my app which is already in playstore. If we are using any other package then we loose the old users. Need a permanent solution !

X-SLAYER commented 2 years ago

But I am using the same package in my app which is already in playstore. If we are using any other package then we loose the old users. Need a permanent solution !

in both ways, you will update your app because even if they fixed this problem the package will not be fixed automatically on the app on play store you will need to update it

utarn commented 2 years ago

This is my code.

  Future<VersionStatus?> _getAndroidStoreVersion(PackageInfo packageInfo) async {
    final id = androidId ?? packageInfo.packageName;
    final uri = Uri.https("play.google.com", "/store/apps/details", {"id": id});
    final response = await http.get(uri);
    if (response.statusCode != 200) {
      debugPrint('Can\'t find an app in the Play Store with the id: $id');
      return null;
    }

    final document = parse(response.body);
    var releaseNotes = document.querySelector('div[itemprop="description"]')!.innerHtml;
    final storeVersion = RegExp(r',\[\[\["([0-9,\.]*)"]],').firstMatch(response.body)!.group(1);

    return VersionStatus._(
      localVersion: _getCleanVersion(packageInfo.version),
      storeVersion: _getCleanVersion(storeVersion ?? '0.0.0'),
      appStoreLink: uri.toString(),
      releaseNotes: releaseNotes,
    );
  }
timtraversy commented 2 years ago

I don't have the environment set up to test it, but 0.3.1 should be live with a fix.