Bloc: Listening for Live User Location - Example Request

Created on 10 Dec 2019  路  3Comments  路  Source: felangel/bloc

Your provided examples are excellent, thanks to them i managed to complete 90% of my project. I chose to follow your implementation because of your brilliant examples and documentation.

Can you possibly add an example or guide me in the right direction of how to listen for location updates when using the "location" plugin. Reading through your existing examples i found it easy to get the current location, but i'm lost when it comes to listening for location changes.

So far this is the location helper i'm using:

import 'dart:async';

import 'package:location/location.dart';

class LocationStore {
  Future<LocationData> getCurrentLocation() async {
    var location = new Location();
    LocationData currentLocation;
    try {
      currentLocation = await location.getLocation();
      print(currentLocation.latitude);
      print(currentLocation.longitude);
      return currentLocation;
    } catch (e) {
      throw Exception(e);
    }
  }

  Future<StreamSubscription> listenForUserLocation() async {
    var location = new Location();

    return location.onLocationChanged().listen((LocationData currentLocation) {
      print(currentLocation.latitude);
      print(currentLocation.longitude);
      return currentLocation;
    });
  }
}

With bloc files:

import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:location/location.dart';
import 'package:meta/meta.dart';

abstract class LocationState extends Equatable {
  const LocationState();

  @override
  List<Object> get props => [];
}

class LocationEmptyState extends LocationState {}
class LocationLoadingState extends LocationState {}
class LocationLoadedState extends LocationState {
  final LocationData location;

  LocationLoadedState(this.location);

  @override
  List<Object> get props => [location];
}
class LocationListeningState extends LocationState {
  final StreamSubscription<LocationData> locationSubscription;

  LocationListeningState(this.locationSubscription);

  @override
  List<Object> get props => [locationSubscription];
}
class LocationErrorState extends LocationState {
  final dynamic error;

  const LocationErrorState({@required this.error});

  @override
  List<Object> get props => [error];

  @override
  String toString() => 'location error { error: $error }';
}

import 'package:equatable/equatable.dart';

abstract class LocationEvent extends Equatable {
  const LocationEvent();

  @override
  List<Object> get props => [];
}

class GetCurrentLocation extends LocationEvent {}
class StartListeningForCurrentLocation extends LocationEvent {}
class UpdatedCurrentLocation extends LocationEvent {}

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:location/location.dart';
import 'package:sasos/stores/location_store.dart';
import './bloc.dart';

class LocationBloc extends Bloc<LocationEvent, LocationState> {
  @override
  LocationState get initialState => LocationEmptyState();

  @override
  Stream<LocationState> mapEventToState(
    LocationEvent event,
  ) async* {
    if (event is GetCurrentLocation) {
      yield* _mapToGetCurrentLocationState();
    } else if (event is StartListeningForCurrentLocation) {
      yield* _mapToStartListeningLocationState();
    } else if (event is UpdatedCurrentLocation) {

    }
  }

  Stream<LocationState> _mapToGetCurrentLocationState() async* {
    yield LocationLoadingState();
    try {
      final LocationData locationData = await LocationStore().getCurrentLocation();
      yield LocationLoadedState(locationData);
    } catch (error) {
      yield LocationErrorState(error: error);
    }
  }

  Stream<LocationState> _mapToStartListeningLocationState() async* {
    yield LocationLoadingState();
    try {
      final StreamSubscription subscription = await LocationStore().listenForUserLocation();
      yield LocationListeningState(subscription);
    } catch (error) {
      yield LocationErrorState(error: error);
    }
  }
}

With UI file

import 'package:flutter/material.dart';

import 'location_form.dart';

class LocationScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Locaiton')),
      body: LocationForm(),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sasos/blocs/location/bloc.dart';

class LocationForm extends StatefulWidget {
  @override
  State<LocationForm> createState() => _LocationFormState();
}

class _LocationFormState extends State<LocationForm> {
  @override
  Widget build(BuildContext context) {
    _onGetLocationPressed() {
      BlocProvider.of<LocationBloc>(context).add(GetCurrentLocation());
    }

    _onGetLocationListenPressed() {
      BlocProvider.of<LocationBloc>(context).add(StartListeningForCurrentLocation());
    }

    return BlocListener<LocationBloc, LocationState>(
      listener: (context, state) {
        if (state is LocationErrorState) {
          Scaffold.of(context).showSnackBar(SnackBar(content: Text('${state.error}'), backgroundColor: Colors.red));
        }
      },
      child: BlocBuilder<LocationBloc, LocationState>(
        builder: (context, state) {
          return Form(
            child: Column(
              children: [
                RaisedButton(
                  onPressed: state is! LocationLoadingState ? _onGetLocationPressed : null,
                  child: Text('Get Location'),
                ),
                RaisedButton(
                  onPressed: state is! LocationLoadingState ? _onGetLocationListenPressed : null,
                  child: Text('Listen for Location'),
                ),
                Container(child: state is LocationLoadingState ? CircularProgressIndicator() : null),
                Container(child: state is LocationLoadedState ? Text(state.location.toString() ?? '') : null),
              ],
            ),
          );
        },
      ),
    );
  }
}
example

Most helpful comment

Hi @Theunodb 馃憢
Thanks for opening an issue as well as for the positive feedback!

I'll try to have an example created either later today or tomorrow 馃憤 In the meantime, you should refactor your bloc to add new events in response to location data from the subscription:

locationSubscription?.cancel();
locationSubscription = LocationStore().userLocation().listen((location) => add(LocationChanged(location)));

Then in mapEventToState you can just handle a LocationChangedEvent by yielding a new state with the latest location (passed via the event).

One thing I would like to add is that I would recommend injecting the LocationProvider/LocationService into the bloc as a dependency instead of creating new instances of it within the bloc. The way you currently have it set up makes it really tough to unit test the bloc code because you can't easily inject a Mock/Fake LocationProvider.

Hope that helps and I'll create a complete example in the next few days 馃憤

All 3 comments

Hi @Theunodb 馃憢
Thanks for opening an issue as well as for the positive feedback!

I'll try to have an example created either later today or tomorrow 馃憤 In the meantime, you should refactor your bloc to add new events in response to location data from the subscription:

locationSubscription?.cancel();
locationSubscription = LocationStore().userLocation().listen((location) => add(LocationChanged(location)));

Then in mapEventToState you can just handle a LocationChangedEvent by yielding a new state with the latest location (passed via the event).

One thing I would like to add is that I would recommend injecting the LocationProvider/LocationService into the bloc as a dependency instead of creating new instances of it within the bloc. The way you currently have it set up makes it really tough to unit test the bloc code because you can't easily inject a Mock/Fake LocationProvider.

Hope that helps and I'll create a complete example in the next few days 馃憤

Thank you for the feedback! Yes your suggestions makes sense, I'm looking forward to your example!

@Theunodb I've put together a gist. Closing for now but feel free to comment if you have any questions/feedback. Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

clicksocial picture clicksocial  路  3Comments

wheel1992 picture wheel1992  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

ricktotec picture ricktotec  路  3Comments

rsnider19 picture rsnider19  路  3Comments