본문 바로가기

일::개발

Flutter: GetX 와 FutureBuilder

StatefulWidget 을 사용해서 만들었던 위젯을 GetX의 Dependency Injection을 이용해서 View - Controller 패턴으로 수정하는 과정에서 GetX와 FutureBuilder를 잘못 사용했던 케이스

// main.dart
// App Main class

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  final AppController c = Get.put(AppController());

  @override
  Widget build(BuildContext context) {
    return FutureBuilder( 
    	future: AppController.to.initialize(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
            return Center(child: Text('Initialization Success.');
          } else if (snapshot.hasError) {
          	return Center(child: Text('Initialization Failed');	// 초기화 실패
          } else {
            return Center(child: Text('Initializing...');		// 초기화중. Splash 화면
          }
        },
    );
  }
}


// app_controller.dart
// Main Controller class

class AppController extnds GetxService {
  static AppController get to => Get.find();

  @override
  void onInit() {
    super.onInit();
  }
  
  Future<void> initialize() async {
    await initSettings();	// 이하 임의의 초기화 루틴들
    await checkVersions();
    await initFirebase();
    ....
    
    return;
  }
}

 

이런 패턴으로 작성하는 경우 별 문제 없이 동작하는 것처럼 보이지만, MyApp 의 build() 가 다시 호출될 때마다 AppController의 initialize() 함수가 다시 실행되어서 초기화 과정이 반복 실행되는 문제가 생긴다.

 

그래서 FutureBuilder의 future 에서는 function을 직접 호출하지 말고, 그 전에 호출된 Future 를 기다리는 형태로 처리해야 한다.

StatefulWidget의 경우에는 아래와 같은 패턴으로 먼저 initState() 에서 초기화 함수를 호출하고, 그 결과를 FutureBuilder의 future에 지정해주는 방식으로 처리해주면 깔끔하다.

class MyApp extends StatefulWidget {
  MyApp({Key? key}) : super(key: key);
  
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  Future<InitResult> _initResult;
  
  @override
  void initState() {
    _initResult = _initializeApp();
    super.initState();
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _initResult,
      builder: (context, snapshot) {
        .......
      }
    );
  }
  
  Future<InitResult> _initializeApp() async {
    await .......
    await .......	// 초기화

    return ret;
  }

 

GetX 를 사용하는 경우에는 대략 아래와 같이 FutureBuilder 대신 Obs, Obx를 이용하면 되겠다.

 

// main.dart
// App Main class

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  final AppController c = Get.put(AppController());
  
  @override
  Widget build(BuildContext context) {
    return Obx(() { 
          if (c.isInitialized.value) {
            return Center(child: Text('Initialization Success.');
          } else {
            return Center(child: Text('Initializing...');		// 초기화중. Splash 화면
          }
        }
    );
  }
}


// app_controller.dart
// Main Controller class

class AppController extnds GetxService {
  static AppController get to => Get.find();

  final isInitialized = false.obs;

  @override
  void onInit() {
    initialize();
    super.onInit();
  }
  
  @override
  void onClose() {
    isInitialized.valie = false;
    super.onClose();
  }
  
  Future<void> initialize() async {
    await initSettings();	// 이하 임의의 초기화 루틴들
    await checkVersions();
    await initFirebase();
    ....
    
    isInitialized.value = true;
    return;
  }
}