Effective BLoC Pattern

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

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

每个界面都要有自己的BLoC

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

Effective BLoC Pattern_第1张图片
image.png

每个BLoC都要有dispose方法

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

class MoviesBloc {
  final _repository = Repository();
  final _moviesFetcher = PublishSubject();

  Observable 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();

  //Our order office
  Stream 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.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)