Get a stream of permissions from a future

Issue

I’m trying to stream two types of location package based data:

final _location = Location();

runApp(
    MultiProvider(
      providers: [
       StreamProvider<PermissionStatus>(create: (_) => _location.hasPermission().asStream()),
       StreamProvider<bool>(create: (_) => _location.serviceEnabled().asStream()),
      ],
      child: MaterialApp();
    )
)

When I ‘stream’ the data, it loads the initial value and streams that. It is not continuously listening to changes which is what I want to do. I have tried abstracting both futures into their own class and creating an async* stream that yields the current status, both of which give the same problem.

My use case involves continuously listening to the permission status and location on/off and shut down certain parts of the UI when these are modified in between tasks.

Simplified usage:

class LocationWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer2<PermissionStatus, bool>(
        builder: (_, permission, isLocationEnabled, __) => _LocationWidget(
            permission: permission, isLocationEnabled: isLocationEnabled));
  }
}

class _LocationWidget extends StatelessWidget {
  const _LocationWidget({this.permission, this.isLocationEnabled})
      : assert(permission != null);

  final PermissionStatus permission;
  final bool isLocationEnabled;

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Center(
            child: (() {
      if (!isLocationEnabled)    // Check Bool and show different text
        return Text(
          "Off",
        );
      else
        return Text("On");
    }())));
  }
}

Solution

ikerfah is right, you’re creating a Stream from a Future, meaning the Stream will only contain a single event when the Future is completed (basically, it’s not a real "stream" in the true sense of the word).

FutureBuilder won’t work either, since the Future only gets completed once, so it will only trigger a single state change too.

If this is the plugin you’re using, it seems the author hasn’t implemented anything to expose a "real" Stream for permission change events. I wouldn’t hold my breath for that either, because as far as I know neither iOS nor Android broadcast an event if/when permissions are changed.

If you need to disable/enable something based on whether permissions have changed, you’ll just need to set a periodic Timer in a StatefulWidget to poll for changes.

class _LocationWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _LocationWidgetState();
  }
}

class _LocationWidgetState extends State<_LocationWidget> {

  PermissionStatus permission;
  bool isLocationEnabled = false;
  Timer _timer;

  @override
  void initState() {
     _timer = Timer.periodic(Duration(seconds:5), (_) {
       var permission = await _location.hasPermission();
       var isLocationEnabled = await _location.serviceEnabled();
       if(permission != this.permission || isLocationEnabled != this.isLocationEnabled)
           setState(() {});
     });  
  }

  @override
  void dispose() {
     _timer.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Center(
            child: (() {
      if (!isLocationEnabled)    // Check Bool and show different text
        return Text(
          "Off",
        );
      else
        return Text("On");
    }())));
  }

It’s up to you whether 5 seconds is an appropriate interval. initState() should probably also set the initial isLocationEnabled/permission values when the state is initialized, too, rather than waiting 5 seconds for the timer to kick in.

Answered By – Nick Fisher

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *