개발하는 두더지

Flutter - 채팅방 구현하기 본문

Flutter

Flutter - 채팅방 구현하기

덜지 2019. 4. 23. 00:29

코드를 먼저 살펴보면 아래와 같습니다.

하단에 대화를 입력할 수 있는 TextField와 send 버튼이 있고, send 버튼을 누르면 리스트에 텍스트 내용이 하나씩 저장되는 구조입니다.

 

 

하나의 채팅 메시지를 나타내는 위젯이 필요합니다. 아래와 같이 ChatMessage라는 StatelessWidget을 만들어줍니다.

메시지를 보낸 사용자를 나타내는 간단한 그래픽 아바타, 보낸 사람 이름이 들어있는 Column 위젯 및 메시지 텍스트를 표시하는 Row 위젯을 리턴합니다.

 

CircleAvatar 위젯을 커스텀하기 위해서 _name 변수 값의 첫 번째 문자를 텍스트 위젯에 전달하여 사용자의 첫 번째 이니셜로 레이블을 지정합니다. CrossAxisAlignment.start를 row 생성자의 crossAxisAlignment 인수로 사용하여 아바타와 메시지를 상위 위젯에 상대적으로 배치 합니다.

 

아바타의 경우, 부모는 기본 축이 수평인 row 위젯이므로 CrossAxisAlignment.start는 수직 축을 따라 가장 높은 위치를 제공합니다. 메시지의 경우 부모는 세로 축인 Column 위젯이므로 CrossAxisAlignment.start는 가로 축을 따라 가장 왼쪽에있는 텍스트를 정렬합니다.

 

아바타 옆에 두 개의 텍스트 위젯을 세로로 정렬하여 보낸 사람의 이름을 맨 위에 표시하고 텍스트의 텍스트를 아래에 표시합니다. 보낸 사람의 이름을 스타일링하고 메시지 텍스트보다 크게 만들려면 Theme.of를 사용하여 적절한 ThemeData 객체를 가져와야합니다.

 

textTheme 속성을 사용하면 부제목과 같은 텍스트의 Material design logical styles에 액세스 할 수 있으므로 글꼴 크기 및 기타 텍스트 속성을 하드 코딩하지 않아도됩니다.

 

다음 단계는 채팅 메시지 목록을 가져 와서 UI에 표시하는 것입니다. 사용자가 채팅 기록을 볼 수 있도록 목록을 스크롤 할 수 있어야 합니다. 또한 목록은 가장 최근 메시지가 맨 아래 행에 표시되도록 해야합니다.

 

우선 List 컬렉션을 이용하여 채팅 메시지를 저장할 자료구조를 선언합니다. 그리고 전송 버튼을 눌러 _handleSubmitted 가 호출될 때 ChatMessage 객체를 만들어 setState 메서드를 통해 _messages를 수정하고 위젯 트리가 변경되었음을 프레임 워크가 알 수 있도록 UI를 다시 작성해야합니다. setState 에서는 동기 작업 만 수행해야합니다. 그렇지 않으면 프레임 워크가 작업을 완료하기 전에 위젯을 다시 그릴 가능성이 있습니다.

import 'package:flutter/material.dart';

void main() => runApp(FriendlychatApp());

const String _name = "Your Name";

class FriendlychatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "FriendlychatApp",
      home: ChatScreen(),
    );
  }
}

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

class ChatScreenState extends State<ChatScreen> {

  final List<ChatMessage> _messages = <ChatMessage>[];
  final TextEditingController _textController = new TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Friendlychat"),
      ),
      body: Column(
        children: <Widget>[
          Flexible(
            child: ListView.builder(
              padding: EdgeInsets.all(8.0),
              reverse: true,
              itemBuilder: (_, int index) => _messages[index],
              itemCount: _messages.length,
            ),
          ),
          Divider(height: 1.0),
          Container(
            decoration: BoxDecoration(
              color: Theme.of(context).cardColor
            ),
            child: _buildTextComposer(),
          )
        ],
      ),
    );
  }

  Widget _buildTextComposer() {
    return IconTheme(
      data: IconThemeData(color: Theme.of(context).accentColor),
      child: Container(
          margin: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Row(
            children: <Widget>[
              Flexible(
                child: TextField(
                  controller: _textController,
                  onSubmitted: _handleSubmitted,
                  decoration: new InputDecoration.collapsed(
                      hintText: "Send a message"),
                ),
              ),
              Container(
                margin: const EdgeInsets.symmetric(horizontal: 4.0),
                child: IconButton(
                    icon: Icon(Icons.send),
                    onPressed: () => _handleSubmitted(_textController.text)),
              ),
            ],
          )
      ),
    );
  }

  void _handleSubmitted(String text) {
    _textController.clear();
    var message = ChatMessage(
      text: text,
    );
    setState(() {
      _messages.insert(0, message);
    });
  }

}

class ChatMessage extends StatelessWidget {
  ChatMessage({this.text});
  final String text;
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
            margin: const EdgeInsets.only(right: 16.0),
            child: CircleAvatar(child: Text(_name[0])),
          ),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(_name, style: Theme.of(context).textTheme.subhead),
              Container(
                margin: const EdgeInsets.only(top: 5.0),
                child: Text(text),
              ),
            ],
          ),
        ],
      ),
    );
  }

}
Comments