【ITニュース解説】Build a Qibla Finder in Flutter
2026年10月20日に「Dev.to」が公開したITニュース「Build a Qibla Finder in Flutter」について初心者にもわかりやすく解説しています。
ITニュース概要
Flutterでキブラ検索アプリを開発するには、現在地からメッカへの正確な方角をDartで計算し、`flutter_compass`でデバイスコンパスを読み取る。センサーノイズ除去や滑らかなUI描画、位置情報・センサー権限の適切な処理が肝となる。エラー時も丁寧にユーザーへ伝え、実用性を高める。
ITニュース解説
キブラファインダーとは、イスラム教徒が礼拝する際にメッカの方向(キブラ)を示すアプリの機能である。一見するとシンプルな機能に見えるが、実は二つの難しい技術的課題を解決して実現されている。一つは地球上のどこからでもメッカへの正確な方向を計算すること、もう一つはデバイスに搭載されたコンパス(磁力計)から得られるデータを、不安定な挙動なくスムーズに読み取ることである。本解説では、Flutterという開発環境を用いて、これらの課題をどのように解決し、実際に製品として利用できるレベルのキブラファインダーを構築するかを説明する。
まず、キブラの方位を正確に計算する方法について解説する。キブラとは、ユーザーの現在地からサウジアラビアのメッカにあるカーバ神殿への方向を指す。地球は球体であるため、平面地図上で直線に見える経路は、長距離移動においては実際には間違った方向を示してしまう。このため、球面三角法という数学的な手法を用いて、地球の表面に沿った最短経路(大円距離)での方位を計算する必要がある。この計算には、複雑なパッケージを導入する必要はなく、Dart言語の標準ライブラリであるdart:mathに含まれる数学関数のみで実現できる。具体的には、ユーザーの緯度と経度、そしてカーバ神殿の緯度と経度を基に、球面三角法の法則である「球面余弦定理」を応用した数式を用いる。この計算において重要な点がいくつかある。第一に、Dartの数学関数は角度をラジアン単位で扱うため、ユーザーの緯度や経度など、通常は度数で表される値を計算の前にラジアンに変換することが必須である。この変換を忘れることは、コンパス関連のコードで最も一般的なバグの一つとなる。第二に、計算結果として得られる角度は-180度から180度の範囲で返されることが多いが、これをユーザーやコンパスウィジェットが期待する0度から360度の範囲に正規化する必要がある。これは、計算結果に360度を足して、さらに360で割った余りを取ることで実現できる。この方位計算ロジックは、ユーザーのGPS座標のみに依存する「純粋関数」であり、入力が同じであれば常に同じ出力が得られるため、ユニットテストを行うことでその正確性を容易に検証できる。例えば、ジャカルタからメッカへの方位は約295度、ニューヨークからは約58度といった既知の値を使い、事前に計算が正しいことを確認することが推奨される。ユーザーの位置は常に変化するため、GPS座標が更新されるたびにこの計算を再実行し、最新のキブラ方位を反映させる。一度位置情報が確定すれば、この計算自体はオフラインでも完全に機能する。
次に、デバイスのコンパス(磁力計)から向きのデータを読み取る方法について説明する。Dart言語には直接磁力計を読み取るための機能は備わっていないため、flutter_compassというコミュニティ標準のパッケージを利用する。このパッケージは、デバイスの向き(見出し、heading)に関する情報を含むCompassEventというデータの流れ(ストリーム)を提供する。アプリケーションでは、このストリームをStreamBuilderというウィジェットで監視し、新しいデータが届くたびにUIを更新する。ここで二つの重要な点がある。一つは、取得される見出しの値がnullになる可能性があることを常に考慮することである。一部の安価なデバイスには磁力計が搭載されていない場合があり、その場合、見出しはnullのままとなる。nullチェックを適切に行うことで、アプリのクラッシュを防ぎ、代わりに「コンパスセンサーがありません」といった適切なメッセージをユーザーに表示できる。もう一つは、このプラグインが報告するのは磁北を基準とした見出しであり、先ほど計算したキブラ方位は真北を基準としているという違いである。磁北と真北の間には「磁気偏角」と呼ばれる角度のずれがあり、これは地域によって数度異なる。ほとんどの地域では無視できる程度の差だが、礼拝の方向というデリケートな機能においては、この違いをアプリ内で明示するか、あるいは磁気偏角を補正する処理を加えるべきである。多くの開発者はこの点を無視しがちだが、偏角が大きい地域のユーザーは間違いに気づく可能性がある。さらに、磁力計から直接得られる生データは、非常にノイズが多く不安定である。このデータをそのままコンパスの針の回転に使うと、針が常に小刻みに震えてしまい、ユーザーには壊れているように見えてしまうだろう。この問題を解決するために、簡単な「ローパスフィルター」を適用してデータを平滑化(スムージング)する。これは、新しい見出しの値と以前のスムージングされた値とを特定の割合で混ぜ合わせることで、急激な変化を抑制し、針の動きを滑らかにする技術である。ただし、この単純なローパスフィルターは、見出しが359度から0度へと一周する際に、一時的に不自然な動きを示すことがあるが、キブラファインダーのダイヤルでは許容される場合が多い。
方位の計算とスムーズなコンパスの読み取りができるようになったら、次はそれらを視覚的に表現するダイヤルをレンダリングする。FlutterではCustomPainterという機能を使って、複雑なカスタムUIを効率的に描画できる。QiblaDialPainterというCustomPainterを実装し、これにデバイスの現在の見出し(heading)と計算されたキブラ方位(qibla)を渡す。このペインターは、ダイヤルの静的なリングや北を示す目盛りを描画し、その後、中心からキブラの方向を示す針を描画する。針の回転角度は、qibla - headingという計算で求められる。これにより、ユーザーがデバイスをどの向きに持っていようと、針は常にメッカの方向を指し示すように見える。描画処理の効率性も重要である。CustomPainterにはshouldRepaintというメソッドがあり、これがtrueを返すとウィジェットが再描画される。コンパスのストリームは毎秒何十回もデータを発するため、shouldRepaintが常にtrueを返すと、不要な再描画が発生し、特に低スペックのデバイスではGPUの負荷が高まり、バッテリー消費も増える。そのため、見出しやキブラ方位の値が実際に変化した場合のみtrueを返すように実装することで、効率的な描画が実現できる。
キブラ方位を計算するにはユーザーの正確な位置情報が必要となるため、位置情報のパーミッションを適切に処理する必要がある。これにはgeolocatorというパッケージが便利である。アプリはまず、デバイスの位置情報サービスが有効になっているかを確認し、有効でなければユーザーに有効化を促す。次に、アプリが位置情報にアクセスする権限が許可されているかを確認し、許可されていなければユーザーに許可を要求する。もしユーザーが「永久に拒否」を選択した場合は、アプリの設定画面で手動で許可するよう促すメッセージを表示する必要がある。これらのパーミッション処理を実装するだけでなく、AndroidではAndroidManifest.xmlに、iOSではInfo.plistという設定ファイルに、アプリが位置情報を使用する目的を記述する必要がある。特にiOSでは、NSLocationWhenInUseUsageDescriptionという記述を省略すると、位置情報が要求された瞬間にアプリがクラッシュしてしまうため、必須の項目である。ユーザー体験を向上させるための工夫として、最後に取得した位置情報をキャッシュしておき、キブラ画面を開いた際にまずそのキャッシュされた位置情報で方位を計算し表示することが有効である。これにより、GPSが現在位置を正確に特定するまでの間、スピナーを表示し続けることなく、瞬時に近似の方位を表示できるため、アプリの応答性が高く感じられる。その後、新しいGPSデータが取得された際に、より正確な方位に更新すればよい。
キブラファインダーを実際のユーザーに提供する際には、いくつかの「エッジケース」と呼ばれる特殊な状況や潜在的な問題に対応する必要がある。これらのハンドリングが、単なるデモと実際に使えるアプリを分ける重要な要素である。
まず、前述の通り、一部の廉価なデバイスには磁力計が搭載されていない場合がある。この場合、コンパスの見出しデータはnullのままであるため、これを検出し、「このデバイスにはコンパスセンサーがありません」といった明確なメッセージをユーザーに表示するべきである。次に、コンパスは移動後や磁石の近くにいた後など、未校正の状態になることがある。針が不安定に揺れる場合、ユーザーにスマートフォンを8の字に振るように促すヒントを表示すると、AndroidとiOSの両方でコンパスが再校正される。また、ユーザーが位置情報サービスをオフにしていたり、アプリからのアクセスを拒否したりする可能性もある。この場合、キブラ方位の計算に必要な位置情報が得られないため、手動で都市を選択できるフォールバック機能を提供することで、おおよその方位でも機能するようにすべきである。GPSがないとクラッシュするようなアプリは、低評価につながる。屋内では、鉄骨構造物やスピーカーなどの磁場を歪める物体が存在し、コンパスの針が不規則に大きくジャンプすることがある。このような状況では、針が信頼できる状態にあるかを示す「安定性インジケーター」を表示することを検討すると良いだろう。最後に、GPSと磁力計のストリームはどちらもバッテリーを消費する。そのため、キブラ画面が表示されていないときは、コンパスのストリームを停止し、位置情報の更新も一時停止することで、バッテリーの無駄な消費を防ぐべきである。
これらの技術を実装し、実際にアプリをリリースして得られた教訓が二つある。一つは、「UIだけでなく、センサーデータそのものを平滑化する」ことの重要性である。数学的な計算が正しくても、毎秒20回も揺れる針はユーザーにとって「壊れている」と感じられてしまう。ローパスフィルターを適用してセンサーデータを滑らかにすることは、ユーザーがアプリの品質を高く評価するための最も大きな改善点であった。もう一つは、「エラーは明確かつ丁寧に伝える」ことである。磁力計がない、位置情報が拒否された、サービスが無効であるといったセンサー関連のすべてのエラー経路において、ユーザーが理解できるメッセージと、次にとるべきステップを具体的に提示することが重要である。ユーザーは、制限を理解していれば許容してくれるが、何の解決策も示されないままスピナーが表示され続ける状況は決して許容しない。
キブラファインダーは、センサー機能を使ったアプリ開発の最初のステップとして非常に適した題材である。方位計算はユニットテスト可能な純粋関数であり、UIは一つのCustomPainterで完結し、一度位置情報が確定すればオフラインでも動作する。この「方位計算+センサー見出し+スムーズなレンダリング」というパターンは、礼拝時間ウィジェット、ランニング方向トラッカー、ARポインターなど、他の多くのナビゲーション系UIにも応用できる普遍的な技術である。