chatting-firebase

Date:     Updated:

카테고리:

태그:

아래는 플러터와 파이어베이스를 이용해 채팅방 기능, 로그인, 말풍선 UI 및 이미지/파일 전송 기능을 포함한 전체적인 코드입니다. 이 코드는 프로젝트 구조를 구성하고, 로그인 화면부터 채팅방까지의 흐름을 완성한 예시입니다.

pubspec.yaml 의존성 설정

먼저 필요한 패키지들을 pubspec.yaml 파일에 추가합니다.

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.10.0
  firebase_auth: ^4.4.0
  cloud_firestore: ^4.8.3
  firebase_storage: ^11.0.16
  image_picker: ^1.0.0
  file_picker: ^5.3.0

main.dart - Firebase 초기화 및 라우팅

main.dart 파일은 Firebase를 초기화하고 로그인 및 채팅 화면을 연결합니다.

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'chat_screen.dart';
import 'login_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chat App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: StreamBuilder(
        stream: FirebaseAuth.instance.authStateChanges(),
        builder: (ctx, snapshot) {
          if (snapshot.hasData) {
            return ChatScreen();
          }
          return LoginScreen();
        },
      ),
      routes: {
        '/chat': (context) => ChatScreen(),
      },
    );
  }
}

login_screen.dart - 로그인 화면

이 파일에서는 이메일/비밀번호로 Firebase 인증을 사용하여 로그인 기능을 구현합니다.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  Future<void> _login() async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: _emailController.text,
        password: _passwordController.text,
      );
      Navigator.pushReplacementNamed(context, '/chat');
    } catch (e) {
      print('Login Failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _login,
              child: Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}

chat_screen.dart - 채팅 화면

사용자가 로그인 후 진입하는 채팅 화면입니다. Firestore에서 채팅 메시지를 받아오고 말풍선 UI로 표시합니다.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
import 'dart:io';
import 'package:skeletons/skeletons.dart'; // Skeletons 패키지 임포트

import 'chat_bubble.dart';

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final _messageController = TextEditingController();

  Future<void> _sendMessage() async {
    final user = FirebaseAuth.instance.currentUser;
    if (_messageController.text.isNotEmpty) {
      await FirebaseFirestore.instance.collection('chat').add({
        'text': _messageController.text,
        'createdAt': Timestamp.now(),
        'userId': user?.uid,
        'username': user?.email,
      });
      _messageController.clear();
    }
  }

  Future<void> _sendImage() async {
    final picker = ImagePicker();
    final pickedImage = await picker.getImage(source: ImageSource.gallery);
    if (pickedImage == null) return;

    final ref = FirebaseStorage.instance
        .ref()
        .child('chat_images')
        .child('${DateTime.now().toIso8601String()}.jpg');

    await ref.putFile(File(pickedImage.path));
    final url = await ref.getDownloadURL();

    FirebaseFirestore.instance.collection('chat').add({
      'imageUrl': url,
      'createdAt': Timestamp.now(),
      'userId': FirebaseAuth.instance.currentUser?.uid,
      'username': FirebaseAuth.instance.currentUser?.email,
    });
  }

  Future<void> _sendFile() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles();
    if (result == null) return;

    final file = File(result.files.single.path!);
    final ref = FirebaseStorage.instance
        .ref()
        .child('chat_files')
        .child('${DateTime.now().toIso8601String()}_${result.files.single.name}');

    await ref.putFile(file);
    final fileUrl = await ref.getDownloadURL();

    FirebaseFirestore.instance.collection('chat').add({
      'fileUrl': fileUrl,
      'fileName': result.files.single.name,
      'createdAt': Timestamp.now(),
      'userId': FirebaseAuth.instance.currentUser?.uid,
      'username': FirebaseAuth.instance.currentUser?.email,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chat Room'),
        actions: [
          IconButton(
            icon: Icon(Icons.logout),
            onPressed: () {
              FirebaseAuth.instance.signOut();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: StreamBuilder(
              stream: FirebaseFirestore.instance
                  .collection('chat')
                  .orderBy('createdAt', descending: true)
                  .snapshots(),
              builder: (ctx, AsyncSnapshot<QuerySnapshot> chatSnapshot) {
                if (chatSnapshot.connectionState == ConnectionState.waiting) {
                  return SkeletonListView();
                }
                final chatDocs = chatSnapshot.data!.docs;
                return ListView.builder(
                  reverse: true,
                  itemCount: chatDocs.length,
                  itemBuilder: (ctx, index) => ChatBubble(
                    chatDocs[index]['text'] ?? '',
                    chatDocs[index]['userId'] == FirebaseAuth.instance.currentUser!.uid,
                  ),
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _messageController,
                    decoration: InputDecoration(labelText: 'Send a message...'),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.send),
                  onPressed: _sendMessage,
                ),
                IconButton(
                  icon: Icon(Icons.image),
                  onPressed: _sendImage,
                ),
                IconButton(
                  icon: Icon(Icons.attach_file),
                  onPressed: _sendFile,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

chat_bubble.dart - 말풍선 UI

채팅 메시지를 말풍선으로 표현하는 위젯입니다.

import 'package:flutter/material.dart';

class ChatBubble extends StatelessWidget {
  final String message;
  final bool isMe;

  ChatBubble(this.message, this.isMe);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
      children: [
        Container(
          decoration: BoxDecoration(
            color: isMe ? Colors.grey[300] : Theme.of(context).primaryColor,
            borderRadius: BorderRadius.circular(12),
          ),
          width: 140,
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
          margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
          child: Text(
            message,
            style: TextStyle(
              color: isMe ? Colors.black : Colors.white,
            ),
          ),
        ),
      ],
    );
  }
}

보안 규칙 설정

Firebase Firestore 및 Storage의 보안 규칙을 적절하게 설정하여 인증된 사용자만 데이터에 접근할 수 있게 해야 합니다.

결론

이 코드는 Firebase와 Flutter를 사용해 로그인, 채팅, 이미지 및 파일 전송이 가능한 간단한 채팅 앱을 구성한 것입니다. 필요한 경우 디자인이나 기능을 확장할 수 있습니다.

Project 카테고리 내 다른 글 보러가기

댓글 남기기