ページを移動する

2023年3月26日

開発環境
OS:Windows 11
SDK:VS Code + flutter 3.7.5

ページ移動=画面遷移とは

1画面にアプリの機能を全て集約すると見た目が煩雑となり、操作性が悪くなります。

そこで機能を分別し、機能ごとに画面を構成します。

そして、操作に合わせて適切な画面を表示するようことでアプリの機能が全て満たせるよう実装できるわけです。

この操作に合わせて適切な画面を表示することを画面遷移と呼びます。

画面遷移ロジック

画面遷移を行うにはNavigatorクラスを使用します。

基本的なロジックは下記となります。

Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const (遷移先画面クラス)()),
  );

ボタンをタップする、状態が変わるなどのトリガーが発生した際に上記ロジックをコールすれば指定した画面に遷移できます。

実装してみる

テキストを表示するだけのサブ画面を作成します。

class SubPage extends StatelessWidget {
  const SubPage({super.key});

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Container(
            width: double.infinity,
            height: double.infinity,
            alignment: Alignment.center,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Text('Sub Page')
            ],)
          ),
    );
  }
}

あとはメイン画面にボタンを配置し、ボタンをタップするとサブ画面に遷移するようにします。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        alignment: Alignment.center,
        child: Column(
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                alignment: Alignment.center,
                child:Image.asset('images/sample.jpg'),),),
            Expanded(
              flex :1,
              child: Container(
                alignment: Alignment.center,
                child: ElevatedButton(
                  onPressed: () {
                    Navigator.push(
                      context, MaterialPageRoute(builder: (context) => const SubPage())
                    );
                  },
                  style: ElevatedButton.styleFrom(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(5),
                    ),
                  ),
                  child: const Text('Next Page'),
                  ),))
          ],
        ),
      ),
    );
  }

実際にWebで実行してみると以下の通りです。

メイン画面
サブ画面

「Next Page」ボタンをタップするとサブ画面に遷移します。

Navigatorクラスには他にも幾つかのメソッドが用意されています。

メソッド挙動
push遷移前の画面を残し、遷移先画面を表示する
pushReplacement遷移前の画面を残さず、遷移先画面を表示する
pushNamed指定したルートの画面を表示する(Route登録必要)
pop画面を消去する

用途に合わせ、使い分けることができます。

今回は以上です。

Widgetを構成する

2023年3月19日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.7.5

Widgetとは

Widgetとは画面を構成するUI要素です。

Widgetを組み合わせることで画面を構築できます。

基本的なWidget一覧

基本的なレイアウト系と操作系を把握しておけば一通りの画面が作れます。

(レイアウト系)

Widget役割
Column行(縦)配置
Row列(横)配置
Expanded画面フルサイズ配置
Align指定配置
Stack重ね配置
Containerコンテナ
レイアウト系一覧

(操作系)

Widget役割
Textテキスト表示
Image画像表示
Iconアイコン表示
GestureDetector操作系コンテナ
ElevatedButtonボタン
操作系一覧

Widgetを組み合わせる

例えば画面を2等分し、画面上に画像を配置し、画面下にテキストを配置するなら

以下のようになります。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        alignment: Alignment.center,
        child: Column(
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                alignment: Alignment.center,
                child:Image.asset('images/sample.jpg'),),),
            Expanded(
              flex :1,
              child: Container(
                alignment: Alignment.center,
                child: const Text('sample', textAlign: TextAlign.center,),))
          ],
        ),
      ),
    );
  }

まず画面フルサイズのコンテナを配置します。
その中にColumnを配置して上下2分割し、Expandedで1対1になるよう画像とテキストを配置しています。

実際にWebで実行してみると以下の通りです。

Widgetの良い所はイメージ通りに組み合わせることができるところ。

更にコンテナ内にRowなどを配置し、画面構成を豊かにしていくことができます。

今回は以上です。

今月のみてみて(2023年2月)

Firebase連携アプリ、みてみてエストーク伊勢125社巡り

このうち、みてみてにフォーカスを当て、Firebaseのアクセス動向やアプリの改善を考察する開発記事です。

Realtime Database+Storage構成の投稿アプリ

まずはアプリのユーザー数の変動とともにFirebaseのアクセス動向を確認します。

2023年2月19日~2023年3月18日のユーザー数の変動

(ユーザー数の変動)

1ヶ月の新規獲得ユーザー数

ユーザー数の変動自体は先月とかわらず。

今月は改善を入れれなかったのでむしろ健闘している、と言いたい。

(インストールの上位国)

今月もフィリピンがインストール数トップです。

ありがとうございます。

マレーシアも伸びがありますが、第3の国が出てきて欲しいところ。

2023年3月19日直近のFirebaseの変動

(Firebaseの変動)

直近のfirebaseの動き
1ヶ月のfirebase使用量

ユーザー数に比例して使用量は減少してます。

flutterで作ったWeb版、みてみてWebも見られているようでなりより。

2023年1月19日~2023年2月18日のAdMob

今月はAdMob広告収入:106円、 Firebase使用料:0円となりました。

今月のみてみて

(今月の投稿ピックアップ)

ありません。。。

誰か投稿してください。

今後の改善案

(未消化の課題)

・アプリUIブラッシュアップの継続

・フレンドグループ化と共有機能の追加

(アプリ使用中の気付き)

・投稿機能の改善

,

サンプルアプリを作成してみる

2023年2月23日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.7.5

事前準備

Flutter開発に必要なソフトウェアをインストールし、環境設定を行います。

必要なソフトウェアについてはこちらを参照してください。

コマンドライン

Flutterの基本操作をコマンドラインで行えるよう覚えておくと便利です。

コマンド動作
flutter create (プロジェクト名)プロジェクト名のフォルダが作られ、フォルダ内にサンプルプログラム一式が出力される
flutter devices実行可能なデバイス一覧が表示される
flutter run -d (デバイス名)指定したデバイスでアプリを起動する
flutter build (ビルド対象)ビルド対象に指定したアプリを生成する
ビルド対象は、apk/ios/webなど
flutter upgradeflutterを最新バージョンに更新する
flutter cleanビルド構成を消去する
flutter pub add (パッケージ名)外部パッケージを指定して取得する
flutter pug get外部パッケージを取得する
コマンドライン一覧

サンプルプログラムを作成する

まずはコマンドプロンプトを起動します。(Wndowsマーク+Rで検索窓を表示し、cmdと入力)

cdコマンドでプロジェクトを作成したい場所に移動した後、flutterコマンドを入力します。

「sample」という名のプロジェクトを作成してみます。

flutter create sample

createコマンド結果

コマンドを入力するとsampleフォルダが作成され、基本的なソースコード一式が出力されます。

sampleプロジェクト構成

これでベースとなるサンプルアプリのソースコードの完成です。

sampleフォルダに移動し、flutterコマンドを入力します。

chromeブラウザで動かしてみます。

flutter run -d chrome

runコマンド結果

カウンターアプリが起動しました。

右下の「+」をタップするとカウントアップするというシンプルなアプリです。

画面レイアウトやカウントアップなどの処理はソースコードlib/main.dartに定義されています。

基本的にFlutterアプリを作成するときは、このサンプルコードをベースにlibフォルダにdartファイルを追加していくなどしてアプリをカスタマイズしていきます。

今回は以上です。

Flutter開発環境を構築する

2023年2月23日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.3.10

Flutter開発に必要なもの

まずはFlutter開発を行うときにインストールする必要があるものを列挙します。

・Flutter

開発環境の基礎となるフレームワークです。

開発言語dartで書かれたソースコードを解析し、アプリを生成します。

Windows版インストール

MAC版インストール

・VS Code

ソースコードを書くためのエディタですが、プラグインの導入をサポートしたり、テスト実行、デバッグ、アプリ生成などがVS Code内でできます。

言わば、統合開発環境(IDE)の役割を果たします。

インストール

・Android Studio

Androidアプリを生成するときに必要となるAndroidアプリ開発環境です。

インストール

・Xcode

iOSアプリを生成するときに必要となるiOSアプリ開発環境です。

XcodeはMacでしか動作しないため、Windowsにインストールすることはできません。

インストール

作成対象と開発PC(OS)との関係を整理すると以下のようになります。

【凡例】〇:作成できる、✖:作成できない

作成対象WindowsMac補足
Webサイト特になし
AndroidアプリAndroid Studio
インストール必要
iOSアプリXcode
インストール必要
OS別対応表

FlutterでWebサイトを作るだけであれば、Flutterのみインストールすれば作成できます。

ですが、エディターとしてVS Codeを使うと開発効率が上がるのでおススメです。

スマホアプリを作成する場合は、Android Stuido、Xcodeのインストールが必要です。

また、Flutterの最大のメリットでもある1つのソースコードからAndroidアプリとiOSアプリを生成したい場合はMacで開発する必要があります。

今回は以上です。

スマートフォンゲームβテスト体験会に参加してみた。

ドラゴンクエスト チャンピオンズ

過去に幾つもの派生ゲームがリリースされているドラゴンクエスト。

そのドラゴンクエストにおいてシリーズ初となるバトルロワイヤル系スマートフォンゲームのリリースが控えています。

その名も「ドラゴンクエスト チャンピオンズ」。

そのリリースに先駆けAndroid1万名、iOS1万名、計2万名によるβテスト体験会が

実施されると聞きつけ、応募してみたところ、見事、当選しました。

当選メール

今回はその体験会の模様を記事にしたいと思います。

体験版アプリインストール

当選メールが届いたので早速、スクエアエニックスのメンバーズサイトをチェックしてみます。

流石、体験版です。

APKファイルを直接インストールする仕組みでした。

早々にAPKファイルのインストールを終わらせ起動してみると・・・

サーバーへのアクセスが制御されていてゲーム自体は進められませんでした。

このあたりのテスト管理はスマートフォンならではの気軽さですね。

βテスト体験会レポート

βテスト体験会の期間は2月6日~2月13日の1週間。

毎日1時間程度のプレイ時間でしたが基本的な要素は一通り楽しめました。

ドラゴンクエスト チャンピオンズの遊び方

ドラゴンクエスト チャンピオンズには冒険と大会、2つの遊び方が用意されています。

ホーム画面

ホーム画面から冒険や大会に移動します。

冒険はクエストを受注しながらストーリーを進める遊び方です。

クエストでは3Dマップを移動してモンスターの討伐を目指します。

1クエストあたり5分かからない程度なので気軽に遊べました。

そして、もう1つが大会、バトルロワイヤルです。

大会エントリー画面

大会へ参加すると2分ほどの待機時間中に参加者が集められます。

待機時間が過ぎるか、人数が集まるとゲーム開始。

フィールドに放たれ、モンスター討伐、アイテム収集で能力値を上げながら、参加者同士のバトルで勝ち上がる遊び方となります。

フィールドはそこそこ広いですが、時間制限でフィールドが絞られていくため、強制的に参加者同士がエンカウントする仕組みです。

最後まで勝ち残ると10分以上かかったので

大会に参加するときはある程度時間の余裕があるときにプレイする必要がありそうです。

モンスターや参加者同士のバトルはコマンド入力式で先制、後攻の概念があるようでした。

先制を仕掛けられると挽回するのはきつそうだったので正式リリース時には調整が入るかも。

1度だけ1位になりました。

それ以外のゲーム要素としては、

デイリークエスト。

ガチャによる武器、防具厳選。

武器、防具の強化。

など。

ストーリーでクエストを進め、レベル上げに勤しみ、大会に参加し、バトルロワイヤルを競う、というのがメインの遊び方になりそうです。

体験会だけあってログインボーナスは大盤振る舞い

星5武器、防具はそこそこ揃った感じですが、

一番の目玉、ドラゴンキラーは当たりませんでした。

体験後感想

グラフィックはリリース前とあって完成度低めでしたが、操作や画面遷移などで問題が発生することはありませんでした。

プレイした日によってはぐれメタルが全然逃げなかったり、逃げまくったりとかあったのでパラメーター調整がメインのようでしたね。

個人的にはもっとデバッグ的なことをするのかと期待してたんですが。。。

プレイした感想としては、

ゲーム的に大会がメインになると思われるんですが、

個人的にバトルロワイヤル系を普段やらないということもあって

もう少しストーリーを盛り上げてストーリーと大会を行き来したくなるような展開が欲しいかなと感じました。

では、大会が面白くなかったかというとそうではなく、

レベル的に倒せないようなモンスター、でんでん竜とか、がうろついて必死に逃げたり、

フィールドにアイテムの宝庫のようなところがあったりと

テンションが上げる瞬間があったのでそのあたりがリリース時にはどう調整されるのか楽しみです。

今月のみてみて(2023年1月)

Firebase連携アプリ、みてみてエストーク伊勢125社巡り

このうち、みてみてにフォーカスを当て、Firebaseのアクセス動向やアプリの改善を考察する開発記事です。

Realtime Database+Storage構成の投稿アプリ

まずはアプリのユーザー数の変動とともにFirebaseのアクセス動向を確認します。

2023年1月19日~2023年2月18日のユーザー数の変動

(ユーザー数の変動)

1ヶ月の新規獲得ユーザー数

ユーザー数の変動が徐々に落ちてきています。

奈良に出かけた際にメモ代わりに駐車場の場所を投稿してみたのですが、使い勝手がいまいちだったため、修正しました。

(インストールの上位国)

今月もフィリピンがインストール数トップです。

ありがとうございます。

そして、2位が不明な地域。

果てしてちゃんと機能しているのだろうか。。。

2023年2月18日直近のFirebaseの変動

(Firebaseの変動)

直近のfirebaseの動き
1ヶ月のfirebase使用量

flutterで作ったWeb版、みてみてWeb

今のところあまり動きがないのでいいですが、ちょっと動いただけで使用量が結構動くんじゃないだろうか。

AdSenseの申請が通らなかったのが痛かった。

マップ機能をメイン機能からサブ機能へ格下げしてコミュニケーション機能をメインにしたほうがいいかもしれない。

2023年1月19日~2023年2月18日のAdMob

今月はAdMob広告収入:45円、 Firebase使用料:0円となりました。

今月のみてみて

(今月の投稿ピックアップ)

玉置神社に行きました。

神社に辿り着くのも一苦労な場所です。

本殿も投稿しようとしたのですが、GPSがブレてしまって投稿できず。。。イングレスは普通にできたのに。

今後の改善案

(未消化の課題)

・アプリUIブラッシュアップの継続

・フレンドグループ化と共有機能の追加

(アプリ使用中の気付き)

・投稿機能の改善

,

Flutter Webをスマホ画面に適応させる

2023年2月12日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.3.10

概要

PC画面とスマホ画面、2画面分のWidgetを用意し、使い分ける

Flutter WebはPCからアクセスするとは限らない

FlutterでAndroidアプリやiOSアプリを作るならスマートフォン利用前提になるので

画面構成は基本的には縦配置のみの考慮でいいはずです。

ですが、Flutter Webの場合、ブラウザでアクセスするため、

PCとスマホ、どちらからでも利用できるよう想定しなれけばいけません。

つまり横配置を前提としてPC画面用Widget、横配置を前提としたスマホ用Wiget、

2パターンの画面構成に対応したWidgetを用意する必要があります。

PC用Widgetとスマホ用Widget

まずはPC用とスマホ用、それぞれの画面構成を作成します。

それぞれWidgetを関数化しておくと便利です。

(PC用Wigetの作成例)

  Widget _createPCView(BuildContext context)  {
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        width: 250,
        height: 200,
        margin: const EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: Colors.yellow,
          border: Border.all(color: Colors.grey, width: 1),
          borderRadius: BorderRadius.circular(5.0)
        ),
        child: Column(
          children: [
           ・・・
          ],)
      ),
    );
  }

(スマホ用Widgetの作成例)

   Widget _createMobileView(BuildContext context)  {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        width: double.infinity,
        height: 80,
        margin: const EdgeInsets.only(bottom: adHeight),
        decoration: BoxDecoration(
          color: Colors.yellow,
          border: Border.all(color: Colors.grey, width: 1),
          borderRadius: BorderRadius.circular(5.0)
        ),
        child: Column(
          children: [
           ・・・
          ],)
      ),
    );
  }

画面サイズでWidget使い分ける

あとは画面サイズを取得し、サイズによってWidgetを使い分けます。

画面サイズの取得には、MediaQuery.of(context).sizeを使用します。

またPCとスマートフォンの画面サイズの境界値は400とします。

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final screenSize = MediaQuery.of(context).size;
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        alignment: Alignment.center,
        child: screenSize.width > screenWidth? _createPCView(context) : _createMobileView(context)
      )
    );
  }

これでPCとスマートフォンどちらにも対応できます。

実際にWeb画面をスマートフォンとPCで表示すると以下のようになります。

https://messageexchanger-fa3e1.web.app

Widgetの組み立てを関数化する

2023年2月7日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.3.10

概要

Widgetの組み立ては関数化したほうが可読性が上がる。

flutterのWidget構成はどうしてもネストが深くなる

flutterではWidgetを組み合わせて画面を作成します。

Widgetによる画面構築は感覚的にも分かりやすくとても簡単に画面を作成できます。

ただ一つ、Widgetを組み合わせていると問題が。

WidgetのなかにWidgetを入れ、そのなかに更にWidgetを追加して・・・

と、Widgetを組み合わせていくとネストが深くなるんです。

その結果、コードの可読性が落ちてしまい、

ぱっと見て画面構成が把握できない難解なコードとなってしまうわけです。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
       width: double.infinity,
       height: double.infinity,
       alignment: Alignment.center,
       child: Column(children: [
         Expanded(
           flex: 1,
           child: Row(children: [
             Expanded(
               flex: 3,
               child: Column(children: [
                 Expanded(flex: 1, child: Container(alignment: Alignment.center, child:Text())),
                 Expanded(flex: 19, child: ListView.builder(
                     itemCount: droplist.length,
                     itemBuilder: (context, index) {
                       return InkWell(
                         onTap: () async {},
                         child: Container(
                           width: double.infinity,
                           alignment: Alignment.center,
                           decoration: BoxDecoration(),
                           child: Stack(
                             alignment: Alignment.bottomCenter,
                             children: [
                               Container()
                           ],)
                         ),);
                     },
                   ))
               ],)
             ),
             Expanded(
               flex: 7,
               child: Stack(children: [
                 GoogleMap(),
                 Container()
               ])
             ),
           ]),),
         Container(height: adHeight)
       ],)
     )
    );
  }

Widgetは関数化できる

そこでどうするか。

Widgetの構成を関数化し分割します。

Widgetを戻り値とする関数として定義することができるのです。

例えば先ほどのmap.dartの場合、関数化してみると。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        alignment: Alignment.center,
        child: _createMainView()
      )
    );
  }
 //メインビュー
 Widget _createMainView() {
   return Column(children: [
      Expanded(
        flex: 1,
        child: Row(children: [
          Expanded(
            flex: 3,
            child: _createListView()
          ),
          Expanded(
            flex: 7,
            child: _createMapView()
          ),
        ]),),
      Container(height: adHeight)
      ],)
    );
  }

  //画面サイドビュー
  Widget _createListView() {
    return Column(children: [
      Expanded(flex: 1, child: Container(alignment: Alignment.center, child:Text())),
      Expanded(flex: 19, child: ListView.builder(
          itemCount: droplist.length,
          itemBuilder: (context, index) {
            return InkWell(
              onTap: () async {},
              child: Container(
                width: double.infinity,
                alignment: Alignment.center,
                decoration: BoxDecoration(),
                child: Stack(
                  alignment: Alignment.bottomCenter,
                  children: [
                    Container()
                ],)
              ),);
          },
        ))
    ],);
  }
  
  //マップビュー
  Widget _createMapView() {
    return Stack(children: [
      GoogleMap(),
      Container()
    ]);
  }

関数はbuild処理の前に定義しておけば大丈夫です。

どうでしょうか?

結局のところ、Widgetの数自体は変わらないのでステップ数が減ることはないですが、可読性は上がったのではないでしょうか?

Widgetはクラス化もできる

関数化して満足していたらクラス化もできたようです。

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

    @override
    Widget build(BuildContext context) {
    return Column(children: [
      Expanded(flex: 1, child: Container(alignment: Alignment.center, child:Text())),
      Expanded(flex: 19, child: ListView.builder(
          itemCount: droplist.length,
          itemBuilder: (context, index) {
            return InkWell(
              onTap: () async {},
              child: Container(
                width: double.infinity,
                alignment: Alignment.center,
                decoration: BoxDecoration(),
                child: Stack(
                  alignment: Alignment.bottomCenter,
                  children: [
                    Container()
                ],)
              ),);
          },
        ))
    ],);
    }
  }

クラス化の利点はsetState()などの再描画においてリフレッシュの範囲をクラス単位で管理できる点のよう。

また、flutter公式もクラス化を推奨しているようです。

CSVファイルの文字化け対策

2023年2月5日

開発環境
OS:Windows 10
SDK:VS Code + flutter 3.3.10

概要

flutterでCSVデータを出力するときはBOMを付けた方がいい。

エクセルはShift-JISに対応している

flutterでCSVを出力します。

テキストエディタで表示すると問題なし。

いざ、エクセルで開いてみた結果。。。

文字化けしています。

なぜなのか調べてみたところ、エクセルは文字コードをShift-JISで取り扱うようです。

それに対し、flutterはutf-8で出力します。

故に文字コードが異なるため、文字化けが発生したということですね。

BOMを付ける

対策はどうするのか。

flutterでutf-8をshift-jisに変換して出力すればいいんじゃないのか。

最初にそう思ったわけですが、「文字コード変換」と「変換後のデータ出力」。

2つの対応が必要になります。

もっとうまい方法がないのかとネットで検索してみると見つけました。

エクセルはBOM付きutf-8なら表示できる。

つまり、

utf-8のデータの先頭にBOMと呼ばれる識別コードを付与するだけです。

これなら簡単。

改修したコードがこちら。

//CSV生成
final csv = await makeFileCsv();
// utf-8バイト変換
final excelCsvBytes = [0xEF, 0xBB, 0xBF, ...utf8.encode(csv)];
// base64エンコード変換
final base64ExcelCsvBytes = base64Encode(excelCsvBytes);
AnchorElement(
    href: 'data:text/plain;charset=utf-8;base64,$base64ExcelCsvBytes')
  ..setAttribute('download', 'test.csv')
  ..click();

2行目でCSVデータをバイト配列に変換して先頭に「0xEF 0xBB 0xBF」を付与。

3行目でそのバイト配列をbase64に再変換。

あとはそのデータを「utf-8;base64」として出力する。

という流れです。

改修コードで確認した結果、テキストエディタでも問題なく開き、エクセルの文字化けもなくなりました。