2
1

More than 1 year has passed since last update.

Flutter×Firebaseでチャットアプリを作る

Last updated at Posted at 2023-01-07

なんとなくFlutterの勉強がてら作りました。
基本的に以下のサイト様を参考にさせていただいたので詳しく知りたい方は↓を見てください。

Firebaseの準備

以下のサイトの2章「Flutter × Firebase のセットアップ」を参考にFirebaseとFlutterをセットアップします。

さっそく実装

今回作成するのは以下の3つです。(ボトムメニューは別です。)

  • チャットルーム一覧
  • チャットルーム新規作成
  • チャットルーム本体

Simulator Screen Shot - iPhone SE (3rd generation) - 2023-01-07 at 21.09.10.png
Simulator Screen Shot - iPhone SE (3rd generation) - 2023-01-07 at 21.37.42.png
Simulator Screen Shot - iPhone SE (3rd generation) - 2023-01-07 at 21.09.16.png

チャットルーム一覧

chat_room.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_app/src/screens/home.dart';

import 'chat_room.dart';
import 'add_room_page.dart';

class RoomListPage extends StatelessWidget {
  const RoomListPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('一覧'),
      ),
      body: Center(
        child: StreamBuilder<QuerySnapshot>(
            stream: FirebaseFirestore.instance
              .collection('chat_room')
              .orderBy('createdAt')
              .snapshots(),
            builder: (context, snapshot) {
              if (!snapshot.hasData) {
                return Center(
                  child: Text('読込中……'),
                );
              }
              else if (snapshot.hasData) {
                final List<DocumentSnapshot> documents = snapshot.data!.docs;
                return ListView.builder(
                  itemCount: documents.length,
                  itemBuilder: (BuildContext context, int index){
                    return Card(
                      child: ListTile(
                        title: Text(documents[index]['name']),
                        trailing: IconButton(
                          icon: Icon(Icons.input),
                          onPressed: () async {
                            await Navigator.of(context).push(
                              MaterialPageRoute(builder: (context) {
                                return ChatRoom(documents[index]['name']);
                              },
                              ),
                            );
                          },
                        ),
                      ),
                    );
                  }
                );
              }
              else{
                return Center(
                  child: Text('なし'),
                );
              }
            },
          ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () async {
          await Navigator.of(context)
              .push(MaterialPageRoute(builder: (context) {
            return AddRoomPage();
          }));
        },
      ),
    );
  }
}

チャットルーム作成

add_room_page.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class AddRoomPage extends StatefulWidget {
  AddRoomPage();

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

class _AddPostPageState extends State<AddRoomPage> {
  String roomName = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ルーム作成'),
      ),
      body: Center(
        child: Container(
          padding: EdgeInsets.all(32),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextFormField(
                decoration: InputDecoration(labelText: 'チャットルーム名'),
                keyboardType: TextInputType.multiline,
                maxLines: 3,
                onChanged: (String value) {
                  setState(() {
                    roomName = value;
                  });
                },
              ),
              const SizedBox(
                height: 8,
              ),
              Container(
                width: double.infinity,
                child: ElevatedButton(
                  child: Text('新規作成'),
                  onPressed: () async {
                    final date = DateTime.now().toLocal().toIso8601String();

                    await FirebaseFirestore.instance
                        .collection('chat_room')
                        .doc(roomName)
                        .set({
                      'name': roomName,
                      'createdAt': date,
                    });
                    Navigator.of(context).pop();
                  },
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

チャットルーム本体

chat_room.dart
import 'dart:convert';
import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';

String randomString() {
  final random = Random.secure();
  final values = List<int>.generate(16, (i) => random.nextInt(255));
  return base64UrlEncode(values);
}

class ChatRoom extends StatefulWidget {
  const ChatRoom(this.name, {Key? key}) : super(key: key);

  final String name;
  @override
  ChatRoomState createState() => ChatRoomState();
}

class ChatRoomState extends State<ChatRoom> {
  List<types.Message> _messages = [];
  final _user = const types.User(id: '82091008-a484-4a89-ae75-a22bf8d6f3ac');

  void initState() {
    _getMessages();
    super.initState();
  }

  void _getMessages() async {
    final getData = await FirebaseFirestore.instance
        .collection('chat_room')
        .doc(widget.name)
        .collection('contents')
        .orderBy('createdAt', descending: true)
        .get();

    final message = getData.docs
        .map((d) =>
        types.TextMessage(
            author:
            types.User(id: d.data()['uid'], firstName: d.data()['name']),
            createdAt: d.data()['createdAt'],
            id: d.data()['id'],
            text: d.data()['text']))
        .toList();

    setState(() {
      _messages = [...message];
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: Text("チャットルーム"),
    ),
    body: Chat(
      user: _user,
      messages: _messages,
      onSendPressed: _handleSendPressed,
    ),
  );

  void _addMessage(types.TextMessage message) async {
    setState(() {
      _messages.insert(0, message);
    });

    await FirebaseFirestore.instance
        .collection('chat_room')
        .doc(widget.name)
        .collection('contents')
        .add({
      'uid': message.author.id,
      'name': message.author.firstName,
      'createdAt': message.createdAt,
      'id': message.id,
      'text': message.text,
    });
  }

  void _handleSendPressed(types.PartialText message) {
    final textMessage = types.TextMessage(
      author: _user,
      createdAt: DateTime.now().millisecondsSinceEpoch,
      id: randomString(),
      text: message.text,
    );
    _addMessage(textMessage);
  }
}

おわりに

一通り動いたらFirebaseのFireStore Databaseも確認してみてください。
中にはチャットの内容が保存されているはずです。

躓いたところ

firebase上のデータが何らかの不手際でおかしくなっており、そのせいで長い間エラーが解決しなかった。
コードは間違ってなそうなのに動かない時は、FirebaseのDB上のデータやセキュリティルールを確認してみると良いかも。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1