🔖

Flutterでアプリの開発からリリースまでの一通りの流れと進め方

2024/02/08に公開

このページではFlutterを独学で学んだ文系卒の社会人が実際にどのようにアプリの開発を進めたのかをざっくり説明します。

前提

使用したツール

  • PC mac mini m1 apple silicon
  • IDE vscode(visual studio code)
  • 言語 dart
  • フレームワーク Flutter
  • Animation Rive
  • 広告 Admob
  • DB Firebase
  • アーキテクチャ MVC(Model View Controller)
  • ソースコード管理 Github

開発したもの

今回は筋トレの記録を取ることを目的としたアプリを作っていきました。
そして開発を進めていく上で考えたことなどをまとめます。

使用したパッケージ

開発の流れ

どういったアプリにしていくかを決める

  1. アプリの画面デザインと画面遷移先の明確化
  2. 主な機能の洗い出し
  3. 使用するDBの選定
  4. 使用するアーキテクチャの選定
  5. 開発の予定期間(納期)の設定

プログラミング

  1. ログイン画面の作成
  2. ログイン後の画面の作成
  3. プロフィール画面の作成
  4. 筋トレタイマーの画面を作成
  5. 筋トレ部位の選択をできる画面の作成
  6. 筋トレのメニューを選択できる画面の作成

開発を始めるにはまずどう言ったものの開発をしてどう言った年齢層、ユーザに使用してもらうかを考えることが大前提です。自分の開発したアプリでいうと、本件で取り扱うアプリは筋トレ初心者向けのアプリとしています。筋トレの管理ではなく筋トレの記録とBMIの計測が行えるものとなっています。

アプリを開発していく上で必要だと考えていたこと

挫折しがちな独学でのアプリ開発で意識をしたこと

  • 開発する目的を明確化する
  • 起床時間を決める
  • 作業時間を決める
  • 1日の目標を決める
  • 翌日やることをメモして机に配置
  • ChatGPTの活用
  • documentを読みパッケージの使用方法をしる

アプリに最低限必要な機能

  • アカウント機能
  • 筋トレ終了後の記録
  • アプリの共有
  • 問い合わせフォーム
  • BMI数値計測画面
  • 通知設定

アプリ開発の具体的な流れ

ここからはアプリ開発の具体的な流れをここに記載します。

アプリの画面デザインと画面遷移先を紙に記載する

自分の場合はアプリのデザインと画面遷移先などを紙に記すことでよりイメージがしやすくなりました。
また学習した内容などを紙に記すことが重要です。

少し汚いですが、このようにpcのデータではなくメモのように記載することでPCをシャットダウンしたとしても寝る前などに目を通すことができます。

アプリの画面デザインを紙に記したらそれを元にUIを構築する

ログイン画面

上記の画像はログイン画面になります。
このログイン画面の必要な機能としては以下となります。

  • メールアドレス、パスワードの入力後にログインボタンを押下するとログインを行えるようになる
  • メールアドレスが空白の場合エラーとする
  • パスワードを入力していない場合にはエラーとなる
  • 新しいアカウント登録フォームに遷移することができる
  • パスワードを忘れた場合のパスワードリセットするためのフォームに遷移する
  • アカウント登録せずにアプリを使えるようにする

アプリを開いた時の画面を作成する

アプリを開いた時の画面はログイン画面としていますが、ログイン状態をアプリ内に保持する必要があります。
アカウント登録をせずに使用する場合にはプロフィール画面をアプリの使い方説明の画面に変えています。

またここで大きな問題が発生します。
その問題とは アプリを立ち上げた際の画面がログイン画面となってしまう というものです。

どういうことかというと
Login画面が初期画面となるため、'flutter run' を実行した時に必ずログイン画面になってしまうということです。
しかしこう言った問題は以下のサイトを参照すれば解決することができるので参考にしてください。
https://zenn.dev/flutteruniv_dev/articles/902b184146ea94

そしてもう一つの問題はログイン画面にて登録をせずにアプリを使用する場合です。
先ほど、登録せずにアプリを使用する場合にはプロフィール画面を使い方説明画面に変更するというように記載をしていましたが、ログインをしない(登録せずに)でアプリを使用する場合には今回使用しているdbであるFirebaseは機能しません。
つまり、アカウント認証、筋トレの記録などをとる必要がないのです。

そしてログイン状態を保持するという先ほどの記事ではアカウントの状態を保持するというものでアカウントの登録が行われていない、すなわち、userがnullである場合にはログイン画面が初期画面となってしまいます。
ではアカウント登録せず、状態を保つにはどうすればいいか。
shared_preferencesを使用します。

先程紹介したサイトのコードを使用してサンプルコードを記述します。
使用するファイル

  • main.dart エントリーポイント
  • App.dart
main.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:training/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  if (prefs.getBool('first_launch') ?? true) {
    final FirebaseAuth auth = FirebaseAuth.instance;
    await auth.signOut();
    prefs.setBool('first_launch', false);
  }

  runApp(App());
}

App.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:takuma_sample/login.dart';
import 'package:shared_preferences/shared_preferences.dart';

class App extends StatelessWidget {
  
  Widget build(BuildContext context) => MaterialApp(
        title: 'Flutter app',
        home: StreamBuilder<User?>(
          stream: FirebaseAuth.instance.authStateChanges(),
          builder: (context, snapshot) {
            // 登録しないでアプリを使用する場合
            final nonRegisterMath = prefs.getInt("NonRegister");
            if (nonRegisterMath == 1 && snapshot.hasData == false) {
              return MainContent();
            }
            if (snapshot.connectionState == ConnectionState.waiting) {
              // スプラッシュ画面などに書き換えても良い
              return const SizedBox();
            }
            if (snapshot.hasData) {
              // User が null でなない、つまりサインイン済みのホーム画面へ
              return MainContent();
            }
            // User が null である、つまり未サインインのサインイン画面へ
            return UserLogin();
          },
        ),
      );
}

アカウントを登録せずにアプリを使用する場合のコードについては以下の部分となります。

App.dart
// 登録しないでアプリを使用する場合
final nonRegisterMath = prefs.getInt("NonRegister");
// もし、nonRegisterMath が 1 であり、尚且つ userがnullである場合には
if (nonRegisterMath == 1 && snapshot.hasData == false) {
  return MainContent();
}

この部分では
shared_preferencesパッケージを使用しています。
アカウントの登録をしない場合(nullである場合)にアプリを使用できるようにする必要があるため、以上のコードのようにsharedpreferencesを使用してフラグを作成します。

画面にある「登録せずに使用」というTextButton()を押下することでshared_preferencesのNonRegisterには1という数字が設定されることとなります。
当然のことながら、ログインやアカウント登録をしていないため、userはnullです。
まとめると以下となります。

  • sharedPreferencesのnonRegisterに1を設定
  • userをnull

とすることで ログイン画面にて登録をせずにアプリを使用する場合 という問題は解決されます。
アカウントの登録をする際にはsharedPreferencesのnonRegisterに0を設定するようにしてみるといいです。

ログイン後のAppDiscription()「使い方説明」画面を作成する

次にログインをした後の使い方説明画面の作成を行いました。
私の場合には使い方を説明する画面を作成しました。

「プロフィール」画面を作成する

instagramやfacebook,X(旧Twitter)などといったモバイルアプリを複数確認すると、プロフィール画面にはプロフィールを編集する機能や、アプリ自体の設定を行うiconなどがあります。
それを模倣して私は以下のようなUIをイメージしました。

上記のように紙にイメージを書くことでより作業が捗ります。
そしてプロフィール画面には以下の機能をつけることとしました。

  • プロフィール編集機能
  • アプリの共有機能
  • 身長と体重を記録することのできるボタン(画面遷移)
  • 開発者に問い合わせることのできるボタン(画面遷移)
  • ログアウト機能
  • アカウント削除機能

プロフィール編集機能についてはFloatingActionButtonとしているため、プロフィール画面の右下に配置しています。
そしてプロフィール画面の左上に配置しているiconButton(setting)を押下するとDrawerが表示されるようなデザインとしています。

筋トレを選択することのできる画面を作成する

次に筋トレする部位を選択することのできる画面の開発をしていきます。
自分のイメージは筋トレの部位を選択したのち、筋トレのメニューを選べるようにするということです。

  • 筋トレ部位の選択画面

  • 筋トレメニュー選択画面

こちらの画面自体の開発は簡単でComponentを作って並べるのみとなります。
ComponentsがonTapされたときに画面遷移するようにしているのみなので、開発自体は1日程度で終わらせることができました。

筋トレを始める画面を作成する

ここでも自分のイメージを紙に記してみました。

UIについては上記の画像を参照しながら作成していきました。
そしてここに追加していく機能は以下になります。
・秒数を開始するボタン
・終了するとDBに筋トレした内容が記録

最終的な画面デザインはこちらになります。

上記で筋トレアプリの大きな部分の開発が終了いたしました。

次に行ったことはプロフィール画面の左上に位置するsettingアイコンを押下した時の動作です。

settingアイコンを押下することで以下が表示されるようにしています。

  • 通知設定
  • 体型登録(身長と体重の入力を行うことのできる画面に遷移)
  • 友達に教える(共有機能)
  • 問い合わせ(自身が作成したHTMLとCSS)
  • ログアウト
  • アカウント削除

通知設定、体型登録画面、ログアウト、アカウント削除については割愛します。

友達に教える(共有機能)

こちらについては以下のようにプログラムを作成しました。

drawer.dart
import 'package:share_plus/share_plus.dart';
...
ListTile(
  leading: Icon(Icons.share),
  title: Text(
    "友達に教える",
    style: TextStyle(color: Colors.white),
   ),
  onTap: () {
     _shareText(
     context, 'イージーリストをダウンロードして超絶簡単なTodoを使ってブラザー',
     'イージーリスト');
  },
),
...
void _shareText(BuildContext context, String text, String subject) async {
  Share.share(text,
      subject: subject,
}

上記のshare_plusパッケージを使用して気づいたことがあります。
それは iPadなどの端末では共有がされない ということです。
上記のコードの状態ではipadでのアプリ共有がなされません。
ではどうすればいいのでしょうか。
以下のコードのように修正する必要があります。

drawer.dart
  final box = context.findRenderObject() as RenderBox?;
  await Share.share(text,
      subject: subject,
      sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
}

このようにすることでipadでもシェアを行うことが可能となります。
以下を参照してください。公式docにも記載しております。
https://halzoblog.com/error-bug-diary/20220920-2/
https://pub.dev/packages/share_plus

問い合わせ

問い合わせ機能についてはHTMLとCSSのみ用意していただければ問題ありません。

index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>お問い合わせ</title>
</head>

<body>
    <div class="login-box">
        <h2>お問い合わせ</h2>
        <form action="https://api.staticforms.xyz/submit" method="post">
        <input type="hidden" name="accessKey" value="4941c037-9bf2-4566-8821-731df3bedec5">

            <div class="user-box">
                <input type="text" name="email" required="">
                <label>メールアドレス</label>
            </div>
            <div class="user-box">
                <input type="text" name="message" required="">
                <label>内容</label>
            </div>

            <div class="button-form">

                <a id="submit" href="#">
                <input type="submit" value="Submit" style="border: none;background-color:transparent;color:white;" />
                </a>
            </div>

        </form>
    </div>
</body>

</html>

上記を参照して問い合わせフォームを作成できます。

Discussion