Flutter 기능 개발 순서

 ・ 6 min

photo by Valeria Kodra on Unsplash

Flutter로 기능을 개발하는 순서를 정리하려고 해요.
기능 정의 및 요구 사항 분석 이후 대략적인 화면은 나와야 하고 UI 및 UX 설계 전까지는 화면 디자인이 나와야지 화면 개발을 할 수 있을 거예요.

개발 순서#

1. 기능 정의 및 요구 사항 분석#

  • 먼저, 특정 기능(하나의 흐름)이 어떻게 작동해야 하는지 명확히 정의해요.
  • 예를 들어, 약속 생성이라면 사용자가 약속 제목, 날짜, 시간, 장소 등을 입력하고 저장할 수 있는 기능을 만들어요.

2. 도메인 모델 정의 (Domain Model)#

  • 기능을 수행할 수 있는 대략적인 도메인 모델을 정의합니다. 약속과 관련된 경우 입력 값들이 저장되는 모델과 UI에서 처리되어야 하는 데이터들이 필요한 모델 등 여러 모델이 하나의 기능을 위해 만들어질 수 있어요.
  • 모델은 도메인 레이어에서 애플리케이션의 핵심 비즈니스 로직과 데이터를 캡슐화해요.
class Appointment {
  final String title;
  final DateTime date;
  final String location;
 
  Appointment({
    required this.title,
    required this.date,
    required this.location,
  });
}

3. 저장소 패턴 구현 (Repository Pattern)#

  • 도메인 모델을 저장하고 가져오는 작업을 처리하기 위해 Repository를 설계해요.
  • 예를 들어, 약속 데이터를 로컬 DB 또는 원격 서버에 저장하고 가져오는 로직을 구현해요.
abstract class AppointmentRepository {
  Future<void> saveAppointment(Appointment appointment);
  Future<List<Appointment>> getAppointments();
}
  • 이때, 실제 데이터 소스를 다루는 LocalAppointmentRepositoryRemoteAppointmentRepository와 같은 구체적인 구현을 작성해요.
class LocalAppointmentRepository implements AppointmentRepository {
  @override
  Future<void> saveAppointment(Appointment appointment) async {
    // Local storage logic here
  }
 
  @override
  Future<List<Appointment>> getAppointments() async {
    // Retrieve data from local storage
  }
}

4. 어플리케이션 서비스 레이어 정의 (Application Layer)#

  • 도메인 모델과 저장소를 사용하여 비즈니스 로직을 수행하는 서비스 클래스를 정의해요.
  • 이 레이어는 약속 생성과 같은 비즈니스 로직을 캡슐화하여 Presentation Layer에서 쉽게 호출할 수 있도록 해요. 꼭 그런 경우는 아니지만, 여기서만 도메인이 만들어져야 한다는 강제성을 부여하는 상황도 만들어도 좋을 거예요. 데이터를 조회하는 경우는 이미 도메인을 갖고 있기 때문에 아닐 수도 있지만요.
class CreateAppointmentService {
  final AppointmentRepository repository;
 
  CreateAppointmentService(this.repository);
 
  Future<void> createAppointment(String title, DateTime date, String location) async {
    final appointment = Appointment(title: title, date: date, location: location);
    await repository.saveAppointment(appointment);
  }
}

5. 프레젠테이션 레이어 설계 (Presentation Layer)#

  • 프레젠테이션 레이어는 UI와 애플리케이션 상태를 관리하는 부분이에요.
  • Riverpod을 활용하여 상태를 관리하고, UI를 사용자와 상호작용하도록 설계해요.
  • repository, service도 riverpod을 이용해요.
part 'appointment_controller.g.dart';
 
@riverpod
class AppointmentController extends _$AppointmentController {
  @override
  Appointment build() async {
	  return Appointment.empty();
  }
 
Future<Appointment> createAppointment(String title, DateTime date, String location) {
	state = state.copyWith(title: title, date: date, location: location);
     final service = ref.read(createAppointmentServiceProvider);
     final appointment = await service.createAppointment(title, date, location);
	 return appointment;
  }
}
  • UI에서 이 appointmentControllerProvider를 사용하여 상태를 읽고, 약속을 생성할 수 있도록 해요..
class CreateAppointmentScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final appointment = ref.watch(appointmentControllerProvider);
 
    return Scaffold(
      appBar: AppBar(title: Text('Create Appointment')),
      body: Column(
        children: [
          // Input fields for title, date, location...
          ElevatedButton(
            onPressed: () {
              // Trigger createAppointment
            },
            child: Text('Save Appointment'),
          ),
        ListTile(
			  title: Text(appointment.title),
			  subtitle: Text('${appointment.date} at ${appointment.location}'),
          ),
        ],
      ),
    );
  }
}

6. UI 및 UX 설계#

  • 위의 프레젠테이션 레이어에 따라 UI를 설계하고, 필요한 폼과 위젯을 구현해요.
  • 화면 전환, 입력 검증, 사용성 테스트 등을 통해 최종 UI를 완성해요.
  • UI에서 어떤 위젯은 normal, loading, success, error, empty와 같은 상황이 있으니 상황별로 어떻게 처리되고 어떻게 보일지 결정되어야 해요.

7. 테스트 작성#

  • 작성한 각 레이어(도메인 모델, 저장소, 서비스, 프레젠테이션)에 대해 단위 테스트 및 통합 테스트를 작성해요.
  • TDD 방식으로 각 단계에서 테스트를 진행하는 것도 좋아요.
  • 테스트를 통해 애플리케이션이 의도한 대로 동작하는지 검증해요.

8. 결합 및 최적화#

  • 모든 구성 요소를 결합하여 전체 기능을 테스트하고, 필요에 따라 최적화 작업을 수행해요.
  • main 브랜치와 병합하면서 배포 자동화를 통해 신규 버전을 출시해요.

정리#

  1. 기능 정의 및 도메인 모델 설계.
  2. 저장소 패턴을 통해 데이터 저장 및 관리.
  3. 애플리케이션 서비스 레이어에서 비즈니스 로직 구현.
  4. 프레젠테이션 레이어를 Riverpod으로 상태 관리 및 UI 구현.
  5. UI 설계 및 UX 최적화.
  6. 테스트 작성 및 검증.

개발 순서와 더불어 알아두면 도움이 되는 것들:
애자일 방법 도입하기: 스프린트 설정 -> 일정 기간 동안 특정 기능 집중 개발 -> 큰 프로젝트면 작은 단위로 나눠서 개발, 우선순위 설정.
TDD 사용하기: 코드 작성 전 테스트 케이스 먼저 작성 -> 테스트 통과할 코드 작성
DevOps 도입하기: 코드 리뷰, 테스트 자동화 & CI/CD

상태 관리#

상태 관리란: UI와 데이터(상태) 간의 일관성을 유지하는 방법. 데이터(상태)가 변경될 때마다, UI가 이에 따라 업데이트되어 사용자에게 최신 정보를 보여줄 수 있도록 하는 거예요. 상태 관리 라이브러리를 쓰는 이유는 코드 편의성과 렌더링 효율성을 위해서 사용해요.

비동기 요청, 캐시, 오류 및 로딩 상태 처리는 아래의 기능들이 영향을 받아요.

  • 당겨서 새로 고침
  • 무한 목록 / 스크롤할 때 가져오기
  • 타이핑하는 동안 검색
  • 비동기 요청의 디바운싱(Debouncing)
  • 더 이상 사용되지 않을 때 비동기 요청 취소
  • 낙관적(Optimistic) UI
  • 오프라인 모드
  • ...

단순한 상태 관리는 Riverpod 사용을 지양해요. Riverpod에서는 Riverpod을 사용하기 적합한 상태 관리 사례로 비즈니스 로직인 경우에만 사용하기를 권장하고 있어요. Riverpod을 사용해서 UI 로직과 상태 관리를 분리하는 경우에 사용하세요.
Riverpod은 오류, 로딩 상태가 필요한 데이터를 처리하기 위해 사용한다고 생각하면 제한적으로 사용할 수 있어요.

로컬 위젯 상태에 provider를 사용하지 마세요
Provider는 공유 비즈니스 상태(shared business state)용으로 설계되었어요.

로컬 위젯 상태와 같은 용도로는 사용하기에 적합하지 않아요:

  • 양식(form) 상태 저장
  • 현재 선택된 항목
  • 애니메이션
  • 일반적으로 Flutter가 "controller"(예: TextEditingController)로 처리하는 모든 것
    로컬 위젯 상태를 처리하는 방법을 찾고 있다면 대신 flutter_hooks를 사용하는 것이 좋습니다.

이를 권장하지 않는 이유 중 하나는 이러한 상태가 경로로 범위가 지정(scoped to a route)되는 경우가 많기 때문이에요. 그렇게 하지 않으면 새 페이지가 이전 페이지의 상태를 재정의하기 때문에 앱의 뒤로 가기 버튼이 손상될 수 있어요.


You have to give up some of the old so that you can make room for the new.

— Yanni


Other posts
cover_image
 ・ 1 min

블로그에 적용할 것들!

cover_image
 ・ 3 min

NEXT.js에서 public 폴더 외에서 이미지 사용하기

cover_image
 ・ 2 min

42서울을 준비하는 사람들을 위한 책 추천