audienceFraction static method
The fraction of the track's audience a release in status has been given.
Play omits its own userFraction exactly when it means one, and that
is the hole this fills. Google sets the field only for inProgress and
halted, so a completed rollout carries no fraction at all, and a
caller reading Play's number alone gets null for the release that
reached everybody, then has to learn from Google's documentation that
null-beside-completed is what 100% looks like. That is the deferral
serving exists to end, one field down.
Where the range comes from, because the values below lean on it.
Google's own words for userFraction, reaching this package through
googleapis 16.0.0's dartdoc, which discoveryapis_generator writes from
the Android Publisher v3 discovery document:
Fraction of users who are eligible for a staged release. 0 < fraction < 1. Can only be set when status is "inProgress" or "halted".
Two honest caveats about that sentence. It is phrased as a constraint on what may be set, so it binds a write directly and a read only by inference. And nothing in this repository has measured it — there is no observation of a live account here, unlike the App Store states next door.
So the values below do not depend on it for correctness, and that is
the second reason PlayReleaseEntry.userFraction is carried rather than
folded away. If Play ever answered 1.0 for an inProgress release,
this would return 1.0 — the same number it infers for completed — and
the two would still be distinguishable, because this is a function of
PlayReleaseEntry.status and PlayReleaseEntry.userFraction and the
document carries both of its inputs. A caller reading status sees
which branch produced the number: completed means the 1.0 is ours,
inProgress means it is Play's.
The status is the discriminator, and not the raw field's nullness —
which is the tempting answer and is only true while the quoted sentence
holds in its second half. Were Play to set userFraction on a
completed release, ours and a full rollout would both read 1.0 beside
1.0, and nullness would have stopped separating them. Nullness is a
convenience for the common case; status is the guarantee.
Reading PlayReleaseEntry.audienceFraction alone is what the range
buys. Reading it beside status needs nothing documented about the
value.
1.0 for completed and 0.0 for draft — both stated by Play's
status rather than measured by it. PlayReleaseEntry.userFraction for
inProgress and for halted: a halted release's fraction is the one it
stopped at, and the users who already took it keep it.
Null for statusUnspecified, for unknown and for an absent status, on the same terms as every other derived answer here — and null for an inProgress or halted release Play sent no fraction for, which is Play contradicting its own documentation and not a case to guess at.
It measures who has it, not what the rollout is doing, which is why
it is safe to read beside serving in either order: a halted release
is serving: false with a non-zero fraction, and both are true at once.
Implementation
static double? audienceFraction(
PlayReleaseStatus? status, {
required double? userFraction,
}) => switch (status) {
completed => 1.0,
draft => 0.0,
inProgress || halted => userFraction,
statusUnspecified || unknown || null => null,
};