スポンサーリンク

Flutterの勉強始めました。アプリ公開までにやったことメモ

以前環境構築できなくて始まる前に投げてしまったFlutterについて最近再挑戦しています。
まだまだ勉強することだらけですがFlutter+sqliteを使った株の資産管理アプリがとりあえず完成してGooglePlayのストアに並ぶところまではできたので調べたことや使った技術についてメモ。

ストアへの直リンクは以下になります。公開したてだからなのか検索に引っかかりません。。;;

株やFXの資産管理アプリ StockPerformance - Apps on Google Play
Record daily assets and visualize with charts and data tables

環境構築、エディタについて

環境構築は以下のページを参考に行いました。

Windows10上にFlutterの開発環境を構築する。 - Qiita
はじめに ネイティブアプリの開発に少し興味が出て、最近クロスプラットフォーム開発のフレームワークで有名なFlutterを試しに使ってみようと思った。スペック的にかなり不安があるが、ひとまず手持ちのWindowsPCを使ってエミュ...
I am getting error "cmdline-tools component is missing" after installing Flutter and Android Studio... I added the Android SDK. How can I solve them?
Android toolchain - I develop for Android devices (Android SDK version 30.0.3): X cmdline-tools component is missing Run `path/to/sdkmanager --install "c...

エディタはVSCodeにFlutter開発用のプラグインをインストールしたものを使っています。
上記リンク内に必要なプラグインも書いてあったのでその辺は全て指示に従ってセットアップしました。

環境変数Pathの設定内容が多すぎて編集できなかったり、既存のJAVA_HOMEが指してるJDKが古すぎたりで上記のリンクの通りに行かない箇所もありましたがその辺はググったりして解決。

インストールが終わったタイミングでバージョン確認したところFlutter2.5.3、Dart 2.14.4でした。

>flutter --version
Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 18116933e7 (6 weeks ago) • 2021-10-15 10:46:35 -0700
Engine • revision d3ea636dc5
Tools • Dart 2.14.4

チュートリアルする

FlutterもDartも何もわからない状態なので、最初は日本語訳されたチュートリアルをやりました。

チュートリアル | Flutter Doc JP
Flutterの日本語解説配信サイト

チュートリアルの時点でStatelessWidgetとStatefulWidgetが出てくるのですがここ最初は理解できませんでした^q^

ネット上のほとんどのプログラムがコピペで動かない件

最初のうちはネットの情報を元にソースコピーしたりしてFlutterの開発言語(Dart)の勉強をしていたのですがめちゃくちゃエラーが出るし、ビルドもできない。。
調べたところ、Dartが途中からNull-Safetyな言語になったのが原因みたい。
割と最近のこと(2021年3月3日?)みたい??;;

Dartの型の理解しておきたいあれこれ(Null safety編) - Qiita
2021 年 3 月 3 日、ついに Dart 2.12 がリリースされて Dart は晴れて null-safe(null 安全)な言語となりました。 同日リリースされた Flutter 2 でも Dart 2.12 が同梱されてい...

ネットのコードをコピペすると主に以下のような指摘が出ます。

  • 変更が加わらないウィジェットにconstつけてって指摘が入る
    • 付けなくても動くけどエディタ上の主張が激しい
  • newの宣言はいらないって指摘が出る
    • これは宣言を消すだけ
  • nullableでない型にnullがセットされる場所でエラーが出る
    • nullableな型に変える(型の後ろにはてな「?」をつける)ことでそこのエラーは止まりますが、参照してる箇所で別のエラーが出るので辿って修正する必要がある

たくさん指摘が出ますが、エディタの補完がしっかり効いているということともいえる?
アラート0個を目指して修正するとだいたい動くようになってるし結構勉強になる(気がする)。

慣れてきたのでアプリを自作する

数日触っているうちになんとなくDartの読み書きができるようになってきたので、以前PHP8+MySQLで作成した日々の株資産の情報を入力してチャート表示するシステムを、Flutter製のアプリ化してみました。

一度作ったものがあるとロジック自体は考えなくていいので結構楽。

パッケージはpubspec.yamlに追加

pubspec.yaml = PHPでいうところのcomposer.json、javascriptでいうところのpackage.json、RubyでいうところのGemfile、Pythonでいうところのrequirement.txtみたいなパッケージ管理のファイル…と思いきや、非推奨のアラートを握りつぶす設定をしたり、画像や音のようなアセットのパスを通したり、リリースビルドの際のバージョン番号を設定したりするのにも使うみたい。

今回は作ったアプリでは最終的に以下のような感じになりました。

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  sqflite: ^2.0.0
  path_provider:
  intl: ^0.17.0
  table_calendar:
  charts_flutter:
  charts_common:
  horizontal_data_table:
  month_picker_dialog:
  persistent_bottom_nav_bar: ^4.0.2
  flutter_colorpicker: ^1.0.2
  provider: ^6.0.1
  fluttertoast: ^8.0.8
  google_mobile_ads: ^0.13.2+1

VSCodeだと保存したタイミングで通信が始まり指定したパッケージを取得してくれて便利です。
稀にパッケージのインストールが終わった後もVSCode内で、読み込み対象のファイルが無い旨のエラーが出たりしました。ウィンドウ読み込みなおせば解決しますがこれは謎。

簡単なCRUDから作成

最初は簡単なところからということで、入力項目の少ない口座情報や入金情報の登録/更新/一覧/削除などの機能から作りました。

入力した情報を記録するのにsqliteデータベースを使用ました。
sqfliteというパッケージを使用してsqliteデータベースの接続を作成するのですが、ここで非同期処理(Future async/await)の知識が必要となりました。始めたてのときはエラーの解消がやや難しかった。。
具体的なデータベース接続のサンプルはネットで探すとたくさん見つかるのですが私は以下のブログの情報が分かりやすかったので参考にさせていただきました。

FlutterのシンプルなSQFliteデータベースの例
SQFliteプラグインを使用してFlutterでSQLiteデータベースをセットアップする方法についてのチュートリアルがたくさんあります。彼らは役に立ちましたが、私は細部に迷い込んでいることに気づきました。

データベースへの接続が出来るようになったら概ね以下のような作業

  1. CREATE TABLEのクエリを作成
  2. sqliteから取得したデータを取り扱いやすいようにする入れ物(モデル)を作成
  3. モデルを元にデータベースとのやり取りを行う処理insert/update/delete処理を作成
  4. 画面作成

「設定」みたいな画面を作っといてボタン押したらデータベースのレコード全削除みたいな初期化機能を作っておくと動作確認が簡単でよかった気がします。

カレンダー表示、クリックした日付の情報表示機能を作成

table_calendarというパッケージを使用してカレンダーを表示、クリックした日付で登録されている資産情報を取得、そこから登録/更新のダイアログ表示などの機能を作ってみました。
table_calendarの使い方は以下の記事で概ねつかめました。

【Flutter】table_calendarを使ってみる。【table_calendar 3.0.0対応】 - Qiita
はじめに Flutterのパッケージにtable_calendarといういい感じでカレンダーを実装できるものがあるのですが、 日本語で紹介された記事があまりなかったので実際に使ってみて実装するまでの記録をまとめたいと思います。 ...

出来たものが以下。

登録したデータを元にホーム画面にチャート&テーブル表示

チャートはcharts_flutter、テーブル表示はhorizontal_data_tableパッケージを使用しました。

チャート表示を行うためのパッケージは他にもいくつか見つかりましたが、Googleが提供していて一番入門によさそうだったものを使いました。
基本的な使い方はパッケージのギャラリーページがあるので参考にしました。
詳細な使い方を調べるのはGitHubのIssueを漁るのが良かった気がします。

テーブル表示について最初はFlutter標準のDataTableというクラスを使用してたのですが、日付列とヘッダー行を固定化したいなーと考え、調べたところ良さげなパッケージが見つかったため作り直しました。
標準のDataTableと比べると表示と動作が少し重くなりましたがなかなかに満足のいく表示が出来るようになった気がします( ・`ー・´)

フッターに固定のナビゲーションを追加

スマホアプリによくあるフッターに固定されたボタンを追加しました。
最初は各ページのScaffoldのbottomNavigationBarに共通化したBottomNavigationBarウィジェットを表示して、クリックの度にpushReplacementな遷移をしてましたが遷移の度にフッターも含めたページが再描画されるため見栄えが悪い状態でした。。

「Flutter ナビゲーション 固定」とかで調べると色々やり方は見つかるのですが、今回はpersistent_bottom_nav_barというパッケージを導入しての解決を行いました。
フッターの固定にあたってのタブビュー導入が最初はイメージがつかめず苦戦しました。以下の2ページを見たりプログラム書いたりして、やっとのことで理解できました。。

FlutterでBottomNavigatorBar を残したまま各画面を遷移させる - Qiita
BottomNavigatorとは下タブのボタンたちです。色々なアプリで使用されていて、アプリを作る際によく使うと思います。ごく普通に使えば問題なく動作するのですが、TwitterやInstagramのように下タブを隠さないように各タ...
BottomNavigationBar をキープしたまま画面遷移する - Qiita
現状の問題 BottomNavigationBarで表示したWidgetから何も考えずにNavigator.of(context).push()で遷移すると以下のように、画面遷移と同時にBottomNavBarが消えてしまう。 ...

変更内容の抜粋

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
+      home: Home(),
+      initialRoute: '/',
-      home: const MainPage(),
-      initialRoute: '/',
-      routes: <String, WidgetBuilder> {
-        '/main': (BuildContext context) => const MainPage(),
-        '/account': (BuildContext context) => const AccountPage(),
-      }
    );
  }
}
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';
import 'package:stock_performance_flutter/pages/account_page.dart';
import 'package:stock_performance_flutter/pages/main_page.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {

  late PersistentTabController _controller;

  final _pages = <Widget>[
    MainPage(),
    const AccountPage(),
  ];

  @override
  void initState() {
    super.initState();
    _controller = PersistentTabController(initialIndex: 0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PersistentTabView(
        context,
        controller: _controller,
        screens: _pages,
        items: [
          PersistentBottomNavBarItem(
            icon: const Icon(Icons.home),
            title: "ホーム",
          ),
          PersistentBottomNavBarItem(
            icon: const Icon(Icons.account_balance),
            title: "口座",
          ),
        ],
        navBarStyle: NavBarStyle.style3,
        backgroundColor: Colors.white,
      )
    );
  }
}

別ページの状態を更新するためにproviderパッケージ導入

これまでStatefulWidgetのsetStateを使用してページ内の状態を更新してきましたが、口座画面や入出金画面でデータを登録/更新/削除したタイミングでホーム画面のチャートとテーブル表示を更新するにはどうすればいいのかを調べた結果、プロバイダと呼ばれる仕組みを導入する必要があることがわかりました。

プロバイダー導入前の状態について

プロバイダーを導入する前の状態でも各種データを登録/更新/削除したタイミングでhome.dartに初期表示したいタブのインデックスを持たせpushReplacementな画面遷移を行い、home.dartでは渡された引数にくっついてるインデックスを元に表示するタブを更新をするという方法を取って、一応目的とした動作はできていました。
ここに載せて供養。

class HomeArguments {
  int initTabIndex;

  HomeArguments(this.initTabIndex);
}
  @override
  Widget build(BuildContext context) {

+    // インデックス引数付きのpushがあった場合タブインデックスを更新
+    if (ModalRoute.of(context)!.settings.arguments != null) {
+      final args = ModalRoute.of(context)!.settings.arguments as HomeArguments;
+      setState(() {
+        _controller.index = args.initTabIndex;
+      });
+    }

    return Scaffold(
      body: PersistentTabView(
....
  // 何らかのボタンをクリックしたときの完了処理
  ElevatedButton(
    child: Text(addOrEdit),
    onPressed: () {
      if (_formKey.currentState!.validate()) {
        saveRecord().then((_) {
          // 登録/更新画面を閉じる
          Navigator.of(context).pop();

+          // 元に戻れない形のページ遷移(pushReplacement)を行う
+          Navigator.of(context, rootNavigator: true).pushReplacement(
+            MaterialPageRoute(
+              builder: (context) => const Home(),
+              settings: RouteSettings(
+                arguments: HomeArguments(AccountPage.tabIndex)
+              )
+            )
+          );
        });
      }
    },
  )
プロバイダー導入

プロバイダーを理解するのに以下の参考になりました。

【Flutter】それ、FutureBuilderだったら綺麗に書けるよ? - Qiita
元ブログ - 技術は熱いうちに打て! | 【Flutter】それ、FutureBuilderだったら綺麗に書けるよ? 概要 今日は FutureBuilderについて解説したいと思います。 アプリを作ってると非同期処理が頻出す...
【Flutter入門】FlutterのProviderについて、めちゃくちゃ易しく解説してみる - Qiita
Flutterを独学で始め、個人アプリのリリースまで至ったのだが、その中でProviderを使うのが一つのハードルだった。 Flutter開発において、初心者→中級者にステップアップする上で、Providerを使うというのは避けては...

ざっくり以下のような作業を行いました。

  1. providerパッケージインストール
  2. ChangeNotifierを継承したプロバイダークラス作成
  3. main.dartに作成したクラスを登録
  4. ページ内の状態を持つ変数をプロバイダークラスに移植
  5. ページ内でsetStateで状態更新していた箇所をプロバイダーに移植
    1. プロバイダーでは状態の更新を通知するのにnotifyListeners()を呼び出す
  6. StatefulWidgetをStatelessWidgetに書き換え

StatefulWidgetをStatelessWidget化する過程では以下の記事にあるようなStatefulWidgetのラッパークラスを作りました。

FlutterのStatelesswidgetでinitstateしたい時 - Qiita
Statelesswidgetでも画面呼び出し時に処理実行したい... と思って調べたら良さそうなのがあったので自分用メモ StatefulWrapperクラスを作る StatefulWrapper.dart class...
How to call a function on start in Flutter stateless widgets
Ever wondered how you would call an async function on start in Flutter from a stateless widget? Well here you go

出来上がった状態が以下

出来上がったアプリをGooglePlayで公開 2021/12/14追記

  1. GooglePlayConsoleのアカウント作成、25$の登録料払う
  2. コンソール上でアプリの登録情報を作成する
  3. AppBundleを作成してアップロードする

初めてってのもありますがめちゃくちゃ苦戦しました。。いろんなサイトを参考にしたり一生懸命ドキュメント読んだり。
上の手順からは端折ってますが実際にはAdMobの広告掲載をしてみたかったのでそちらも登録、広告ユニットIDをプログラム内に組み込んだりとか色々やりました。
AppBundleをアップロード後、GooglePlay側で審査が入るのですがこれが即時で行われるわけではなく3日ほど待ちが発生しました;;

出来上がったアプリが以下になります。
もしよろしかったら株やFXの資産管理に使ってみてください。

株やFXの資産管理アプリ StockPerformance - Apps on Google Play
Record daily assets and visualize with charts and data tables

メモ

FlutterのSDK内を見ると中身はGitのリポジトリなのでもしかしたらバージョンは任意のものにダウングレードとかできるのかもしれない??

実機デバッグで使用してる手持ちのAndroid端末が縦に長い(たぶん21:9)のでもしかしたら普通の16:9のスマホで使ったらウィジェットが衝突とか起きてそうなのが心配。。iPhoneはそもそも持ってないので動作確認一切できていない。。

その他の参考リンク

vscode StatelessWidgetからStatefulWidgetに自動変換するショートカットキー – Qiita
Flutterで中国語フォントになってしまうときの設定 – Qiita
How to refresh an AlertDialog in Flutter? – Stack Overflow
Flutterで画面の向きを固定する – Qiita
FlutterでAdMobを表示(iOS/Android) – Qiita
flutterアプリでfirebase_admobからgoogle_mobile_adsに切り替えてみた – Qiita
Flutterで開発したアプリアイコンの変更方法! iOS・Android別に紹介
Flutter StrutStyleで日本語と英語のTextの高さを揃える – Qiita
Google PlayにAndroidアプリを公開する | Flutterで始めるアプリ開発
Flutter製のAndroidアプリをGooglePlayにリリースする
Flutter端末の文字サイズを参照しないようにする | がーみブログ

まとめ

今回のアプリ制作を通して以下のことが出来るようになりました。

  • Flutter2.5.3、Dart2.14.4を使用したアプリ開発
  • sqliteを使用したデータ管理
  • providerを使用した状態管理
  • persistent_bottom_nav_barを使用したタブビュー表示
  • table_calendarを使用したカレンダー表示
  • charts_flutterを使用したラインチャート表示(ツールチップ、凡例は独自の描画クラス作成)
  • horizontal_data_tableを使用したデータテーブル表示
  • データベースのバックアップ/リストア
  • アプリの日本語化(table_calendar、DatePicker、MonthPicker)
  • google_mobile_adsを使用した広告表示

など

次にアプリ開発をする場合は

  • 認証機能(FirebaseのAuthentication?)
  • FirebaseのCloudFirestoreを使用したデータ管理
  • riverpodを使用した状態管理

の辺りをやってみたいと考え中。

タイトルとURLをコピーしました