Search code examples
flutterdartflutter-provider

Get a stream of permissions from a future


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.