Integrate reverse location search in your Android app (Flutter)

1. Before you begin

This tutorial teaches you how to implement getting the address location of users using their longitude and latitude information in your Android app in Flutter.

This is called reverse location search.

You will be implementing the Check Location button in the below UI

The UI had already been created in this article. Please check it out for step-by-step instructions on how to create it.

When the Check Location button is tapped, the address of the location you are in will replace the Check Location button at the centre of the UI.

Under the hood, the app is using your longitude and latitude information to get your address.

To make things fun, you will also be using the longitude and latitude information to get the precise zip code associated with your address.

Prerequisites

  • Basic knowledge of Flutter application development

  • Familiarity with using Android Studio to create Flutter projects

What you'll learn

  • Using the location package

  • Using the geocoding package

  • Dynamically returning a widget based on some conditions

2. Create a new project/use existing locateme project

If you've taken the mentioned tutorial in the first section and you created the project, skip this part and the next part.

If you haven't, launch Android Studio to create a new project (You might need to close a current project before you can be able to do this). In the New Project panel,

  • Give the project the name locateme

  • Give the project the description: A reverse location search app

  • Select Java and Objective-C as languages

  • Deselect IOS, Linux, MacOS, Web and Windows as target platforms (Our focus is the Android platform)

  • Select Create project offline

3. Create the UI

Go to the lib folder, and open the main.dart file and clear the content of the file.

Next, paste the below codes into the file:

import 'package:flutter/material.dart';

void main() { runApp(const MyApp()); }

class MyApp extends StatelessWidget { 
    const MyApp({super.key}); 
    @override Widget build(BuildContext context) { 
        return MaterialApp( 
                theme: ThemeData( 
                        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 
                        useMaterial3: true ), 
                home: const HomePage(title: "LocateMe") 
               ); 
    } 
}

class HomePage extends StatefulWidget { 
    final String title; 
    const HomePage({super.key, required this.title}); 
    @override State createState() => _HomePageState(); 
}

class _HomePageState extends State<HomePage> { 
    @override Widget build(BuildContext context) { 
        return Scaffold( 
                appBar: AppBar( 
                         backgroundColor: Theme.of(context).colorScheme.inversePrimary, 
                         title: Text(widget.title) 
                        ), 
                body: Center( 
                        child: Padding( 
                                padding: const EdgeInsets.all(20), 
                                child: ElevatedButton( 
                                        onPressed: (){}, 
                                        child: const Text('Check Location')
                                       )
                               )
                       ) 
               ); 
    } 
}

4. Create a LocationService class

You will start by creating a LocationService class.

In this class, you will be using the following methods of the location package:

  • serviceEnabled()

  • requestService()

  • hasPermission()

  • requestPermission()

  • getLocation()

You will also be using the placemarkFromCoordinates() method of the geocoding package.

You will code this class to return a location data object. You will also code the class to return specific data in the location data object - street address, and zip code.

Before you can get location data, you need to first run a check to see if location service has been enabled and if location permission has been granted. If they have, you can request location data. If they haven't, you will need to make the necessary ask and if the user chooses not to comply, the attempt to get location data should abort.

Creating the LocationService class

Create a dart file that goes by the name location_service.

Next, add the below code to the file:

class LocationService {

}

Adding dependencies

Because you need to use the location and geocoding packages, open the pubspec.yaml file, modify the dependencies block to the below:

dependencies: 
    flutter: 
        sdk: flutter 
    location: ^4.4.0 
    geocoding: ^2.1.0

The location and the geocoding packages have been added. Just copy the below to add them:

location: ^4.4.0 
geocoding: ^2.1.0

Next, click on Pub get at the top right corner of Android Studio. If the option doesn't appear, run flutter pub get from the Terminal.

Creating getUserLocation method

Next, you will create a getUserLocation method in the LocationService class. This method will asynchronously get the location data using the getLocation method of the Location class.

Add the below code to the class:

Future<void> getUserLocation() async {

}

The return type of the method you declared is Future<void>.

In Dart, when a method has a return type of Future<void>, it indicates that the method will complete asynchronously and not return any value. The void keyword signifies the absence of a specific return value.

The Future type is used to represent a computation that may complete in the future. In this case, the method getUserLocation is marked as async, which means it can use await to pause its execution and wait for asynchronous operations to complete.

By specifying Future<void> as the return type, the method communicates that it will complete its execution but won't produce any value. The Future allows the caller of the method to handle the asynchronous completion and perform any necessary follow-up actions.

Next, you will start the coding of the getUserLocation method by writing code to check if location service has been enabled in the app. The method should request for the service if it has not been enabled and if it is then not enabled, should terminate.

Since you will be using a method of the Location class, you need to create a Location object. Add the below to the getUserLocation method to create a Location object:

Location location = Location();

You will get the error message: Undefined class 'Location'.

This is appearing because you are using a class you haven't imported. While you've added the location package to the project, you still need to import the package in the location_service file.

Add the below to the top of the file to import the location package:

import 'package:location/location.dart';

The error should now disappear.

Next, you will call the serviceEnabled method of the Location class and store its response in a boolean variable serviceEnabled. The method returns a boolean value. The method checks if location service has been enabled or not.

Add the below code to the getUserLocation method:

bool serviceEnabled = await location.serviceEnabled();

There is a need for the await keyword to be used in the code. This is because the method is performing an asynchronous operation. The await keyword tells the getUserLocation method to only continue with its execution after getting results from the serviceEnabled method.

Next, add the below code to the method:

if (!serviceEnabled) { 
    serviceEnabled = await location.requestService(); 
    if (!serviceEnabled) { 
        return; 
    } 
}

A check is being made to see if location service is enabled. If it is not enabled, the code block will be executed. If it is already enabled, the code block will be skipped.

Now, in the code block, there is a request for location service using the requestService method of the Location class. The serviceEnabled boolean variable is then being reassigned based on the results. Another check is then made on the value of the serviceEnabled variable. If the value is still false, it means the user failed to enable location service. At this point, the getUserLocation method should terminate.

You've checked if location service has been enabled. Next, you will check if location permission has been granted.

You will call the hasPermission() method and store its response in a variable permissionStatus. A check will now be made on the variable. Because the hasPermission method returns a PermissionStatus object as a response, you will be preceding the declaration of the permissionStatus variable with a PermissionStatus class name.

Add the below code to the getUserLocation method:

PermissionStatus permissionStatus= await location.hasPermission();

Next, add the below code:

if ( permissionStatus == PermissionStatus.denied) { 
    permissionStatus = await location.requestPermission(); 
    if (permissionStatus == PermissionStatus.denied) { 
        return; 
    } 
}

A check was made on the value of the permissionStatus variable. If the value is PermissionStatus.denied, the code block will be executed. If it isn't, the code block will be skipped and the getUserLocation method will continue with its execution.

If the code block is executed, it means location permission has not been granted. So a request for permission will be made and the response will be assigned to the permissionStatus variable. A new check will then be made to the variable. If the value is still PermissionStatus.denied, the getUserLocation method should abort its operation.

Next, the getLocation method will be used to fetch the user's location.

Add the below code to the getUserLocation method:

LocationData locationData = await location.getLocation();

You can see the return value of the getLocation method is a LocationData object.

Because the location data (latitude and longitude information) is not your primary object of interest but rather the street address of the user, at this point, you will introduce the placemarkFromCoordinates method of the geocoding package.

Add the below code to the getUserLocation method:

final placemarks = await placemarkFromCoordinates( locationData.latitude!, locationData.longitude!);

You will get the error: The method 'placemarkFromCoordinates' isn't defined for the type 'LocationService'.

The error is appearing because you haven't imported the geocoding package.

Add the below to the import-related codes at the top of the location_service file to import it:

import 'package:geocoding/geocoding.dart';

Now, you will get the error: The name 'Location' is defined in the libraries 'package:geocoding_platform_interface/src/mo.. and 'package:location/location.dart'.

What the message is saying is that the geocoding package you just imported also has a Location class. There is therefore conflict. To resolve it, you will hide the Location class of the geocoding package.

Modify the code importing the package to the below:

import 'package:geocoding/geocoding.dart' hide Location;

With that, the problem is solved.

With the placemarks variable, you now have access to the street address associated with the latitude and longitude information. You also now have access to the Zip code.

There are other pieces of information you also now have access to.

Question: How do you return the data? We have several options. But the option you will be exploring is the approach of defining private instance variables, initializing those instance variables in the getUserLocation method, and then creating getter methods that when called will return the values in those private instance variables.

Add the below private instance variables to the LocationService class:

LocationData? _userLocation; 
String? _streetAddress; 
String? _zipCode;

The question mark in each declaration ensures you don't get the message: Non-nullable instance field must be initialized.

That is, it makes it possible for you to declare the variables without initialising them.

Next, add the below to the getUserLocation method:

_userLocation = locationData; 
_streetAddress = placemarks.first.street; 
_zipCode = placemarks.first.postalCode;

You've now assigned the three data of interest to the three private instance variables.

Creating getter methods

Add the below to the LocationService class to create the getter methods:

LocationData? get userLocation => _userLocation; 
String? get streetAddress => _streetAddress; 
String? get zipCode => _zipCode;

The first creates a getter method userLocation. The second creates the getter method streetAddress. The third creates the getter method zipCode.

With this, you are done creating the LocationService class.

The below codes will be the codes in the file:

import 'package:location/location.dart'; 
import 'package:geocoding/geocoding.dart' hide Location;

class LocationService { 
    LocationData? _userLocation; 
    String? _streetAddress;     
    String? _zipCode;

Future<void> getUserLocation() async { 
    Location location = Location(); 
    bool serviceEnabled = await location.serviceEnabled(); 
    if (!serviceEnabled) { 
        serviceEnabled = await location.requestService(); 
        if (!serviceEnabled) { 
            return; 
        } 
    } 
    PermissionStatus permissionStatus = await location.hasPermission(); 
    if ( permissionStatus == PermissionStatus.denied) { 
        permissionStatus = await location.requestPermission(); 
        if (permissionStatus == PermissionStatus.denied) { 
            return; 
        } 
    }

    LocationData locationData = await location.getLocation(); 
    final placemarks = await placemarkFromCoordinates( locationData.latitude!, locationData.longitude!);

    _userLocation = locationData; 
    _streetAddress = placemarks.first.street; 
    _zipCode = placemarks.first.postalCode;
}

LocationData? get userLocation => _userLocation; 
String? get streetAddress => _streetAddress; 
String? get zipCode => _zipCode; 

}

5. Modify the _HomePageState class

Next, you will import the class in the main.dart file. Add the below to the top of the main.dart file alongside the other import statements:

import 'package:locateme/location_service.dart';

Next, you need an instance of LocationService so you can access the getter methods in the _HomePageState class.

Add the below at the top of the _HomePageState class:

LocationService? _locationService;

Next, override the initState method of the class and create an instance of _locationService inside it:

@override void initState() { 
    super.initState(); 
    _locationService = LocationService(); 
}

Next, implement a method that will internally call the getUserLocation method of the LocationService class and proceed to rebuild the UI:

Future getUserLocation() async { 
    await locationService.getUserLocation(); 
    setState(() {}); // Trigger a rebuild after fetching the location 
}

You will get the error message: The method 'getUserLocation' can't be unconditionally invoked because the receiver can be 'null'.

Fix it by either wrapping the two codes in an if statement that checks for null:

Future *getUserLocation() async { 
    if (*locationService != null) { 
        await _locationService.getUserLocation(); 
        setState(() {}); 
    } 
}

Or by adding ? before the dot preceding the method call.

Future getUserLocation() async { 
    await locationService?.getUserLocation(); 
    setState(() {}); 
}

Whichever, both will work.

Next, you will create a method that will base on the value of the _locationService.userLocation variable either return the Check Location button or will be returning a widget containing the two pieces of information of interest - street address and zip code.

Add the below to the _HomePageState class:

Widget *buildLocationInfo() { 
    if (*locationService?.userLocation != null) { 
        return Column( 
            children: [ 
                const Text('Your street address:'), 
                const SizedBox(height: 10), 
                Text(_locationService?.streetAddress ?? 'Loading...'), 
                const SizedBox(height: 10), 
                const Text('Your zip code:'), 
                const SizedBox(height: 10), 
                Text(_locationService?.zipCode ?? 'Loading...'), 
            ], 
        ); 
    } else { 
        return ElevatedButton( 
            onPressed: _getUserLocation, 
            child: const Text('Check Location'), 
        ); 
    } 
}

NB: The asterisk (*) before the return type Widget indicates that the method can return a null value. This is known as a "nullable type."

Finally, modify the body attribute of the Scaffold widget to the below:

body: Center( 
    child: Padding( 
        padding: const EdgeInsets.all(20), 
        child: Column( 
            mainAxisAlignment: MainAxisAlignment.center, 
            children: [ 
                _buildLocationInfo(), 
            ], 
        ), 
    ), 
)

With this, you are done modifying the _HomePageState class.

The below codes should be the codes in the class:

class HomePageState extends State { 
    LocationService? locationService;

    @override void initState() { 
        super.initState(); 
        _locationService = LocationService(); 
    }

    Future getUserLocation() async { 
        await locationService?.getUserLocation(); 
        setState(() {}); // Trigger a rebuild after fetching the location 
    }

    Widget *buildLocationInfo() { 
        if (*locationService?.userLocation != null) { 
            return Column( 
                children: [ 
                    const Text('Your street address:'), 
                    const SizedBox(height: 10), 
                    Text(_locationService?.streetAddress ?? 'Loading...'), 
                    const SizedBox(height: 10), 
                    const Text('Your zip code:'), 
                    const SizedBox(height: 10), 
                    Text(_locationService?.zipCode ?? 'Loading...'), 
                ], 
            ); 
        } else { 
            return ElevatedButton( 
                onPressed: _getUserLocation, 
                child: const Text('Check Location'), 
            ); 
        } 
    }

    @override Widget build(BuildContext context) { 
        return Scaffold( 
            appBar: AppBar( 
                backgroundColor: Theme.of(context).colorScheme.inversePrimary, 
                title: Text(widget.title) 
            ), 
            body: Center( 
                child: Padding( 
                    padding: const EdgeInsets.all(20), 
                    child: Column( 
                        mainAxisAlignment: MainAxisAlignment.center, 
                        children: [ 
                            _buildLocationInfo(), 
                        ], 
                    ), 
                ), 
            ) 
        ); 
    } 

}

Wrapping up

In this tutorial, you've learnt how to implement getting user location in a Flutter Android app.

Hopefully, when you're tasked with something similar, you won't struggle to write the codes.

Please, if you followed the tutorial and you encountered issues, or you think something is missing, or you have questions, or you would like to offer any thoughts or suggestions, go ahead and leave a comment below.

I’d appreciate the feedback!