Flutterのlocationライブラリを使って現在地機能を追加する

どうも、前回作った天気予報アプリに、現在地の天気を取得する機能を追加します。

Flutterで天気予報アプリを作る - かれきゃぶろぐ

さくっと設計します。

f:id:castlejou:20200926180116p:plain

 

さくっと実装していきます。

 

どうやら位置情報を取得する(主要な)ライブラリは2種類あるらしく、

geolocator | Flutter Package

location | Flutter Package

それぞれにしかできない機能は、ざっくり以下のとおりです。

  • geolocator は取得する最後に取得した位置情報といったちょっとリッチなAPIが使える
  • locationにはそういったAPIはなさそうだが、前者では無理っぽいweb対応もしてる

前者の機能は最悪自分で書ける気がしますが、後者の機能はめんどくさそうなので今回は後者を使うことにしました。

使い方です。まず普通にpubspec.ymlに追加し、Pub getします。

dependencies:
location: ^3.0.2

Androidは特に何も設定しないで取得できるらしい

Android
With Flutter 1.12, all the dependencies are automatically added to your project. If your project was created before Flutter 1.12, you may need to follow this.

iOSios/Runner/Info.plistに項目二つ追加するらしい。

iOS
And to use it in iOS, you have to add this permission in Info.plist :

NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription

qiita.com

を参考にさせてもらい、ios/Runner/Info.plistに4行追記。

 <key>NSLocationAlwaysUsageDescription</key>             // 追加
<string>Your location is required for this app</string> // 追加
<key>NSLocationWhenInUseUsageDescription</key>          // 追加
<string>Your location is required for this app</string> // 追加

これで準備完了。

 

位置情報を取得するlocation.getLocation関数を呼ぶ前には、スマホの位置情報がオンになっているか、アプリが位置情報にアクセスできるか、の2点を確認する。

Future<bool> checkLocation() async {
Location location = new Location();
bool _serviceEnabled;
PermissionStatus _permissionGranted;

_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return false;
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return false;
}
}
return true;
}

チェックするのは、地域設定画面で現在位置ボタンを押下した時(1)と、現在位置設定後、アプリを再度起動した時にデフォルトの設定が現在位置になっている時(2)の2カ所で良さそう。

  • (1)の場合で、位置情報が取得できなかった場合、選択地域は変更せず、地域設定画面に止まる。
  • (2)の場合で、位置情報が取得できなかった場合、とりあえず東京にして天気を返す。

というロジックにします。

 

では、まず選択肢を追加します。

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("地域設定"),
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return _areaItem(context,
AreaEntity(area: CurrentLocation.area, lon: null, lat: null));
} else {
return _areaItem(context, areas[index - 1]);
}
},
itemCount: 48,
));
}

あんまりよくない気しかしないですが、ListView.builderでindexが0の時を現在位置とし、itemCount を勝手に+1しちゃいます。

 _areaItemはこんな感じ。

Widget _areaItem(BuildContext context, AreaEntity area) {
final areaModel = Provider.of<AreaModel>(context, listen: true);
return GestureDetector(
child: Container(
padding: EdgeInsets.all(8.0),
decoration: new BoxDecoration(
border: new Border(
bottom: BorderSide(width: 1.0, color: Colors.grey))),
child: Row(
children: <Widget>[
Container(
margin: EdgeInsets.all(10.0),
child: area.area == areaModel.areaEntity.area
? Icon(Icons.check)
: Icon(null),
),
Text(
area.area ?? "",
),
],
)),
onTap: () async {
bool isPop = true;
if (area.area == CurrentLocation.area) {
bool isGetLocation = await areaModel.checkLocation();
if (!isGetLocation) isPop = false;
}
if (isPop) {
areaModel.update(area);
Navigator.pop(context);
} else {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("位置情報エラー"),
content: Text("設定から位置情報の取得を許可してください"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () => Navigator.pop(context),
),
],
));
}
},
);
}

CurrentLocationというクラスを作り、areaを比較することでタップされた項目が現在位置か否かを調べてます。

class CurrentLocation {
// 二回目以降の起動フラグ
static const String area = '現在位置';
}

権限を確かめ、もし権限に不備があればisPopがfalseになり、AlertDialogが表示されることになります。情報取得できる場合はupdateで情報更新します。

Future<bool> update(AreaEntity areaEntity) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (areaEntity.area == CurrentLocation.area) {
List<String> loc = await getLocation();
areaEntity.lon = loc[0];
areaEntity.lat = loc[1];
}
prefs.setString(SharedPreferencesKeys.areaEntity, json.encode(areaEntity));
_areaEntity = areaEntity;
_weatherEntity = await getWeather();
notifyListeners();
}
Future<List<String>> getLocation() async {
Location location = new Location();
LocationData _locationData = await location.getLocation();
return [
_locationData.longitude.toString(),
_locationData.latitude.toString()
];
}

これで、現在位置を取得できるようになりました!

動作はこんな感じです。

 

 

コードは次のリポジトリに置いています!

github.com