DEV Community

suesitran
suesitran

Posted on

Flutter — Interactive Diary — A developer diary #12

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.

Request flow

And how the processor returns result to the requester.

Results are return to 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 change Location package or Permission Handler package, and also ensure to reduce changes to the main app.

  • Using Permission Handler to handle location permission in stead of using Location’s permission handler is because Permission Handler also provide convenient method to open App 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 TextButtons 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 value

  • permission_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

Add permission_handler to 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

Convert permission status into Exception

I also need to add methods to request permission, and open app settings

Request permission method

With requestPermission method, I also wanna know what option user chooses, by checking permission again after user makes their choice.

open App Settings

requestOpenAppSettings is straight forward, no tricky logic here.

Next step, I need to update LocationBloc, to add 2 new states.

New states in LocationBloc

And I also need 2 new events for LocationBloc

New events for LocationBloc

Now, implement handler for those events

Handle events from UI

And implement functions for each event

Functions to handle events

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.

New State when user is navigating to App Settings screen

And new event as well

New event when user return from App Setting screen

When user taps on Open Settings button, I change the state to AwaitLocationPermissionFromAppSettingsState

Emit new state when open App Settings

And by using WidgetsBindingObserver, I can now override didChangeAppLifecycleState in the UI, and notify LocationBloc when users returns to the app

Handle app resume state

Then when LocationBloc received event ReturnedFromAppSettingsEvent, it will just try to get user’s current location again.

Request current location again when user returns from App Settings

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)