[Flutter] API 호출 횟수 줄이기 위해 전역 상태 사용하기 | GetX

리팩토링 전

API 호출 최적화에 대한 고민

기존에는 탭에 들어갈 때마다 `initState()`에서 api를 호출해서 데이터를 가져오는 구조였다.

그래서 탭에 들어갈 때마다 로딩스피너가 돌아가다가 데이터가 뜨는 것을 볼 수 있다.

 

그런데 교정일기 앱은 사용자가 데이터를 추가하는 경우가 아니면 데이터를 업데이트할 필요가 없기 때문에

불필요한 api 호출을 줄이기 위해 리팩토링을 진행했다.

 

일단 `AutomaticKeepAliveClientMixin`을 적용해서 탭을 이동해도 상태가 그대로 유지되도록 했다.

최초 1회만 데이터를 받으면 탭을 이동해도 상태가 그대로 유지되기 때문에

무한대로 api를 호출하던 횟수를 홈 탭 2 + 타임라인 탭 1회 + 앨범 탭 1회, 총 4회로 줄일 수 있었다.

[Flutter] 탭 이동해도 상태 유지하기 | AutomaticKeepAliveClientMixin & PageView

 

그럼에도 아직 최적화할 수 있는 부분이 남아있다고 생각했다. 타임라인 데이터로 예를 들어 설명하겠다.

  • 홈 탭과 타임라인 탭에서는 동일한 데이터를 사용하는데 홈 탭에서는 데이터를 3개만 쿼리하고, 타임라인 탭에서는 전체 데이터를 각각 호출하는 구조였다.
  • 사용자가 데이터를 추가하는 경우에 스택이 초기화되면서 모든 탭에서 api를 다시 호출하게 된다.

 

만약에 데이터를 전역 상태로 관리한다면 다음과 같이 개선될 것이라고 생각했다.

  • 타임라인 데이터를 최초 1회만 불러와서 전역 상태에 저장하고, 홈 탭에서는 `timelineData.take(3)`를 보여주고 타임라인 탭에서는 전체 데이터를 그대로 보여준다.
  • 만약 사용자가 타임라인 데이터를 추가한다면 타임라인 api만 호출해서 데이터를 업데이트한다.

 

GetX 컨트롤러 생성하기

import 'package:get/get.dart';

enum TimelineStatus { loading, success, empty, error }

class TimelineController extends GetxController {
  var timelineData = <Timeline>[].obs;
  var status = TimelineStatus.loading.obs;

  @override
  void onInit() {
    super.onInit();
    fetchTimelineData();
  }

  // Fetch timeline data from provider
  void fetchTimelineData() async {
    status(TimelineStatus.loading);

    try {
      var timelines = await TimelinesProvider.fetchTimelineList();
      if (timelines.isNotEmpty) {
        timelineData.assignAll(timelines);
        status(TimelineStatus.success);
      } else {
        status(TimelineStatus.empty);
      }
    } catch (e) {
      status(TimelineStatus.error);
    }
  }

  // Refresh timeline data
  void refreshTimelineData() {
    fetchTimelineData();
  }

  // Method to reset the controller's data (e.g., after logout)
  void resetTimelineData() {
    timelineData.clear(); 
    status(TimelineStatus.loading);
  }
}
  • 전역 상태 관리를 위해 컨트롤러 클래스를 만들었다.
  • 상태를 `obs`로 선언하면 상태가 변경되었을 때 이를 감지해서 자동으로 UI를 업데이트할 수 있다.
  • 전역 상태로 데이터를 관리하면 `FutureBuilder`를 사용할 수 없기 때문에 데이터 상태에 따라 다른 UI를 제공하기 위해 `TimelineStatus`라는 `enum`을 생성했다.

 

final TimelineController timelineController = Get.put(TimelineController());
  • `BottomNavigationBar`를 관리하는 스크린 위젯에서 컨트롤러를 초기화한다.

 

위젯에 전역 상태 적용하기

final TimelineController timelineController = Get.find<TimelineController>();
...
Expanded(
  child: Obx(() {
    switch (timelineController.status.value) {
      case TimelineStatus.loading:
        return const LoadingSpinner();
      case TimelineStatus.success:
        return _buildTimelineList(timelineController.timelineData);
      case TimelineStatus.empty:
        return Center(
          child: Text(
            Messages.noDataCreated('타임라인'),
          ),
        );
      case TimelineStatus.error:
        return ErrorScreen(
          refreshPage: timelineController.refreshTimelineData,
        );
      default:
        return const SizedBox.shrink();
    }
  }),
),
  • 타임라인 탭에서 `TimelineStatus`의 상태에 따라 사용자에게 각기 다른 UI를 제공한다.
  • 사용자가 타임라인 데이터를 추가하는 경우에는 `Get.find<TimelineController>().refreshTimelineData();`를 실행해서 데이터를 업데이트한다.

 

전역 상태 적용한 결과

리팩토링 후

 

앱이 시작되면 홈 탭을 빌드하기도 전에 `TimelineController`가 초기회되면서

`onInit()`에 `fetchTimelineData()`를 실행하기 때문에 아예 로딩스피너가 사라진 것을 확인할 수 있다.

덕분에 api 호출도 최적화하고 사용자들에게 더 빠르고 쾌적한 사용환경을 제공할 수 있게 되었다.

 

결과적으로 무한대로 api를 호출하던 횟수를 타임라인 데이터 1회 + 앨범 데이터 1회, 총 2회로 줄일 수 있었다.

또한 데이터를 추가할 경우 모든 api를 다시 호출하지 않고 변경된 데이터의 api만 최소한으로 호출할 수 있게 되었다.