我正在使用Provider(3.1.0)中的MultiProvider根据this tutorial中描述的模式构建一个flutter应用程序。

在我的主页中,我想加载配置数据(地点货币符号),但不显示模型。我在上面链接的教程中使用了类似于登录服务的模式,但是当我尝试在另一个 View ( list )中使用值Provider.of<Venue>(context).currency时,出现此错误。



我无法弄清楚与获得Provider.of (context).name的登录名有何不同

这是我的代码:

主镖

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: providers,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        initialRoute: RoutePaths.Login,
        onGenerateRoute: Router.generateRoute,
      ),
    );
  }
}

我要在其中使用提供程序值的 View 。
class BillListItem extends StatelessWidget {
  final Bill bill;
  final Function onTap;

  const BillListItem({this.bill, this.onTap});
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 15.0),
        padding: EdgeInsets.all(10.0),
        decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(5.0),
            boxShadow: [
              BoxShadow(
                  blurRadius: 3.0,
                  offset: Offset(0.0, 2.0),
                  color: Color.fromARGB(80, 0, 0, 0))
            ]),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(bill.billNumber.toString(), style: TextStyle(fontWeight: FontWeight.w900, fontSize: 16.0),),

            Text("${Provider.of<Venue>(context).currency}${bill.payable.toString()}", style: TextStyle(fontWeight: FontWeight.w900, fontSize: 16.0),),
          ],
        ),
      ),
    );
  }
}

我在其中注入(inject) field 的选项卡 View :
class TabContainer extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TabContainerState();
}

class TabContainerState extends State<TabContainer> {
  @override
  void initState(){
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _fetchVenue(context));
  }
  @override
  Widget build(BuildContext context) {
     return BaseWidget<VenueModel>(
      model: VenueModel(venueService: Provider.of(context)),
      builder: (context, model, child) => DefaultTabController(
      length: 2,
      child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: false,
              tabs: [
                Tab(icon: Icon(Icons.home)),
                Tab(icon: Icon(Icons.room_service)),
              ],
            ),
          centerTitle : true,
          title: Text('Exact POS'),
          actions: <Widget>[
             FutureBuilder<String>(
                future: SharedPreferencesHelper.getLanguageCode(),
                initialData: 'en',
                builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                    return snapshot.hasData
                    ? BuildFlag.buildFlag(context, snapshot.data)
                    : Container();
                }
             ),
          ]
        ),

        body: SafeArea(
          child: TabBarView(
            children: [
              HomeView(),
              LoginView(),
            ],
          ),
        ),
       )
      )
    );
  }
   void _fetchVenue(BuildContext context) async {
     Api _api = new Api();
     VenueService _venueService = new VenueService(api: _api);
     VenueModel _venueModel = new VenueModel(venueService: _venueService);
     var success = await _venueModel.fetchVenue();
  }
}
home_view.dart。我怀疑这可能是我需要注入(inject)Venue提供程序而不是TabContainer的地方。
class HomeView extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => HomeViewState();
}

class HomeViewState extends State<HomeView> {
  @override
  void initState(){
    super.initState();

    WidgetsBinding.instance.addPostFrameCallback((_) => showSnackBar());
  }

  void showSnackBar(){
      Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Welcome ${Provider.of<User>(context).name}',
                              style: snackBarStyle),
                backgroundColor: snackBarColor,
                duration: Duration(seconds: 4),
      ));
  }

  @override
  Widget build(BuildContext context) {
     return SafeArea(
               child: Scaffold(
                       backgroundColor: backgroundColor,
                       body: Column(
                               crossAxisAlignment: CrossAxisAlignment.start,
                               children: <Widget>[
                                  UIHelper.verticalSpaceSmall,
                                  Expanded(
                                     child: Tables(),)
                               ],
                       ),
             )
    );

  }
}

我的 View 模型VenueModel
class VenueModel extends BaseModel {
  VenueService _venueService;

  VenueModel(
      {@required VenueService venueService,}
  ) : _venueService = venueService;

  Future<bool> fetchVenue() async {
    setBusy(true);
    var success = await _venueService.fetchVenue();
    setBusy(false);
    return success;
  }
}

我的服务VenueService
class VenueService {
  final Api _api;
  VenueService({Api api}) : _api = api;

  StreamController<Venue> _venueController = StreamController<Venue>();
  Stream<Venue> get venue => _venueController.stream;

  Future<bool> fetchVenue() async {
    var fetchedVenue = await _api.getVenue();
    var hasVenue = fetchedVenue != null;
    if (hasVenue) {
      _venueController.add(fetchedVenue);
    }
    return hasVenue;
  }
}

我的BaseModel.dart
class BaseModel extends ChangeNotifier {
  bool _busy = false;
  bool get busy => _busy;

  void setBusy(bool value) {
    _busy = value;
    notifyListeners();
  }
}

我的提供商设置代码:
List<SingleChildCloneableWidget> providers = [
  ...independentServices,
  ...dependentServices,
  ...uiConsumableProviders
];

List<SingleChildCloneableWidget> independentServices = [
  Provider.value(value: Api())
];

List<SingleChildCloneableWidget> dependentServices = [
  ProxyProvider<Api, AuthenticationService>(
    builder: (context, api, authenticationService) => AuthenticationService(api: api),
  ),

  ProxyProvider<Api, VenueService>(
    builder: (context, api, venueService) => VenueService(api: api),
  ),
];

List<SingleChildCloneableWidget> uiConsumableProviders = [

  StreamProvider<User>(
    builder: (context) => Provider.of<AuthenticationService>(context, listen: false).user,
  ),

    StreamProvider<Venue>(
    builder: (context) => Provider.of<VenueService>(context, listen: false).venue,
  ),
];

base_widget.dart
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
  final T model;
  final Widget child;
  final Function(T) onModelReady;

  BaseWidget({
    Key key,
    this.builder,
    this.model,
    this.child,
    this.onModelReady,
  }) : super(key: key);

  _BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}

class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
  T model;

  @override
  void initState() {
    model = widget.model;

    if (widget.onModelReady != null) {
      widget.onModelReady(model);
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>(
      builder: (context) => model,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }
}

最佳答案



此错误意味着Provider.of<Venue>(context)返回null。这可能是由于两个原因:

  • null值添加到VenueService中的流中。对于您的代码,情况并非如此,这使我相信这是第二个原因:
  • VenueService中没有流发出的值,因此流的初始数据为null

  • 浏览完代码后,只有在选项卡 View 中才在fetchVenue中调用VenueService:
    WidgetsBinding.instance.addPostFrameCallback((_) => _fetchVenue(context));
    
    void _fetchVenue(BuildContext context) async {
      Api _api = new Api();
      VenueService _venueService = new VenueService(api: _api);
      VenueModel _venueModel = new VenueModel(venueService: _venueService);
      var success = await _venueModel.fetchVenue();
    }
    

    _fetchVenue函数中,您实际上是在创建ApiVenueServiceVenueModel的新实例,这些实例在应用程序的其他任何地方都不会使用。提供者列表中的实际VenueService保持不变。实际fetchVenue中的VenueService函数永远不会被调用,这就是Provider.of<Venue>(context)返回null的原因,因为流没有值。

    因此,您可以将_fetchVenue函数替换为:
    void _fetchVenue(BuildContext context) async {
      VenueService _venueService = Provider.of(context);
      VenueModel _venueModel = new VenueModel(venueService: _venueService);
      var success = await _venueModel.fetchVenue();
    }
    

    还需要进行一些改进,例如_venueModel不在任何地方使用,但我希望这可以解决您的问题!

    09-04 21:52