# Get Your Location On: A Step-by-Step Guide to Creating a Flutter App with Riverpod and Geolocator with Clean Architecture

## Intro:

While I was working on our upcoming app GotInfo. I needed to handle location permissions for my user since we use a google map to show the nearby posts pertinent to the user.

While I could just use the default [permission\_handler](https://pub.dev/packages/permission_handler) and be done with it. Unfortunately it's not so simple in a production environment as we have to handle a lot of location variables.

Here are the instances that we are going to cover through the article:

* Initial Permission Request
    
* Permission Denied
    
* Permission Revoked
    
* Location Services Disabled
    

## Initial packages setup

We are going to keep our list of packages to minimum. We just need two packages : [geolocator](https://pub.dev/packages/geolocator) and [flutter\_riverpod](https://pub.dev/packages/flutter_riverpod)

So let's get the latest versions for both the packages in our project by running the following commands:

```bash
flutter pub add flutter_riverpod
```

```bash
flutter pub add geolocator
```

```bash
flutter pub add app_settings
```

Finally run the following to make sure all the packages are in sync with our framework:

```bash
flutter pub get
```

## Setup Permissions:

Geolocator has a very detailed [usage guide](https://pub.dev/packages/geolocator#usage) as to how to setup location permissions. So let's see them:

### Android Setup:

**Android X:**

The geolocator plugin requires the AndroidX version of the Android Support Libraries. This means you need to make sure your Android project supports AndroidX.

So we need to do things to get Android X Support:

1. Add the following to your android/`gradle.properties` file:
    

```c
android.useAndroidX=true
android.enableJetifier=true
```

2. Make sure you set the `compileSdkVersion` in your "android/app/build.gradle" file to 34:
    

```c
android {
  compileSdkVersion 34

  ...
}
```

**Permissions:**

On Android you'll need to add either the `ACCESS_COARSE_LOCATION` or the `ACCESS_FINE_LOCATION`permission to your Android Manifest.

So let's go to our <mark>AndroidManifest.xml</mark> file located in `android/app/src/main` folder and the following permissions as children of `<mainfest>` tag:

```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

You can find my AndroidManifest in this [gist](https://gist.github.com/khkred/e5b6e3ab1ca7067bbe05a29d27b57c2a).

### iOS Setup:

**info.Plist File:**

On iOS you'll need to add the following entry to your Info.plist file (located under <mark>ios/Runner</mark>) in order to access the device's location.

Simply open your Info.plist file and add the following (make sure you update the description so it is meaningful in the context of your App):

```xml
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We use your location data to send you notifications when you check in to certain places to enhance your experience in our app.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Our app may also use your location in the background to send you timely notifications about important community alerts and updates relevant to your area, even when you're not actively using the app.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Our app uses your location to connect you with your local community, allowing you to discover nearby events, groups, and marketplace listings. For example, enabling location helps us show you the neighborhood happenings and items for sale in your vicinity.</string>
```

You can find my complete `info.plist` in the following [gist](https://gist.github.com/khkred/9718ef17f05bdc057b8f5160d4237a59).

### Code Setup:

So we have bunch of files where we are going to write the code. Since are using `flutter_riverpod`. we need to make sure that we add `ProviderScope` for our `MyApp` class

So this is our main.dart:

```bash
void main() {
  runApp(const ProviderScope(
    child: MyApp(),
  ));
}
```

**Location State:**  
Initially we'll create a `LocationState` to get the status of the service. We essentially want to check if the service is enabled and what is the permission status.

You can find the complete code for location state in the following [gist.](https://gist.github.com/khkred/b19ec6bdb158c80faa216e751f541960)

```dart
import 'package:geolocator/geolocator.dart';

class GetLocationState {
  final Position? position;
  final LocationPermission permission;

  GetLocationState({
     this.position,
    required this.permission,
  });

  factory GetLocationState.initial() {
    return GetLocationState(
      permission: LocationPermission.unableToDetermine,
    );
  }

  GetLocationState copyWith({
    Position? position,
    LocationPermission? permission,
  }) {
    return GetLocationState(
      position: position ?? this.position,
      permission: permission ?? this.permission,
    );
  }
}
```

**Location Repository:**

Now e create the Location Repository where we can checkPermissions and get CurrentLocation

```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';

final locationRepositoryProvider = Provider<LocationRepository>((ref) => LocationRepository());

class LocationRepository {
  Future<Position?> getCurrentLocation() async {
    try {
      LocationPermission permission = await checkPermissions();
      if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
        return null;
      }
      return await Geolocator.getCurrentPosition();
    } catch (e) {
      print('Error: $e');
      return null;
    }
  }

  Future<LocationPermission> checkPermissions() async {
    LocationPermission permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();
    }

    return permission;
  }
}
```

Finally on the basis of `LocationState` and `locationServiceStreamProvider` we can write the `LocationNotifier`

```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';

// Assuming your LocationState and LocationRepository are in the same feature directory
import '../data/location_repository.dart';
import '../data/location_state.dart';

final locationProvider =
    StateNotifierProvider<LocationNotifier, GetLocationState>((ref) {
  final locationRepository = ref.read(locationRepositoryProvider);
  return LocationNotifier(locationRepository);
});

class LocationNotifier extends StateNotifier<GetLocationState> {
  final LocationRepository _locationRepository;

  LocationNotifier(this._locationRepository)
      : super(GetLocationState.initial());

  Future<void> getLocation(BuildContext context) async {

    final permission = await _locationRepository.checkPermissions();

if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
      _showPermissionDialog(context);
      return;
    }

    final position = await _locationRepository.getCurrentLocation();

    if (position != null) {
      state = state.copyWith(position: position);
    } else {
      // Handle location permission denied (explained below)
    }
  }

  void _showPermissionDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Location Permission Denied'),
        content: const Text(
            'This app needs location permission to access your location.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () async {
              final status = await Geolocator.requestPermission();
              if (status == LocationPermission.whileInUse ||
                  status == LocationPermission.always) {
                getLocation(context); // Retry fetching location if permission granted
              } else {
                // Handle user refusing permission (optional)
              }
              Navigator.pop(context);
            },
            child: const Text('Settings'),
          ),
        ],
      ),
    );
  }
}
```

And this our final `LocationScreen` where we get the latitude and longitude:  

```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';

// Assuming your LocationState and LocationRepository are in the same feature directory
import '../data/location_repository.dart';
import '../data/location_state.dart';

final locationProvider =
    StateNotifierProvider<LocationNotifier, GetLocationState>((ref) {
  final locationRepository = ref.read(locationRepositoryProvider);
  return LocationNotifier(locationRepository);
});

class LocationNotifier extends StateNotifier<GetLocationState> {
  final LocationRepository _locationRepository;

  LocationNotifier(this._locationRepository)
      : super(GetLocationState.initial());

  Future<void> getLocation(BuildContext context) async {

    final permission = await _locationRepository.checkPermissions();

if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
      _showPermissionDialog(context);
      return;
    }

    final position = await _locationRepository.getCurrentLocation();

    if (position != null) {
      state = state.copyWith(position: position);
    } else {
      // Handle location permission denied (explained below)
    }
  }

  void _showPermissionDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Location Permission Denied'),
        content: const Text(
            'This app needs location permission to access your location.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () async {
              final status = await Geolocator.requestPermission();
              if (status == LocationPermission.whileInUse ||
                  status == LocationPermission.always) {
                getLocation(context); // Retry fetching location if permission granted
              } else {
                // Handle user refusing permission (optional)
              }
              Navigator.pop(context);
            },
            child: const Text('Settings'),
          ),
        ],
      ),
    );
  }
}
```

Finally you should be able to get the location in your screen. It should also handle location denied permissions too.

### Source Code:

You can find the source code for my project here: [Github Link](https://github.com/khkred/flutter_location_geolocator_riverpod)
