Skip to content

Commit 72e0e06

Browse files
authored
feat: add PermissionManager (#120)
- [x] add example app page - [x] check permissions - [x] request permissions - [x] listen to requested result - [x] smaller buttons on MenuPage - [x] use the new `PermissionManager` instead a 3rd party package.
1 parent 0a1875d commit 72e0e06

29 files changed

+834
-35
lines changed

android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ class MapLibreMapController(
374374
val bytes = URL(url).openConnection().getInputStream().readBytes()
375375
callback(Result.success(bytes))
376376
} catch (e: IOException) {
377-
println(e)
378377
callback(Result.failure(e))
379378
}
380379
}

android/src/main/kotlin/com/github/josxha/maplibre/MapLibrePlugin.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
package com.github.josxha.maplibre
22

3+
import PermissionManagerHostApi
34
import android.content.Context
45
import androidx.lifecycle.Lifecycle
56
import androidx.lifecycle.LifecycleOwner
67
import io.flutter.embedding.engine.plugins.FlutterPlugin
78
import io.flutter.embedding.engine.plugins.activity.ActivityAware
89
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
910
import io.flutter.plugin.common.BinaryMessenger
11+
import io.flutter.plugin.common.PluginRegistry
1012
import io.flutter.plugin.common.StandardMessageCodec
1113
import io.flutter.plugin.platform.PlatformView
1214
import io.flutter.plugin.platform.PlatformViewFactory
15+
import org.maplibre.android.location.permissions.PermissionsListener
16+
import org.maplibre.android.location.permissions.PermissionsManager
1317

1418
/** MapLibrePlugin */
1519
class MapLibrePlugin :
1620
FlutterPlugin,
17-
ActivityAware {
21+
ActivityAware,
22+
PluginRegistry.RequestPermissionsResultListener,
23+
PermissionManagerHostApi {
1824
private var lifecycle: Lifecycle? = null
25+
private lateinit var permissionManagerApi: PermissionManagerHostApi
1926

2027
private lateinit var flutterAssets: FlutterPlugin.FlutterAssets
2128

2229
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
2330
MapLibreRegistry.context = binding.applicationContext
31+
PermissionManagerHostApi.setUp(binding.binaryMessenger, this)
2432
flutterAssets = binding.flutterAssets
2533
binding
2634
.platformViewRegistry
@@ -36,6 +44,8 @@ class MapLibrePlugin :
3644
}
3745

3846
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
47+
MapLibreRegistry.activity = binding.activity
48+
binding.addRequestPermissionsResultListener(this)
3949
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding)
4050
}
4151

@@ -53,6 +63,37 @@ class MapLibrePlugin :
5363
override fun onDetachedFromActivityForConfigChanges() {
5464
onDetachedFromActivity()
5565
}
66+
67+
override fun onRequestPermissionsResult(
68+
requestCode: Int,
69+
permissions: Array<out String>,
70+
grantResults: IntArray,
71+
): Boolean {
72+
permissionsManager?.onRequestPermissionsResult(requestCode, permissions, grantResults)
73+
return true
74+
}
75+
76+
private var permissionsManager: PermissionsManager? = null
77+
78+
override fun requestLocationPermissions(
79+
explanation: String,
80+
callback: (Result<Boolean>) -> Unit,
81+
) {
82+
permissionsManager =
83+
PermissionsManager(
84+
object : PermissionsListener {
85+
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
86+
// This method fires when the user gets prompted to accept the permissions.
87+
// No not handle the return here, onPermissionResult will still be called.
88+
}
89+
90+
override fun onPermissionResult(granted: Boolean) {
91+
callback(Result.success(granted))
92+
}
93+
},
94+
)
95+
permissionsManager?.requestLocationPermissions(MapLibreRegistry.activity)
96+
}
5697
}
5798

5899
/**

android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Autogenerated from Pigeon (v22.5.0), do not edit directly.
1+
// Autogenerated from Pigeon (v22.6.0), do not edit directly.
22
// See also: https://pub.dev/packages/pigeon
33
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
44

@@ -127,9 +127,9 @@ data class MapOptions (
127127
val compass: Boolean,
128128
/** Toggle the MapLibre Native logo. */
129129
val logo: Boolean,
130-
/** Toggle the MapLibre Native logo. */
130+
/** Toggle the MapLibre Native attribution. */
131131
val attribution: Boolean,
132-
/** Toggle the MapLibre Native logo. */
132+
/** Toggle the texture mode on android. */
133133
val androidTextureMode: Boolean
134134
)
135135
{
@@ -958,3 +958,40 @@ class MapLibreFlutterApi(private val binaryMessenger: BinaryMessenger, private v
958958
}
959959
}
960960
}
961+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
962+
interface PermissionManagerHostApi {
963+
/** Request location permissions. */
964+
fun requestLocationPermissions(explanation: String, callback: (Result<Boolean>) -> Unit)
965+
966+
companion object {
967+
/** The codec used by PermissionManagerHostApi. */
968+
val codec: MessageCodec<Any?> by lazy {
969+
PigeonPigeonCodec()
970+
}
971+
/** Sets up an instance of `PermissionManagerHostApi` to handle messages through the `binaryMessenger`. */
972+
@JvmOverloads
973+
fun setUp(binaryMessenger: BinaryMessenger, api: PermissionManagerHostApi?, messageChannelSuffix: String = "") {
974+
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
975+
run {
976+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.maplibre.PermissionManagerHostApi.requestLocationPermissions$separatedMessageChannelSuffix", codec)
977+
if (api != null) {
978+
channel.setMessageHandler { message, reply ->
979+
val args = message as List<Any?>
980+
val explanationArg = args[0] as String
981+
api.requestLocationPermissions(explanationArg) { result: Result<Boolean> ->
982+
val error = result.exceptionOrNull()
983+
if (error != null) {
984+
reply.reply(wrapError(error))
985+
} else {
986+
val data = result.getOrNull()
987+
reply.reply(wrapResult(data))
988+
}
989+
}
990+
}
991+
} else {
992+
channel.setMessageHandler(null)
993+
}
994+
}
995+
}
996+
}
997+
}

example/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'package:maplibre_example/layers_symbol_page.dart';
2020
import 'package:maplibre_example/menu_page.dart';
2121
import 'package:maplibre_example/offline_page.dart';
2222
import 'package:maplibre_example/parameters_page.dart';
23+
import 'package:maplibre_example/permissions_page.dart';
2324
import 'package:maplibre_example/styled_map_page.dart';
2425
import 'package:maplibre_example/two_maps_page.dart';
2526
import 'package:maplibre_example/user_interface_page.dart';
@@ -53,6 +54,7 @@ class MyApp extends StatelessWidget {
5354
UserLocationPage.location: (context) => const UserLocationPage(),
5455
UserInterfacePage.location: (context) => const UserInterfacePage(),
5556
OfflinePage.location: (context) => const OfflinePage(),
57+
PermissionsPage.location: (context) => const PermissionsPage(),
5658
LayersSymbolPage.location: (context) => const LayersSymbolPage(),
5759
LayersCirclePage.location: (context) => const LayersCirclePage(),
5860
LayersHeatmapPage.location: (context) => const LayersHeatmapPage(),

example/lib/menu_page.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'package:maplibre_example/layers_raster_page.dart';
1919
import 'package:maplibre_example/layers_symbol_page.dart';
2020
import 'package:maplibre_example/offline_page.dart';
2121
import 'package:maplibre_example/parameters_page.dart';
22+
import 'package:maplibre_example/permissions_page.dart';
2223
import 'package:maplibre_example/styled_map_page.dart';
2324
import 'package:maplibre_example/two_maps_page.dart';
2425
import 'package:maplibre_example/user_interface_page.dart';
@@ -38,7 +39,7 @@ class MenuPage extends StatelessWidget {
3839
slivers: [
3940
const SliverToBoxAdapter(child: SectionTitle('General')),
4041
SliverGrid.extent(
41-
maxCrossAxisExtent: 200,
42+
maxCrossAxisExtent: 150,
4243
childAspectRatio: 1.5,
4344
children: const [
4445
ItemCard(
@@ -99,11 +100,17 @@ class MenuPage extends StatelessWidget {
99100
iconData: Icons.wifi_off,
100101
location: OfflinePage.location,
101102
),
103+
if (!kIsWeb)
104+
ItemCard(
105+
label: 'Permissions',
106+
iconData: Icons.key,
107+
location: PermissionsPage.location,
108+
),
102109
],
103110
),
104111
const SliverToBoxAdapter(child: SectionTitle('Annotations')),
105112
SliverGrid.extent(
106-
maxCrossAxisExtent: 200,
113+
maxCrossAxisExtent: 150,
107114
childAspectRatio: 1.5,
108115
children: const [
109116
ItemCard(
@@ -135,7 +142,7 @@ class MenuPage extends StatelessWidget {
135142
),
136143
const SliverToBoxAdapter(child: SectionTitle('Layers')),
137144
SliverGrid.extent(
138-
maxCrossAxisExtent: 200,
145+
maxCrossAxisExtent: 150,
139146
childAspectRatio: 1.5,
140147
children: const [
141148
ItemCard(
@@ -206,7 +213,7 @@ class ItemCard extends StatelessWidget {
206213
onTap: () => Navigator.of(context).pushNamed(location),
207214
child: Column(
208215
mainAxisAlignment: MainAxisAlignment.center,
209-
children: [Icon(iconData), Text(label)],
216+
children: [Icon(iconData), Text(label, textAlign: TextAlign.center)],
210217
),
211218
),
212219
);
@@ -220,6 +227,8 @@ class SectionTitle extends StatelessWidget {
220227

221228
@override
222229
Widget build(BuildContext context) {
223-
return ListTile(title: Text(label, style: const TextStyle(fontSize: 18)));
230+
return ListTile(
231+
title: Text(label, style: const TextStyle(fontSize: 18)),
232+
);
224233
}
225234
}

example/lib/permissions_page.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:maplibre/maplibre.dart';
3+
4+
@immutable
5+
class PermissionsPage extends StatefulWidget {
6+
const PermissionsPage({super.key});
7+
8+
static const location = '/permissions';
9+
10+
@override
11+
State<PermissionsPage> createState() => _PermissionsPageState();
12+
}
13+
14+
class _PermissionsPageState extends State<PermissionsPage> {
15+
final _manager = PermissionManager();
16+
17+
@override
18+
Widget build(BuildContext context) {
19+
return Scaffold(
20+
appBar: AppBar(title: const Text('Location Permissions')),
21+
body: ListView(
22+
children: [
23+
ListTile(
24+
title: const Text('runtimePermissionsRequired'),
25+
subtitle: const Text(
26+
'Weather the OS requires to ask for permissions at runtime.',
27+
),
28+
trailing: _manager.runtimePermissionsRequired.toIcon(),
29+
),
30+
ListTile(
31+
title: const Text('locationPermissionsGranted'),
32+
subtitle: const Text(
33+
'Weather location permissions are granted.',
34+
),
35+
trailing: _manager.locationPermissionsGranted.toIcon(),
36+
),
37+
ListTile(
38+
title: const Text('backgroundLocationPermissionGranted'),
39+
subtitle: const Text(
40+
'Weather permission is granted to access location in '
41+
'the background.',
42+
),
43+
trailing: _manager.backgroundLocationPermissionGranted.toIcon(),
44+
),
45+
ListTile(
46+
title: OutlinedButton(
47+
child: const Text('requestLocationPermissions'),
48+
onPressed: () async {
49+
try {
50+
final granted = await _manager.requestLocationPermissions(
51+
explanation: 'Show the user location on the map.',
52+
);
53+
debugPrint('requestLocationPermissions granted: $granted');
54+
setState(() {}); // refresh the screen
55+
} catch (error, stacktrace) {
56+
debugPrint(error.toString());
57+
debugPrintStack(stackTrace: stacktrace);
58+
}
59+
},
60+
),
61+
),
62+
],
63+
),
64+
);
65+
}
66+
}
67+
68+
extension BoolExt on bool {
69+
Widget toIcon() => Icon(
70+
this ? Icons.check_box : Icons.cancel,
71+
color: this ? Colors.green : Colors.red,
72+
);
73+
}

example/lib/user_location_page.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:maplibre/maplibre.dart';
33
import 'package:maplibre_example/styled_map_page.dart';
4-
import 'package:permission_handler/permission_handler.dart';
54

65
@immutable
76
class UserLocationPage extends StatefulWidget {
@@ -14,6 +13,7 @@ class UserLocationPage extends StatefulWidget {
1413
}
1514

1615
class _UserLocationPageState extends State<UserLocationPage> {
16+
final _permissionManager = PermissionManager();
1717
late final MapController _controller;
1818

1919
@override
@@ -31,8 +31,11 @@ class _UserLocationPageState extends State<UserLocationPage> {
3131
children: [
3232
OutlinedButton(
3333
onPressed: () async {
34-
final status = await Permission.locationWhenInUse.request();
35-
debugPrint(status.toString());
34+
final granted =
35+
await _permissionManager.requestLocationPermissions(
36+
explanation: 'Show the user location on the map.',
37+
);
38+
debugPrint(granted.toString());
3639
},
3740
child: const Text(
3841
'Get permission',

example/pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ dependencies:
1616
http: ^1.2.2
1717
maplibre:
1818
path: ../
19-
permission_handler: ^11.3.1
2019

2120
dev_dependencies:
2221
flutter_test:

example/windows/flutter/generated_plugin_registrant.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@
77
#include "generated_plugin_registrant.h"
88

99
#include <maplibre/maplibre_plugin_c_api.h>
10-
#include <permission_handler_windows/permission_handler_windows_plugin.h>
1110
#include <url_launcher_windows/url_launcher_windows.h>
1211

1312
void RegisterPlugins(flutter::PluginRegistry* registry) {
1413
MaplibrePluginCApiRegisterWithRegistrar(
1514
registry->GetRegistrarForPlugin("MaplibrePluginCApi"));
16-
PermissionHandlerWindowsPluginRegisterWithRegistrar(
17-
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
1815
UrlLauncherWindowsRegisterWithRegistrar(
1916
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
2017
}

example/windows/flutter/generated_plugins.cmake

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
list(APPEND FLUTTER_PLUGIN_LIST
66
maplibre
7-
permission_handler_windows
87
url_launcher_windows
98
)
109

0 commit comments

Comments
 (0)