Flutter | バスケスコアアプリにメンバー登録機能を追加

今回は、Flutterで構築したバスケットボールスコア入力アプリ(ランニングスコア)にメンバー登録機能を追加実装しました。

メンバー登録機能の概要

メンバー登録機能では、以下の情報を入力してメンバーを登録できます。

  • 番号
  • 名前
  • ポジション
  • 身長
  • 体重

また、登録したメンバーの編集や削除も可能です。この機能を追加することで、アプリケーションの利便性が向上し、より多くの情報を一元管理できるようになります。

データベースの設定

database_helper.dart

まずは、メンバーの情報を保存するためのデータベースを設定します。database_helper.dartに以下のコードを追加して、membersテーブルを作成します。

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static final DatabaseHelper instance = DatabaseHelper._internal();
  factory DatabaseHelper() => instance;
  static Database? _database;

  DatabaseHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), 'basketball_score_app.db');
    return await openDatabase(
      path,
      version: 2, // データベースバージョンを2に更新
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute(
      '''
      CREATE TABLE games (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        venue TEXT NOT NULL,
        date TEXT NOT NULL
      )
      ''',
    );
    await db.execute(
      '''
      CREATE TABLE plays (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        team TEXT NOT NULL,
        points INTEGER NOT NULL,
        quarter INTEGER NOT NULL,
        playType TEXT NOT NULL,
        time TEXT NOT NULL,
        playerNumber TEXT NOT NULL,
        gameId INTEGER NOT NULL,
        FOREIGN KEY (gameId) REFERENCES games (id)
      )
      ''',
    );
    await db.execute(
      '''
      CREATE TABLE teams (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        location TEXT NOT NULL,
        registrationDate TEXT NOT NULL,
        lastEditedDate TEXT NOT NULL,
        displayOrder INTEGER NOT NULL
      )
      ''',
    );
    await db.execute('''
      CREATE TABLE members (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        number INTEGER NOT NULL,
        position TEXT NOT NULL,
        height REAL,
        weight REAL,
        teamId INTEGER NOT NULL,
        registrationDate TEXT NOT NULL,
        lastEditedDate TEXT NOT NULL,
        displayOrder INTEGER NOT NULL,
        FOREIGN KEY (teamId) REFERENCES teams(id)
      )
    ''');
  }

  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      await db.execute('ALTER TABLE members ADD COLUMN height REAL');
      await db.execute('ALTER TABLE members ADD COLUMN weight REAL');
    }
  }

  Future<void> close() async {
    final db = await database;
    db.close();
  }
}

このコードでは、新しくmembersテーブルを作成し、身長と体重の情報を追加しました。

メンバープロバイダーの設定

次に、メンバーのデータを管理するためのプロバイダークラスを作成します。member_provider.dartに以下のコードを追加します。

member_provider.dart

import 'package:sqflite/sqflite.dart';
import 'database_helper.dart';

class Member {
  final int? id;
  final String name;
  final int number;
  final String position;
  final double? height;
  final double? weight;
  final int teamId;
  final String registrationDate;
  final String lastEditedDate;
  final int displayOrder;

  Member({
    this.id,
    required this.name,
    required this.number,
    required this.position,
    this.height,
    this.weight,
    required this.teamId,
    required this.registrationDate,
    required this.lastEditedDate,
    required this.displayOrder,
  });

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'number': number,
      'position': position,
      'height': height,
      'weight': weight,
      'teamId': teamId,
      'registrationDate': registrationDate,
      'lastEditedDate': lastEditedDate,
      'displayOrder': displayOrder,
    };
  }
}

class MemberProvider {
  final DatabaseHelper _dbHelper = DatabaseHelper.instance;

  Future<void> insertMember(Member member) async {
    final db = await _dbHelper.database;
    await db.insert(
      'members',
      member.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<Member>> getMembersByTeamId(int teamId) async {
    final db = await _dbHelper.database;
    final List<Map<String, dynamic>> maps = await db.query(
      'members',
      where: 'teamId = ?',
      whereArgs: [teamId],
      orderBy: 'displayOrder',
    );

    return List.generate(maps.length, (i) {
      return Member(
        id: maps[i]['id'],
        name: maps[i]['name'],
        number: maps[i]['number'],
        position: maps[i]['position'],
        height: maps[i]['height'],
        weight: maps[i]['weight'],
        teamId: maps[i]['teamId'],
        registrationDate: maps[i]['registrationDate'],
        lastEditedDate: maps[i]['lastEditedDate'],
        displayOrder: maps[i]['displayOrder'],
      );
    });
  }

  Future<void> updateMember(Member member) async {
    final db = await _dbHelper.database;
    await db.update(
      'members',
      member.toMap(),
      where: 'id = ?',
      whereArgs: [member.id],
    );
  }

  Future<void> deleteMember(int id) async {
    final db = await _dbHelper.database;
    await db.delete(
      'members',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
}

メンバー登録画面の実装

メンバーを登録するためのUIを実装します。ここでは、メンバーを登録、編集、削除するためのダイアログを用意しました。

member_registration_screen.dart

import 'package:flutter/material.dart';
import 'member_provider.dart';
import 'team_provider.dart';

class MemberRegistrationScreen extends StatefulWidget {
  final Team team;

  MemberRegistrationScreen({required this.team});

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

class _MemberRegistrationScreenState extends State<MemberRegistrationScreen> {
  final MemberProvider _memberProvider = MemberProvider();
  List<Member> _members = [];

  @override
  void initState() {
    super.initState();
    _loadMembers();
  }

  Future<void> _loadMembers() async {
    List<Member> members =
        await _memberProvider.getMembersByTeamId(widget.team.id!);
    setState(() {
      _members = members;
    });
  }

  Future<void> _addMember() async {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        TextEditingController numberController = TextEditingController();
        TextEditingController nameController = TextEditingController();
        TextEditingController positionController = TextEditingController();
        TextEditingController heightController = TextEditingController();
        TextEditingController weightController = TextEditingController();

        return AlertDialog(
          title: Text("メンバーを追加"),
          content: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                TextField(
                  controller: numberController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '番号'),
                ),
                TextField(
                  controller: nameController,
                  decoration: InputDecoration(labelText: '名前'),
                ),
                TextField(
                  controller: positionController,
                  decoration: InputDecoration(labelText: 'ポジション'),
                ),
                TextField(
                  controller: heightController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '身長 (cm)'),
                ),
                TextField(
                  controller: weightController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '体重 (kg)'),
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('キャンセル'),
            ),
            TextButton(
              onPressed: () async {
                if (nameController.text

.isNotEmpty &&
                    positionController.text.isNotEmpty &&
                    numberController.text.isNotEmpty) {
                  Member newMember = Member(
                    name: nameController.text,
                    number: int.parse(numberController.text),
                    position: positionController.text,
                    height: double.tryParse(heightController.text),
                    weight: double.tryParse(weightController.text),
                    teamId: widget.team.id!,
                    registrationDate: DateTime.now().toString(),
                    lastEditedDate: DateTime.now().toString(),
                    displayOrder: _members.length + 1,
                  );
                  await _memberProvider.insertMember(newMember);
                  _loadMembers();
                  Navigator.of(context).pop();
                }
              },
              child: Text('追加'),
            ),
          ],
        );
      },
    );
  }

  void _showEditMemberDialog(Member member) {
    TextEditingController numberController =
        TextEditingController(text: member.number.toString());
    TextEditingController nameController =
        TextEditingController(text: member.name);
    TextEditingController positionController =
        TextEditingController(text: member.position);
    TextEditingController heightController =
        TextEditingController(text: member.height?.toString() ?? '');
    TextEditingController weightController =
        TextEditingController(text: member.weight?.toString() ?? '');

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("メンバー情報を編集"),
          content: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                TextField(
                  controller: numberController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '番号'),
                ),
                TextField(
                  controller: nameController,
                  decoration: InputDecoration(labelText: '名前'),
                ),
                TextField(
                  controller: positionController,
                  decoration: InputDecoration(labelText: 'ポジション'),
                ),
                TextField(
                  controller: heightController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '身長 (cm)'),
                ),
                TextField(
                  controller: weightController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(labelText: '体重 (kg)'),
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('キャンセル'),
            ),
            TextButton(
              onPressed: () async {
                Member updatedMember = Member(
                  id: member.id,
                  name: nameController.text,
                  number: int.parse(numberController.text),
                  position: positionController.text,
                  height: double.tryParse(heightController.text),
                  weight: double.tryParse(weightController.text),
                  teamId: member.teamId,
                  registrationDate: member.registrationDate,
                  lastEditedDate: DateTime.now().toString(),
                  displayOrder: member.displayOrder,
                );
                await _memberProvider.updateMember(updatedMember);
                _loadMembers();
                Navigator.of(context).pop();
              },
              child: Text('保存'),
            ),
          ],
        );
      },
    );
  }

  void _showDeleteConfirmationDialog(Member member) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("メンバー削除"),
          content: Text("本当にこのメンバーを削除しますか?"),
          actions: [
            TextButton(
              onPressed: () async {
                await _memberProvider.deleteMember(member.id!);
                _loadMembers();
                Navigator.of(context).pop();
              },
              child: Text("はい"),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text("いいえ"),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('${widget.team.name} メンバー登録'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: _addMember,
          ),
        ],
      ),
      body: ReorderableListView.builder(
        itemCount: _members.length,
        onReorder: (int oldIndex, int newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final Member member = _members.removeAt(oldIndex);
            _members.insert(newIndex, member);

            for (int i = 0; i < _members.length; i++) {
              _members[i] = _members[i].copyWith(displayOrder: i + 1);
              _memberProvider.updateMember(_members[i]);
            }
          });
        },
        itemBuilder: (context, index) {
          final member = _members[index];
          return ListTile(
            key: ValueKey(member.id),
            title: Text(member.name),
            subtitle: Text(
                '番号: ${member.number}, ポジション: ${member.position}'),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                  icon: Icon(Icons.edit),
                  onPressed: () => _showEditMemberDialog(member),
                ),
                IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => _showDeleteConfirmationDialog(member),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

イメージ

最後に

これで、Flutterアプリにメンバー登録機能が追加されました。今回の実装では、メンバーの基本情報を登録、編集、削除する機能を提供しました。これにより、ユーザーはチームに所属するメンバーの情報を簡単に管理できるようになります。

関連記事

コメント

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