[Flutter] Splash 화면을 통한 초기 로딩 구현하기


이슈

다양한 상태를 관리해야 하는 앱을 개발하게 될 시에는 앱 최초 실행 시에 초기 로딩이 필요하고, 대부분 이 과정을 splash 화면을 사용자에게 보여주며 수행하고 있다. 단순히 디자인적인 요소로 splash 화면을 넣을 수는 있지만 실제로 이 과정에서 앱의 초기 로딩을 효율적으로 수행할 수 없을까?


해결

핵심

main 함수에서 runApp() 을 통해 최초로 수행되는 WidgetFutureBuilder 를 적용한다.

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void main() {
  runApp(MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Init.instance.initialize(context),
      builder: (context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return MaterialApp(home: SplashScreen()); // 초기 로딩 시 Splash Screen
        } else if (snapshot.hasError) {
          return MaterialApp(home: ErrorScreen()); // 초기 로딩 에러 시 Error Screen
        } else {
          return MaterialApp( 
            title: 'Flutter',
            theme: ThemeData(
                primarySwatch: Colors.blue
            ),
            home: snapshot.data, // 로딩 완료 시 Home Screen
            builder: (context, child) => MediaQuery(
                child: child!,
                data: MediaQuery.of(context).copyWith(
                    textScaleFactor: MediaQuery.of(context)
                        .textScaleFactor
                        .clamp(0.95, 1.05))), // 폰트 스케일 범위 고정
          );
        }
      },
    );
  }
}

class Init {
  Init._();
  static final instance = Init._();

  Future<Widget?> initialize(BuildContext context) async {
    // await Future.delayed(Duration(milliseconds: 1000));
    
    // . . .
    // 초기 로딩 작성
    // . . .

    return HomeScreen(); // 초기 로딩 완료 시 띄울 앱 첫 화면
  }
}


고찰

동작 원리

동작 원리는 간단하다. 앱 최초 실행 시 FutureBuilder를 통해 snapshotconnectionState로 화면에 띄울 widget을 구분해주는 것이다. 이때 connectionState를 결정 지을 future 요소를 static 으로 선언한 Init 클래스 인스턴스의 future method로 잡아준다. 여기서는 initialize() 가 된다. 따라서 initialize()가 비동기적으로 수행되는 동안 connectionStatewaiting 을 유지하고 SplashScreen()을 return하게 된다. 이때 initialize() 과정 중 오류가 발생하여 snapshot 에 오류가 생기면 앱 실행 실패 화면 ErrorScreen()을 return하고, 이상이 없을 경우 initialize()가 return한 Widgetsnapshot으로 하여금 MaterialApp의 홈스크린으로 화면에 띄우게 된다. 여기서는 HomeScreen()이 된다.

초기 로딩 예시

초기 로딩 시에 주로 수행하는 일 중에는 다음과 같은 예시들이 있다.

  • Secure Storage 또는 SharedPreference에 접근하여 자동 로그인 및 사용자 설정값 로딩
  • Push Notification 을 구현하였다면 Firebase Cloud Message 초기 세팅
  • 이 외의 다양한 네트워크 비동기 통신

Splash Screen 표시 최소 시간 설정

또한, 초기 로딩 과정이 너무 가벼운 탓에 SplashScreen을 거의 보지 못하고 넘어가게 되는 상황이 있을 수 있다. 이럴 때는 위 코드에서 주석 처리된 await Future.delayed() 를 통해 SplashScreen을 표시할 최소 시간을 설정할 수 있다. 다만 너무 시간을 길게 잡을 경우 사용자가 불편함을 느낄 수 있으므로, 1초 ~ 1.5초 정도로 길지 않게 잡는 것이 좋다.

0%