ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Flutter] 상태 변수 최적화 - late final 하나로 코드가 안정적
    IT 2025. 4. 22. 10:52

    Flutter에서 화면을 구성하다 보면 위젯이나 상태값을 나중에 초기화해야 하는 경우가 많습니다.
    저도 앱의 MainScreen을 구현하면서 상태 변수 관리가 자꾸 불편했는데,
    이번에 late final 키워드를 도입하면서 코드가 놀랍게 깔끔해졌어요.

     

    💡 late, final, late final이 뭔가요?

    먼저 Dart의 세 가지 변수 선언 키워드를 간단히 정리해볼게요:

    키워드의미
    final 한 번만 값을 할당할 수 있어요. 선언과 동시에 초기화해야 해요.
    late 나중에 초기화할 수 있어요. 단, 반드시 한 번은 초기화해야 해요.
    late final 나중에 단 한 번만 초기화할 수 있어요. 즉, 늦게 주지만 바꾸진 않겠다는 뜻!

     

     

    😕 기존 코드 – ScheduleScreen을 다룰 때 불편했던 점

    ScheduleScreen? _scheduleScreen;
    
    @override
    void initState() {
      _scheduleScreen = ScheduleScreen(key: scheduleScreenKey);
    }

     

    • nullable(?) 처리로 인해 사용할 때마다 _scheduleScreen?.doSomething() 이런 식으로 null 체크를 해야 했고,
    • final이 아니어서 실수로 재할당할 위험도 있었어요.

     

    ✅ 개선 후 – late final로 선언하니 이렇게 좋아졌어요

    late final ScheduleScreen _scheduleScreen;
    
    @override
    void initState() {
      super.initState();
      _scheduleScreen = ScheduleScreen(key: scheduleScreenKey);
    }

    이렇게 하면:

    • null이 아니기 때문에 ?.가 아니라 .로 바로 접근 가능
    • final이라서 딱 한 번만 초기화 가능 → 실수 방지
    • 선언과 초기화 시점을 분리하면서도 안전함 유지!

     

    전체 적용된 MainScreenState 일부 예시

    class MainScreenState extends State<MainScreen> {
      int selectedIndex = 0;
      final scheduleScreenKey = GlobalKey<ScheduleScreenState>();
    
      late final ScheduleScreen _scheduleScreen; // 개선 포인트!
    
      @override
      void initState() {
        super.initState();
        selectedIndex = widget.externalTab ?? 0;
        _scheduleScreen = ScheduleScreen(key: scheduleScreenKey);
        // ...
      }
    
      @override
      Widget build(BuildContext context) {
        return IndexedStack(
          index: selectedIndex,
          children: [
            HomeScreen(...),
            _scheduleScreen, // null 체크 없이 안전하게 사용
            ...
          ],
        );
      }
    }

     

     

    🎁 보너스 - 하단 네비게이션도 컴포넌트로 분리했어요

    이번 기회에 MainBottomBar와 MainFloatingButton도 따로 분리했는데요,
    하단 탭바와 가운데 채팅 버튼을 각각 위젯으로 만들고 나니 UI가 훨씬 읽기 쉬워졌습니다.

    MainBottomBar(
      selectedIndex: selectedIndex,
      onTabSelected: setPage,
      isGuardian: UserManager.currentUser?.is_guardian == true,
    ),
    MainFloatingButton(
      isGuardian: UserManager.currentUser?.is_guardian == true,
      onTapGuardian: () => setPage(1),
      onTapUser: () async {
        await Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => ChatScreen(category: 0)),
        );
        setState(() {});
      },
    ),

     

     

    💥 그런데 late final의 단점도 있어요

    단점설명
    ❗ 초기화 전에 접근 시 런타임 에러 초기화 전에 사용하면 LateInitializationError가 발생해요.
    ❗ 초기화 여부가 IDE에서 보이지 않음 변수 선언만 보고는 언제, 어디서 초기화되는지 한눈에 파악하기 어려울 수 있어요.
    ❗ 과용하면 혼란 초래 무조건 late를 남용하면 코드가 예측 불가능해질 수 있어요.
    print(_scheduleScreen); // ❌ 아직 초기화되지 않았다면 런타임 에러!

    그래서 사용 시엔 정확한 초기화 흐름을 보장할 수 있을 때만 사용하는 게 중요해요.

     

    🧠 최종 요약

    항목개선 전개선 후
    변수 선언 nullable + 재할당 가능 non-null + 재할당 불가
    코드 안정성 null 체크 필수 null 걱정 없음
    가독성 흐름 분산 선언 → 초기화 → 사용 흐름 명확
    UI 구조 MainScreen에 모두 포함 컴포넌트 분리로 가독성 향상

    ✨ 후기

    처음엔 작은 개선이라고 생각했지만,
    late final 하나로 변수의 안전성과 명확성을 동시에 얻을 수 있었습니다.
    특히 화면 초기화 타이밍이나 BuildContext가 필요한 위젯을 다룰 때는 꼭 써보시길 추천드려요!

     

Designed by Tistory.