본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓24(fixing Getx router error)

이번에는 getx routing 관련 개인적으로 경험한 오류/문제에 대해서 개인적인 해결 방법을 정리했습니다.

 

 

 

먼저 어플이 시작되는 순서를 정리해보았습니다. 순서는 아래와 같습니다.

1. 3초 동안 SplashScreen 화면이 보이고 3초 이후에는 AppleApp() 로 이동

2. AppleApp() 에서 UserController 객체 생성 및 listen 등록( FirebaseAuth.instance.authStateChanges().listen((user) {} )

        UserController 클래스 정보는 "더보기"   참고.

더보기
class UserController extends GetxController {
  static UserController get to => Get.find();

  final _user = Rxn<User?>();
  Rxn<User?> get user => _user;

  @override
  void onInit() {
    // getx 인스턴스 생성전에 호출됨, 인스턴스와 관련된 것을 호출시 주의할것,
    debugPrint('************************* >>> UserController >> onInit');
    super.onInit();
  }

  @override
  void onReady() {
    // TODO: implement onReady
    super.onReady();
    debugPrint('************************* >>> UserController >> onReady');
    initUser();
    // logger.d('(onReady)user status - $user');
  }

  void initUser() {
    FirebaseAuth.instance.authStateChanges().listen((user) async {
      // user 정보가 변경되면, 호출됨,
      debugPrint('************************* (UserController/listen)');
      _user.value = user;
      Get.offAllNamed('/');
      logger.d('(listen) user status - $user');
    });
  }
}

 

3. 설정된 initialRoute(미설정시 기본값은 '/') 를 참고하여 '/' 로 이동, GetPage 설정에서 지정한 middlewares(CheckAuth() 함수로 이동) 에서 UserController.user 변수의 상태 확인하여 redirect 처리한다. 자세한 코드는  "더보기"   참고.

더보기
List<GetPage<dynamic>> getPages() {
  return [
    GetPage(
      name: '/',
      page: () => const HomeScreen(),
      middlewares: [CheckAuth()], // 미들웨어를 먼저 확인하고 "page:" 로 이동함
    ),
    GetPage(
      name: '/auth',
      page: () => StartScreen(),
      // transition: Transition.fadeIn,
    ),
    // GetPage(name: '/user/:uid', pages: () => const UserInfoPage()),
  ];
}

 

class CheckAuth extends GetMiddleware {
  @override
  int? get priority => 2;

  bool isAuthenticated = false;

  @override
  RouteSettings? redirect(String? route) {
    debugPrint('************************* (CheckAuth): ' + UserController.to.hashCode.toString());

    if (UserController.to.user.value == null) {
      return const RouteSettings(name: '/auth');
    }
    return null;
  }
}

 

4. UserController.user 변수의 상태가 'null' 이면 로그인 과정으로 이동하고, 값이 있다면 HomeScreen('/') 으로 이동

5. 로그인 과정에서 정상적으로 로그인이 완료되면 user 의 상태가 변경되므로 2번에서 설명한 listen 이 자동으로 호출되고, 라우팅이 다시 초기 페이지('/') 로 이동하면서 HomeScreen 으로 이동한다.

 

 

 

 

 

최초 빌드할때는 문제가 없으나 hot reload 또는 hot restart 를 시도하면 특정 시점에서 GetMaterialController 종료되었다가 다시 실행되는 문제이다. 이 시점에서 페이지 이동이 발생하면 추가적인 오류도 발생 가능하다.

 

[GETX] "GetMaterialController" onDelete() called
[GETX] "GetMaterialController" deleted from memory
...
...
[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized

 

 

 

이 부분은 정확한 원인은 알수 없지만, main 함수 내부의 FutureBuilder 에서 조건을 아래처럼 변경했다. 구글링 결과로는 2개의 차이가 거의 없는것으로 나오는데, 정확한 개선 이유나 원인은 아직 모르겠음,

단순한 타이밍 문제라면 이후 개발 과정에서 다시 발생할 가능성이 높기때문에 차후에 다시 업데이트 예정입니다.

 

이전 조건 - else if (snapshot.connectionState == ConnectionState.done) 

새로운 조건 - else if (snapshot.hasData) 

 

// main.dart 내부
class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Object>(
        future: Future.delayed(const Duration(seconds: 2), () => 100),
        builder: (context, snapshot) {
          // 장면전환을 천천히 부드럽게 처리하는 위젯
          return AnimatedSwitcher(
              duration: const Duration(milliseconds: 500), child: _splashLoadingWidget(snapshot));
        });
  }

// FutureBuilder 에서 딜레이 있음, 딜레이 동안은 SplashScreen(), 딜레이후에는 AppleApp() 로 이동
  StatelessWidget _splashLoadingWidget(AsyncSnapshot<Object> snapshot) {
    // future has 3 state, hasError, hasData, waiting
    if (snapshot.hasError) {
      logger.d('error occur while loading ~');
      return const Text('Error Occur');
      } else if (snapshot.hasData) {
    // } else if (snapshot.connectionState == ConnectionState.done) {
      logger.d('data is ${snapshot.data.toString()}');
      return AppleApp();
    } else {
      return const SplashScreen();
    }
  }
}

 

 

 

핵심 내용은

1. CheckAuth 로직 간결하게 정리

2. user_state.dart 정리(UserController 클래스)

3. main.dart 의  FutureBuilder 조건 수정.