Let’s BLoC
The work I pickup today is Location Permission handling, which is divided into 2 parts:
Part 1: Handle location permission within app
Part 2: Handle location permission between app and App Settings
Actually the work for Part 1 has been done by KoC, however, we did not have a good technical discussion before the work started, therefore he did not use the correct lib to open the native App Setting page, and so the work is undone. That’s why I continue his work any complete both Part 1 and Part 2 of this issue.
Analyse issue
Before I start coding, I need to understand all scenarios in the issue (story) that’s assigned to me. And to do that, I start by translating scenarios from user’s point of view into technical point of view.
Convert scenarios to technical knowledge
Here is the link to the issue I need to implement: Location Permission
Each scenario describes a small technical requirement I will need to implement.
Scenario 1:
GIVEN Location permission is denied
AND Internet connection is available
AND I do not have any location stored in storage
WHEN I launch the app
THEN I’ll be presented a dialog to explain why location permission is needed
This part means “when location permission is NOT granted”, then show dialog to explain why location permission is needed.
Scenario 2:
GIVEN scenario 1
WHEN I tap on ‘Continue’ button
THEN the map is loaded with default location lat/lng
With the dialog above, the user taps on Continue
button, then dismiss the dialog, and show the map with default location.
Scenario 3:
GIVEN scenario 1
WHEN I tap on ‘Allow’ button
THEN native Location permission request dialog is presented to me
With the dialog in scenario 1, when user taps on Allow
button, then dismiss the dialog, and request native location permission
Scenario 4:
GIVEN scenario 3
WHEN I tap on ‘Deny’ button
THEN Location permission dialog is dismissed
AND I’ll be presented a dialog to explain why location permission is needed
When native location permission dialog is showing, if user denies permission, then we show dialog in scenario 1 again. This is when Location permission status is NOT GRANTED
.
Scenario 5:
GIVEN scenario 3
WHEN I tap on ‘Deny, do not ask again’
THEN Location permission dialog is dismissed
THEN I’ll be presented a dialog to explain why location permission is needed
In Android, there’s a term for permission permanently denied, and similarly, in iOS, there’s also a similar term. So this scenario describes this case, when user permanently denied location permission. When this happens, if I try to request native location permission dialog, nothing happens, and location permission status remains FOREVER NOT GRANTED
Scenario 6:
GIVEN scenario 5
WHEN I tap on ‘Allow’ button
THEN I’ll be navigated to native App Setting screen, where I can change location permission for my app.
This continues from scenario 5 above, in the dialog within my app, when user taps Allow
, because the permission now is FOREVER NOT GRANTED
, and requesting native location dialog will take no effect, the only way for user to allow location permission to this app is to change it in App Setting screen.
Summary of scenarios
All 6 scenarios above can be summarised to this short technical requirement:
When request current user’s location, and location permission is granted, then show Map with current user’s location
When request current user’s location, if location permission is NOT GRANTED, then show Dialog 1 to explain location needs
If user taps Continue in Dialog 1, then load default location
If user taps Allow in Dialog 1, then
=> If location permission is NOT GRANTED, then request native location permission dialog
=> If location permission is FOREVER NOT GRANTED, then open App Settings.
That’s easier to understand now.
Tech time
After understanding the requirement in tech’s POV, it’s time to do code plan. This time, since most of the logic is done by KoC, I just need to continue base on that logic.
I’ll be using flutter_bloc
for this complex issue. There’s a choice between using bloc
, or just cubit
, but for learning purpose, I choose to use bloc this time. Maybe next time I’ll use cubit.
As it’s quite complex issue, it’s better to draw a diagram of the flow.
First diagram is how each component send request to its processor.
And how the processor returns result to the requester.
So the UI
will only communicate to the BLoC
, the BLoC
will communicate to Location Wrapper
, and Location Wrapper
will communicate with Location
and Permission Handler
.
There are 3 notes here:
The reason for breakdown communication is to simplify the flow between each component, and makes it easier to test.
Location Wrapper
is in-place in order to make the code modular and easy to change or update later if there’s ever a need to changeLocation
package orPermission Handler
package, and also ensure to reduce changes to the main app.Using
Permission Handler
to handle location permission in stead of usingLocation
’s permission handler is becausePermission Handler
also provide convenient method to openApp Settings
. Besides, it also provides handling to other permissions that I’ll need later.
Now I need to plan activity between each components.
Code plan
Let’s do code plan for each block.
For UI
What’s needed in the UI?
UI
will need to show Dialog 1
, with a Text
for title, a Text
for content, and 2 TextButton
s for buttons.
When is Dialog 1
needed? When Location Permission
is NOT GRANTED
, aka, DENIED
.
How does UI
know location permission is DENIED
? use LocationPermissionDeniedState
from Location BLoC
.
When user taps on Allow
button in Dialog 1
, send event to LocationBLoC
to request location permission.
But if Location
permission is PERMANENTLY NOT GRANTED
, aka PERMANENTLY DENIED
, then the wording on Allow
button needs to be different, so that user knows he’s being taken to App Settings
screen right?
OK, then let’s have a state for PERMANENTLY DENIED
, call it LocationPermissionDeniedForeverState
, then Dialog 1
will show with TextButton
as Open Settings
, and Continue
.
For Location BLoC
From UI
plan above, I know there are 2 States I’ll need in Location BLoC: LocationPermissionDeniedState
and LocationPermissionDeniedForeverState
.
How to reach LocationPermissionDeniedState
? When permission is DENIED
, and NOT PERMANENTLY DENIED
How to reach LocationPermissionDeniedForeverState
? When permission is PERMANENTLY DENIED
.
These info are from Location Wrapper
, in term of Exceptions
.
LocationPermissionDeniedException
=> State is LocationPermissionDeniedState
LocationPermissionDeniedForeverException
=> State is LocationPermissionDeniedForeverState
For the events, there will be 2 events:
Request to show native location permission event => call location wrapper to show native location permission request dialog
Request to open App Settings => call location wrapper to open app settings
For Location Wrapper
Location Wrapper
class will wrap around location
and permission_handler
packages, and use these packages for each needs:
location
package: to get user’s current lat/lng valuepermission_handler
package: to handle permission status, request native location permission request dialog, and open app settings.
Within this issue today, I’ll focus on location permission, which is mainly maintained by permission_handler
package.
As code plan above, this Location Wrapper
class will need to throw 2 Exceptions
LocationPermissionDeniedException
: When permission status is denied, limited, or restricted.LocationPermissionDeniedForeverException
: when permission status is permanently_denied.
That’s it. Simple. Now it’s time to code.
Code time
First step, I need to add permission_handler into pubspec.yaml
Then I need to run flutter pub get
to update my pub. Actually let’s leave it to Android Studio, and I just click the Pub get button instead.
Second step, edit LocationService
class, inside method getCurrentLocation()
, and convert permission status into Exceptions
as needed
I also need to add methods to request permission, and open app settings
With requestPermission
method, I also wanna know what option user chooses, by checking permission again after user makes their choice.
requestOpenAppSettings
is straight forward, no tricky logic here.
Next step, I need to update LocationBloc
, to add 2 new states.
And I also need 2 new events for LocationBloc
Now, implement handler for those events
And implement functions for each event
Notice that in _showDialogRequestPermissionEvent()
, when use allow location permission, I immediately request current location. This is to refresh UI immediately with updated user’s currently location.
That’s almost it. The next step is to update the code in UI, but the code is too long to copy here, so here is the link to my PR.
However, there’s something I did not anticipate during code plan, which is when user returns from App Settings. There are 2 scenarios when user returns from App Settings, and I described them in this issue here.
In order to accommodate this new scenarios, I changed my StatelessWidget
to be a StatefulWidget
, in order to use WidgetsBindingObserver
to listen to AppLifecycleState
changes.
And I added new state to my LocationBloc.
And new event as well
When user taps on Open Settings button, I change the state to AwaitLocationPermissionFromAppSettingsState
And by using WidgetsBindingObserver
, I can now override didChangeAppLifecycleState
in the UI, and notify LocationBloc
when users returns to the app
Then when LocationBloc
received event ReturnedFromAppSettingsEvent
, it will just try to get user’s current location again.
That’s it. The rest is tinkering work to make it works, and write test.
Full code can be found here in this PR.
That’s all for today. If you like my article, please follow for more tips.
Top comments (0)