Skip to content
This repository has been archived by the owner on Jun 7, 2022. It is now read-only.

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

Open
peterhijma opened this issue May 20, 2022 · 37 comments
Open

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

peterhijma opened this issue May 20, 2022 · 37 comments

Comments

@peterhijma
Copy link

peterhijma commented May 20, 2022

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 ->"

@peterhijma peterhijma changed the title Play store changes? Play store changes => leads to errors May 20, 2022
@timtraversy
Copy link
Owner

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

@timtraversy timtraversy pinned this issue May 20, 2022
@peterhijma
Copy link
Author

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

@yenyichau

This comment was marked as off-topic.

@JaviCore

This comment was marked as off-topic.

@mrsoehtet

This comment was marked as off-topic.

@angadiumakant

This comment was marked as off-topic.

@ahmetberber

This comment was marked as off-topic.

@yenyichau

This comment was marked as off-topic.

@nebiyuelias1

This comment was marked as duplicate.

@shabeenabarde

This comment was marked as off-topic.

@SamarthSerpentCS

This comment was marked as duplicate.

@peterhijma
Copy link
Author

peterhijma commented May 27, 2022

@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?

@peterhijma peterhijma changed the title Play store changes => leads to errors Play store changes => leads to [StateError: Bad state: No element] May 27, 2022
@peterhijma peterhijma changed the title Play store changes => leads to [StateError: Bad state: No element] Play store changes =>StateError: Bad state: No element May 27, 2022
@peterhijma peterhijma changed the title Play store changes =>StateError: Bad state: No element Play store changes => StateError: Bad state: No element May 27, 2022
@SamarthSerpentCS
Copy link

@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

This comment was marked as off-topic.

@timtraversy
Copy link
Owner

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

This comment was marked as duplicate.

@sbergmair
Copy link

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

@timtraversy
Copy link
Owner

timtraversy commented May 27, 2022

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
Copy link
Owner

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
Copy link
Owner

timtraversy commented May 27, 2022

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
Copy link

prakasharyaman commented May 28, 2022

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
Copy link

annkitpanwar commented May 28, 2022

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

This comment was marked as duplicate.

@kldawad

This comment was marked as duplicate.

@timtraversy
Copy link
Owner

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
Copy link
Owner

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
Copy link

3 days ago

can u explain code _getCleanVersion is what for?

@prakasharyaman
Copy link

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

This comment was marked as duplicate.

@eramitsharma957

This comment was marked as duplicate.

@annkitpanwar
Copy link

3 days ago

can u explain code _getCleanVersion is what for?

Check this function

String _getCleanVersion(String version) =>

@timtraversy timtraversy mentioned this issue Jun 1, 2022
@X-SLAYER
Copy link

X-SLAYER commented Jun 1, 2022

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
Copy link

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
Copy link

ghost commented Jun 3, 2022

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
Copy link

X-SLAYER commented Jun 3, 2022

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
Copy link

utarn commented Jun 4, 2022

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
Copy link
Owner

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests