Compass Heading with AnimatedRotation in Flutter
Tags: flutter, dart, sensors, animation
Published: 2022-06-09 04:13:48

## Create a smooth angle transition for the users in your map application

So, I'm working on a project that involves a map. One of the requirements was to update not only the position, but also were the user points to.

To do this, I integrated the following plugins into the app:
* [flutter_compass](https://pub.dev/packages/flutter_compass) - A Flutter compass. The heading varies from 0-360, 0 being north.
* [flutter_map](https://pub.dev/packages/flutter_map) - A versatile mapping package for Flutter, based off leaflet.js, that's simple and easy to learn, yet completely customizable and configurable.

For full documentation on the flutter_compass plugin, see the [the example page](https://pub.dev/packages/flutter_compass/example) on [pub.dev](https://pub.dev).

Now, to get the continuous changes in the user's heading we need to subscribe to the API provided by the *flutter_compass* plugin:

```dart
double? userHeading;

FlutterCompass.events?.listen((CompassEvent event) {
  userHeading = event.heading;
}
```

If *direction* is *null* this means the client does not have the compass sensor on their device, **and this scenario must be handled so as not to have exceptions in the execution of our app.**

To expose this *direction* value, you could have several approaches:
* StreamBuilder
* Provider
* ValueNotifier
* etc.

But this is not the main topic, so just use your preferred one ;)

For the widget, the plugin's example suggest us to use *Transform.rotate* as follows: 
```dart
Transform.rotate(
  angle: (direction * (math.pi / 180) * -1),
  child: Image.asset('assets/compass.jpg'),
)
```
Even though it works, I have found another way to make the angle changes in a smooth way.

### Which other option do we have???
[**AnimatedRotation**](https://api.flutter.dev/flutter/widgets/AnimatedRotation-class.html)
*Animated version of Transform.rotate which automatically transitions the child's rotation over a given duration whenever the given rotation changes.*

But this widget requires a **turn** value to check against its previous value and then animate the transition of the rotation, left to right (if the value increases) or right to left (if the value decreases).

### Transform the angle into turns
Ok cool, this is easy right? Simply divide the direction / 360 to get a value based on where the user is pointing.

But now we will face the first BIG issue:
The compass will misbehave in two scenarios:
* If the user's rotation it's from 1 degree to 360.
* If the user's rotation it's from 360 degrees to 1.

### This is the tricky part
Because we will need to do some math to just get the variation of the angle and then get the expected behavior.

Lets upgrade our listener function like this:

```dart
double turns = 0;
double prevValue = 0;

FlutterCompass.events?.listen((CompassEvent event) {
  double? direction = event.heading;

  direction = direction < 0 ? (360 + direction): direction;
  double diff = direction - prevValue;
  if(diff.abs() > 180) {
    if(prevValue > direction) {
      diff = 360 - (direction-prevValue).abs();
    } else {
      diff = 360 - (prevValue-direction).abs();
      diff = diff * -1;
    }
  }
  turns += (diff / 360);
  prevValue = direction;
}
```
We now have in **turns** the desire value, just the change between the last and the new angle.

The widget should look like this:
```dart
AnimatedRotation(
  turns: turns,
  duration: const Duration(milliseconds: 250),
  child: const Icon(Icons.arrow_upward_outlined),
)
```

I hope this helps you and saves you some time ;)