My app uses UpgradeCard to show when there is an upgrade available, but the message I want to show depends on whether the upgrade is mandatory. And whether the upgrade is mandatory depends on async queries to PackageInfo.fromPlatform() and to the backend server. Due to the async requests, there is a race condition between showing the UpgradeCard and choosing the correct message to show.
To deal with this, I decided to force the UpgradeCard to re-render when the message has potentially changed. Apparently this is not actually necessary, as re-rendering the parent component causes the UpgradeCard to re-render and pick up the new message even when none of its properties have changed.
Still, it was confusing that changing the upgrader parameter makes the UpgradeCard disappear (or, in case of two renders in quick succession, prevents it from showing up in the first place) given my code which looked something like this:
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override State<StatefulWidget> createState() => MainScreenState();
}
class MainScreenState extends State<MainScreen> {
MainScreenState() {
PackageInfo.fromPlatform().then((packageInfo) {
setState(() {
_upgraderMessages.currentAppVersion = Version.parse(packageInfo.version);
});
});
}
final UpgraderMessagePicker _upgraderMessages = UpgraderMessagePicker();
Upgrader? _upgrader;
String? _upgraderMessageBody;
@override
Widget build(BuildContext context) {
// Pick upgrader message, in case an upgrade is available
_upgraderMessages.state = Provider.of<_____>(context, listen: true).state;
if (_upgraderMessageBody != _upgraderMessages.body) {
_upgraderMessageBody = _upgraderMessages.body;
_upgrader = Upgrader(messages: _upgraderMessages, debugLogging: true);
}
return Scaffold(
appBar: AppBar(title: const Text("My App")),
body: ListView(children: [
UpgradeCard(showIgnore: false, upgrader: _upgrader),
const OtherStuff(),
])
);
}
}
class UpgraderMessagePicker extends UpgraderMessages with ChangeNotifier {
Version currentAppVersion = Version(999, 0, 0);
...
}
I believe this happens because UpgradeCardState.initState calls Upgrader.initialize(). If UpgradeCardState.build were to call Upgrader.initialize() instead, the card would not disappear.
I was able to work around the problem by adding _upgrader!.initialize() and the end of the if (_upgraderMessageBody != _upgraderMessages.body) block. Unfortunately this has the side effect of causing a second upgrade check to happen, so in the end I figured out that I should actually send the same instance of Upgrader every time.
Other issues I noticed
The default upgrade message says "Version {{currentAppStoreVersion}} is now available-you have {{currentInstalledVersion}}." This is not correct English orthography. It would be better to use em dash "―" or semicolon "; " rather than a short dash "-".
UpgraderPlayStore throws three exceptions every time it checks for an update (one when calling additionalInfoElements.firstWhere on an empty list, and two more when calling sectionElements[0] on an empty list), which is very disruptive to the developer debugging experience.
The upgrade API request re-runs every time the screen is turned on, even if the UpgradeCard is invisible (whether because the user is on a different screen, or because the user asked to Ignore the update, or because of the UpgradeCard version of bug #416). I don't like this because some users have limited mobile data, and debugger keeps stopping on the exceptions too.
If the user clicks "Update" but doesn't actually update the app, the UpgradeCard disappears even if !showIgnore and !showLater. I tried to work around this with Upgrader.clearSavedSettings().then((_) => setState(() {})), but it didn't help.
My app uses
UpgradeCard
to show when there is an upgrade available, but the message I want to show depends on whether the upgrade is mandatory. And whether the upgrade is mandatory depends on async queries toPackageInfo.fromPlatform()
and to the backend server. Due to the async requests, there is a race condition between showing theUpgradeCard
and choosing the correct message to show.To deal with this, I decided to force the
UpgradeCard
to re-render when the message has potentially changed. Apparently this is not actually necessary, as re-rendering the parent component causes theUpgradeCard
to re-render and pick up the new message even when none of its properties have changed.Still, it was confusing that changing the
upgrader
parameter makes theUpgradeCard
disappear (or, in case of two renders in quick succession, prevents it from showing up in the first place) given my code which looked something like this:I believe this happens because
UpgradeCardState.initState
callsUpgrader.initialize()
. IfUpgradeCardState.build
were to callUpgrader.initialize()
instead, the card would not disappear.I was able to work around the problem by adding
_upgrader!.initialize()
and the end of theif (_upgraderMessageBody != _upgraderMessages.body)
block. Unfortunately this has the side effect of causing a second upgrade check to happen, so in the end I figured out that I should actually send the same instance ofUpgrader
every time.Other issues I noticed
UpgraderPlayStore
throws three exceptions every time it checks for an update (one when callingadditionalInfoElements.firstWhere
on an empty list, and two more when callingsectionElements[0]
on an empty list), which is very disruptive to the developer debugging experience.UpgradeCard
is invisible (whether because the user is on a different screen, or because the user asked to Ignore the update, or because of theUpgradeCard
version of bug #416). I don't like this because some users have limited mobile data, and debugger keeps stopping on the exceptions too.UpgradeCard
disappears even if!showIgnore
and!showLater
. I tried to work around this withUpgrader.clearSavedSettings().then((_) => setState(() {}))
, but it didn't help.