BLoC, context 외부에서 관리하기
BuildContext 없이 BLoC에 접근하고, listen 하는 방법을 알아봅니다.읽는데 5분 정도 걸려요.보통 Bloc은 BlocProvider로 context(위젯트리)에서 관리하고, context.read<Bloc>()
형태로 읽어오죠?
하지만, 이 경우 문제가, context 밖에서는 Bloc에 접근할 방법이 없다는 것입니다.
제 경우에는, dio interceptor에서 Bloc에 접근하고 싶은 상황이었죠..
이런 상황, 혹은 앱 실행시간동안 계속 필요한 Bloc의 경우 Singleton Pattern을 이용하여 관리하는 유용한 방법에 대해 설명하려 합니다.
BlocBuilder
우선 BlocBuilder, Listern의 bloc 파라미터를 먼저 살펴봅시다.
보통은 bloc 파라미터를 null값으로 놔두고 사용합니다.
하지만, 아래의 bloc 소스 코드를 살펴보겠습니다.
class _BlocBuilderBaseState<B extends StateStreamable<S>, S>
extends State<BlocBuilderBase<B, S>> {
late B _bloc;
late S _state;
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_state = _bloc.state;
}
...
}
보이는 바와 같이, bloc이 null값이면 context에서, notnull이면 bloc 에서 bloc을 가져오는 방식으로 작동합니다.
즉, context에서 bloc에 접근할 수 없는 경우에는 bloc 파라미터에 bloc을 직접 주입시키면 되는 것입니다.
이를 응용해서 코드 어디에서나 Singleton Pattern을 통해서 bloc에 접근하는 방법을 사용해 볼 수 있겠습니다.
구현
Singleton
우선, context 외부에서 관리할 Bloc의 인스턴스를 보관할 Singleton 객체를 하나 만들어줍니다.
class AuthBlocSingleton {
AuthBlocSingleton._constructor();
static final AuthBlocSingleton _signleton = AuthBlocSingleton._constructor();
static AuthBlocSingleton get instance => _signleton;
static late final AuthBloc _bloc;
static AuthBloc get bloc => _bloc;
static initializer({required AuthenticationRepository repository}) {
_bloc = AuthBloc(authenticationRepository: repository);
}
}
이렇게하면, AuthBlocSingleton.bloc 으로 코드 전역에서 접근할 수 있습니다.
(물론 사용하기 전에 initializer 메서드를 호출해야만 합니다)
Initialized
그리고, bloc에 종속성을 주입해야 하는 경우, 사용하기 전에 주입하도록 합시다.
class _AppRoutesState extends State<AppRoutes> {
late GoRouter _routerConfig;
void initState() {
super.initState();
// Initialize AuthBloc
AuthBlocSingleton.initializer(
repository: context.read<AuthenticationRepository>(),
);
...
}
...
}
제 경우에는 repository를 context에 주입하여 사용하고 있기 때문에, context에 접근 가능한 최상위 위젯에서 bloc에 주입시켜 초기화를 진행하였습니다.
결과물
이제 BlocBuilder, Listener는 아래와 같이 코드 한 줄만 추가하면 됩니다.
BlocBuilder<AuthBloc, AuthState>(
bloc: AuthBlocSingleton.bloc,
builder: (context, state) {
...
이렇게 하면, BlocProvider로 Bloc을 생성하지 않고도 Bloc을 사용할 수 있습니다.
아래와 같은 장점도 챙길 수 있죠!
- 위젯트리가 가벼워진다.
- 싱글톤으로 코드 어디서나 Bloc에 접근할 수 있다. (state, add() 모두 가능)
따라서 dio의 interceptor 클래스에서 아래와 같은 코드로 가능해집니다.
class AuthInterceptor extends Interceptor {
...
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) async {
// Logging
debugPrint(
'[RES] [${response.requestOptions.method}] ${response.requestOptions.uri}',
);
// Error handling
var res = ResModel.fromJson(response.data, (json) => null);
if (res.code != 1000) {
if (res.code == 2000) {
AuthBlocSingleton.bloc.add(AuthSignoutEvent());
}
handler.reject(
DioException.connectionError(
requestOptions: response.requestOptions,
reason: res.message ?? Strings.unknownFail,
error: res,
),
);
return;
}
handler.next(response);
}
...
}
api 응답 중 jwt관련 인증 문제가 발생하는 경우, 자동으로 로그아웃 하는 로직도 이런식으로 구현할 수 있습니다.