[Flutter 스터디] 12. 채팅

강 27. 채팅 메시지를 보냅니다.

1. new_message.dart 및 message.dart 파일을 생성하여 채팅 메시지 화면을 구성하고 message.dart에 stateless 메시지 위젯을 생성합니다.

2. Firestore 데이터베이스에 채팅 모음을 추가하고 테스트 데이터를 추가합니다.

3. chat_screen.dart 스캐폴드 본문을 삭제한 후 새로 생성된 메시지 위젯으로 이동합니다.

body: Container(
  child: Column(
    children: (
      Expanded(child: Messages()) // 리스트뷰가 무조건 화면 내의 모든 공간을 확보해버리기 때문에 expanded 로 감싸주었따.
    ),
  ),
),

메시지 다트

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

class Messages extends StatelessWidget {
  const Messages({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: FirebaseFirestore.instance.collection('chat').snapshots(),
        builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot){
          if(!snapshot.hasData){
            return Text('no data');
          }
          if(snapshot.connectionState==ConnectionState.waiting){
            return Center(
              child: CircularProgressIndicator(),
            );
          }
          final chatDocs = snapshot.data!.docs;
          return ListView.builder(
              itemCount: chatDocs.length,
              itemBuilder: (context, index){
            return Text(chatDocs(index)('text'));
          });
        }

    );
  }
}


이제 new_message.dart에서 새 메시지를 입력하는 부분을 구현합니다.

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

class NewMessage extends StatefulWidget {
  const NewMessage({Key? key}) : super(key: key);

  @override
  State<NewMessage> createState() => _NewMessageState();
}

class _NewMessageState extends State<NewMessage> {
  var _userEnterMessage="";

  void _sendMessage(){
    FocusScope.of(context).unfocus();
    FirebaseFirestore.instance.collection('chat').add({
      'text' : _userEnterMessage,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top:8),
      padding: EdgeInsets.all(8.0),
      child: Row(
        children: (
          Expanded(
            child: TextField(
              decoration: InputDecoration(
                labelText: 'Send a message',
              ),
              onChanged: (value){
                setState(() {
                  _userEnterMessage = value;
                  //모든 키 입력에서 setState 불러옴.

                });
              },
            ),
          ),
          IconButton(
            onPressed: _userEnterMessage.trim().isEmpty?null:_sendMessage

          , icon: Icon(Icons.send), color: Colors.blue,)
        ),
      ),
    );
  }
}


채팅 앱처럼 최근에 보낸 메시지를 아래에 보관하세요.

message.dart의 ListView.builder에서

reverse: true,
void _sendMessage(){
  FocusScope.of(context).unfocus();
  FirebaseFirestore.instance.collection('chat').add({
    'text' : _userEnterMessage,
    'time' : Timestamp.now()
  });

시간 키를 추가합니다.

message.dart의 스냅샷 부분을 다음과 같이 수정합니다.

    stream: FirebaseFirestore.instance.collection('chat').orderBy('time', descending: true).snapshots()


메시지 전송 후 TextField 삭제: 컨트롤러 추가

final _controller = TextEditingController();

마지막 변수를 추가한 후,

TextField(
  controller: _controller,

sendMessage 메서드에서 TextFiled에 바인딩한 후

_controller.clear();

16강 채팅(메시지) 말풍선 만들기 및 메시지 넣기.

부울 isMe를 추가합니다.

userID를 추가하여 접근자의 uid와 메시지의 userID가 같으면 오른쪽에, 그렇지 않으면 왼쪽에 파란색으로 배치됩니다.

new_message.dart

void _sendMessage(){
  FocusScope.of(context).unfocus();
  final user = FirebaseAuth.instance.currentUser;
  FirebaseFirestore.instance.collection('chat').add({
    'text' : _userEnterMessage,
    'time' : Timestamp.now(),
    'userID' : user!.uid,
  });
  _controller.clear();
}

chat_bubble.dart

더보기

import 'package:flutter/material.dart';

class ChatBubble extends StatelessWidget {
  const ChatBubble(this.message, this.isMe, {Key? key}) : super(key: key);
  final String message;
  final bool isMe;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: isMe?MainAxisAlignment.end:MainAxisAlignment.start,
      children: (
        Container(
          decoration: BoxDecoration(
            color: isMe? Colors.grey(300):Colors.blue,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(12),
              topRight: Radius.circular(12),
              bottomRight: isMe?Radius.circular(0):Radius.circular(12),
              bottomLeft: isMe?Radius.circular(12,):Radius.circular(0)
            ),

          ),
          width: 145,
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
          margin: EdgeInsets.symmetric(vertical:4, horizontal: 8),
          child: Text(message,
            style: TextStyle(
              color: Colors.white
            ),
          ),
        ),
      ),
    );
  }
}

메시지 다트

return ListView.builder(
    reverse: true,
    itemCount: chatDocs.length,
    itemBuilder: (context, index){
  return ChatBubble(chatDocs(index)('text'),
      chatDocs(index)('userID').toString()==user!.uid);
});


29 강. 채팅 메시지에 사용자 이름과 아바타를 표시합니다.

chat_bubble 패키지 설치

flatter_chat_bubble | Flutter 패키지(pub.dev)

chat_bubble.dart로 가져오기

import 'package:flutter_chat_bubble/bubble_type.dart';
import 'package:flutter_chat_bubble/chat_bubble.dart';
import 'package:flutter_chat_bubble/clippers/chat_bubble_clipper_8.dart';

메시지 위에 userName을 추가합니다.

메시지 다트

더보기

import 'package:chatting_app/chat_bubble.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class Messages extends StatelessWidget {
  const Messages({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final user = FirebaseAuth.instance.currentUser;
    return StreamBuilder(
        stream: FirebaseFirestore.instance.collection('chat').orderBy('time', descending: true).snapshots(),
        builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot){
          if(!snapshot.hasData){
            return Text('no data');
          }
          if(snapshot.connectionState==ConnectionState.waiting){
            return Center(
              child: CircularProgressIndicator(),
            );
          }
          final chatDocs = snapshot.data!.docs;
          return ListView.builder(
              reverse: true,
              itemCount: chatDocs.length,
              itemBuilder: (context, index){
            return ChatBubbles(chatDocs(index)('text'),
                chatDocs(index)('userID').toString()==user!.uid,
                chatDocs(index)('userName')
            );
          });
        }

    );
  }
}

chat_bubble.dart

더보기

import 'package:flutter/material.dart';
import 'package:flutter_chat_bubble/bubble_type.dart';
import 'package:flutter_chat_bubble/chat_bubble.dart';
import 'package:flutter_chat_bubble/clippers/chat_bubble_clipper_8.dart';
class ChatBubbles extends StatelessWidget {
  const ChatBubbles(this.message,this.isMe,  this.userName, {Key? key}) : super(key: key);
  final String message;
  final bool isMe;
  final String userName;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: isMe?MainAxisAlignment.end:MainAxisAlignment.start,
      children: (
        if(isMe)
        Padding(
          padding: EdgeInsets.fromLTRB(0, 0, 5, 0),
          child: ChatBubble(
          clipper: ChatBubbleClipper8(type: BubbleType.sendBubble),
      alignment: Alignment.topRight,
      margin: EdgeInsets.only(top: 20),
      backGroundColor: Colors.blue,
      child: Container(
          constraints: BoxConstraints(
            maxWidth: MediaQuery.of(context).size.width * 0.7,
    ),
      child: Column(
        crossAxisAlignment: isMe?CrossAxisAlignment.end:CrossAxisAlignment.start,
        children: (
          Text(userName,
            style: TextStyle(
              color: Colors.white,
            fontWeight: FontWeight.bold
          ),
          ),
          Text(message,
          style: TextStyle(color: Colors.white),
    ),
        ),
      ),
    ),
    ),
        )
        else if(!isMe)
          Padding(
            padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
            child: ChatBubble(
              clipper: ChatBubbleClipper1(type: BubbleType.receiverBubble),
              backGroundColor: Color(0xffE7E7ED),
              margin: EdgeInsets.only(top: 20),
              child: Container(
                constraints: BoxConstraints(
                  maxWidth: MediaQuery.of(context).size.width * 0.7,
                ),
                child: Column(
                  crossAxisAlignment: isMe?CrossAxisAlignment.end:CrossAxisAlignment.start,
                  children: (
                    Text(userName, style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.black
                    ),),
                    Text(
                      message,
                      style: TextStyle(color: Colors.black),
                    ),
                  ),
                ),
              ),
            ),
          )



      ),
    );
  }
}


이제 회원가입 시 프로필 사진을 등록하는 기능을 구현하였습니다.

Main_screen.dart에 Icons.image를 추가합니다.

add_image.dart

더보기

import 'package:flutter/material.dart';

class AddImage extends StatefulWidget {
  const AddImage({Key? key}) : super(key: key);

  @override
  State<AddImage> createState() => _AddImageState();
}

class _AddImageState extends State<AddImage> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top:30),
      width: 150,
      height: 300,
      child: Column(
        children: (
          CircleAvatar(
            radius: 40,
            backgroundColor: Colors.blue,
          ),
          SizedBox(height: 10,),
          OutlinedButton.icon(
            onPressed: (){},
            icon:Icon(Icons.image),
            label: Text('Add icon'),),
          SizedBox(height: 80,),
          TextButton.icon(
              onPressed: (){
                Navigator.pop(context);
              },
              icon:Icon(Icons.close),
              label:Text('close'))
        ),

      ),
    );
  }
}

메인 화면 다트

더보기

import 'package:chatting_app/add_image.dart';
import 'package:chatting_app/palette.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
import 'chat_screen.dart';

class LoginSignupScreen extends StatefulWidget {
  const LoginSignupScreen({Key? key}) : super(key: key);

  @override
  State<LoginSignupScreen> createState() => _LoginSignupScreen();
}

class _LoginSignupScreen extends State<LoginSignupScreen> {
  final _authentication = FirebaseAuth.instance;

  bool isSignupScreen = true;
  bool showSpinner = false;
  final _formKey = GlobalKey<FormState>();
  String userName="";
  String userEmail="";
  String userPassword = '';

  void showAlert(BuildContext context){
    showDialog(context: context, builder: (context){
      return Dialog(
        backgroundColor: Colors.white,
        child: AddImage(),
      );
    });
  }

  void _tryValidation() {
    final isValid = _formKey.currentState!.validate(); // form의 유효성 체크
    if (isValid) {
      _formKey.currentState!.save();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Palette.backgroundColor,
      body: ModalProgressHUD(
        inAsyncCall: showSpinner,
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).unfocus();
          },
          child: Stack(
            children: (
              Positioned(
                  top: 0,
                  right: 0,
                  left: 0,
                  child: Container(
                    height: 420,
                    decoration: BoxDecoration(
                        image: DecorationImage(
                            image: AssetImage('image/t2.png'),
                            opacity: 0.5,
                            fit: BoxFit.fill)),
                    child: Container(
                      padding: const EdgeInsets.only(
                        top: 50,
                        left: 20,
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: (
                          RichText(
                            text: TextSpan(
                                text: 'Welcome',
                                style: TextStyle(
                                    letterSpacing: 1.0,
                                    fontSize: 25,
                                    color: Colors.black),
                                children: (
                                  TextSpan(
                                      text: isSignupScreen
                                          ? ' to Timothee chat!'
                                          : ' back',
                                      style: TextStyle(
                                          letterSpacing: 1.0,
                                          fontSize: 25,
                                          color: Colors.black,
                                          fontWeight: FontWeight.bold))
                                )),
                          ),
                          SizedBox(
                            height: 15,
                          ),
                          Text(
                            isSignupScreen
                                ? 'Sign up to continue'
                                : 'Signin to continue',
                            style: TextStyle(
                              fontSize: 10,
                              letterSpacing: 1.0,
                              color: Colors.black,
                            ),
                          ),
                        ),
                      ),
                    ),
                  )),
              // 배경

              AnimatedPositioned(
                duration: Duration(milliseconds: 500),
                curve: Curves.easeIn,
                top: 220,
                left: 20,
                child: AnimatedContainer(
                  duration: Duration(milliseconds: 500),
                  curve: Curves.easeIn,
                  padding: EdgeInsets.all(20),
                  height: isSignupScreen ? 310 : 250,
                  width: MediaQuery.of(context).size.width - 40,
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(15.0),
                      boxShadow: (
                        BoxShadow(
                            color: Colors.black.withOpacity(0.3),
                            blurRadius: 15,
                            spreadRadius: 5)
                      )),
                  child: SingleChildScrollView(
                    padding: EdgeInsets.only(bottom: 20),
                    child: Column(
                      children: (
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceAround,
                          children: (
                            GestureDetector(
                              onTap: () {
                                setState(() {
                                  isSignupScreen = false;
                                });
                              },
                              child: Column(
                                children: (
                                  Text(
                                    'LOGIN',
                                    style: TextStyle(
                                        fontSize: 10,
                                        color: !isSignupScreen
                                            ? Palette.activeColor
                                            : Palette.textColor1,
                                        fontWeight: FontWeight.bold),
                                  ),
                                  if (!isSignupScreen)
                                    Container(
                                      height: 2,
                                      width: 55,
                                      color: Colors.black87,
                                    )
                                ),
                              ),
                            ),
                            GestureDetector(
                              onTap: () {
                                setState(() {
                                  isSignupScreen = true;
                                });
                              },
                              child: Column(
                                children: (
                                  Row(
                                    children: (
                                      Text(
                                        'SIGNUP',
                                        style: TextStyle(
                                            fontSize: 10,
                                            color: isSignupScreen
                                                ? Palette.activeColor
                                                : Palette.textColor1,
                                            fontWeight: FontWeight.bold),
                                      ),
                                      SizedBox(width: 15,),
                                      GestureDetector(
                                        onTap: (){
                                          showAlert(context);
                                        },
                                        child: Icon(Icons.image,
                                          color: isSignupScreen? Colors.cyan:Colors.grey(300) ,),
                                      )
                                    ),
                                  ),
                                  if (isSignupScreen)
                                    Container(
                                      margin: EdgeInsets.fromLTRB(0, 3, 35, 0),
                                      height: 2,
                                      width: 55,
                                      color: Colors.black87,
                                    )
                                ),
                              ),
                            )
                          ),
                        ),
                        if (isSignupScreen)
                          Container(
                            margin: EdgeInsets.only(top: 20),
                            child: Form(
                              key: _formKey,
                              child: Column(
                                children: (
                                  TextFormField(
                                    key: ValueKey(1),
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 4) {
                                        return 'Please enter at least 4 charactors';
                                      }

                                      return null;
                                    },
                                    onSaved: (value) {
                                      userName = value!;
                                    },
                                    onChanged: (value) {
                                      userName = value;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.account_circle,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'User name',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(2),
                                    keyboardType: TextInputType.emailAddress,
                                    validator: (value) {
                                      if (value!.isEmpty ||
                                          !value!.contains('@')) {
                                        return 'Please enter a valid email address.';
                                      }
                                      return null;
                                    },
                                    onSaved: (value) {
                                      userEmail = value!;
                                    },
                                    onChanged: (value) {
                                      userEmail = value;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.email,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'email',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(3),
                                    obscureText: true,
                                    onSaved: (value) {
                                      userPassword = value!;
                                    },
                                    onChanged: (value) {
                                      userPassword = value;
                                    },
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 6) {
                                        return 'Password must be at least 7 characters long.';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.lock,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'password',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  )
                                ),
                              ),
                            ),
                          ),
                        if (!isSignupScreen)
                          Container(
                            margin: EdgeInsets.only(top: 20),
                            child: Form(
                              key: _formKey,
                              child: Column(
                                children: (
                                  TextFormField(
                                    key: ValueKey(4),
                                    onSaved: (value) {
                                      userEmail = value!;
                                    },
                                    onChanged: (value) {
                                      userEmail = value;
                                    },
                                    keyboardType: TextInputType.emailAddress,
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 4) {
                                        return 'Please enter a least 4 character';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.account_circle,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'User Email',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(5),
                                    obscureText: true,
                                    onSaved: (value) {
                                      userPassword = value!;
                                    },
                                    onChanged: (value) {
                                      userPassword = value;
                                    },
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 6) {
                                        return 'Password must be at least 7 characters';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.lock,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'password',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  )
                                ),
                              ),
                            ),
                          )
                      ),
                    ),
                  ),
                ),
              ),
              // 텍스트 폼 필드
              AnimatedPositioned(
                duration: Duration(milliseconds: 500),
                curve: Curves.easeIn,
                top: isSignupScreen ? 500 : 440,
                left: 0,
                right: 0,
                child: Center(
                  child: Container(
                    padding: EdgeInsets.all(6),
                    height: 65,
                    width: 65,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(80)),
                    child: GestureDetector(
                      onTap: () async {
                        setState(() {
                          showSpinner=true;
                        });
                        if (isSignupScreen) {
                          _tryValidation();
                          try {
                            final newUser = await _authentication
                                .createUserWithEmailAndPassword(
                                    email: userEmail, password: userPassword);
                            print(newUser.user);
                            print(newUser.user!.uid);
                            print(userName);
                            await FirebaseFirestore.instance.collection('user').doc(newUser.user!.uid).set(
                                {'userName':userName,
                                'email':userEmail});

                            if (newUser.user != null) {
                              //등록 성공
                              Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => ChatScreen()));
                              setState(() {
                                showSpinner=false;
                              });
                            }
                          } catch (e) {
                            print(e);
                            setState(() {
                              showSpinner=false;
                            });
                            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                              content:
                                  Text('Please check your email and password'),
                              backgroundColor: Colors.blue,
                            ));
                          }
                        }
                        if (!isSignupScreen) {
                          try {
                            _tryValidation();
                            final newUser =
                                await _authentication.signInWithEmailAndPassword(
                                    email: userEmail, password: userPassword);
                            if (newUser.user != null) {
                              // 등록 성공
                              Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => ChatScreen())
                              );
                            }
                          } catch (e) {
                            print(e);
                          }
                        }
                      },
                      child: Container(
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                              colors: (Colors.grey, Colors.blue),
                              begin: Alignment.topLeft,
                              end: Alignment.bottomRight),
                          borderRadius: BorderRadius.circular(30),
                          boxShadow: (
                            BoxShadow(
                              color: Colors.black.withOpacity(0.3),
                              spreadRadius: 1,
                              blurRadius: 1,
                              offset: Offset(1, 0),
                            )
                          ),
                        ),
                        child: Icon(
                          Icons.arrow_forward,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
              // 전송버튼
              AnimatedPositioned(
                  duration: Duration(milliseconds: 500),
                  curve: Curves.easeIn,
                  top: isSignupScreen
                      ? MediaQuery.of(context).size.height - 125
                      : MediaQuery.of(context).size.height - 150,
                  right: 0,
                  left: 0,
                  child: Column(
                    children: (
                      Text(
                        isSignupScreen ? 'or Signup with' : 'or Signin with',
                        style: TextStyle(
                          fontSize: 10,
                        ),
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      TextButton.icon(
                          style: TextButton.styleFrom(
                            primary: Colors.white,
                            minimumSize: Size(155, 40),
                            shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(20)),
                            backgroundColor: Colors.blueAccent,
                          ),
                          onPressed: () {},
                          icon: Icon(Icons.add),
                          label: Text('Google'))
                    ),
                  ))
              //구글
            ),
          ),
        ),
      ),
    );
  }
}

30 강. 채팅 풍선에 아바타를 표시합니다.

image_picker 패키지 설치

이미지 선택기 | Flutter 패키지(pub.dev)

이미지 선택기 | 플러터 팩

Android 및 iOS 이미지 라이브러리에서 이미지를 선택하고 카메라로 새 이미지를 캡처하는 Flutter 플러그인.

pub.dev

데이터베이스에 찍은 사진을 업로드하려면 firebase_storage를 설치하세요.

firebase_storage | Flutter 패키지(pub.dev)

firebase_storage | 플러터 팩

Firebase Cloud Storage용 Flutter 플러그인은 Android 및 iOS용 강력하고 단순하며 경제적인 개체 스토리지 서비스입니다.

pub.dev

Firebase 저장소 시작


규칙 수정

rules_version = ‘2’;
서비스 파이어베이스. 저장 {
일치 /b/{버킷}/o {
일치 /{allPaths=**} {
읽기 허용, 생성: if request.auth != null;
}
}
}

이미지 추가로 이미지 추가 후 로그인을 하시면,


파일이 저장소에 업로드되었는지 확인할 수 있습니다.

이제 이 이미지를 채팅 화면에 출력하세요.

강의만 따라가는게 너무 벅차서 설명을 쓸 수가 없고,

전체 코드를 작성하면서 마무리하고 다음에 리뷰를 해야 할 것 같습니다.

메인 다트

더보기

import 'package:flutter/material.dart';
import 'main_screen.dart';
import 'package:firebase_core/firebase_core.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chatting app',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginSignupScreen()
    );
  }
}

메인 화면 다트

더보기

import 'dart:io';

import 'package:chatting_app/add_image.dart';
import 'package:chatting_app/palette.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
import 'chat_screen.dart';
import 'package:firebase_storage/firebase_storage.dart';

class LoginSignupScreen extends StatefulWidget {
  const LoginSignupScreen({Key? key}) : super(key: key);

  @override
  State<LoginSignupScreen> createState() => _LoginSignupScreen();
}

class _LoginSignupScreen extends State<LoginSignupScreen> {
  final _authentication = FirebaseAuth.instance;

  bool isSignupScreen = true;
  bool showSpinner = false;
  final _formKey = GlobalKey<FormState>();
  String userName="";
  String userEmail="";
  String userPassword = '';
  File? userPickedImage;

  void pickedImage(File image){
    userPickedImage = image;
  }

  void showAlert(BuildContext context){
    showDialog(context: context, builder: (context){
      return Dialog(
        backgroundColor: Colors.white,
        child: AddImage(pickedImage),
        // 왜 메소드 뒤에 괄호를 넣지 않는지? ->
        // 메소드를 실행시키려는게 아니라, 포인터만을 전달.
      );
    });
  }

  void _tryValidation() {
    final isValid = _formKey.currentState!.validate(); // form의 유효성 체크
    if (isValid) {
      _formKey.currentState!.save();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Palette.backgroundColor,
      body: ModalProgressHUD(
        inAsyncCall: showSpinner,
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).unfocus();
          },
          child: Stack(
            children: (
              Positioned(
                  top: 0,
                  right: 0,
                  left: 0,
                  child: Container(
                    height: 420,
                    decoration: BoxDecoration(
                        image: DecorationImage(
                            image: AssetImage('image/t2.png'),
                            opacity: 0.5,
                            fit: BoxFit.fill)),
                    child: Container(
                      padding: const EdgeInsets.only(
                        top: 50,
                        left: 20,
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: (
                          RichText(
                            text: TextSpan(
                                text: 'Welcome',
                                style: TextStyle(
                                    letterSpacing: 1.0,
                                    fontSize: 25,
                                    color: Colors.black),
                                children: (
                                  TextSpan(
                                      text: isSignupScreen
                                          ? ' to Timothee chat!'
                                          : ' back',
                                      style: TextStyle(
                                          letterSpacing: 1.0,
                                          fontSize: 25,
                                          color: Colors.black,
                                          fontWeight: FontWeight.bold))
                                )),
                          ),
                          SizedBox(
                            height: 15,
                          ),
                          Text(
                            isSignupScreen
                                ? 'Sign up to continue'
                                : 'Signin to continue',
                            style: TextStyle(
                              fontSize: 10,
                              letterSpacing: 1.0,
                              color: Colors.black,
                            ),
                          ),
                        ),
                      ),
                    ),
                  )),
              // 배경

              AnimatedPositioned(
                duration: Duration(milliseconds: 500),
                curve: Curves.easeIn,
                top: 220,
                left: 20,
                child: AnimatedContainer(
                  duration: Duration(milliseconds: 500),
                  curve: Curves.easeIn,
                  padding: EdgeInsets.all(20),
                  height: isSignupScreen ? 310 : 250,
                  width: MediaQuery.of(context).size.width - 40,
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(15.0),
                      boxShadow: (
                        BoxShadow(
                            color: Colors.black.withOpacity(0.3),
                            blurRadius: 15,
                            spreadRadius: 5)
                      )),
                  child: SingleChildScrollView(
                    padding: EdgeInsets.only(bottom: 20),
                    child: Column(
                      children: (
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceAround,
                          children: (
                            GestureDetector(
                              onTap: () {
                                setState(() {
                                  isSignupScreen = false;
                                });
                              },
                              child: Column(
                                children: (
                                  Text(
                                    'LOGIN',
                                    style: TextStyle(
                                        fontSize: 10,
                                        color: !isSignupScreen
                                            ? Palette.activeColor
                                            : Palette.textColor1,
                                        fontWeight: FontWeight.bold),
                                  ),
                                  if (!isSignupScreen)
                                    Container(
                                      height: 2,
                                      width: 55,
                                      color: Colors.black87,
                                    )
                                ),
                              ),
                            ),
                            GestureDetector(
                              onTap: () {
                                setState(() {
                                  isSignupScreen = true;
                                });
                              },
                              child: Column(
                                children: (
                                  Row(
                                    children: (
                                      Text(
                                        'SIGNUP',
                                        style: TextStyle(
                                            fontSize: 10,
                                            color: isSignupScreen
                                                ? Palette.activeColor
                                                : Palette.textColor1,
                                            fontWeight: FontWeight.bold),
                                      ),
                                      SizedBox(width: 15,),
                                      if(isSignupScreen)
                                      GestureDetector(
                                        onTap: (){
                                          showAlert(context);
                                        },
                                        child: Icon(Icons.image,
                                          color: isSignupScreen? Colors.cyan:Colors.grey(300) ,),
                                      )
                                    ),
                                  ),
                                  if (isSignupScreen)
                                    Container(
                                      margin: EdgeInsets.fromLTRB(0, 3, 35, 0),
                                      height: 2,
                                      width: 55,
                                      color: Colors.black87,
                                    )
                                ),
                              ),
                            )
                          ),
                        ),
                        if (isSignupScreen)
                          Container(
                            margin: EdgeInsets.only(top: 20),
                            child: Form(
                              key: _formKey,
                              child: Column(
                                children: (
                                  TextFormField(
                                    key: ValueKey(1),
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 4) {
                                        return 'Please enter at least 4 charactors';
                                      }

                                      return null;
                                    },
                                    onSaved: (value) {
                                      userName = value!;
                                    },
                                    onChanged: (value) {
                                      userName = value;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.account_circle,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'User name',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(2),
                                    keyboardType: TextInputType.emailAddress,
                                    validator: (value) {
                                      if (value!.isEmpty ||
                                          !value!.contains('@')) {
                                        return 'Please enter a valid email address.';
                                      }
                                      return null;
                                    },
                                    onSaved: (value) {
                                      userEmail = value!;
                                    },
                                    onChanged: (value) {
                                      userEmail = value;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.email,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'email',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(3),
                                    obscureText: true,
                                    onSaved: (value) {
                                      userPassword = value!;
                                    },
                                    onChanged: (value) {
                                      userPassword = value;
                                    },
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 6) {
                                        return 'Password must be at least 7 characters long.';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.lock,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'password',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  )
                                ),
                              ),
                            ),
                          ),
                        if (!isSignupScreen)
                          Container(
                            margin: EdgeInsets.only(top: 20),
                            child: Form(
                              key: _formKey,
                              child: Column(
                                children: (
                                  TextFormField(
                                    key: ValueKey(4),
                                    onSaved: (value) {
                                      userEmail = value!;
                                    },
                                    onChanged: (value) {
                                      userEmail = value;
                                    },
                                    keyboardType: TextInputType.emailAddress,
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 4) {
                                        return 'Please enter a least 4 character';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.account_circle,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'User Email',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  ),
                                  SizedBox(
                                    height: 8,
                                  ),
                                  TextFormField(
                                    key: ValueKey(5),
                                    obscureText: true,
                                    onSaved: (value) {
                                      userPassword = value!;
                                    },
                                    onChanged: (value) {
                                      userPassword = value;
                                    },
                                    validator: (value) {
                                      if (value!.isEmpty || value!.length < 6) {
                                        return 'Password must be at least 7 characters';
                                      }
                                      return null;
                                    },
                                    decoration: InputDecoration(
                                        prefixIcon: Icon(
                                          Icons.lock,
                                          color: Palette.iconColor,
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Palette.textColor1),
                                          borderRadius: BorderRadius.all(
                                              Radius.circular(35)),
                                        ),
                                        hintText: 'password',
                                        hintStyle: TextStyle(
                                            fontSize: 12,
                                            color: Palette.textColor1),
                                        contentPadding: EdgeInsets.all(5)),
                                  )
                                ),
                              ),
                            ),
                          )
                      ),
                    ),
                  ),
                ),
              ),
              // 텍스트 폼 필드
              AnimatedPositioned(
                duration: Duration(milliseconds: 500),
                curve: Curves.easeIn,
                top: isSignupScreen ? 500 : 440,
                left: 0,
                right: 0,
                child: Center(
                  child: Container(
                    padding: EdgeInsets.all(6),
                    height: 65,
                    width: 65,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(80)),
                    child: GestureDetector(
                      onTap: () async {
                        setState(() {
                          showSpinner=true;
                        });
                        if (isSignupScreen) {
                          if(userPickedImage == null){
                            setState(() {
                              showSpinner = false;
                              SnackBar(content: Text('Please pick your image'),);
                            });
                          }
                          _tryValidation();
                          try {
                            final newUser = await _authentication
                                .createUserWithEmailAndPassword(
                                    email: userEmail, password: userPassword);
                            print(newUser.user);
                            print(newUser.user!.uid);
                            print(userName);

                            final refImage = await FirebaseStorage.instance.ref().
                            child('picked_image').
                            child(newUser.user!.uid+'.png');
                            // 이미지가 저장되는 버킷에 접근할 수 있도록.

                            await refImage.putFile(userPickedImage!);
                            final url = await refImage.getDownloadURL();

                            await FirebaseFirestore.instance.collection('user')
                                .doc(newUser.user!.uid).set(
                                {'userName':userName,
                                'email':userEmail,
                                'picked_image' : url
                                }
                            );


                            if (newUser.user != null) {
                              //등록 성공
                              Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => ChatScreen()));
                              setState(() {
                                showSpinner=false;
                              });
                            }
                          } catch (e) {
                            print(e);
                            if(mounted){
                              setState(() {
                                showSpinner=false;
                              });
                              ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                                content:
                                    Text('Please check your email and password'),
                                backgroundColor: Colors.blue,
                              ));
                          }}
                        }
                        if (!isSignupScreen) {
                          try {
                            _tryValidation();
                            final newUser =
                                await _authentication.signInWithEmailAndPassword(
                                    email: userEmail, password: userPassword);
                            if (newUser.user != null) {
                              // 등록 성공
                              Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => ChatScreen())
                              );
                            }
                          } catch (e) {
                            print(e);
                          }
                        }
                      },
                      child: Container(
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                              colors: (Colors.grey, Colors.blue),
                              begin: Alignment.topLeft,
                              end: Alignment.bottomRight),
                          borderRadius: BorderRadius.circular(30),
                          boxShadow: (
                            BoxShadow(
                              color: Colors.black.withOpacity(0.3),
                              spreadRadius: 1,
                              blurRadius: 1,
                              offset: Offset(1, 0),
                            )
                          ),
                        ),
                        child: Icon(
                          Icons.arrow_forward,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
              // 전송버튼
              AnimatedPositioned(
                  duration: Duration(milliseconds: 500),
                  curve: Curves.easeIn,
                  top: isSignupScreen
                      ? MediaQuery.of(context).size.height - 125
                      : MediaQuery.of(context).size.height - 150,
                  right: 0,
                  left: 0,
                  child: Column(
                    children: (
                      Text(
                        isSignupScreen ? 'or Signup with' : 'or Signin with',
                        style: TextStyle(
                          fontSize: 10,
                        ),
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      TextButton.icon(
                          style: TextButton.styleFrom(
                            primary: Colors.white,
                            minimumSize: Size(155, 40),
                            shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(20)),
                            backgroundColor: Colors.blueAccent,
                          ),
                          onPressed: () {},
                          icon: Icon(Icons.add),
                          label: Text('Google'))
                    ),
                  ))
              //구글
            ),
          ),
        ),
      ),
    );
  }
}

chat_screen.dart

더보기

import 'package:chatting_app/message.dart';
import 'package:chatting_app/new_message.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class ChatScreen extends StatefulWidget {
  const ChatScreen({Key? key}) : super(key: key);

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final _authentication = FirebaseAuth.instance;
  User? loggedUser;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getCurrentUser();
  }

  void getCurrentUser() {
    try {
      final user = _authentication.currentUser;
      if (user != null) {
        loggedUser = user;
        print(loggedUser!.email);
      }
    } catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Chat screen'),
          actions: (
            IconButton(
              onPressed: () {
                _authentication.signOut();
                //Navigator.pop(context);
              },
              icon: Icon(Icons.exit_to_app_sharp),
              color: Colors.white,
            )
          ),
        ),
        body: Container(
          child: Column(
            children: (
              Expanded(
                child: Messages(),
              ),
              NewMessage(), // 리스트뷰가 무조건 화면 내의 모든 공간을 확보해버리기 때문에 expanded 로 감싸주었따.
            ),
          ),
        ),
        // body: StreamBuilder(
        //   stream: FirebaseFirestore.instance.collection('chats/PMNxP6mCoF32Fuz0xEth/message').snapshots(),
        //   builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        //
        //     if(snapshot.connectionState==ConnectionState.waiting){
        //       return Center(
        //         child: CircularProgressIndicator(),
        //       );}
        //     final docs = snapshot.data!.docs;
              // return ListView.builder(
              //   itemCount: docs.length,
              //   itemBuilder: (context, index){
              //     return Container(
              //       padding: EdgeInsets.all(8.0),
              //       child: Text(
              //         docs(index)('text'),
              //         style: TextStyle(fontSize: 20.0),),
              //     );
              //   },
              // );
        //   },
        // )
         );
  }
}

메시지 다트

더보기

import 'package:chatting_app/chat_bubble.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class Messages extends StatelessWidget {
  const Messages({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final user = FirebaseAuth.instance.currentUser;
    return StreamBuilder(
        stream: FirebaseFirestore.instance.collection('chat').orderBy('time', descending: true).snapshots(),
        builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot){
          if(!snapshot.hasData){
            return Text('no data');
          }
          if(snapshot.connectionState==ConnectionState.waiting){
            return Center(
              child: CircularProgressIndicator(),
            );
          }
          final chatDocs = snapshot.data!.docs;
          return ListView.builder(
              reverse: true,
              itemCount: chatDocs.length,
              itemBuilder: (context, index){
            return ChatBubbles(chatDocs(index)('text'),
                chatDocs(index)('userID').toString()==user!.uid,
                chatDocs(index)('userName'),
                chatDocs(index)('userImage')
            );
          });
        }

    );
  }
}

new_message.dart

더보기

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

class NewMessage extends StatefulWidget {
  const NewMessage({Key? key}) : super(key: key);

  @override
  State<NewMessage> createState() => _NewMessageState();
}

class _NewMessageState extends State<NewMessage> {
  var _userEnterMessage="";
  final _controller = TextEditingController();

  Future<void> _sendMessage() async {
    FocusScope.of(context).unfocus();
    final user = FirebaseAuth.instance.currentUser;
    final userData = await FirebaseFirestore.instance.collection('user').doc(user!.uid).get();
    FirebaseFirestore.instance.collection('chat').add({
      'text' : _userEnterMessage,
      'time' : Timestamp.now(),
      'userID' : user!.uid,
      'userName' : userData.data()!('userName'),
      'userImage' : userData('picked_image')
    });
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top:8),
      padding: EdgeInsets.all(8.0),
      child: Row(
        children: (
          Expanded(
            child: TextField(
              maxLines: null,
              controller: _controller,
              decoration: InputDecoration(
                labelText: 'Send a message',
              ),
              onChanged: (value){
                setState(() {
                  _userEnterMessage = value;
                  //모든 키 입력에서 setState 불러옴.
                });
              },
            ),
          ),
          IconButton(
            onPressed: _userEnterMessage.trim().isEmpty?null:_sendMessage

          , icon: Icon(Icons.send), color: Colors.blue,)
        ),
      ),
    );
  }
}

add_image.dart

더보기

import 'dart:io';

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

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

  final Function(File pickedImage) addImageFunc;

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

class _AddImageState extends State<AddImage> {

  File? pickedImage;

  void _pickImage() async {
    final imagePicker = ImagePicker();
    final pickedImageFile = await imagePicker.pickImage(
        source: ImageSource.camera,
        imageQuality: 50,
        maxHeight: 150
    );
    setState(() {
      if(pickedImageFile !=null) {
        pickedImage = File(pickedImageFile.path);
      }
    });
    widget.addImageFunc(pickedImage!);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: 10),
      width: 150,
      height: 300,
      child: Column(
        children: (
          CircleAvatar(
            radius: 40,
            backgroundColor: Colors.blue,
            backgroundImage: pickedImage !=null ? FileImage(pickedImage!) : null,
          ),
          SizedBox(
            height: 10,
          ),
          OutlinedButton.icon(
            onPressed: () {
              _pickImage();
            },
            icon: Icon(Icons.image),
            label: Text('Add image'),
          ),
          SizedBox(
            height: 80,
          ),
          TextButton.icon(
            onPressed: () {
              Navigator.pop(context);
            },
            icon: Icon(Icons.close),
            label: Text('Close'),
          ),
        ),
      ),
    );
  }
}

팔레트 다트

더보기

import 'package:flutter/painting.dart';

class Palette {
  static const Color iconColor = Color(0xFFB6C7D1);
  static const Color activeColor = Color(0xFF09126C);
  static const Color textColor1 = Color(0XFFA7BCC7);
  static const Color textColor2 = Color(0XFF9BB3C0);
  static const Color facebookColor = Color(0xFF3B5999);
  static const Color googleColor = Color(0xFFDE4B39);
  static const Color backgroundColor = Color(0xFFECF3F9);
}