juice_power 0.1.0 copy "juice_power: ^0.1.0" to clipboard
juice_power: ^0.1.0 copied to clipboard

Battery and charging state as a Juice bloc — plugged-in, charge level and power-saver mode behind a swappable provider seam.

juice_power #

Battery and charging state as a Juice bloc — plugged-in, charge level and power-saver mode behind a swappable provider seam.

pub package License: MIT

What it owns #

The device's power situation: whether external power is connected, how much charge remains, and whether the OS power saver is on (iOS Low Power Mode, Android Battery Saver).

It does not decide what to do about any of that. Whether your expensive work may run on battery — and below what percentage — is your policy, because only you know how expensive the work is and how badly the user wants it. This bloc reports; consumers decide.

Install #

dependencies:
  juice_power: ^0.1.0     # pulls battery_plus for the default provider

Use #

import 'package:juice/juice.dart';
import 'package:juice_power/juice_power.dart';

final power = PowerBloc.withConfig(PowerConfig());

class ChargeBadge extends StatelessJuiceWidget<PowerBloc> {
  ChargeBadge({super.key}) : super(groups: {PowerGroups.source, PowerGroups.level});

  @override
  Widget onBuild(BuildContext context, StreamStatus status) {
    final s = bloc.state;
    return Row(mainAxisSize: MainAxisSize.min, children: [
      Icon(s.isPluggedIn ? Icons.power : Icons.battery_std),
      Text(s.percent == null ? '—' : '${s.percent}%'),
    ]);
  }
}

The usual reason to reach for this package is gating expensive background work. Express that as a relay, not a listener:

// Stop when the cable comes out; start again when it goes back in.
StateRelay<PowerBloc, WellBloc, PowerState>(
  toEvent: (_) => PauseWellEvent(),
  when: (s) => s.oldState.isPluggedIn && !s.state.isPluggedIn,
);
StateRelay<PowerBloc, WellBloc, PowerState>(
  toEvent: (_) => ResumeWellEvent(),
  when: (s) => !s.oldState.isPluggedIn && s.state.isPluggedIn,
);

Three values that can be unknown #

Platforms lie, decline, and throw. This package reports unknown as unknown rather than substituting a plausible number, because every guess here is wrong in a direction that matters:

Reading Unknown when Why it is not guessed
status before the first read; platform says so isPluggedIn is false for unknown — "may I burn power?" must answer no until something says yes
percent Simulator (-1); some devices throw isAtOrBelow(20) returns false on unknown. A fabricated 0 would slam every gate shut on a full battery
saverOn platform throws false is the permissive answer — guessing it silently grants permission to burn power on a device that was asking to stop

PowerState.saverAsked gives you the reading most consumers want (true only when the user positively asked to conserve) — but as a choice made in the open, next to the work it affects, rather than one buried in a provider.

Why the level is polled #

Platforms broadcast plugged/unplugged promptly and the percentage hardly at all. Without polling, a consumer gating on "below 20%" would act on a number frozen at the last time a cable moved. PowerConfig.pollInterval defaults to one minute — far finer than a battery moves, one cheap platform call. Set it to Duration.zero if you only care whether power is connected.

The provider seam (and why it's testable) #

PowerBloc depends on the PowerProvider interface, not on a platform plugin. The shipped BatteryPlusProvider is one implementation; in tests you inject a fake and drive every transition — unplugged, saver on, charge crossing a threshold — in a line each, with no device:

final fake = FakePowerProvider();
final bloc = PowerBloc.withConfig(PowerConfig(provider: fake, pollInterval: Duration.zero));

fake.emit(const PowerSnapshot(status: BatteryStatus.discharging, percent: 12));

Rebuild groups #

Group Emitted when
power:source plugged in or unplugged
power:level the charge level moved
power:saver the OS power saver was switched on or off

A reading that changes nothing emits nothing — the poll would otherwise wake every consumer once a minute for a number that had not moved.

What it deliberately leaves out #

Thermal state. A hot phone is the other reason to back off expensive work, and ProcessInfo.thermalState / Android's thermal API would answer it — but no package in this family ships native code, and adding it here would make every consumer of a battery bloc take a platform dependency. If you need thermal throttling, put a small channel in your app and combine it with this state.

License #

MIT

0
likes
140
points
12
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Battery and charging state as a Juice bloc — plugged-in, charge level and power-saver mode behind a swappable provider seam.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#battery #power #bloc #state-management #background

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

battery_plus, flutter, juice

More

Packages that depend on juice_power