본문 바로가기

Flutter

[Flutter] 채팅앱 만들기 #8

What to do?

회원가입(On Boarding)  기능 개발하기

- 프로필 사진 업로드 기능을 위해 이미지 업로드용 Server를 생성

- 이미지 업로드 기능 구현 

- 상태 관리를 위해 Multi Provider 생성


Reference

 


라이브러리 설치

 

pubspec.yaml 파일 수정

dependencies:
  ... 
  flutter_bloc: ^7.0.1
  google_fonts: ^1.1.2
  http: ^0.12.2
  image_picker: ^0.6.7+22
  rethink_db_ns: ^0.0.4
  shared_preferences: ^2.0.6

pubspec.yaml


State

 

OnBoardingState(회원가입 상태)

  1. 회원가입 로딩중
    • props : None
  2. 회원가입 성공
    • props : 회원가입 성공한 유저

state_management/on_board/on_boarding_state.dart

import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';

abstract class OnBoardingState extends Equatable {}

class OnBoardingInitial extends OnBoardingState {
  @override
  List<Object> get props => [];
}

class OnBoardingLoading extends OnBoardingState {
  @override
  List<Object> get props => [];
}

class OnBoardingSuccess extends OnBoardingState {
  final User _user;

  OnBoardingSuccess(this._user);

  @override
  List<Object> get props => [_user];
}

 

ProfileImageCubit(프로필 사진)

 

  • 선택한 이미지 경로를 UI에 전달하는 역할

 

state_management/on_board/profile_image_cubit.dart

import 'dart:io';

import 'package:bloc/bloc.dart';
import 'package:image_picker/image_picker.dart';

class ProfileImageCubit extends Cubit<File> {
  ProfileImageCubit() : super(null);
  final _imagePicker = ImagePicker();

  Future<void> getImage() async {
    PickedFile image = await _imagePicker.getImage(
        source: ImageSource.gallery, imageQuality: 50);
    if (image == null) return;
    emit(File(image.path));
  }
}

 

OnBoardingCubit(프로필 사진)

 

 

state_management/on_board/on_boarding_cubit.dart

import 'dart:io';

import 'package:bloc/bloc.dart';
import 'package:chat/chat.dart';
import 'package:flutter_prj/states_management/on_board/on_boarding_state.dart';

import '../../service/image_upload_service.dart';

class OnBoardingCubit extends Cubit<OnBoardingState> {
  final IUserService _userService;
  final ImageUploadService _imageUploader;

  OnBoardingCubit(this._userService, this._imageUploader)
      : super(OnBoardingInitial());

  Future<void> connect(String username, File profileImage) async {
    // UI에 로딩중 state를 전달
    emit(OnBoardingLoading());
    
    // 선택한 이미지 경로 가져오기
    final profileImageUrl = await _imageUploader.uploadImage(profileImage);
    
    // 유저 생성하기
    final user = User(
        username: username,
        photoUrl: profileImageUrl,
        active: true,
        lastSeen: DateTime.now());
        
    // 회원가입을 수행하고, 회원가입 성공 state를 UI에 전달
    emit(OnBoardingSuccess(await _userService.connect(user)));
  }
}

이미지 업로드 서버

 

 

  • Express를 사용해 이미지 업로드용 서버 생성
    • 이미지 업로드 경로 : server/images/profile
    • 서버 파일 : app.js

파일 구조

 

  • 라이브러리 설치
# server라는 폴더 생성
mkdir server

# server라는 폴더로 이동
cd server

# 라이브러리 설치
npm init
npm install i express
npm install i express-fileupload

 

  • 서버
    • express를 사용해 서버 생성
    • server/images/profile 경로에 업로드된 이미지 저장
    • /upload 경로로 POST요청 발생 시 이미지 업로드
    • 3000번 포트 사용

server/images/profile/app.js

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();

app.use(fileUpload());
app.use('/images/profile', express.static('images/profile'));

app.post('/upload', (req, res)=>{
    let uploadFile = req.files?.picture;
    let uploadPath = `${__dirname}/images/profile/${uploadFile.name}`;
    uploadFile.mv(uploadPath, (err)=>{
    if (err) return res.status(500).send(err);
        console.log('err')
        res.send(`/images/profile/${uploadFile.name}`);
    });
})

app.listen(3000, ()=>{
    return console.log('Server for image upload is running on port 3000...')
});

 

  • 서버 실행
node server/app.js

서버 실행


이미지 업로드 기능

 

위에서는 Server가 업로드된 이미지를 받아서 저장하는 기능을 만듬

이제 Client 쪽에서 이미지 업로드 요청을 보내는 기능 구현 해야함

 

service/image_upload.dart

import 'dart:io';

import 'package:http/http.dart';

class ImageUploadService {
  final String _url;

  ImageUploadService(this._url);

  Future<String> uploadImage(File image) async {
    final req = MultipartRequest('POST', Uri.parse(_url));
    req.files.add(await MultipartFile.fromPath('picture', image.path));
    final result = await req.send();
    if (result.statusCode != 200) return null;
    final response = await Response.fromStream(result);
    return Uri.parse(_url).origin + response.body;
  }
}

Provider 세팅

 

  • OnBoardUI(회원가입화면)에서 Cubit(OnBoardingCubit, ProfileCubit)을 사용할 수 있도록 세팅
  • Connection 생성시 host명을 10.0.2.2로 함
  • └ localhost로 하면 DB 연결에 문제가 생겨서, 아래 블로그 글에 방법을 따라함
 

[flutter] flutter socketexception os error connection refused errno = 111

문제 상황 로컬에서 flask로 간단하게 만든 서버에 요청을 보냈더니 이러한 에러가 발생했다. 문제 원인 원인은 안드로이드 에뮬레이터가 localhost 대신 10.0.2.2를 사용하기 때문이다. 해결 방법 loca

g0n1.tistory.com

 

view_model/compositon_root.dart

import 'package:chat/chat.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_prj/screen/pages/on_board/on_board_page.dart';
import 'package:flutter_prj/service/image_upload_service.dart';
import 'package:flutter_prj/states_management/on_board/on_boarding_cubit.dart';
import 'package:flutter_prj/states_management/on_board/profile_image_cubit.dart';
import 'package:rethink_db_ns/rethink_db_ns.dart';

class CompositionRoot {
  static RethinkDb _db;
  static Connection _connection;
  static IUserService _userService;

  static configure() async {
    _db = RethinkDb();
    _connection = await _db.connect(
      // 에뮬레이터는 host를 localhost지정하면 에러발생...
      host: '10.0.2.2',
      port: 28015,
    );
    _userService = UserService(_db, _connection);
  }

  static Widget composeOnBoardingUi() {
    ImageUploadService imageUploader =
    // 에뮬레이터는 host를 localhost지정하면 에러발생...
        ImageUploadService('http://10.0.2.2:3000/upload');
    OnBoardingCubit onBoardingCubit =
        OnBoardingCubit(_userService, imageUploader);
    ProfileImageCubit profileImageCubit = ProfileImageCubit();
    return MultiBlocProvider(
      providers: [
        BlocProvider(create: (BuildContext context) => onBoardingCubit),
        BlocProvider(create: (BuildContext context) => profileImageCubit),
      ],
      child: const OnBoarding(),
    );
  }
}

 

 

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_prj/screen/custom_desgin/theme.dart';
import 'package:flutter_prj/view_model/composition_root.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await CompositionRoot.configure();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "Chat App",
        theme: lightTheme(context),
        darkTheme: darkTheme(context),
        home: CompositionRoot.composeOnBoardingUi());
  }

  const MyApp({key}) : super(key: key);
}

 

'Flutter' 카테고리의 다른 글

[Flutter] 채팅앱 만들기 #10  (0) 2023.03.11
[Flutter] 채팅앱 만들기 #9  (0) 2023.03.08
[Flutter] 채팅앱 만들기 #7  (0) 2023.03.05
[Flutter] 채팅앱 만들기 #6  (0) 2023.03.05
[Flutter] 채팅앱 만들기 #5  (0) 2023.03.04