Mapbox Flutter Bloc Location Package

1



Hi everyone.

Mapbox is a tremendous carrier to use maps, is high-quality due to the fact you can edit the maps as you prefer to exhibit them primarily based on your requirements. You may want to swap between exclusive types of layers to exhibit the maps, like outdoors, streets, monochromatic, and so on. For extra info, this is the respectable website: 

https://www.mapbox.com/

In this example, we are going to use:

1. Mapbox

2. Location package

3. Flutter dot env package

4. Flutter bloc.

5. Very Good CLI to create a new package called location_repository.

Create an account and create a secret access token


1- Go to https://account.mapbox.com/ and create an account

2- Go to https://account.mapbox.com/access-tokens/ and click on the “create a token” button.

3- It’s important to create the secret token with the property download: read.



Then you are going to see your public secret access tokens

Native configurations

Android

1- Go to the gradle.properties file and set the secret access token: MAPBOX_DOWNLOADS_TOKEN=YOUR_SECRET_MAPBOX_ACCESS_TOKEN


2- Create a new strings.xml file to set your secret key, to do that, you have to go to android/app/main/res/values and create a new file referred to as strings.xml and set up the information. 



3- Configure the permissions on the AndroidManifest.xml file.


4- Change the minSdkVersion to 21 into the module-level build.gradle file.


iOS

1- The first step that you want to do is to create a .netrc file in your HOME Mac directory, observe these steps to create: 

1. Open a Terminal

2. cd ~ (this is to go to the home directory)

3. touch .netrc (this is to create a file)

4. open .netrc (open .netrc file)

When the file is open you need to copy/paste this and set your secret password:

machine api.mapbox.com

login Mapbox

password YOUR_SECRET_MAPBOX_ACCESS_TOKEN

Save the records and shut the file.


The .netrc file is an undeniable textual content file that is used in sure improvement environments to save credentials used to get admission to far-flung servers.


2- Configure the public get right of entry to token:

Open the listing Runner.xcworkspace in XCode. Select on the left facet the root of the venture “Runner”, then on ambitions pick “Runner” and in the end, you have to pick out the Info tab. On Custom iOS Targets Properties you want to add a new one referred to as MBXAccessToken with your public token. 



If you shut XCode and go to your flutter task on the ios/runner/info.plist file you may want to see a new line like this: 



Additionally, you want to add a message to inform the consumer that the vicinity is mandatory, so you can add a new line into Info.plist like this: 



3- Change the platform versión into the Podfile file.



4- Execute the subsequent instructions to clean, replace and installation the new pods: 

cd /ios

rm -rf Pods/ Podfile.lock Flutter/Flutter.podspec

pod deintegrate

pod repo update

pod install


Try to execute the app. Are you facing this error?


You should add these lines to the Podfile to fix it.

installer.pods_project.build_configurations.eachdo|config|

   config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"]="arm64"

end

Okay, at this factor the native configurations are done. The subsequent step is to get the system vicinity to set our region on the map. 


.ENV File

In this project, I’m going to use the flutter_dotenv bundle to cover my get admission to the token. It’s easy to use, you want to create a property folder in your root software and then create a .env file. 



Inside this folder you are going to put your public Mapbox get entry to token like this:

 

MAPBOX_ACCESS_TOKEN = 'YOUR PUBLIC ACCESS TOKEN'


Then, to load the file and be capable to use it in our application, we want to add this line to our primary method. 


await dotenv.load(fileName: "assets/.env");


If you want to get the price of our MAPBOX_ACCESS_TOKEN, you want to do it in this way: 


dotenv.get('MAPBOX_ACCESS_TOKEN')


To cover this .env file in our Github assignment we want to add a new line to our .gitnore file: 


/assets/.env


Location

In this tutorial we are going to do something different, we are going to create our personal package deal to host all the content material associated with “location”. This is an excellent way to have all greater shape and clean, and additionally, if you prefer to use this package deal in any other app you ought to do it due to the fact is impartial to the relaxation of the code.


To create it we are going to use Very Good CLI. 

For more info, check this link: https://verygood.ventures/blog/flutter-starter-app-very-good-core-cli.

Location Repository Package

1- Create an applications folder on your root undertaking and open a new terminal on the new folder and put this:

 

dart pub global activate very_good_cli


very_good create location_repository -t dart_pkg



If you take a look at your programs folder you may want to note that there is a new bundle known as “location_repository”. 



To use the vicinity we want to add the area package deal to our location_repository/pubspec.yaml file and run flutter pub get. 



Now we can use the region package. To get the consumer machine region we want to test two vital matters earlier than getting the location:


Check if the provider is enabled.

Check if the consumer has the critical permissions (these permissions are the ones we introduced above).

I’ve created a “custom” exception referred to as CurrentLocationFailure to manage the exclusive blunders messages that we can throw to supply to the person extra information.


This is the full method. 


Future<CurrentUserLocationEntity> getCurrentLocation() async {

    final serviceEnabled = await _location.serviceEnabled();

    if (!serviceEnabled) {

      final isEnabled = await _location.requestService();

      if (!isEnabled) {

        throw CurrentLocationFailure(

          error: "You don't have location service enabled",

        );

      }

    }


    final permissionStatus = await _location.hasPermission();

    if (permissionStatus == PermissionStatus.denied) {

      final status = await _location.requestPermission();

      if (status != PermissionStatus.granted) {

        throw CurrentLocationFailure(

          error: "You don't have all the permissions granted."

              '\nYou need to activate them manually.',

        );

      }

    }


    late final LocationData locationData;

    try {

      locationData = await _location.getLocation();

    } catch (_) {

      throw CurrentLocationFailure(

        error: 'Something went wrong getting your location, '

            'please try again later',

      );

    }


    final latitude = locationData.latitude;

    final longitude = locationData.longitude;


    if (latitude == null || longitude == null) {

      throw CurrentLocationFailure(

        error: 'Something went wrong getting your location, '

            'please try again later',

      );

    }


    return CurrentUserLocationEntity(

      latitude: latitude,

      longitude: longitude,

    );

  }


Also, I created an object referred to as CurrentUserLocation to set the latitude and longitude of the person when the get current location technique returns the location data.


class CurrentUserLocationEntity {

  const CurrentUserLocationEntity({

    required this.latitude,

    required this.longitude,

  });

  final double latitude;

  final double longitude;


  static const empty = CurrentUserLocationEntity(latitude: 0, longitude: 0);

}

 

This is the location_repository class:


import 'package:location/location.dart';

import 'package:location_repository/src/model/current_location.dart';


class CurrentLocationFailure implements Exception {

  CurrentLocationFailure({

    required this.error,

  });

  final String error;

}


/// {@template location_repository}

/// A Very Good Project created by Very Good CLI.

/// {@endtemplate}

class LocationRepository {

  /// {@macro location_repository}

  LocationRepository({

    Location? location,

  }) : _location = location ?? Location();

  final Location _location;


  Future<CurrentUserLocationEntity> getCurrentLocation() async {

    final serviceEnabled = await _location.serviceEnabled();

    if (!serviceEnabled) {

      final isEnabled = await _location.requestService();

      if (!isEnabled) {

        throw CurrentLocationFailure(

          error: "You don't have location service enabled",

        );

      }

    }


    final permissionStatus = await _location.hasPermission();

    if (permissionStatus == PermissionStatus.denied) {

      final status = await _location.requestPermission();

      if (status != PermissionStatus.granted) {

        throw CurrentLocationFailure(

          error: "You don't have all the permissions granted."

              '\nYou need to activate them manually.',

        );

      }

    }


    late final LocationData locationData;

    try {

      locationData = await _location.getLocation();

    } catch (_) {

      throw CurrentLocationFailure(

        error: 'Something went wrong getting your location, '

            'please try again later',

      );

    }


    final latitude = locationData.latitude;

    final longitude = locationData.longitude;


    if (latitude == null || longitude == null) {

      throw CurrentLocationFailure(

        error: 'Something went wrong getting your location, '

            'please try again later',

      );

    }


    return CurrentUserLocationEntity(

      latitude: latitude,

      longitude: longitude,

    );

  }

}


Features

Now it’s time to go to our lib folder and create the shape to use the area and maps.




We are going to have two folders:


Location: right here we are going to create a bloc to deal with all the good judgment associated to getting the person location.

Map: right here we are going to cope with all the common sense associated to the maps.

To exhibit a map the first aspect that we want to do is to get the consumer location, to do that we are going to use our bundle location_repository, so we want to add the dependency to our pubspec.yaml file. 



Okay, we have the dependency introduced and now we can use it to get the consumer location.


Location

In this feature, we want to create a bloc to use the vicinity repository bundle and be capable to get the person service, permissions, and location. As constantly we want three classes.


Location kingdom class. 

part of 'location_bloc.dart';


enum LocationStateStatus { initial, success, error, loading }


extension LocationStateStatusX on LocationStateStatus {

  bool get isInitial => this == LocationStateStatus.initial;

  bool get isSuccess => this == LocationStateStatus.success;

  bool get isError => this == LocationStateStatus.error;

  bool get isLoading => this == LocationStateStatus.loading;

}


class LocationState extends Equatable {

  const LocationState({

    this.status = LocationStateStatus.initial,

    LatLng? initLocation,

    CurrentUserLocationEntity? currentUserLocation,

    String? errorMessage,

  })  : currentUserLocation =

            currentUserLocation ?? CurrentUserLocationEntity.empty,

        initLocation = initLocation ?? const LatLng(40.4167, -3.70325),

        errorMessage = errorMessage ?? '';


  final LocationStateStatus status;

  final CurrentUserLocationEntity currentUserLocation;

  final LatLng initLocation;

  final String errorMessage;


  @override

  List<Object?> get props => [

        status,

        currentUserLocation,

        initLocation,

        errorMessage,

      ];


  LocationState copyWith({

    LocationStateStatus? status,

    CurrentUserLocationEntity? currentUserLocation,

    LatLng? initLocation,

    Location? location,

    String? errorMessage,

  }) {

    return LocationState(

      status: status ?? this.status,

      currentUserLocation: currentUserLocation ?? this.currentUserLocation,

      initLocation: initLocation ?? this.initLocation,

      errorMessage: errorMessage ?? this.errorMessage,

    );

  }

}


Location event class.

part of 'location_bloc.dart';

class LocationEvent extends Equatable {

  @override

  List<Object?> get props => [];

}

class GetLocation extends LocationEvent {}


Location bloc class: In this class, we are going to have an occasion for our region repository class.


import 'package:equatable/equatable.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:location/location.dart';

import 'package:location_repository/location_repository.dart';

import 'package:mapbox_gl/mapbox_gl.dart';


part 'location_event.dart';

part 'location_state.dart';


class LocationBloc extends Bloc<LocationEvent, LocationState> {

  LocationBloc({

    required this.locationRepository,

  }) : super(LocationState()) {

    on<GetLocation>(_getLocationEvent);

  }

  final LocationRepository locationRepository;


  void _getLocationEvent(GetLocation event, Emitter<LocationState> emit) async {

    try {

      emit(state.copyWith(status: LocationStateStatus.loading));


      var _currentLocation = await locationRepository.getCurrentLocation();


      emit(

        state.copyWith(

          currentUserLocation: _currentLocation,

          status: LocationStateStatus.success,

        ),

      );

    } on CurrentLocationFailure catch (e) {

      emit(

        state.copyWith(

          status: LocationStateStatus.error,

          errorMessage: e.error,

        ),

      );

      // This is important to check errors on tests. 

      // Also you can see the error on the [BlocObserver.onError].

      addError(e);

    }

  }

} 


Now we can use this bloc on our Map feature.


Map

Here we are going to have two classes:


Map web page class: right here we are going to initialize our vicinity repository and additionally the place bloc. 


import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:location_repository/location_repository.dart';

import 'package:maps_flutter/location/location_barrel.dart';

import 'package:maps_flutter/map/pages/map_layout.dart';


class MapPage extends StatelessWidget {

  const MapPage({Key? key}) : super(key: key);


  @override

  Widget build(BuildContext context) {

    return RepositoryProvider(

      create: (context) => LocationRepository(),

      child: BlocProvider<LocationBloc>(

        create: (context) => LocationBloc(

          locationRepository: context.read<LocationRepository>(),

        )..add(GetLocation()),

        child: const MapLayout(),

      ),

    );

  }

}


Map design class: right here we are going to hear to the one-of-a-kind states of our region bloc to exhibit the right widget. 


import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:maps_flutter/location/bloc/location_bloc.dart';

import 'package:maps_flutter/location/widgets/location_error_widget.dart';

import 'package:maps_flutter/map/widgets/map_success.dart';


class MapLayout extends StatelessWidget {

  const MapLayout({Key? key}) : super(key: key);


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: BlocBuilder<LocationBloc, LocationState>(

        buildWhen: (previous, current) =>

            current.status.isLoading ||

            current.status.isError ||

            current.status.isSuccess,

        builder: (context, state) {

          if (state.status.isSuccess) {

            return MapSuccess(

              locationData: state.locationData,

              initLocation: state.initLocation,

            );

          }

          if (state.status.isError) {

            return LocationErrorWidget(

              errorMessage: state.errorMessage,

            );

          }


          return const Center(

            child: CircularProgressIndicator(),

          );

        },

      ),

    );

  }

}



As you can see, in this type we are going to construct the view solely if the kingdom of the bloc isLoading, isError, or success.

isLoading: suggests a CircularProgressIndicator.

isError: suggests an error widget. This widget shows an error message with an icon. 


import 'package:flutter/material.dart';

class LocationErrorWidget extends StatelessWidget {
  const LocationErrorWidget({
    Key? key,
    required this.errorMessage,
  }) : super(key: key);
  final String errorMessage;
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Icon(
          Icons.error,
          size: 36.0,
          color: Colors.red,
        ),
        const SizedBox(
          height: 18.0,
        ),
        Text(
          errorMessage,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.bodyText1,
        ),
      ],
    );
  }
}


isSuccess: with this widget, we can exhibit the map and the region of the user. 


import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:location_repository/location_repository.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:maps_flutter/map/widgets/info_card_widget.dart';
import 'package:maps_flutter/map/widgets/zoom_in_out_widget.dart';

class MapSuccessWidget extends StatefulWidget {
  const MapSuccessWidget({
    Key? key,
    required this.currentUserLocation,
  }) : super(key: key);
  final CurrentUserLocationEntity currentUserLocation;

  @override
  State<MapSuccessWidget> createState() => _MapSuccessWidgetState();
}

class _MapSuccessWidgetState extends State<MapSuccessWidget> {
  MapboxMapController? mapController;

  _onMapCreated(MapboxMapController controller) async {
    mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        MapboxMap(
          styleString: 'mapbox://styles/anapolo/cl1gia3ae001a14q4eahc8q55',
          accessToken: dotenv.get('MAPBOX_ACCESS_TOKEN'),
          onMapCreated: _onMapCreated,
          myLocationEnabled: true,
          trackCameraPosition: true,
          initialCameraPosition: CameraPosition(
            target: LatLng(
              widget.currentUserLocation.latitude ,
              widget.currentUserLocation.longitude ,
            ),
            zoom: 9.0,
          ),
          onMapClick: (_, latlng) async {
            await mapController?.animateCamera(
              CameraUpdate.newCameraPosition(
                CameraPosition(
                  bearing: 10.0,
                  target: LatLng(latlng.latitude, latlng.longitude),
                  tilt: 30.0,
                  zoom: 12.0,
                ),
              ),
            );
          },
        ),
        Positioned(
          bottom: 0,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            child: Card(
              clipBehavior: Clip.antiAlias,
              child: Padding(
                padding: const EdgeInsets.all(15),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text(
                      'Hi there!',
                      style:
                      TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 20),
                    const Text('You are currently here:'),
                    Text(
                        'Longitude: ${widget.currentUserLocation.longitude} - Latitude: ${widget.currentUserLocation.latitude}',
                        style: const TextStyle(color: Colors.indigo)),
                    const SizedBox(height: 20),

                  ],
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}


This is the simulator





Customize map

As I noted at the establishing of the article, Mapbox lets in you to adjust the maps to do them greater uniques, so in this article, I’m going to exhibit you how to do it a little bit.

1- Go to Mapbox studio: https://studio.mapbox.com/

2- Click on the new fashion button. 



3- Choose a style and go inside the editor.



Mapbox Editor

This is the main page of the editor.






In the editor, you ought to trade a lot of matters like colors, fonts, icons, and lots more. For a rapid example, I’m going to exchange a little bit the hues of the roads and the water on the map.
 




Then click submit to post the changes, as you can see this is the earlier than and after of the map




 

Going returned to the styles. Now, we have my new style, to use it we want to click on on the “share” icon on the right. 




In the popup menu, I need to copy the “style URL”.





Use customize map

To use the custom-made map, in our code we want to set up the fashion for our map, to do that, set the style string property on the MapSuccessWidget:


MapboxMap(
          styleString: 'mapbox://styles/anapolo/cl1gia3ae001a14q4eahc8q55',
          accessToken: dotenv.get('MAPBOX_ACCESS_TOKEN'),
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
              target: LatLng(
                widget.locationData.latitude ?? widget.initLocation.latitude,
                widget.locationData.longitude ?? widget.initLocation.longitude,
              ),
              zoom: 14.0),
        ),


Be patient, the map ought to take around 10 minutes to upload, so if you don’t see the adjustments immediately, don’t worry! attempt once more later. 



 

Animate digital camera to a new location

To manipulate this, we want to use our mapController. This controller ought to be created at the establishing of the classification and initialized on the _onMapCreated method. 


MapboxMapController? mapController;

_onMapCreated(MapboxMapController controller) async {
  mapController = controller;
}


We are going to replace the digital camera role when the person clicks on the map, to do that we want to add to our MapboxMap widget the feature onMapClick and put into effect the following code.
 

 onMapClick: (_, latlng) async {
            await mapController?.animateCamera(
              CameraUpdate.newCameraPosition(
                CameraPosition(
                  bearing: 10.0,
                  target: LatLng(latlng.latitude, latlng.longitude),
                  tilt: 30.0,
                  zoom: 12.0,
                ),
              ),
            );
          },


ZoomIn &amp; ZoomOut

One time once more we want to use the mapController to do zoom in and zoom out. I’ve created two FAB buttons to manage it. 

Positioned(
          bottom: MediaQuery.of(context).size.height * .18,
          right: 10,
          child: ZoomInOutWidget(
            zoomInCallback: () async => await mapController?.animateCamera(
              CameraUpdate.zoomIn(),
            ),
            zoomOutCallback: () async => await mapController?.animateCamera(
              CameraUpdate.zoomOut(),
            ),
          ),
        ),




This is the ZoomInOutWidget:


import 'package:flutter/material.dart';

class ZoomInOutWidget extends StatelessWidget {
  const ZoomInOutWidget({
    Key? key,
    required this.zoomInCallback,
    required this.zoomOutCallback,
  }) : super(key: key);

  final VoidCallback zoomInCallback;
  final VoidCallback zoomOutCallback;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FloatingActionButton(
          onPressed: zoomInCallback,
          child: const Icon(Icons.zoom_in),
        ),
        const SizedBox(height: 8.0),
        FloatingActionButton(
          onPressed: zoomOutCallback,
          child: const Icon(Icons.zoom_out),
        ),
      ],
    );
  }
}


This is the closing end result of the MapWidgetSuccess after including these functionalities: 


import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:location_repository/location_repository.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:maps_flutter/map/widgets/info_card_widget.dart';
import 'package:maps_flutter/map/widgets/zoom_in_out_widget.dart';

class MapSuccessWidget extends StatefulWidget {
  const MapSuccessWidget({
    Key? key,
    required this.currentUserLocation,
  }) : super(key: key);
  final CurrentUserLocationEntity currentUserLocation;

  @override
  State<MapSuccessWidget> createState() => _MapSuccessWidgetState();
}

class _MapSuccessWidgetState extends State<MapSuccessWidget> {
  MapboxMapController? mapController;

  _onMapCreated(MapboxMapController controller) async {
    mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        MapboxMap(
          styleString: 'mapbox://styles/anapolo/cl1gia3ae001a14q4eahc8q55',
          accessToken: dotenv.get('MAPBOX_ACCESS_TOKEN'),
          onMapCreated: _onMapCreated,
          myLocationEnabled: true,
          trackCameraPosition: true,
          initialCameraPosition: CameraPosition(
            target: LatLng(
              widget.currentUserLocation.latitude,
              widget.currentUserLocation.longitude,
            ),
            zoom: 9.0,
          ),
          onMapClick: (_, latlng) async {
            await mapController?.animateCamera(
              CameraUpdate.newCameraPosition(
                CameraPosition(
                  bearing: 10.0,
                  target: LatLng(
                    latlng.latitude,
                    latlng.longitude,
                  ),
                  tilt: 30.0,
                  zoom: 12.0,
                ),
              ),
            );
          },
        ),
        Positioned(
          bottom: 0,
          child: InfoCardWidget(
            currentUserLocation: widget.currentUserLocation,
          ),
        ),
        Positioned(
          bottom: MediaQuery.of(context).size.height * .18,
          right: 10,
          child: ZoomInOutWidget(
            zoomInCallback: () async => await mapController?.animateCamera(
              CameraUpdate.zoomIn(),
            ),
            zoomOutCallback: () async => await mapController?.animateCamera(
              CameraUpdate.zoomOut(),
            ),
          ),
        ),
      ],
    );
  }
}


If this content is useful for you.Thanks

Post a Comment

1Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.

  1. What a comprehensive post! Extremely helpful and informative for a beginner like me. Thank you. By WDP Technologies

    ReplyDelete
Post a Comment

#buttons=(Accept !) #days=(20)

Our website uses cookies to enhance your experience. Learn More
Accept !