Skip to content

grrooom/iOS

Repository files navigation

그린룸

header

app_store

목차

프로젝트 소개

식물을 캐릭터화하고, 관리 알림과 일지 기능을 제공하는 모바일 애플리케이션입니다.

개발 기간

1차 배포: 2025.01.02 ~ 2025.04.09 (약 4개월)

팀 구성 및 역할

Flutter(iOS) 1명, AOS 1명, BE 1명

기술 스택

  • 언어: Dart

  • 프레임워크: Flutter

  • 아키텍쳐: Bloc
    architecture

  • 사용한 패키지

    # State Management
    flutter_bloc: ^9.0.0
    
    # Navigation
    go_router: ^14.6.2
    
    # HTTP Client
    dio: ^5.8.0+1
    
    # Local Storage
    flutter_secure_storage: ^9.2.4
    shared_preferences: ^2.5.2
    
    # Notification
    firebase_core: ^3.10.1
    firebase_messaging: ^15.2.1
    
    # JSON Serialization
    freezed_annotation: ^3.0.0
    json_annotation: ^4.9.0
    
    # UI/UX
    cupertino_icons: ^1.0.8
    flutter_svg: ^2.0.16
    extended_image: ^9.1.0
    flutter_screenutil: ^5.9.3
    smooth_page_indicator: ^1.2.0+3
    contentsize_tabbarview: ^0.0.2
    
    # Utils
    permission_handler: ^11.4.0
    image_picker: ^1.1.2
    email_validator: ^3.0.0
    intl: ^0.19.0
    rxdart: ^0.28.0

기능 실행 화면

회원가입 로그인 그린룸 추가
sign_up sign_in add_green_room
그린룸 상세 & 메모 추가 프로필 수정
green_room_detail profile

주요 기술

✅ 회원가입 시, 딥링크를 이용한 이메일 토큰 검증 로직

email_verification

redirect: (context, state) {
  // 딥링크를 통해 전달된 토큰이 있는지 확인
  if (state.uri.queryParameters.containsKey('token')) {
    final token = state.uri.queryParameters['token'];
    return '/sign_up_password?token=$token'; // 리다이렉트
  }
  return null;
},

routes: [
  GoRoute(
    path: '/sign_up_password',
    builder: (_, state) {
      final token = state.uri.queryParameters['token']!;
      return SignUpPasswordScreen(token: token);
    },
  ),
],
@override
void initState() {
  super.initState();

  // SignUpPasswordScreen에서 토큰 검증 요청
  context.read<SignUpBloc>().add(
    SignUpEvent.emailTokenValidationRequested(widget.token),
  );
}

✅ Dio Interceptor를 활용한 JWT 토큰 인증 및 자동 갱신 구현

  • 모든 API 요청 헤더에 인증 토큰을 추가하고 401 에러 발생 시 Refresh Token으로 Access Token을 자동 갱신
  • 갱신된 토큰으로 실패한 요청을 재시도하여 사용자 경험 끊김 방지
  • dio_interceptor.dart

jwt_token_refresh_flow


✅ ThemeExtension을 활용한 디자인 시스템 구축

  • 다크 모드테마 전환과 같은 요구사항에도 유연하게 대응할 수 있도록 확장성을 고려
  • 확장 메서드를 통해 코드의 가독성과 접근성을 향상
  • app_colors_theme.dart
  • app_text_theme.dart
// 확장 메서드 적용
extension AppThemeX on ThemeData {
  AppColorsTheme get appColors => extension<AppColorsTheme>()!;
  AppTextTheme get appTexts => extension<AppTextTheme>()!;
}
// 사용예시
Text(
  '그린룸',
  style: Theme.of(context).appTexts.body2.copyWith(
    color: Theme.of(context).appColors.green300,
  ),
)

트러블슈팅

💥 검색 기능에서 과도한 API 호출 최적화

문제 상황

식물 검색 기능에서 사용자가 글자를 입력할 때마다 API가 실시간으로 호출되면서 서버에 과부하가 발생했습니다. 특히 한글 입력 특성상 자음과 모음이 조합되는 과정에서, 예를 들어 "몬스테라" 한 단어를 검색할 때도 9번이나 API 호출이 이루어졌습니다. 이로 인해 불필요한 네트워크 트래픽이 급증하고, 사용자 경험이 크게 저하되는 문제가 발생했습니다.

해결 방법

RxDartdebounceTime 연산자를 활용해 사용자가 연속 입력하는 동안 API 호출을 지연시키고, 타이핑이 멈춘 뒤 500밀리초 후에만 실제 검색 요청을 보내도록 구현했습니다. 또한 BLoC 패턴의 이벤트 스트림에 transformer를 적용해 마지막 검색 이벤트만 처리함으로써, 완성되지 않은 중간 검색어로 인한 불필요한 API 호출을 효과적으로 줄일 수 있었습니다.

해결 코드

// AddGreenRoomBloc에서 debounce 적용
on<_SearchPlantsFetched>(
 _onSearchPlantsFetched,
 transformer: (events, mapper) =>
     events.debounceTime(const Duration(milliseconds: 500)).flatMap((event) => mapper(event)),
);

// 검색어 입력 시 이벤트 발생
GRRTextfield(
 controller: _searchTEC,
 hintText: '식물 이름 또는 품종 검색',
 isSearch: true,
 onChanged: (text) => context
     .read<AddGreenRoomBloc>()
     .add(AddGreenRoomPlantEvent.searchPlantsFetched(text)),
),

About

내 손 안의 재미있는 식물 키우기

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages