Правильный способ обработки навигации с использованием BLoC

Привет, ребята, я использую BLoC для приложения, которое я сейчас разрабатываю, но есть некоторые случаи, о которых я не знаю, когда вы входите в систему, вы запускаете вызов API и ожидаете результата, естественно, я бы послал загрузку состояния и загрузчика, но после этого заканчивается например, для перехода на другой экран. У меня в настоящее время есть что-то вроде этого

typedef void LoginSuccessCallback();
    class LoginBloc(){
    LoginBloc(Api this.api,LoginSuccessCallback loginSuccesCallback){
      _login.switchMap((ev) => api.login(ev.payload.email,ev.payload.password)).listen((_) => loginSuccessCallback);
     }
    }

Но я уверен, что для обработки этого метода гораздо более чистый способ. Я попытался найти некоторые образцы, которые имеют что-то похожее, но ничего не нашли.

Ответ 1

Изменение: После нескольких месяцев с этим решением, я заметил, что есть несколько проблем с ним:

  1. Не работает аппаратная кнопка возврата Android
  2. Приложение сбрасывается при переключении режима "осмотра".
  3. Переходы невозможны
  4. Нет гарантии, что запрещенный маршрут не отображается

Поэтому я больше не рекомендую использовать этот подход!


Для обычной навигации по инициативе пользователя вам вообще не нужен шаблон BLoC. Просто используйте Navigator.

Логин - это особый случай. Следуя шаблону BLoC, имеет смысл предоставить поток isAuthenticated:

abstract class MyBloc {
  Stream<bool> get isAuthenticated;
}

Вероятно, в вашем приложении будет 2 разных именованных дерева маршрутов: одно для зарегистрированных пользователей и одно для анонимных пользователей:

final Map<String, WidgetBuilder> anonymousRoutes = {
  '/': (context) => new LoginScreen(), // default for anon
  '/register': (context) => new RegisterScreen(),
};

final Map<String, WidgetBuilder> authenticatedRoutes = {
  '/': (context) => new HomeScreen(), // default for logged in
  '/savings': (context) => new SavingsScreen(),
  // ...
};

Обычно Navigator и его именованные маршруты тесно связаны с MaterialApp, но вы также можете определить свой собственный, который будет перестроен при обновлении потока isAuthenticated:

class MyApp extends StatelessWidget {
  const MyApp({Key key, this.bloc}) : super(key: key);

  final MyBloc bloc;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (BuildContext context, Widget child) {
        return StreamBuilder<bool>(
          stream: bloc.isAuthenticated,
          builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
            if (!snapshot.hasData) {
              return Text('loading...');
            }

            bool isAuthenticated = snapshot.data;
            return _buildNavigator(isAuthenticated);
          },
        );
      },
    );
  }
}

Navigator _buildNavigator(bool isAuthenticated) {
  // different route tree and different default route depending on auth state
  final routes = isAuthenticated ? authenticatedRoutes : anonymousRoutes;

  return Navigator(
    key: new ValueKey(isAuthenticated),
    onGenerateRoute: (RouteSettings settings) {
      final name = settings.name;
      return new MaterialPageRoute(
        builder: routes[name],
        settings: settings,
      );
    },
    onUnknownRoute: (RouteSettings settings) {
      throw Exception('unknown route');
    },
  );
}

К сожалению, сейчас (2018-07-14) в коде Flutter есть 2 конфликтующих утверждения, которые необходимо удалить, чтобы приведенный выше код работал (вы можете просто отредактировать его с помощью своей IDE):

Строки 93 и 96 в packages\flutter\lib\src\widgets\app.dart

//assert(navigatorObservers != null),
//assert(onGenerateRoute != null || navigatorObservers == const <NavigatorObserver>[]),