Go_Router

shin·2023년 3월 13일
0

Flutter

목록 보기
12/12

설치하기

  • Go_Router를 사용하기 위해서 pubspec.yaml에 Go_Router를 추가해야한다.

Go Router

  • 설치가 끝나면 main.dart 파일에서 MaterialApp.router()라는 메소드를 사용할 수 있다.
  • router 안에는 3가지 값들이 들어간다.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  final GoRouter _router = GoRouter(routes: []);

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
			
// GoRouter에서 제공해주는 함수

	// route 정보 전달
	routeInformationProvider: _router.routeInformationProvider,
    // URI String을 상태 및 Go Router에서 사용할 수 있는 형태로 변환해주는 함수
    routeInformationParser: _router.routeInformationParser,
    // 위에서 변경된 값으로 실제로 어떤 라우트를 보여줄지 정하는 함수
    routerDelegate: _router.routerDelegate,
    );
  }
}

  • GoRouter _router에는 여러 값들이 들어가는 데 몇가지만 우선 알아보겠다.
final GoRouter _router = GoRouter(routes: []);
  • initialLocation 에는 첫 스크린 경로가 들어가게된다.
  • routes는 리스트로 값을 받고 이 곳에 screen 값들이 들어가게 된다.
  • routes: [] 안에는 GoRoute가 들어간다. path에는 경로를 지정해주고 builder에는 스크린을 지정해준다.
GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(),
      ),

Go 함수

  • 또 다른 스크린을 추가할 때는 라우트를 새로 등록을 해줘야한다.
  • 스크린을 추가할 때 2가지 방식이 있다.
  • 첫번째는 GoRoute를 밑에 추가하는 방식이다
  • 이 방식의 특징은 도매인이 독립적으로 만들어진다.`,
routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(),
      ),
      //http://~~~~/one
      GoRoute(
        path: '/one',
        builder: (context, state) => OneScreen(),
      ),
      //http://~~~~/two
      GoRoute(
        path: '/two',
        builder: (context, state) => TwoScreen(),
      ),
    ],
  • 두번째 방식은 GoRoute 안에 routes를 추가 하는 것이다.
  • 이 방식으로 코드를 작성하면 각각의 스크린들이 네스팅되어 주소가 쌓이는 구조가 된다. http://~~~/one/two
routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(),
        routes: [
          GoRoute(
            path: 'one',
            builder: (context, state) => OneScreen(),
            routes: [
              GoRoute(
                path: 'two',
                builder: (context, state) => TwoScreen(),
              ),
            ]
          ),
        ],
      ),
    ],
  • 이번 예제에서는 네스팅 구조 쓰이는 방식을 사용한다.
  • 라우트를 작성하고나면 스크린을 이동할 함수를 만들어야한다.
  • go_route를 사용하기 전에는 Navigator 를 사용해서 스크린을 이동했지만 이번에는 다른 방식으로 스크린을 이동하는 방법을 사용해 볼 것이다.
  • HomeScreen에 버튼을 추가한다. onPressed 함수 bodycontext.go()를 작성한다. go함수 안에는 이동할 페이지 주소를 넣어준다.
  • 버튼을 눌러 확인해보면 Push를 사용했던 것과 같은 효과를 볼 수 있다.
class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return DefaultLayout(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          ElevatedButton(
            onPressed: () {
              context.go('/one');
            },
            child: Text('Screen One'),
          ),
        ],
      ),
    );
  }
}

GoNamed 함수

  • 스크린을 2개 더 추가해서 총 3개를 만들어 라우트 해준다.
  • 도메인 주소는 다음과 같이 된다. http://~~~/one/two/three
routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(),
        routes: [
          GoRoute(
            path: 'one',
            builder: (context, state) => OneScreen(),
            routes: [
              GoRoute(
                path: 'two',
                builder: (context, state) => TwoScreen(),
                routes: [
                  GoRoute(
                    path: 'Three',
                    builder: (context, state) => ThreeScreen(),
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    ],
  • HomeScreen()에서 ThreeScreen으로 이동하는 버튼을 만들고 실행해 본다.
ElevatedButton(
            onPressed: () {
              context.go('/one/two/three');
            },
            child: const Text('Screen Three'),
          ),

  • 네스팅을 해서 스크린을 집어넣고 마지막 라우트 스크린을 go하게되면 Push 하지 않았던 중간에 있는 스크린들도 보이게 된다.
  • context.go('/one/two/three'); 에 있는 /one/two/three를 간단하게 바꿔 줄 수 있다.
  • GoRoute에 name을 사용할 수 있는데 이곳에 스크린이름을 작성해준다.
GoRoute(
          path: 'three',
          name: 'three',
          builder: (context, state) => ThreeScreen(),
                  ),
  • HomeScreen에 있는 onPressed 함수에 go가 아닌 goNamed를 사용해서 간단히 바꿔줄 수 있다.
ElevatedButton(
            onPressed: () {
              context.goNamed('three');
            },
            child: const Text('Screen Three'),
          ),
  • 경로를 String 값으로 작성하다는 것보다 안전한게 코드를 작성하는 방법이 있다.
  • 해당하는 스크린에서 static String get routeName => 'three';을 작성해준다. 그러면 라우트와 버튼 경로에 String 값이 아닌 자동완성으로 코드를 작성해서 실수를 줄일 수 있다.

pop

  • pop은 뒤로가기를 할 때 사용한다. Navigator.of(context).pop()과 같은 방식으로 작성하면 된다.
  • go_route에서는 context.pop()을 사용하면 된다.

errorScreen

  • GoRoutererrorBuilder를 추가한다. 그리고 context와 state를 파라미터로 받는 함수를 작성하고 ErrorScreen을 반환한다.
  • Navigotion 상에서 발생한 에러는 state에 들어오게 된다.
final GoRouter _router = GoRouter(
    initialLocation: '/',
    errorBuilder: (context, state) {
      return ErrorScreen(
        error: state.error.toString(),
      );
    },

Redirect & Refresh

  • 특정 url로 이동하려고 했을 때 이동하려는 곳이 아닌 다른곳으로 이동하는 것을 감지하고 자동으로 라우팅을 변경하는 것을 말한다.
  • 상태관리를 위해 먼저 Provider를 설치해준다.
  • 모델을 만들어 로그인 여부를 확인하는데 사용한다.
// user_model.dart
// 유저가 등록이되면 로그인이 성공
// 그렇지 않으면 실패
class UserModel {
  final String name;

  UserModel({
    required this.name,
  });
}
  • provider 파일을 만들고 이 곳에 라우터를 넣어준다.
final routerProvider = Provider<GoRouter>(
  (ref) {
    return GoRouter(
      initialLocation: '/',
      errorBuilder: (context, state) {
        return ErrorScreen(error: state.error.toString());
      },
      routes: [
        GoRoute(
          path: '/',
          builder: (_, state) => HomeScreen(),
          routes: [
            GoRoute(
              path: 'one',
              builder: (_, state) => OneScreen(),
              routes: [
                GoRoute(
                  path: 'two',
                  builder: (_, state) => TwoScreen(),
                  routes: [
                    // http://.../one/two/three
                    GoRoute(
                      path: 'three',
                      name: ThreeScreen.routeName,
                      builder: (_, state) => ThreeScreen(),
                    )
                  ],
                )
              ],
            ),
          ],
        ),
      ],
    );
  },
);
  • main.dart 파일도 수정해준다.
  • Provider를 사용해서 watch() 해주고 router를 넣어준다.
void main() {
  runApp(
    ProviderScope(
      child: _App(),
    ),
  );
}

class _App extends ConsumerWidget {
  _App({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final router = ref.watch(routerProvider);

    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerDelegate: router.routerDelegate,
      routeInformationProvider: router.routeInformationProvider,
      routeInformationParser: router.routeInformationParser,
    );
  }
}

  • redirect, refresh로직과 route 정보들을 따로 changeNotifier에다가 집어넣는다.
  • refresh 기능을 사용하기 위해서는 무조건 changeNotifier를 사용해야 한다.
  • ChangeNotifier를 상속하는 AuthNotifier 클래스를 만든다. 이 클래스에는 앞서 작성했던 GoRoute로직을 잘라넣어 생성한다.
class AuthNotifier extends ChangeNotifier {
  List<GoRoute> get _route => [
        GoRoute(
          path: '/',
          builder: (_, state) => HomeScreen(),
          routes: [
            GoRoute(
              path: 'one',
              builder: (_, state) => OneScreen(),
              routes: [
                GoRoute(
                  path: 'two',
                  builder: (_, state) => TwoScreen(),
                  routes: [
                    // http://.../one/two/three
                    GoRoute(
                      path: 'three',
                      name: ThreeScreen.routeName,
                      builder: (_, state) => ThreeScreen(),
                    )
                  ],
                )
              ],
            ),
          ],
        ),
      ];
}
  • 이렇게 만든 AuthNotifier를 사용하기 위해 final authStateProvider = AuthNotifier();를 선언한다.
    그리고 routes값에 authStateProvider를 넣어준다.
  • 여기까지는 일반 클래스를 불러오는 것과 같다.
final routerProvider = Provider<GoRouter>(
  (ref) {
    final authStateProvider = AuthNotifier();

    return GoRouter(
      initialLocation: '/',
      errorBuilder: (context, state) {
        return ErrorScreen(error: state.error.toString());
      },
      routes: authStateProvider._route,
    );
  },
);
  • 유저 상태를 관리하기 위해 StateNotifier를 만들어준다.
  • provider를 사용하기 위해 만들어준다.
final userProvider = StateNotifierProvider<UserStateNotifier, UserModel?>(
  (ref) => UserStateNotifier(),
);

// 유저의 상태를 관리한다.
// null이 될 수 있는 이유는 로그인하지 않는 상태를 고려해야하기 때문이다.
class UserStateNotifier extends StateNotifier<UserModel?> {
  // 처음에는 로그인을 하지 않기 때문에 null을 넣어준다.
  UserStateNotifier() : super(null);

  // 로그인한 상태면 UserModel 인스턴스 상태로 넣어주기
  // 로그아웃이면 null 상태로...
  login({
    required String name,
  }) {
    state = UserModel(name: name);
  }

  logout() {
    state = null;
  }
}
  • 생성한 userProvider를 AuthNotifier에 사용하기위해(listen) ref를 받아오고 보내준다.
class AuthNotifier extends ChangeNotifier {
  final Ref ref;

  AuthNotifier({
    required this.ref,
  })
final routerProvider = Provider<GoRouter>(
  (ref) {
    final authStateProvider = AuthNotifier(ref: ref);
  • 생성자 함수를 이용해서 생성이 되자마자 listen을 한다.
  • userProvider를 listen하고 반환 받을 값을 <UserModel?>로 정해준다.
  • 반환을 받게 되면 상태가 변경됬다라는 것만 알려주면 된다. ChangeNotifier에서는 notifyListeners();를 사용하면 된다.
  • notifyListeners는 ChangeNotifier를 바라보고 있는 모든 위젯들을 리빌드하게 한다. 이 때 notifyListeners는 기존값과 다음값이 다를때 실행하게 한다.
class AuthNotifier extends ChangeNotifier {
  final Ref ref;

  AuthNotifier({
    required this.ref,
  }) {
    ref.listen<UserModel?>(
      userProvider,
      (previous, next) {
        if (previous != next) {
          notifyListeners();
        }
      },
    );
  }

  • 이제 redirect 로직을 작성해 보겠다.
  • 반환 값이 route여서 String? 타입을 작성한다.
  • GoRouterState state에는 라우팅 정보, 경로들이 담겨있다.
String? _redirectLogic(GoRouterState state) {
    // userModel의 인스턴스(로그인) or null(로그아웃)
    final user = ref.read(userProvider);
    // 로그인을 하려는 상태
    final logginIn = state.location == '/login';

    // 유저 정보가 없다 - 로그인 상태가 아닌다
    // 유저 정보가 없고 로그인 중이 아니라면 로그인페이지로 이동한다.
    if (user == null) {
      return logginIn ? null : '/login';
    }

    // 유저 정보가 있고 로그인 페이지라면
    // 홈으로 이동한다
    if (logginIn) {
      return '/';
    }

    // 나머지 경우
    return null;
  }
  • redirectauthStateProvider._redirectLogic 작성하면 이 함수 안에 GoRouterState state를 자동으로 넣어준다. 그래서 네비게이션이 될 때 마다 state가 들어가게 된다.
  • refreshListenableauthStateProvider를 넣어준다.refreshListenable은 이곳에 들어가는 값의 상태가 바뀌었을 때마다 redirect를 재실행 해준다.
final routerProvider = Provider<GoRouter>(
  (ref) {
    final authStateProvider = AuthNotifier(ref: ref);

    return GoRouter(
      initialLocation: '/',
      errorBuilder: (context, state) {
        return ErrorScreen(error: state.error.toString());
      },
      routes: authStateProvider._route,
      redirect: authStateProvider._redirectLogic,
      refreshListenable: authStateProvider,
    );
  },
);

  • 로그인 스크린과 라우트를 생성해준다.
  • initialLocation의 경로도 initialLocation: '/login'으로 변경해 준다.
 GoRoute(
          path: '/login',
          builder: (_, state) => LoginScreen(),
        ),
class LoginScreen extends ConsumerWidget {
  const LoginScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    return DefaultLayout(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          ElevatedButton(
            onPressed: () {
              ref.read(userProvider.notifier).login(name: 'apple');
            },
            child: Text('로그인'),
          ),
        ],
      ),
    );
  }
}
  • HomeScreen에 로그인 버튼과 로그아웃 버튼을 생성한다.
// HomeScreen
		 ElevatedButton(
            onPressed: () {
              context.go('/login');
            },
            child: const Text('Login Screen'),
          ),
          ElevatedButton(
            onPressed: () {
              ref.read(userProvider.notifier).logout();
            },
            child: const Text('Logout Screen'),
          ),

  • 로그인 버튼을 누르면 홈으로 이동하게 되고, 로그아웃 버튼을 누르면 다시 로그인 스크린으로 이동하게 된다. 그리고 홈에 만들어 놓은 로그인 스크린버튼을 누르게 되면 위에서 작성한 로직에 의해 아무 반응도 일어나지 않는다

0개의 댓글