[Flutter] 블로그 만들기 - 게시글 작성 기능 구현

2025. 2. 10. 13:52·Flutter/App
728x90

📌 이번 글에서는 게시글 작성 기능을 구현합니다.

  • 커스텀 위젯(CustomTextFormField, CustomTextArea)을 활용하여 게시글 입력 UI를 만듭니다.
  • PostWriteViewModel을 사용하여 게시글 등록 기능을 구현하고 상태를 관리합니다.
  • 게시글 작성 완료 후 자동으로 입력 필드 초기화 및 피드백 메시지 표시 기능을 추가합니다.

디자인 시안

 


1️⃣ 게시글 목록 인증 없이 확인

게시글 목록을 인증 없이 확인할 수 있는 API 엔드포인트입니다.
📌 URI: http://localhost:8080/init/post
이 API를 사용하여 기본적인 게시글 목록을 가져올 수 있습니다.

2️⃣ Custom 위젯 만들기 (입력 필드, 텍스트 영역)

게시글 작성 화면에서 사용할 입력 필드(CustomTextFormField) 와 텍스트 영역(CustomTextArea)을 커스텀 위젯으로 만듭니다.

 

📌 CustomTextFormField (입력 필드)

import 'package:flutter/material.dart';

class CustomTextFormField extends StatelessWidget {
  final String hint;
  final bool obscureText;
  final TextEditingController controller;
  final String initValue;

  const CustomTextFormField({
    required this.hint,
    this.obscureText = false,
    required this.controller,
    this.initValue = "",
  });

  @override
  Widget build(BuildContext context) {
    if (initValue.isNotEmpty) {
      controller.text = initValue;
    }
    return TextFormField(
      controller: controller,
      obscureText: obscureText,
      decoration: InputDecoration(
        hintText: "Enter $hint",
        enabledBorder: OutlineInputBorder(
          // 3. 기본 TextFormField 디자인
          borderRadius: BorderRadius.circular(20),
        ),
        focusedBorder: OutlineInputBorder(
          // 4. 손가락 터치 시 TextFormField 디자인
          borderRadius: BorderRadius.circular(20),
        ),
        errorBorder: OutlineInputBorder(
          // 5. 에러 발생 시 TextFormField 디자인
          borderRadius: BorderRadius.circular(20),
        ),
        focusedErrorBorder: OutlineInputBorder(
          // 5. 에러 발생 후 손가락 터치 시 TextFormField 디자인
          borderRadius: BorderRadius.circular(20),
        ),
      ),
    );
  }
}

 

📌 설명

✔ TextFormField를 감싸는 커스텀 위젯입니다.
✔ hint 값을 받아 입력 필드의 힌트로 사용합니다.
✔ initValue가 있을 경우, 초기값을 설정합니다.
✔ obscureText 속성을 추가하여 비밀번호 입력도 가능하게 합니다.
✔ 입력 필드 디자인을 커스텀할 수 있도록 OutlineInputBorder를 사용합니다.


📌 CustomTextArea (텍스트 영역)

import 'package:flutter/material.dart';

class CustomTextArea extends StatelessWidget {
  final String hint;
  final TextEditingController controller;
  final String initValue;

  const CustomTextArea(
      {required this.hint, required this.controller, this.initValue = ""});

  @override
  Widget build(BuildContext context) {
    if (initValue.isNotEmpty) {
      controller.text = initValue;
    }

    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 5),
      child: TextFormField(
        controller: controller,
        maxLines: 10,
        decoration: InputDecoration(
          hintText: "Enter $hint",
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
          errorBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
          focusedErrorBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
        ),
      ),
    );
  }
}

 

📌 설명
✔  TextFormField를 기반으로 다중 줄 입력을 지원하는 텍스트 입력 위젯입니다.
✔  maxLines: 10을 설정하여 기본적으로 10줄을 입력할 수 있습니다.
✔  initValue를 활용하여 초기값이 있을 경우 컨트롤러에 자동 설정됩니다.
✔  hint를 통해 입력 필드의 안내 텍스트를 표시합니다.
✔  입력 필드 디자인을 통일하기 위해 OutlineInputBorder를 설정하였습니다.


3️⃣ PostWritePage 생성 (게시글 작성 화면)

게시글 작성을 위한 페이지를 생성합니다.

 

📌 PostWritePage (페이지)

import 'package:class_f_story/ui/pages/post/write_page/widgets/post_write_body.dart';
import 'package:flutter/material.dart';

class PostWritePage extends StatelessWidget {
  const PostWritePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: PostWriteBody(),
    );
  }
}

 

📌 설명

✔ Scaffold를 사용하여 기본 페이지 구조를 만듭니다.
✔  AppBar()를 추가하여 앱 상단바를 유지합니다.
✔  PostWriteBody()를 호출하여 게시글 작성 UI를 포함하는 본문을 렌더링합니다.


📌 PostWriteBody (본문)

import 'package:class_f_story/ui/pages/post/write_page/widgets/post_write_form.dart';
import 'package:flutter/material.dart';

class PostWriteBody extends StatelessWidget {
  const PostWriteBody({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        children: [
          // 기본적으로 사용하자
          Flexible(
            child: PostWriteForm(),
          ),
        ],
      ),
    );
  }
}

 

📌 설명
✔  Padding을 적용하여 적절한 여백을 추가합니다.
✔  PostWriteForm()을 포함하여 입력 폼을 표시합니다.
✔  Flexible을 사용하여 레이아웃을 유연하게 조정할 수 있도록 합니다.


4️⃣ PostWriteForm (게시글 작성 폼)

import 'package:class_f_story/_core/constants/size.dart';
import 'package:class_f_story/ui/widgets/custom_elevated_button.dart';
import 'package:class_f_story/ui/widgets/custom_text_area.dart';
import 'package:class_f_story/ui/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class PostWriteForm extends ConsumerWidget {
  // 폼에 상태/ 유효성 검사 ..save()
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();

  PostWriteForm({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Form(
      key: _formKey,
      child: ListView(
        shrinkWrap: true,
        children: [
          CustomTextFormField(
            hint: 'title',
            controller: _titleController,
          ),
          const SizedBox(height: smallGap),
          CustomTextArea(
            hint: 'content',
            controller: _contentController,
          ),
          const SizedBox(height: largeGap),
          CustomElevatedButton(
            text: '글쓰기',
            click: () {
              // 글 작성 버튼 클릭 이벤트
            },
          ),
        ],
      ),
    );
  }
}

 

📌 설명
✔  CustomTextFormField와 CustomTextArea를 사용하여 제목과 내용을 입력할 수 있도록 합니다.
✔  CustomElevatedButton을 추가하여 "글쓰기" 버튼을 표시합니다.
✔  GlobalKey<FormState>를 사용하여 폼의 상태를 관리합니다.


📌 PostWriteViewModel (게시글 작성 뷰모델)

게시글 작성 기능을 담당하는 뷰모델을 생성합니다.


✔ 상태 관리를 위해 Riverpod의 Notifier를 사용합니다.
✔  게시글을 작성할 때 서버로 데이터를 전송하고 성공 여부를 관리합니다.
✔ 게시글 작성 완료 시 입력 필드를 초기화합니다.

// 글쓰기 화면 뷰 모델
// 화면 클래스에서 관리해야 하는 데이터, 기능을 여기로 옮기자.
// 그리고 상태 관리까지

import 'package:class_f_story/_core/utils/exception_handler.dart';
import 'package:class_f_story/_core/utils/my_http.dart';
import 'package:class_f_story/data/repository/user_repository.dart';
import 'package:class_f_story/main.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// dto 만들기 싫어서 배웠던 문법 -> 레코드
// 모델 POST 사용해도 되지만 --> 레코드 --> POST(모델 활용)
class PostWriteViewModel
    extends Notifier<(String? title, String? content, bool isWriteCompleted)> {
  // 뷰 모델에서 컨텍스트를 사용하는 방안
  final mContext = navigatorkey.currentContext!;

  UserRepository userRepository = const UserRepository();

  // 상태값을 초기화해야 한다.
  @override
  (String? title, String? content, bool isWriteCompleted) build() {
    // state == (String? title, String? content, bool isWriteCompleted)
    return (null, null, false);
  }

  // 행위 - 게시글 작성
  // 뷰 모델에서는 기본 데이터 타입 형태로 설계
  // 0. 뷰모델에서 예외 처리를 하자.
  // 1. 데이터 Map 구조로 변환 처리
  // 2. 응답 --> success -- false
  // 3. 응답 --> success -- true
  Future<void> createPost(
      {required String title, required String content}) async {
    try {
      // 데이터 가공 처리
      final body = {"title": title, "content": content};

      Response response = await dio.post('/api/post', data: body);
      Map<String, dynamic> responseBody = response.data;

      // 2. 서버 응답이 실패(success: false)일 경우 예외 처리
      if (!responseBody['success']) {
        ExceptionHandler.handleException(
            responseBody['errorMessage'], StackTrace.current);
        return;
      }

      // 시스템 키보드가 있다면 자동 닫기
      FocusScope.of(mContext).unfocus();

      // 게시글 작성 완료 메시지
      ScaffoldMessenger.of(mContext)
          .showSnackBar(SnackBar(content: Text('게시글 등록 완료')));

      // 상태 갱신 처리
      state = (null, null, true);
    } catch (e, stackTrace) {
      ExceptionHandler.handleException('게시글 등록 시 오류 발생', stackTrace);
    }
  }
}

// 창고 관리 만들기
final postWriteViewModelProvider = NotifierProvider<
    PostWriteViewModel,
    (
      String? title,
      String? content,
      bool isWriteCompleted
    )>(() => PostWriteViewModel());

 

📌 설명
✔ 게시글 작성 요청을 서버에 보냄 (createPost 메서드)
✔  state를 이용하여 입력값과 작성 완료 여부를 관리
✔ 게시글 작성 후 입력값 초기화 및 "게시글 등록 완료" 메시지 출력
✔ 서버 응답이 실패일 경우 예외 처리
✔  NotifierProvider를 사용하여 전역에서 뷰모델을 관리


PostWriteForm에서 뷰모델 적용

게시글 작성 폼에서 PostWriteViewModel을 활용하여 입력값을 관리하고 게시글 작성 요청을 수행합니다.

import 'package:class_f_story/_core/constants/size.dart';
import 'package:class_f_story/data/_vm/post_write_view_model.dart';
import 'package:class_f_story/ui/widgets/custom_elevated_button.dart';
import 'package:class_f_story/ui/widgets/custom_text_area.dart';
import 'package:class_f_story/ui/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class PostWriteForm extends ConsumerWidget {
  // 폼에 상태/ 유효성 검사 ..save()
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();

  PostWriteForm({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // post_write_view_model 만든 게 핵심
    // 뷰 모델 상태를 구독
    // (title, content, isWriteCompleted)
    final data = ref.watch(postWriteViewModelProvider);
    // 뷰 모델 행위를 사용해야 한다. (뷰 모델 자체 들고 오기)
    final vm = ref.read(postWriteViewModelProvider.notifier);

    return Form(
      key: _formKey,
      child: ListView(
        shrinkWrap: true,
        children: [
          CustomTextFormField(
            hint: 'title',
            controller: _titleController,
          ),
          const SizedBox(height: smallGap),
          CustomTextArea(
            hint: 'content',
            controller: _contentController,
          ),
          const SizedBox(height: largeGap),
          CustomElevatedButton(
            text: '글쓰기',
            click: () {
              // 게시글 작성 요청
              vm.createPost(
                title: _titleController.text.trim(),
                content: _contentController.text.trim(),
              );

              // 레코드 문법 활용 가능
              if (data.$3 == true) {
                // 입력 필드 초기화
                _titleController.clear();
                _contentController.clear();
              }
            },
          ),
        ],
      ),
    );
  }
}

 

📌 설명
✔ 뷰모델의 상태를 구독하여 입력 필드의 상태를 반영합니다.
✔  vm.createPost()를 호출하여 게시글 작성 요청을 보냅니다.
✔  if (data.$3 == true) 조건을 통해 게시글이 작성 완료되면 입력 필드를 초기화합니다.


✅ 마무리: 구현된 기능 정리

✔ CustomTextFormField, CustomTextArea를 활용한 입력 UI 구성
✔ PostWritePage → PostWriteBody → PostWriteForm 구조로 페이지 설계
✔ PostWriteViewModel을 사용하여 게시글 작성 요청 관리
✔ 게시글 작성 후 입력 필드 초기화 및 성공 메시지 표시
✔ 뷰모델과 Riverpod을 활용한 상태 관리 적용

 

🚀 이제 게시글 작성 기능이 완성되었습니다! 🚀

 

다음 글에서는 pull_to_refresh 라이브러리를 사용하여 게시글 목록에서 새로고침 기능을 구현합니다. 🚀

 

게시글 관리와 로그아웃 기능 구현이 궁금하다면??

 

2025.02.09 - [Flutter/App] - [Flutter] 블로그 만들기 - 게시글 관리 및 로그아웃 기능 구현

 

'Flutter > App' 카테고리의 다른 글

[Flutter] 블로그 만들기 - 게시글 모델링  (0) 2025.02.11
[Flutter] 블로그 만들기 - pull_to_refresh 라이브러리 사용  (0) 2025.02.11
[Flutter] 블로그 만들기 - 게시글 관리 및 로그아웃 기능 구현  (0) 2025.02.10
[Flutter] 블로그 만들기 -자동 로그인 기능 구현 및 UI  (0) 2025.02.10
[Flutter] 블로그 만들기 - 회원가입 기능 구현 및 UI  (1) 2025.02.09
'Flutter/App' 카테고리의 다른 글
  • [Flutter] 블로그 만들기 - 게시글 모델링
  • [Flutter] 블로그 만들기 - pull_to_refresh 라이브러리 사용
  • [Flutter] 블로그 만들기 - 게시글 관리 및 로그아웃 기능 구현
  • [Flutter] 블로그 만들기 -자동 로그인 기능 구현 및 UI
공돌이 출신 개발자
공돌이 출신 개발자
공돌이 출신 개발자입니다
  • 공돌이 출신 개발자
    공돌이 출신 개발자
    공돌이 출신 개발자
  • 전체
    오늘
    어제
    • 분류 전체보기 (124)
      • Database (0)
        • SQL (0)
        • 1일 1쿼리 (9)
      • Flutter (40)
        • Dart 언어 (18)
        • App (22)
      • Git (0)
      • Http 기초 지식 (14)
      • HTML5 & CSS3 (0)
      • Java (33)
      • JSP (0)
      • JavaScript (0)
      • Linux (0)
      • MSA (0)
      • Project (0)
      • React (0)
      • Spring (19)
      • 설치 메뉴얼 (1)
      • [Flutter] 프로젝트 (눈길) (8)
        • 작업일지 (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    HTTP
    jsp
    앱개발
    공부
    안드로이드 앱 개발
    코딩
    dart
    JAVA 기초
    안드로이드
    Android
    프로젝트
    메서드
    블로그 만들기
    회원가입
    앱 개발
    플러터
    SQLD
    프로그래밍
    spring boot
    Java
    객체
    클래스
    android studio
    flutter
    객체지향
    SQL
    1일1쿼리
    개발
    데이터
    로그인
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
공돌이 출신 개발자
[Flutter] 블로그 만들기 - 게시글 작성 기능 구현
상단으로

티스토리툴바