Sunday, April 26, 2020

Learning Flutter - Part 3 - Pulling Real-Time GPS Location and Updating the UI

In my last post, I laid out the design of a prototype complete with a paper drawing 'spec'.

The next step was to understand how to access (and update in the UI) real-time GPS location data using Flutter.

First, I looked up what the Google recommended method of accessing device GPS data was. Google recommends a package called geolocator which is maintained not by Google itself but by a team at Baseflow (my preference is to have to depend on fewer third parties to reduce security risk but the little checkmark icon baseflow.com at pub.dev indicates at least that it "Published by a pub.dev verified publisher" whatever that entails).

To understand and get started, the geolocator page recommends an article called Flutter: Getting a User's Location with the Geolocator Plugin which shows you the steps to configure and use location services:

  1. Importing the package in the pubspec.yaml file (like for every other package)
  2. Setting the permission requests for location data (which is unique to packages that want GPS access on the iPhone or Android device)
  3. Adding the code into the app to pull and use the GPS data

What the article did not show was a simple code snippet on how to pull real-time GPS location data (the code with the article gets location upon the user clicking). The geolocator package has an example with a section on getting a stream of locations but for someone new to Flutter I wanted the pare it down to understand the code a bit more.

Before understanding the example in geolocator, I needed to go back to fundamentals as I'd not yet learned about streams, async/await, and futures in Dart so I took a detour over to the Dart codelab on Asynchronous programming: futures, async, await to understand the language a bit better. As it turns out, most of that is hidden in the example created but it is a fundamental part of the language so doing the codelab was useful.

Once I'd done that, I pulled the example code into the Flutter default app and pared it down to a very simple app in a single dart file that displays on the GPS location data pulled in real-time:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import 'dart:async';

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'Real Time Location Demo App'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _location = 'No Location';
  var geolocator = Geolocator();
  StreamSubscription<Position> _positionStreamSubscription;

  @override
  void initState() {
    super.initState();
    const LocationOptions locationOptions =
        LocationOptions(accuracy: LocationAccuracy.high);
    final Stream<Position> positionStream =
        Geolocator().getPositionStream(locationOptions);
    _positionStreamSubscription =
        positionStream.listen((Position position) => setState(() {
              _location = """
LAT: ${position.latitude.toString()}\nLNG: ${position.longitude.toString()}""";
            }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Text(_location),
      ),
    );
  }
}

The code above creates a stream of Position objects and sets up a listener that calls setState to update a String variable that then gets redrawn into a Text widget. 

Once you run the app, the geolocator package will trigger the request for location data (of course, if you say no or your device (or virtual device) doesn't support GPS then the prototype won't work):



Once you've allowed, the app will display real-time GPS coordinates:




If you change the line of code that updates the string displayed, you can also add speed and heading:

40
41
42
43
44
              _location = """
SPEED: ${position.speed.toString()}\n
HEADING: ${position.heading.toString()}\n
LAT: ${position.latitude.toString()}\n
LNG: ${position.longitude.toString()}"""




Of course, with the code above you don't close your stream subscription (you don't even use it) so head over to the geolocator example section where this code was pulled from to show a more complete example along with other code you'll need to actually use this functionality in an app.

Going forward, I'll need to update the location code into my prototype app, use the speed and time between GPS updates to calculate distance travelled, and from there calculate cost, fuel use, and carbon footprint. To start the parameters will be hard-coded but eventually the app will ask the user to select a vehicle and pull fuel cost and perhaps temperature based on location to much more accurately calculate fuel cost, fuel use, and carbon footprint.

No comments:

Post a Comment