Effective BLoC Pattern

作者: 小菜鸟程序媛 | 来源:发表于2019-05-26 14:55 被阅读4次

    原文地址:https://medium.com/flutterpub/effective-bloc-pattern-45c36d76d5fe

    这篇文章中列一些BLoC模式的经验,避免在开发过程中出现常用的错误。下面是使用BLoC遵循的八个点。

    每个界面都要有自己的BLoC

    这是需要记住的最重要的一点,如果你有很多界面,比如登录、注册,而每个都需要处理数据,如果你认为一个共用的BLoC可以方便的让他们共享数据的话,那样其实并不好,更好的方式是数据存储库向BLoC提供数据,然后BLoC获取到数据之后提供给界面进行显示。

    image.png

    每个BLoC都要有dispose方法

    每个BLoC都需要创建dispose方法,因为这是你用来清理和关闭所创建的那些流的地方。下面的代码是一个简单的dispose方法的示例。

    class MoviesBloc {
      final _repository = Repository();
      final _moviesFetcher = PublishSubject<ItemModel>();
    
      Observable<ItemModel> get allMovies => _moviesFetcher.stream;
    
      fetchAllMovies() async {
        ItemModel itemModel = await _repository.fetchAllMovies();
        _moviesFetcher.sink.add(itemModel);
      }
    
      dispose() {
        _moviesFetcher.close();
      }
    }
    

    不要将StatelessWidget和BLoC一起使用

    当你想向创建一个将数据传递到BLoC,然后从BLoC获取数据并显示的界面时,一定要使用StatefulWidget。使用StatefulWidget而不是StatelessWidget最大的优点就是StatefulWidget中有可以使用的声明周期方法。StatelessWidget只适合用来构建一个小型的简单的界面。

    覆盖didChangeDependencies初始化BLoC

    如果你需要一个Context来初始化BLoC,那么这是最适合的一个方法,你可以把它当成一个初始化方法(仅适用于BLoC),你可能会说有initState,为什么要用didChangeDependencies,文档清楚地提到,从didChangeDependencies()方法调用BuildContext.inheritFromWidgetOfExactType是安全的。下面是使用这个方法的示例:

    @override
      void didChangeDependencies() {
        bloc = MovieDetailBlocProvider.of(context);
        bloc.fetchTrailersById(movieId);
        super.didChangeDependencies();
      }
    

    覆盖dispose方法

    我们需要提供一个方法用来调用BLoC的dispose方法。这个方法用来在你离开该页面的时候调用。

    @override
      void dispose() {
        bloc.dispose();
        super.dispose();
      }
    

    使用RxDart处理复杂的逻辑

    RxDart是google的响应式编程库,该库只是Dart提供的Stream Api的包装器,我建议你仅在链接多个网络请求时才使用这个库,对于简单的功能,使用Dart提供的Stream Api就可以。下面就是使用Stream Api的例子。

    import 'dart:async';
    
    class Bloc {
    
      //Our pizza house
      final order = StreamController<String>();
    
      //Our order office
      Stream<String> get orderOffice => order.stream.transform(validateOrder);
    
      //Pizza house menu and quantity
      static final _pizzaList = {
        "Sushi": 2,
        "Neapolitan": 3,
        "California-style": 4,
        "Marinara": 2
      };
    
      //Different pizza images
      static final _pizzaImages = {
        "Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",
        "Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",
        "California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",
        "Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"
      };
    
    
      //Validate if pizza can be baked or not. This is John
      final validateOrder =
          StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {
        if (_pizzaList[order] != null) {
          //pizza is available
          if (_pizzaList[order] != 0) {
            //pizza can be delivered
            sink.add(_pizzaImages[order]);
            final quantity = _pizzaList[order];
            _pizzaList[order] = quantity-1;
          } else {
            //out of stock
            sink.addError("Out of stock");
          }
        } else {
          //pizza is not in the menu
          sink.addError("Pizza not found");
        }
      });
    
      //This is Mia
      void orderItem(String pizza) {
        order.sink.add(pizza);
      }
    }
    

    Use PublishSubject over BehaviorSubject

    BehaviorSubject是一个特殊的StreamController,它捕获已添加到控制器的最新Subject,并作为新侦听器的第一项发送出去。即使你调用BehaviorSubject的close和drain方法,它仍将保留最后一项在取消订阅时发出。如果不了解该特性的话,将是一场噩梦。而PublishSubject不存储最后一项,查看此项目以便了解BehaviorSubject的行为。

    正确使用BLoC providers

    import 'package:flutter/material.dart';
    import 'ui/login.dart';
    import 'blocs/goals_bloc_provider.dart';
    import 'blocs/login_bloc_provider.dart';
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return LoginBlocProvider(
          child: GoalsBlocProvider(
            child: MaterialApp(
              theme: ThemeData(
                accentColor: Colors.black,
                primaryColor: Colors.amber,
              ),
              home: Scaffold(
                appBar: AppBar(
                  title: Text(
                    "Goals",
                    style: TextStyle(color: Colors.black),
                  ),
                  backgroundColor: Colors.amber,
                  elevation: 0.0,
                ),
                body: LoginScreen(),
              ),
            ),
          ),
        );
      }
    }
    

    上面代码中你可以看到多个Provider嵌套在一起,如果你继续在同一个链中添加更多的BLoC,你将会发现代码很难扩展,BLoC仅保存应用程序所需要的UI配置,但是如果你需要在widgets树中访问多个BLoC时,那么上面的示例是没问题的。但是建议你在大多数情况下不要这样嵌套,只在实际需要的地方提供BLoC。向下面这样使用:

    openDetailPage(ItemModel data, int index) {
        final page = MovieDetailBlocProvider(
          child: MovieDetail(
            title: data.results[index].title,
            posterUrl: data.results[index].backdrop_path,
            description: data.results[index].overview,
            releaseDate: data.results[index].release_date,
            voteAverage: data.results[index].vote_average.toString(),
            movieId: data.results[index].id,
          ),
        );
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) {
            return page;
          }),
        );
      }
    

    这样MovieDetailBlocProvider只为MovieDetail提供BLoC,而不是整个组件树,而且将MovieDetailScreen存储在一个变量中,这样不用每次都重新创建MovieDetailScreen。

    相关文章

      网友评论

        本文标题:Effective BLoC Pattern

        本文链接:https://www.haomeiwen.com/subject/hsbzzqtx.html