块、 Flutter 和导航

xkrw2x1b  于 2023-06-07  发布在  Flutter
关注(0)|答案(6)|浏览(147)

所以像大多数人一样,我是新的集团和扑和 dart 和 Package 我的头周围。我在谷歌上搜索过,看过这里的帖子,但没有找到任何答案。
所以这是关于有块和 Flutter 的导航。以Login为例。所以有一个登录页面,后面有一个块,在某个时候有人按下一个按钮登录。
所以我们可以调用块中的一个函数来进行验证。我认为这是违反严格的做法,但我看到人们这样做。但是,如果登录成功,您如何导航到下一个屏幕?你不应该在一个区域航行吗?
但是如果登录页面使用StreamBuilder来更改状态,那么您也不能在构建器中添加导航,可以吗?你不能返回导航,你返回的是小部件。
initstate是您可以导航的地方,但是您可以在initstate中使用流构建器来侦听块中的状态更改吗?
现在一切都有点混乱,但我坚持下去,因为这是 * 应该 * 是前进的道路…
谢谢Paul

wnvonmuf

wnvonmuf1#

为了让BLoC成为 * 前进的道路的神话:处理状态没有完美的方法。每个状态管理体系结构都比其他体系结构更好地解决了一些问题;总有一些权衡,在决定架构时,意识到它们是很重要的。
一般来说,好的架构是实用的:它具有可伸缩性和可扩展性,同时只需要最小的开销。由于人们对实用性的看法不同,架构总是涉及到意见,所以请对以下内容持保留态度,因为我将阐述我个人对如何在您的应用程序中采用BLoC的看法。
BLoC是Flutter中状态管理的一种非常有前途的方法,因为它有一个标志性的成分:它们允许将UI与业务逻辑解耦,并且它们与Flutter式的方法配合良好,即在过时时重建整个小部件子树。所以很自然地,来自和去往BLoC的每个通信都应该使用流,对吗?

+----+  Stream   +------+
| UI | --------> | BLoC |
|    | <-------- |      |
+----+   Stream  +------+

算是吧
需要记住的重要一点是状态管理架构是达到目的的手段;你不应该只是为了做某事而做某事,而是要保持开放的心态,仔细评估每个选择的利弊。我们将BLoC与UI分开的原因是BLoC不需要关心UI是如何结构化的-它只是提供一些简单的流,数据发生的任何事情都是UI的责任。
但是,虽然流已被证明是将信息从BLoC传输到UI的绝佳方式,但它们在另一个方向上增加了不必要的开销:流被设计为传输连续的数据流(它甚至在名称中),但大多数时候,UI只需要触发BLoC中的单个事件。这就是为什么有时你会看到一些Stream<void>或类似的黑客解决方案¹,只是为了坚持严格的BLoC-y做事方式。
此外,如果我们根据来自BLoC的流推送新路由,BLoC基本上会控制UI流--但是直接控制UI和业务逻辑的代码正是我们试图阻止的!
这就是为什么一些开发人员(包括我)只是打破完全基于流的解决方案,并采用一种自定义的方式从UI触发BLoC中的事件。就我个人而言,我只是使用方法调用(通常返回Future s)来触发BLoC的事件:

+----+   method calls    +------+
| UI | ----------------> | BLoC |
|    | <---------------- |      |
+----+   Stream, Future  +------+

这里,BLoC返回Stream s作为“实时”数据,返回Future s作为方法调用的应答。
让我们看看如何为您的示例工作:

  • BLoC可以提供用户是否登录的Stream<bool>,甚至Stream<Account>,其中Account包含用户的帐户信息。
  • BLoC还可以提供一个异步Future<void> signIn(String username, String password)方法,如果登录成功,则不返回任何内容,否则抛出错误。
  • UI可以自己处理输入管理,并在按下登录按钮后触发如下内容:
try {
  setState(() => _isLoading = true); // This could display a loading spinner of sorts.
  await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
  Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
  setState(() => _isLoading = false);
  // TODO: Display the error on the screen.
}

通过这种方式,您可以很好地分离关注点:

  • BLoC实际上只是做了它应该做的事情--处理业务逻辑(在本例中,用户登录)。
  • UI只关心两件事:
  • 显示来自Stream s和
  • 通过在BLoC中触发用户操作并根据结果执行UI操作来对用户操作做出React。²

最后,我想指出的是,这只是一种可能的解决方案,它是通过在复杂的应用程序中尝试不同的处理状态的方法而逐渐发展起来的。了解关于状态管理如何工作的不同观点是很重要的,因此我鼓励您深入研究这个主题,也许可以通过观看Google I/O的"Pragmatic State Management in Flutter"会议。

编辑:刚刚在Brian Egan's architecture samples中找到了这个架构,它被称为“Simple BLoC”。如果你想了解不同的架构,我真的建议你看看repo。

¹当试图为BLoC动作提供多个参数时,它会变得更加丑陋-因为这样你就需要定义一个 Package 器类来将其传递给Stream。
²我承认在启动应用程序时它会变得有点丑陋:您需要某种启动屏幕,它只是检查BLoC的流,并根据用户是否登录将其重定向到适当的屏幕。这个规则的例外是因为用户执行了一个动作--启动应用程序--但是Flutter框架不允许我们直接挂钩到这个动作(至少据我所知不是很优雅)。

7xllpg7q

7xllpg7q2#

BlocListener是您可能需要的小部件。如果状态更改为(例如)LoginSuccess,则块侦听器可以调用通常的Navigate.of(context)。你可以找到一个example of BlocListener in action near the bottom of this page
另一种选择是向事件传递回调。

BlocProvider.of<MyBloc>(context).add(MyEvent(
              data: data,
              onSuccess: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) {
                    return HomePage();
                  }),
                );
              }));
u5rb5r59

u5rb5r593#

正如felangel在Github下面的issue中提到的,我们可以使用BlocListner来实现这个目的。

BlocListener(
    bloc: BlocProvider.of<DataBloc>(context),
    listener: (BuildContext context, DataState state) {
        if (state is Success) {              
            Navigator.of(context).pushNamed('/details');
        }              
    },
    child: BlocBuilder(
        bloc: BlocProvider.of<DataBloc>(context),
        builder: (BuildContext context, DataState state) {        
            if (state is Initial) {
                return Text('Press the Button');
            }
            if (state is Loading) {
                return CircularProgressIndicator();
            }  
            if (state is Success) {
                return Text('Success');
            }  
            if (state is Failure) {
                return Text('Failure');
            }
        },
    }
)
ejk8hzay

ejk8hzay4#

首先:如果没有业务逻辑,则不需要转到YourBloc类。
但是有时候一些用户的活动需要在Bloc类中执行一些逻辑,然后Bloc类必须决定下一步做什么:只需重新构建小部件显示对话框甚至导航到下一个路由。在这种情况下,您必须向UI发送一些State来完成操作。然后另一个问题出现了:当Bloc发送State显示吐司时,我应该如何处理小部件?
这是这个故事的主要问题。
很多答案和文章都推荐使用flutter_block。这个库有BlocBuilderBlocListener。有了这些类,你可以解决一些问题,但不是100%。
在我的例子中,我使用了BlocConsumer,它管理BlocBuilderBlocListener,并提供了管理状态的出色方法。
来自文档:

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

如您所见,使用BlocConsumer可以过滤状态:你可以很容易地定义状态来重建小部件和状态来显示一些弹出窗口或导航到下一个屏幕。

am46iovg

am46iovg5#

完全同意**@Volodymyr Yatsykiv的关于使用BlocConsumerWidget的回答,但只是添加一个实现管理导航的示例,以防成功登录并重建小部件**当有请求时可能会有所帮助,所以我在下面添加。

class LogInBtn extends StatelessWidget {
  const LogInBtn({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LogInBloc, LogInStates>(
        listener: (context, state){
          if(state is SuccessLogInState){
            Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => const HomeScreen()));
          }
        },
        builder: (context, state) {
      return state is ProgressLogInState
          ? const Padding(
        padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 100.0),
        child: LinearProgressIndicator(),
      )
          : Padding(
        padding: const EdgeInsets.only(top: 100.0),
        child: Container(
          height: 40,
          decoration: const BoxDecoration(
              color: Color(whiteColor),
              borderRadius: BorderRadius.all(Radius.circular(5.0))),
          child: const Center(
            child: Text('Log In',
                style: TextStyle(
                    color: Color(primaryColor),
                    fontWeight: FontWeight.bold,
                    fontSize: 20,
                    fontFamily: 'Laila')),
          ),
        ),
      );
    });
  }

}

myss37ts

myss37ts6#

完整的Flutter BLOC示例

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  BlocOverrides.runZoned(
    () => runApp(const App()),
    blocObserver: AppBlocObserver(),
  );
}

/// Custom [BlocObserver] that observes all bloc and cubit state changes.
class AppBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    if (bloc is Cubit) print(change);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
  }
}

/// {@template app}
/// A [StatelessWidget] that:
/// * uses [bloc](https://pub.dev/packages/bloc) and
/// [flutter_bloc](https://pub.dev/packages/flutter_bloc)
/// to manage the state of a counter and the app theme.
/// {@endtemplate}
class App extends StatelessWidget {
  /// {@macro app}
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => ThemeCubit(),
      child: const AppView(),
    );
  }
}

/// {@template app_view}
/// A [StatelessWidget] that:
/// * reacts to state changes in the [ThemeCubit]
/// and updates the theme of the [MaterialApp].
/// * renders the [CounterPage].
/// {@endtemplate}
class AppView extends StatelessWidget {
  /// {@macro app_view}
  const AppView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ThemeCubit, ThemeData>(
      builder: (_, theme) {
        return MaterialApp(
          theme: theme,
          home: const CounterPage(),
        );
      },
    );
  }
}

/// {@template counter_page}
/// A [StatelessWidget] that:
/// * provides a [CounterBloc] to the [CounterView].
/// {@endtemplate}
class CounterPage extends StatelessWidget {
  /// {@macro counter_page}
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: const CounterView(),
    );
  }
}

/// {@template counter_view}
/// A [StatelessWidget] that:
/// * demonstrates how to consume and interact with a [CounterBloc].
/// {@endtemplate}
class CounterView extends StatelessWidget {
  /// {@macro counter_view}
  const CounterView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) {
            return Text('$count', style: Theme.of(context).textTheme.headline1);
          },
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () => context.read<CounterBloc>().add(Increment()),
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.remove),
            onPressed: () => context.read<CounterBloc>().add(Decrement()),
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.brightness_6),
            onPressed: () => context.read<ThemeCubit>().toggleTheme(),
          ),
        ],
      ),
    );
  }
}

/// Event being processed by [CounterBloc].
abstract class CounterEvent {}

/// Notifies bloc to increment state.
class Increment extends CounterEvent {}

/// Notifies bloc to decrement state.
class Decrement extends CounterEvent {}

/// {@template counter_bloc}
/// A simple [Bloc] that manages an `int` as its state.
/// {@endtemplate}
class CounterBloc extends Bloc<CounterEvent, int> {
  /// {@macro counter_bloc}
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
    on<Decrement>((event, emit) => emit(state - 1));
  }
}

/// {@template brightness_cubit}
/// A simple [Cubit] that manages the [ThemeData] as its state.
/// {@endtemplate}
class ThemeCubit extends Cubit<ThemeData> {
  /// {@macro brightness_cubit}
  ThemeCubit() : super(_lightTheme);

  static final _lightTheme = ThemeData(
    floatingActionButtonTheme: const FloatingActionButtonThemeData(
      foregroundColor: Colors.white,
    ),
    brightness: Brightness.light,
  );

  static final _darkTheme = ThemeData(
    floatingActionButtonTheme: const FloatingActionButtonThemeData(
      foregroundColor: Colors.black,
    ),
    brightness: Brightness.dark,
  );

  /// Toggles the current brightness between light and dark.
  void toggleTheme() {
    emit(state.brightness == Brightness.dark ? _lightTheme : _darkTheme);
  }
}

相关问题