Google Flutter

日本システム開発株式会社からGoogle Flutterに関するコラムをお届けします。

車載システムなど豊富な組み込み領域の実績を持つ弊社から、組み込み業界でもホットなFlutterに関する情報を発信します。

日本システム開発の技術、
覗いてみませんか。

Google Flutterコラム 第5回「仮想的なカーナビ作ってみた」

本コラムシリーズで紹介してきたGoogle Flutterおよびmapboxを使って仮想的なカーナビを作成してみます。

第5回では第2~4回の内容を踏まえた上で作成するため、先にこちらをご覧ください。

画面構成

以下のような構成で作成します。※画像クリックで拡大できます。

画面遷移

Google Futterでは画面遷移をする際に、Navigatorクラスを使用します。

スタックと呼ばれる構造を使用して、画面を管理しています。

Google Flutterでは、スタックの下の要素から順に重ねて出力します。

1つめの画像では、スタックに地図画面のみ存在するため、そのまま地図画面が表示されています。

2つめの画像では、地図画面の上に検索画面が存在するため、地図画面の上に重ねて検索画面が表示されます。

実際には重なっているため、地図画面は見えません。

スタックと言われると以前に紹介したStackウィジェットが思い当たると思います。

Navigatorの実態はStackウィジェットを使って各ページを管理しているそうです。

そこで上に重ねるページのサイズを小さくしたり、背景を透明にしたりすろと下のページが見えるのか?と思い実験してみたところ・・・

pushしたページしか表示されませんでした。

どうやらpushする際に、もともと表示されていたページはoffstageという画面に表示しない領域に移動されているようです。

気を取り直して、本コラムで画面遷移に使用したコードを紹介します。

まず、画面遷移するページを登録します。ページ名と遷移先の画面を指定します。以下のように記載します。

1. return MaterialApp(
2.  /*** 省略 ***/
3.  routes: {
4.   ”ページ名1″: (BuildContext context) => ページ1(),
5.   ”ページ名2″: (BuildContext context) => ページ2(),
6.  },
7. );

次に、実際に画面遷移するコードです。ここでは3種類を紹介します。

  • 次の画面に遷移したい場合は以下のように記載します。
    ※画面構成の赤色の矢印にあたります。
Navigator.of(context).pushNamed(“ページ名”)
  • 1つ前の画面に戻る場合は以下のように記載します。
    ※画面構成の青色の矢印にあたります。
Navigator.of(context).pop()
  • 指定したページまで戻る場合は以下のように記載します。
    ※画面構成の緑色の矢印にあたります。
Navigator.of(context).popUntil(ModalRoute.withName(“ページ名”))

これらのコードを画面遷移したいところに記載します。

例えば、あるボタンを押した時や入力が完了した時、タイマーが作動した時などです。

以下はボタンを押した時に画面遷移を実施するコードの例です。

【Navigatorサンプル】 ※Runボタンを押すことでサンプルを実行できます


import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {"page1":(context)=>Page1(),
               "page2":(context)=>Page2(),
              },
      home: Page1(),
    );
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      body: const Center(
        child: Text("page1"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          Navigator.of(context).pushNamed("page2");
        },
        child: const Text("進む"),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.orange,
      body: const Center(
        child: Text("page2"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          Navigator.of(context).pop();
        },
        child: const Text("戻る"),
      ),
    );
  }
}
            

データベース

◎データベースにアクセスする

本コラムでは名古屋市役所までいくことを想定しています。

そのため、名古屋市役所の住所を検索するにあたってデータベース(以下DB)を活用します。

Google FlutterでDBを使用する場合はsqliteを使用します。

sqliteとはDBを管理するための仕組みで、Google Flutter公式でも紹介されています。

使用するパッケージはsqfliteです。
DBは住所DBを使用します。

インターネットからダウンロードし、プロジェクトに配置します。

その後、DBにアクセスして必要な情報を抽出します。

失敗例

ここで先に失敗例を紹介します。

DBにアクセスする際にopenDatabaseというメソッドを用いるのですが、引数にデータベースのパスを設定する必要があります。

そこで先ほどプロジェクトに配置したDBを設定したところ、DBが見つからない旨のエラーが表示されてしまいました。

1. // 失敗例;
2. try {
3.  // DB接続
4.  database = await openDatabase(“プロジェクトに配置したDBファイルのパス/ファイル名”, version: 1);
5.  print(“connect DB”);
6. } catch (e) {
7.  print(e);
8.  print(“ERROR”);
9. }

成功例

少し調べてみたところ、どうやらアプリケーションがDBを操作するための領域があるようです。

そのため、一度DBを専用の領域に作成(コピー)したのち、そのDBを使っていきます。

1. // 成功例
2. _projectDBPath = “プロジェクトに配置したDBファイルのパス/ファイル名”;
3. _databasePath = await getDatabasesPath();
4. _database = join(_databasePath, “DBファイル名”);
5.
6. try {
7.  bool exists = await databaseExists(_database);
8.  // DBが存在するかどうか
9.  if (!exists) {
10.   // 出力先のファイル作成
11.   await Directory(dirname(_database)).create(recursive: true);
12.
13.   // DLしたデータベースファイルを読み込む
14.   ByteData data = await rootBundle.load(_projectDBPath);
15.   List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
16.   // 内部領域に書き込む
17.   await File(_database).writeAsBytes(bytes, flush: true);
18.   print(“DB create”);
19.  } else {
20.   print(“DB exists”);
21.  }
22.  // DB接続
23.  database = await openDatabase(_database, version: 1);
24.  print(“connect DB”);
25. } catch (e) {
26.  print(e);
27.  print(“ERROR”);
28. }

3,4行目は内部領域に保存するDBのパスと名前を設定しています。

7行目では、内部領域にDBが存在するかを確認しています。

プロジェクトフォルダにDBファイルを配置するだけではアプリからアクセスすることができないためです。

9~19行目では、内部領域にDBが存在しないためプロジェクトフォルダに配置したDBファイルを読み込んで内部領域にコピーします。

25行目で改めて内部領域に存在するDBにアクセスしています。

◎データベースを使用する

次に、DBから必要な値を抽出します。

DBに格納されているデータは以下のようになっています。

※一部省略、データは架空

都道府県市区町村町域字丁目事業所
愛知県名古屋市中村区☆☆町1丁目
愛知県名古屋市中村区☆☆町2丁目
愛知県名古屋市中村区☆☆町3丁目
愛知県名古屋市中村区○○町
愛知県名古屋市中村区××町○×株式会社
愛知県豊田市□□町○○ビル
愛知県豊田市△△町1丁目△△

以下は選択した都道府県を持つレコードから市区町村を抽出するコードです。

抽出した市名を元にリストを作成します。

1. ken = _searchList[0];
2. maps = await database.query(“DBのテーブル名”, where: “都道府県 = ?”, whereArgs: [ken]);
3. Future.forEach(maps, (element) {
4.   retList.add(maps[count][“市区町村”]);
5.   count++;
6. });
7. // 重複削除
8. retList = retList.toSet().toList();<

1行目の_searchList[0]には選択した都道府県名が入っています。

2行目は、テーブルの都道府県と選択した都道府県が一致するレコードを抽出しています。

3~6行目では、抽出したレコードから「市区町村」を抜き出してretListへ追加します。

最後に、ListクラスのtoSet()メソッドを使用して重複したデータを削除したのち、toList()メソッドを使って元に戻して完了です。

上記テーブルの例では、「名古屋市中村区」と「豊田市」の2つのデータを持つリストになります。

このように選択した都道府県に紐づく市区町村のリストを作成しています。

市区町村以降の住所についても同様です。

画面切り替え

◎タイマーによる画面切り替え

実際のカーナビでは交差点に接近すると「この信号を右折です」のようなアナウンスと共に交差点の画像が表示されます。

本コラムではタイマーを使って表現してみます。

Google Flutterでタイマーを使用する場合はTimerクラスを使用します。

Timerクラスはdart:asyncパッケージに含まれています。

以下のように使います。

1. // タイマー起動
2. Timer(期間,コールバック);

期間には、Durationを使います。

Timerをコールして指定した期間が経過するとコールバックされます。

以下のサンプルは5秒のタイマーを起動し、画面の表示方法を切り替えるコードです。

【Timerサンプル】 ※Runボタンを押すことでサンプルを実行できます


import 'package:flutter/material.dart';
import 'dart:async';


void main() {
  runApp(MaterialApp(home: TimerSample()));
}

class TimerSample extends StatefulWidget {
  @override
  _TimerSampleState createState() => _TimerSampleState();
}

class _TimerSampleState extends State {
  bool flag = false;

  @override
  Widget build(BuildContext context) {
    // タイマー起動
    Timer(const Duration(seconds: 5), () {
      // コールバックではフラグの切り替えを実施
      setState(() {
        flag = true;
      });
    });

    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 200,
          height: 200,
          child: flag ? 
            Row(children:[
              Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ),
              Container(
                width: 100,
                height: 100,
                color: Colors.green,
              ),
            ]):
            Column(children:[
              Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ),
              Container(
                width: 100,
                height: 100,
                color: Colors.green,
              ),
            ]),
        ),
      ),
    );
  }
}
            

◎ボタンによる画面切り替え

実際のカーナビでは自車位置周辺の交通情報を表示するための画面が存在します。

本コラムではボタンを押すことで画面を表示するようにしてみます。

地図上に表示するために、showDialogを使用しています。

一般的なダイアログはユーザに情報を出した後は閉じるだけのことが多く、ダイアログ自体の変化はありません。

今回はダイアログ内で複数の情報を切り替えて表示させるため、少し工夫が必要です。

1. showDialog(
2.  context: context,
3.  builder: (context) => StatefulBuilder(
4.   builder: (context, setState) => SimpleDialog(省略),
5.  ),
6. );

3,4行目でStatefulBuilderを使って、SimpleDialogを作成しています。

こうすることで、showDialogで表示する情報を切り替えることができます。

ダイアログ下部に配置したボタンを押すことで、表示する交通情報を変更しています。

表示中のページが1つ目の場合は戻るボタンを、最後のページの場合は進むボタンをトーンダウンしてボタンを無効化しています。

ボタンのトーンダウンは以下のようにonPressedでnullを指定します。

1. ElevatedButton(
2.  child: const Text(“ボタン”),
3.  onPressed: 条件 ? () {処理} : null
4. )

以下はダイアログの表示、ダイアログ内の表示変更を実施するコードの例です。

【ダイアログ画面サンプル】 ※Runボタンを押すことでサンプルを実行できます


import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: DialogSample()));
}

class DialogSample extends StatefulWidget {
  @override
  _DialogSampleState createState() => _DialogSampleState();
}

class _DialogSampleState extends State {
  // ダイアログに表示する数字
  int _num = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ダイアログ表示ボタン
      body: Center(
        child: ElevatedButton(
          child: const Text('ダイアログ表示'),
          onPressed: _showDialogSample,
        ),
      ),
    );
  }

  // ダイアログ表示
  void _showDialogSample() {
    // ダイアログを表示する
    showDialog(
      context: context,
      builder: (context) => StatefulBuilder(
        builder: (context, setState) => SimpleDialog(
          children: [
            Align(
              alignment: Alignment.topLeft,
              child: _closeButton(),
            ),
            Center(
              child: Text(_num.toString()),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                // 数字減少ボタン
                ElevatedButton(
                  child: const Text('-'),
                  // 反映が遅いので連打すると-1になることがあります
                  onPressed: _num <= 0 ? null : () {
                    setState(() {
                      _num--;
                    });
                  },
                ),

                // 数字増加ボタン
                ElevatedButton(
                  child: const Text('+'),
                  // 反映が遅いので連打すると6になることがあります
                  onPressed: _num >= 5 ? null : () {
                    setState(() {
                      _num++;
                    });
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  // 閉じるボタン
  Widget _closeButton() {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        shape: const CircleBorder(),
      ),
      child: const Icon(
        Icons.clear,
      ),
      onPressed: (() {
        // ダイアログを消去
        Navigator.pop(context);
      }),
    );
  }
}
                

まとめ

Google Flutterを使って仮想的なカーナビを作成してみました。

画面遷移とダイアログを用いることで、画面同士をつなげて動きのあるアプリを作成できました。

また、本コラムを通じてデータベースやタイマーなど、少しレベルの高いコードに挑戦できました。

最後に、今回作成したアプリを動かした動画を紹介します。

おわりに

さて、本コラムシリーズ第1~5回にわたってGoogle Flutterについて紹介してきました。

本コラムシリーズを通して「Google Flutterおもしろそう!」「こういう考え方もあるのか!」と皆様のお役に立てれば幸いです。

Google Flutterの汎用性は高く、今後もいろいろな活動を実施していこうと思います。

キーワード

本コラムシリーズで紹介したキーワードとリンクです。ぜひご覧になってください。

flutterの導入flutterを使うための環境構築を解説しています
GoogleMap連携flutterでGoogleMapを使う方法を解説しています
mapbox連携flutterでmapboxを使う方法を解説しています
ローカライズGoogleMap、mapboxでのローカライズを比較しています
地図タイプ切り替えGoogleMap、mapboxでの地図タイプ切り替えを比較しています
渋滞表示GoogleMap、mapboxでの渋滞表示を比較しています
緯度経度出力GoogleMap、mapboxでの緯度経度の出力方法を比較しています
マーカー表示GoogleMap、mapboxでのマーカー表示を比較しています
カスタムスタイルGoogleMap、mapboxでのカスタムスタイルを比較しています
YAMLflutterを使う上で重要なpubspec.yamlについて解説しています
Pub.devflutterを使う上で重要なパッケージについて解説しています
widget画面を構成する要素であるwidgetについて解説しています
レイアウト制御widgetの配置をどのように制御するかを解説しています
マテリアルデザインマテリアルデザインに準拠したflutterアプリについて解説しています
釦表示flutterでボタンを表示する方法について解説しています
アイコン表示mapboxを使ってアイコンを表示する方法を解説しています
ドロワーメニューflutterでドロワーメニューを表示する方法を解説しています
画面遷移flutterで画面を切り替える方法を解説しています
データベースflutterでデータベースを使う方法を解説しています
タイマーflutterでタイマーを使う方法を解説しています
ダイアログflutterでダイアログを使う方法を解説しています

■関連サービス

IoTシステム開発

デバイスからの接続、クラウド、
アプリケーションに至るまで、豊富な実績で支援

車載システム画像文字つき

2003年より培ってきた豊富な実績を活かし、
車載ECUシステムの開発・評価を支援

pagetop