11/28~주말 조금~12/01 진행 내용
상태 관리를 기존의 수동 sealed class 방식에서 Freezed 기반 구조로 정리함.
특히 화면 성격별로 플래그 기반 단일 State와 유니온 타입 State를 구분하여 적용함.
메인 화면은 동시에 여러 데이터를 들고 있고, 부분 업데이트가 잦음.
따라서 여러 클래스로 나누기보다 하나의 State 안에 플래그로 묶는 방식이 적합하다고 판단함.
class MainScreenState with _$MainScreenState {
const factory MainScreenState({
(UserRegistrationData()) UserRegistrationData userData,
(<Pet>[]) List<Pet> pets,
(false) bool isLoading,
String? error,
}) = _MainScreenState;
}
이 구조는 다음과 같은 장점을 가짐:
copyWith()로 필요한 부분만 갱신하기 쉬움if (state.isLoading) 같은 단순 조건으로 분기함등록 플로우는 단계가 명확한 구조라 유니온 타입이 더 자연스러움.
각 단계가 “명시적으로 표현되는” 것이 중요하다고 판단해 Freezed의 union type을 그대로 유지함.
class PetRegistrationState with _$PetRegistrationState {
const factory PetRegistrationState.initial(Pet pet) = PetRegistrationInitial;
const factory PetRegistrationState.loaded(Pet pet) = PetRegistrationLoaded;
const factory PetRegistrationState.loading() = PetRegistrationLoading;
const factory PetRegistrationState.success(Pet pet) = PetRegistrationSuccess;
const factory PetRegistrationState.failure(String message) = PetRegistrationFailure;
}
유니온 타입의 장점은 다음과 같음:
when, map으로 모든 상태를 반드시 처리하게 되어 실수 방지정리:
변경 사항 자동 반영, 요청 버전 관리, 타이밍 문제 해결을 위해 Repository 레이어에 ReactiveX 패턴을 도입함.
각 Repository에서 BehaviorSubject<String?>를 사용해 “어떤 유저의 데이터가 변경됐는지”를 스트림 형태로 발행함.
final _userUpdateSubject = BehaviorSubject<String?>.seeded(null);
Stream<String?> get userUpdates => _userUpdateSubject.stream;
Future<void> registerUser(String userId, UserRegistrationData data) async {
await dataSource.registerUser(userId, data);
_userUpdateSubject.add(userId);
}
BehaviorSubject의 핵심 특징은 다음과 같음:
MainScreenCubit은 userUpdates 스트림을 구독하며,
switchMap을 이용해 이전 API 요청을 취소하고 마지막 요청만 처리하는 방식을 적용함.
_userUpdateSubscription = userRegistrationRepository.userUpdates
.where((userId) => userId != null && _currentUserId == userId)
.switchMap(
(userId) => Stream.fromFuture(
userRegistrationRepository.getUserDataByUserId(userId!),
),
)
.listen(
(userData) {
emit(state.copyWith(
userData: userData ?? state.userData,
error: null,
));
},
);
이 방식의 장점은 다음과 같음:
요약하면,
BehaviorSubject는 최신 이벤트 보장, switchMap은 요청 버전 관리
이 두 가지가 결합해 타이밍 이슈 전반이 해결됨.
FastAPI로 직접 로컬 서버를 구축해 실제 API 구조와 더 유사한 환경을 구성함.
user_id_counter, pet_id_counter로 ID 자동 증가이 구조로 Flutter 측의 loginUserId와 서버 내부 primary key를 명확히 분리할 수 있었음.
FastAPI에서 CORS 설정 후 Flutter에서 바로 호출하도록 구성했고,
Flutter 앱에서는 환경 변수를 다음과 같이 주입하는 방식으로 관리함.
flutter run --dart-define=API_BASE_URL=http://192.168.0.2:3000
환경별 서버 주소를 유연하게 바꿀 수 있는 구조로 개선함.
FastAPI와 통신하는 Flutter 클라이언트를 Retrofit + Dio 기반으로 재구성함.
@RestApi(), @GET, @POST로 인터페이스만 작성build_runner로 *.g.dart, *.freezed.dart 파일 생성일관된 API 모델과 상태 모델을 유지할 수 있고, 404 응답을 에러가 아닌 “데이터 없음”으로 처리하는 식의 흐름 제어가 용이함.
if (e is DioException && e.response?.statusCode == 404) {
return null;
}
등록 정보가 없을 때는 비정상 상황이 아니므로 이렇게 처리함.
listenWhen: (previous, current) {
return previous.user?.id != current.user?.id;
},
BlocListener는 첫 빌드 때 동작하지 않기 때문에,
필요한 경우 addPostFrameCallback으로 초기 loadData를 강제 실행하도록 구성함.
if (!state.isLoading &&
state.userData.nickname == null &&
state.pets.isEmpty &&
loginState.loginUserId != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<MainScreenCubit>().loadData(loginState.loginUserId!);
});
}
BehaviorSubject의 캐싱과 결합되면서
“초기 진입 / 로그인 변경 / 등록 완료 후 복귀 등 모든 타이밍에서 최신 상태 유지”가 가능해짐.
여기서부터 팀장님 피드백
현재 DataSource가 단일 구현체라 Repository가 구체 클래스에 직접 결합된 구조.
테스트용 목업과 실제 API를 동일한 흐름으로 교체하려면 DataSource를 인터페이스로 추상화하는 구조가 필요.
개선 방향 요약
abstract UserRegistrationDataSource 정의MainScreen은 flag 기반 단일 state인데,
UserRegistration / PetRegistration은 union type 구성.
등록 플로우는 단계가 많지 않고 전이가 단순하므로,
MainScreen과 동일하게 단일 state + flag 기반이 더 적합한 구조.
예시 형태
class PetRegistrationState with _$PetRegistrationState {
const factory PetRegistrationState({
(Pet.empty()) Pet pet,
(false) bool isLoading,
String? error,
}) = _PetRegistrationState;
}
핵심 개념
Freezed는 상태 변경 지점을 제한하는 목적.
state는 불변이며, 모든 변경은 copyWith 기반으로만 생성.
상태 추적, 디버깅, 변경 히스토리 파악이 쉬운 구조.
현재 모델은
fromJson / toJsonEquatable 기반 비교필드 추가·변경 시 JSON 변환 코드도 매번 직접 수정해야 하는 구조라 유지보수 비용 증가.
개선 방향
모델을 Freezed + json_serializable 기반으로 재구성.
기대 효과 요약
이번 리팩까지 하고나면 플러터 학습 마무리하고 앱 실제 구조 살펴볼 예정
서비스 리뉴얼 참여(약 1주)해서 프레임워크 안쓰고 순수 웹 3대장으로 UI 고치고 배포하는 과정에서 병목 등의 문제 해결..?