Я использовал шаблон BloC для создания приложения. Существует уровень поставщика данных, который отвечает за запрос кода сеанса с удаленного сервера.
abstract class AppApi{
/// Logins a user to server and return a [Session].
///
/// Throw [HttpException] if response status code is not 200.Throw
/// [FetchSessionException] if response message is not "OK" or response data is null.
Future<Session> fetchSession(String username, String password);
}
Существует уровень репозитория, который отвечает за предоставление сеанса с Stream<Session> get session уровню приложения.
abstract class LoginRepository {
factory LoginRepository(AppApi appApi) =>
LoginRepositoryImpl(appApi);
Stream<Session> get session;
Future<void> getSession(String username, String password);
}
На уровне приложения есть кубит, который подписывается на поток сеанса репозитория и с помощью hybricCubit сохраняет сеанс.
class SessionCubit extends HydratedCubit<PersistenceSession> {
final LoginRepository _loginRepository;
late StreamSubscription<Session> _loginStreamSubscription;
static final defaultSessionState = PersistenceSession.empty;
SessionCubit(this._loginRepository) : super(defaultSessionState) {
_loginStreamSubscription =
_loginRepository.session.asBroadcastStream().listen((session) {
if (session == Session.empty) {
emit(defaultSessionState);
} else {
emit(
state.copyWith(session: session.sessionCode),
);
}
});
}
}
Каждый раз, когда другие кубиты хотят запросить сервер, они запрашивают сеанс с помощью context.read<PersisSessionCubit>.state.session.code и передают код в качестве аргумента репозиторию и уровню данных.
Но я хочу сохранить сеанс на уровне репозитория или уровне данных, а затем другие кубиты на уровне приложения используют этот сохраненный сеанс с помощью StreamSubscription.
На каком уровне я могу сохранить сеанс, чтобы предотвратить тесную связь?





Вы можете просто оставить StreamSubscription<Session> в классе LoginRepository. Этот экземпляр LoginRepository будет передан в конструктор любого блока/кубита, которому он нужен, и все они будут слушать один и тот же поток.
При этом я не вижу ничего плохого в том, как вы настроили его сейчас, когда вы просто берете код сеанса из SessionCubit, когда он вам нужен. У вас уже есть настройка слабой связи, потому что у вас нет других локтей, напрямую зависящих от SessionCubit. Это упрощает тестирование без необходимости заглушать другие Cubits в каждом экземпляре blocTest.
Любой из этих подходов соответствует шаблону Bloc и поддерживает слабую связь между классами Bloc.
Я управлял архитектурой на основе руководства по архитектуре BLoC, чтобы разделить уровень данных, уровень предметной области и уровень приложения.
Затем на уровне репозитория создайте алгоритм, чтобы проверить, не существует ли сеанс на уровне сохраняемых данных или срок его действия истек, затем вызовите метод выхода из системы (поскольку API не поддерживает конечную точку токена обновления, я вызвал метод выхода из системы, который очищает хранилище и перенаправляет пользователя на экран входа) Затем, когда пользователь запрашивает логин и мы получаем код состояния ответа 200 с сеансом, мы сохраняем сеанс на уровне сохранения.
Ключ к решению этой проблемы находится на уровне репозитория и политике сохранения для управления вызовами на удаленный сервер или использования сеанса на уровне сохраняемых данных.
Шаг 1: Определите класс SessionState
class SessionState {
final bool isAuthenticated;
final String? username;
SessionState({required this.isAuthenticated, this.username});
SessionState copyWith({bool? isAuthenticated, String? username}) {
return SessionState(
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
username: username ?? this.username,
);
}
}
Шаг 2. Определите класс S## Heading ##essionEvent.
abstract class SessionEvent {}
class LoginEvent extends SessionEvent {
final String username;
final String password;
LoginEvent({required this.username, required this.password});
}
class LogoutEvent extends SessionEvent {}
Шаг 3: Определите класс SessionBloc
class SessionBloc extends Bloc<SessionEvent, SessionState> {
SessionBloc() : super(SessionState(isAuthenticated: false));
Шаг 4: Загрузите исходное состояние сеанса из постоянного хранилища
@override
Future<void> onLoad() async {
super.onLoad();
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isAuthenticated = prefs.getBool('isAuthenticated') ?? false;
String? username = prefs.getString('username');
SessionState initialState =
SessionState(isAuthenticated: isAuthenticated, username: username);
emit(initialState);
}
@override
Stream<SessionState> mapEventToState(SessionEvent event) async* {
if (event is LoginEvent) {
// Step 5: Handle the LoginEvent by updating the session state and saving it to persistent storage
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isAuthenticated = true;
String username = event.username;
await prefs.setBool('isAuthenticated', isAuthenticated);
await prefs.setString('username', username);
yield state.copyWith(isAuthenticated: isAuthenticated, username: username);
} else if (event is LogoutEvent) {
Шаг 5. Обработайте LogoutEvent, обновив состояние сеанса и сохранив его в постоянном хранилище.
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isAuthenticated = false;
String? username = null;
await prefs.setBool('isAuthenticated', isAuthenticated);
await prefs.remove('username');
yield state.copyWith(isAuthenticated: isAuthenticated, username: username);
}}}
Шаг 6. Используйте SessionBloc в своем приложении
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (BuildContext context) => SessionBloc(),
child: BlocBuilder<SessionBloc, SessionState>(
builder: (context, state) {
if (!state.isAuthenticated) {
return LoginPage();
} else {
return HomePage(username: state.username!);
}
},
),
),
);
}
}
Взгляните на это. bloclibrary.dev/#/architecture?id=bloc-to-bloc-коммуникация