Flutter

블로그 프로젝트 - 게시글 수정하기

whs5758 2025. 9. 5. 11:44
Post
import 'package:flutter_blog/data/models/user.dart';

class Post {
  // TODO - (댓글은 추후 추가)
  int id; // 게시물 ID
  String title;
  String content;
  DateTime createdAt; // 생성일시
  DateTime updatedAt; // 수정일시
  User user; // 작성자 (관계형 데이터)
  int bookmarkCount; // 북마크 수

  // 현재 사용자의 북마크 여부 (로그인 상태에 따라 달라짐)
  bool? isBookmark;

  // post.createdAt.year - 2025
  // post.createdAt.month - 5
  // DateTime.now().difference(post.createdAt);

  Post({
    required this.id,
    required this.title,
    required this.content,
    required this.createdAt,
    required this.updatedAt,
    required this.user,
    required this.bookmarkCount,
    this.isBookmark,
  });

  // User.fromMap(..)
  // 문자열을 DateTime 형변환 처리 해주어야 한다.
  Post.fromMap(Map<String, dynamic> data)
      : id = data['id'],
        title = data['title'],
        content = data['content'],
        createdAt = DateTime.parse(data['createdAt']),
        updatedAt = DateTime.parse(data['updatedAt']),
        isBookmark = data['isBookmark'],
        user = User.fromMap(data['user']),
        bookmarkCount = data['bookmarkCount'];

  Post copyWith({
    int? id,
    String? title,
    String? content,
    User? user,
    DateTime? createdAt,
    DateTime? updatedAt,
    int? bookmarkCount,
  }) {
    return Post(
      id: id ?? this.id,
      title: title ?? this.title,
      content: content ?? this.content,
      user: user ?? this.user,
      createdAt: createdAt ?? this.createdAt,
      updatedAt: updatedAt ?? this.updatedAt,
      bookmarkCount: bookmarkCount ?? this.bookmarkCount,
    );
  }

  // 디버깅 용
  @override
  String toString() {
    return 'Post{id: $id, title: $title, content: $content, createdAt: $createdAt, updatedAt: $updatedAt, user: $user, bookmarkCount: $bookmarkCount, isBookmark: $isBookmark}';
  }
}

 

PostUpdateModel
// 서버 통신 요청 결과 --->
// 통신 -- 로딩 -- 응답 (성공, 실패)
// 게시글 수정 모델
import 'package:flutter_blog/data/models/post.dart';
import 'package:flutter_blog/data/models/repository/post_repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import 'package:validators/validators.dart';

class PostUpdateModel {
  final bool? isLoading;
  final String? error;
  //final Post? updatePost;

  PostUpdateModel({this.isLoading, this.error});

  PostUpdateModel copyWith({bool? isLoading, String? error}) {
    return PostUpdateModel(
      isLoading: isLoading ?? this.isLoading,
      error: error ?? this.error,
    );
  }

  @override
  String toString() {
    return 'PostUpdateModel{isLoading: $isLoading, error: $error}';
  }
} // end of PostUpdateModel

// 게시글 수정 비즈니스 모델 (메뉴얼)
class PostUpdateNotifier extends AutoDisposeNotifier<PostUpdateModel> {
  @override
  PostUpdateModel build() {
    ref.onDispose(
      () {
        Logger().d("PostUpdateNotifier 파괴됨");
      },
    );
    return PostUpdateModel();
  }

  Future<Map<String, dynamic>> updatePost(Post post) async {
    try {
      state = state.copyWith(isLoading: true);
      Map<String, dynamic> response = await PostRepository().updateOne(post);
      if (response["success"]) {
        state = state.copyWith(isLoading: false);
        return {"success": true, "message": "게시글이 성공적으로 수정 됨"};
      } else {
        state =
            state.copyWith(isLoading: false, error: response["errorMessage"]);
        return {"success": false, "message": response["errorMessage"]};
      }
    } catch (e) {
      state = state.copyWith(isLoading: false);
      return {"success": false, "errorMessage": "수정 중 오류 발생"};
    }
  }
}

 

// 일반 NotifierProvider
final counterProvider = NotifierProvider<CounterNotifier, int>(() {
  return CounterNotifier();
});

// 문제: 한 번 생성되면 앱이 종료될 때까지 메모리에 남아있음
// - 화면을 벗어나도 상태가 유지됨
// - 메모리 누수 가능성
// - 불필요한 리소스 점유

 

 

AutoDisposeNotifier는 "일회성 또는 화면별 상태 관리"에 적합한 메모리 효율적인 Provider입니다.

  • 사용해야 하는 경우: 화면별 폼, API 호출, 일회성 작업
  • 사용하지 말아야 하는 경우: 전역 상태, 캐시, 여러 화면에서 공유하는 데이터
  • 장점: 자동 메모리 관리, 리소스 절약, 깨끗한 상태 초기화
  • 주의사항: 상태가 자동으로 사라지므로 지속적으로 유지해야 하는 데이터에는 부적합

 

만약 게시글별로 독립적인 수정 상태가 필요하다면

// 각 게시글 ID별로 독립적인 수정 상태 관리
final postUpdateProvider = AutoDisposeNotifierProvider.family<
    PostUpdateNotifier, PostUpdateModel, int>(() {
  return PostUpdateNotifier();
});

 

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

class SnackBarUtil {
  /// 성공 메시지 표시 (녹색 배경)
  static void showSuccess(BuildContext context, String message) {
    if (!context.mounted) return;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
        duration: Duration(seconds: 2),
      ),
    );
  }

  /// 에러 메시지 표시 (빨간색 배경)
  static void showError(BuildContext context, String message) {
    if (!context.mounted) return;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: Duration(seconds: 3),
      ),
    );
  }

  /// 일반 정보 메시지 표시 (기본 배경)
  static void showInfo(BuildContext context, String message) {
    if (!context.mounted) return;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: Duration(seconds: 2),
      ),
    );
  }

  /// 경고 메시지 표시 (주황색 배경)
  static void showWarning(BuildContext context, String message) {
    if (!context.mounted) return;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.orange,
        duration: Duration(seconds: 2),
      ),
    );
  }
}