Flutter — Interactive Diary — A developer diary #12
Published: 2022-09-18 11:17:32

## Let’s BLoC

The work I pickup today is Location Permission handling, which is divided into 2 parts:

[Part 1:](https://github.com/suesitran/interactive_diary/issues/26) Handle location permission within app

[Part 2:](https://github.com/suesitran/interactive_diary/issues/61) Handle location permission between app and App Settings

Actually the work for Part 1 has been done [by KoC](https://github.com/suesitran/interactive_diary/pull/37), 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](https://github.com/suesitran/interactive_diary/issues/26)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tz5gw5aw7ee3xczrk8ft.png)

And how the processor returns result to the requester.


![Results are return to requester](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ac84wfrsf54ctnam6qmz.png)

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 `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 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq0zuf60okvj8ro6yo7h.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16jsh6gfos2q2kfl6kmu.png)

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

![Request permission method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lggwxrufq9oot7k5qmdv.png)

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

![open App Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/790w6i0rbf1dq6ohbzdc.png)

`requestOpenAppSettings` is straight forward, no tricky logic here.

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

![New states in LocationBloc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7eyjgn5xzoc6omt7vt5i.png)

And I also need 2 new events for `LocationBloc`

![New events for LocationBloc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4qr2l072dsrv9rnbgsz.png)

Now, implement handler for those events

![Handle events from UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksigx9euyeh6cyl6r3zq.png)

And implement functions for each event

![Functions to handle events](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pootilwgqi5rjz8xpm7c.png)

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](https://github.com/suesitran/interactive_diary/pull/62).

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](https://github.com/suesitran/interactive_diary/issues/61).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zob8cb6lrh68g75wpdnl.png)

And new event as well

![New event when user return from App Setting screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14eqeybm3ijjqscr6brt.png)

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

![Emit new state when open App Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukeohwpxmi7fgvgwdhz0.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vwzr7rj5kevofhw2wal.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr5wx5jhvhqj1t493tor.png)

That’s it. The rest is tinkering work to make it works, and write test.

Full code can be found here in [this PR](https://github.com/suesitran/interactive_diary/pull/62).

---

That’s all for today. If you like my article, please follow for more tips.