이벤트 가져오기 위젯 테스트 추가 및 테스트 훅 도입
This commit is contained in:
@@ -9,12 +9,14 @@ import './event_bus.dart';
|
||||
|
||||
class MemberService with ChangeNotifier {
|
||||
List<Member> _members = [];
|
||||
Member? _currentMember;
|
||||
bool _isLoading = false;
|
||||
String? _token;
|
||||
String? _clubId;
|
||||
final ApiService _apiService;
|
||||
StreamSubscription? _eventSubscription;
|
||||
String? _lastError;
|
||||
DateTime? _lastLoadedAt;
|
||||
bool _disposed = false;
|
||||
|
||||
// 기본 생성자
|
||||
MemberService() : _apiService = ApiService();
|
||||
@@ -24,6 +26,8 @@ class MemberService with ChangeNotifier {
|
||||
|
||||
List<Member> get members => [..._members];
|
||||
bool get isLoading => _isLoading;
|
||||
String? get lastError => _lastError;
|
||||
DateTime? get lastLoadedAt => _lastLoadedAt;
|
||||
|
||||
// 초기화 함수
|
||||
void initialize(String token) {
|
||||
@@ -33,80 +37,52 @@ class MemberService with ChangeNotifier {
|
||||
// 이벤트 구독 - 기존 구독이 있으면 취소 후 다시 구독
|
||||
_eventSubscription?.cancel();
|
||||
_eventSubscription = EventBus().on<ClubChangedEvent>().listen((event) {
|
||||
if (_disposed) return; // dispose 이후 방어
|
||||
if (event.clubId.isNotEmpty) {
|
||||
setClubId(event.clubId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 비동기 리소스 상태 로깅
|
||||
Future<void> _logResourceState(String context) async {
|
||||
if (kDebugMode) {
|
||||
debugPrint('===== MemberService 비동기 리소스 상태 ($context) =====');
|
||||
debugPrint('시간: ${DateTime.now().toIso8601String()}');
|
||||
debugPrint('구독 상태: ${_eventSubscription != null ? '활성' : '비활성'}');
|
||||
debugPrint('클럽 ID: $_clubId');
|
||||
debugPrint('토큰 상태: ${_token != null ? '있음' : '없음'}');
|
||||
debugPrint('로딩 상태: $_isLoading');
|
||||
debugPrint('회원 수: ${_members.length}');
|
||||
debugPrint('EventBus 활성 구독 수: ${EventBus.activeSubscriptionCount}');
|
||||
debugPrint('===========================================');
|
||||
}
|
||||
}
|
||||
// (개발 중 로깅 유틸 제거됨)
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
void dispose() {
|
||||
// dispose 시작 상태 로깅
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: dispose 시작');
|
||||
}
|
||||
await _logResourceState('dispose 시작');
|
||||
|
||||
// 이벤트 구독 취소 확실히 처리
|
||||
// 비동기 상태 로깅은 테스트/런타임 안정성을 위해 동기 경로로 축소
|
||||
// (필요 시 개발 중 수동 호출)
|
||||
|
||||
// 이벤트 구독 취소: 동기 dispose 규약을 지키기 위해 unawaited로 처리
|
||||
_disposed = true; // 이후 작업 차단
|
||||
if (_eventSubscription != null) {
|
||||
try {
|
||||
// 구독 취소 전 로깅
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: 구독 취소 시작');
|
||||
}
|
||||
|
||||
// 구독 취소
|
||||
_eventSubscription!.cancel();
|
||||
|
||||
// 마이크로태스크 실행으로 비동기 작업 완료 보장
|
||||
await Future.microtask(() {});
|
||||
|
||||
// 구독 취소 후 로깅
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: 구독 취소 완료');
|
||||
}
|
||||
|
||||
// 구독 참조 제거
|
||||
unawaited(_eventSubscription!.cancel());
|
||||
_eventSubscription = null;
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: 구독 취소 요청');
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: 구독 취소 중 오류 - $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 추가 마이크로태스크 실행으로 비동기 작업 완료 보장 (타이머 생성 회피)
|
||||
await Future.microtask(() {});
|
||||
await Future.microtask(() {});
|
||||
|
||||
// dispose 완료 후 상태 로깅
|
||||
await _logResourceState('dispose 완료');
|
||||
|
||||
// 구독 수 로깅 - 지연 없이 즉시 처리
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService: dispose 완료 후 구독 수 - ${EventBus.activeSubscriptionCount}');
|
||||
debugPrint('MemberService: dispose 종료');
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 클럽 ID 설정
|
||||
Future<void> setClubId(String clubId) async {
|
||||
if (_disposed) return; // dispose 이후 무시
|
||||
// 클럽 ID가 변경된 경우에만 처리
|
||||
if (_clubId != clubId) {
|
||||
_clubId = clubId;
|
||||
@@ -126,9 +102,14 @@ class MemberService with ChangeNotifier {
|
||||
|
||||
// 클럽의 모든 회원 가져오기
|
||||
Future<void> fetchClubMembers() async {
|
||||
print('fetchClubMembers 호출됨: token=${_token}');
|
||||
if (_disposed) return; // dispose 이후 무시
|
||||
final sw = Stopwatch()..start();
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 시작 (token=${_token != null}, clubId=$_clubId)');
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
_lastError = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
@@ -142,6 +123,9 @@ class MemberService with ChangeNotifier {
|
||||
}
|
||||
|
||||
if (clubId == null) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: clubId 없음 - SharedPreferences에도 없음');
|
||||
}
|
||||
throw Exception('선택된 클럽이 없습니다');
|
||||
}
|
||||
|
||||
@@ -150,7 +134,9 @@ class MemberService with ChangeNotifier {
|
||||
data: {'clubId': clubId},
|
||||
);
|
||||
|
||||
print('회원 응답 데이터: $data');
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 응답 수신 (elapsed=${sw.elapsedMilliseconds}ms)');
|
||||
}
|
||||
|
||||
// 응답 데이터가 리스트 형태로 직접 왔으므로 그대로 사용
|
||||
if (data is List) {
|
||||
@@ -158,21 +144,40 @@ class MemberService with ChangeNotifier {
|
||||
_members = membersData
|
||||
.map((memberData) => Member.fromJson(memberData))
|
||||
.toList();
|
||||
print('변환된 회원 리스트 길이: ${_members.length}');
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 리스트 파싱 완료 (count=${_members.length})');
|
||||
if (_members.isEmpty) {
|
||||
debugPrint('MemberService.fetchClubMembers: 회원 목록이 비어있음');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 응답 데이터가 객체 형태로 왔을 경우 members 키를 통해 접근
|
||||
final List<dynamic> membersData = data['members'] ?? [];
|
||||
_members = membersData
|
||||
.map((memberData) => Member.fromJson(memberData))
|
||||
.toList();
|
||||
print('변환된 회원 리스트 길이: ${_members.length}');
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 객체 파싱 완료 (count=${_members.length})');
|
||||
if (_members.isEmpty) {
|
||||
debugPrint('MemberService.fetchClubMembers: 회원 목록이 비어있음');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
_lastLoadedAt = DateTime.now();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 성공 (total=${_members.length}, elapsed=${sw.elapsedMilliseconds}ms, loadedAt=${_lastLoadedAt?.toIso8601String()})');
|
||||
}
|
||||
} catch (e, st) {
|
||||
_isLoading = false;
|
||||
_lastError = e.toString();
|
||||
notifyListeners();
|
||||
if (kDebugMode) {
|
||||
debugPrint('MemberService.fetchClubMembers: 실패 (${e.toString()})');
|
||||
debugPrint('MemberService.fetchClubMembers: stacktrace -> $st');
|
||||
}
|
||||
throw Exception('회원 목록을 불러오는데 실패했습니다: $e');
|
||||
}
|
||||
}
|
||||
@@ -210,9 +215,13 @@ class MemberService with ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final payload = {
|
||||
'clubId': _clubId,
|
||||
...memberData,
|
||||
};
|
||||
final data = await _apiService.post(
|
||||
'${ApiConfig.clubs}/members/add',
|
||||
data: memberData,
|
||||
data: payload,
|
||||
);
|
||||
|
||||
// 백엔드 응답 처리: 직접 회원 객체가 반환되거나 {member: {...}} 형태로 반환될 수 있음
|
||||
|
||||
Reference in New Issue
Block a user