Flutter - Cubit 和一些需要澄清

Posted

技术标签:

【中文标题】Flutter - Cubit 和一些需要澄清【英文标题】:Flutter - Cubit and some clarification needed 【发布时间】:2021-01-01 09:04:47 【问题描述】:

我真的是 Flutter 和 Cubit 模式的新手。 据我所知,Cubit 是相当新的东西,现在它是 BloC 模式的基础。

我试图理解它是如何工作的,我已经理解了一些关于状态管理的概念和一些东西,我试图构建一个简单的应用程序。

该应用程序与一个 API 连接,该 API 响应商店列表并具有 BottomTabBar。 这是我的代码:

main.dart

import 'package:myapp/cubit/maison_cubit.dart';
import 'package:myapp/pages/maison.dart';
import 'package:myapp/pages/home.dart';
import 'package:myapp/repository/maisons_repository.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    final textTheme = Theme.of(context).textTheme;

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'My Cubit app',
      home: BlocProvider(
        create: (context) => MaisonCubit(MaisonsRepository()),
        child: HomePage(),
      ),
      routes: 
        MaisonPage.routeName: (ctx) => MaisonPage(),
      ,
    );
  

我的maison_repository.dart 只需调用一个外部 API 并将这些maisons 添加到列表中。

import 'dart:convert';
import 'package:myapp/const.dart';
import 'package:http/http.dart' as http;

import '../models/maison.dart';

class MaisonsRepository 
  List<Maison> items = [];

  Future<List<Maison>> getMaisons() async 
    final response = await http.get('https://get-maison.example', headers: 
      "Accept": "application/json",
      "content-type": "application/json",
    );

    if (response.statusCode == 200) 
      items.clear();
      List<dynamic> list = json.decode(response.body);
      list.forEach((element) 
        items.add(Maison.fromJson(element));
      );

      return items;
     else 
      throw Exception('Failed to load maisons');
    
  

  Maison find(String id) 
    return items.firstWhere((element) => element.id == id);
  

这是home.dart。在MaisonLoaded 我调用BottomBar 需要构建底部栏并显示正确的页面。在我读过的所有文档中,我都没有找到关于在从存储库中获取数据后如何管理数据的良好解释,因此我在BottomBar 中添加了一个构造函数,并且我已经传递了数据。对吗?

import 'package:myapp/bottom_bar.dart';
import 'package:myapp/cubit/maison_cubit.dart';
import 'package:myapp/pages/maisons_listing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomePage extends StatefulWidget 
  @override
  _HomePageState createState() => _HomePageState();


class _HomePageState extends State<HomePage> 
  @override
  Widget build(BuildContext context) 
    final maisonCubit = context.bloc<MaisonCubit>();
    maisonCubit.getMaisons();

    return BlocConsumer<MaisonCubit, MaisonState>(
      listener: (context, state) 
        if (state is MaisonError) 
          return Container(
            child: Text('Missing connection'),
          );
        
      ,
      builder: (context, state) 
        if (state is MaisonLoading) 
          return Container(
            child: Text('My loader here'),
          );
        

        if (state is MaisonLoaded) 
          return Container(
            child: BottomBar(state.maisons),
          );
        

        return Container();
      ,
    );
  

这是BottomBar 小部件(MapPage 只是一个包含文本的容器)

import 'package:myapp/models/maison.dart';
import 'package:myapp/pages/maisons_listing.dart';
import 'package:myapp/pages/map.dart';
import 'package:flutter/material.dart';

class BottomBar extends StatefulWidget 
  final List<Maison> maisons;
  BottomBar(this.maisons);
  @override
  _BottomBarState createState() => _BottomBarState();


class _BottomBarState extends State<BottomBar> 
  int _currentIndex = 0;

  void onTabTapped(int index) 
    setState(() 
      _currentIndex = index;
    );
  

  @override
  Widget build(BuildContext context) 
    List<Widget> _children = [
      MaisonListingPage(widget.maisons),
      MapPage(widget.maisons),
    ];

    return Scaffold(
      body: _children[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Color(0xFFDDCDC8),
        currentIndex: _currentIndex,
        onTap: onTabTapped,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text('Maisons'),
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.map,
            ),
            title: Text('Map'),
          ),
        ],
      ),
    );
  

这是应该显示品牌列表的页面。

import 'package:myapp/models/maison.dart';
import 'package:myapp/pages/maison.dart';
import 'package:flutter/material.dart';

class MaisonListingPage extends StatefulWidget 
  final List<Maison> maisons;

  MaisonListingPage(this.maisons);
  @override
  _MaisonListingPageState createState() => _MaisonListingPageState();


class _MaisonListingPageState extends State<MaisonListingPage> 
  List<Maison> _currentList = [];
  List<Maison> _maisons = [];
    
  @override
  Widget build(BuildContext context) 
    _maisons = widget.maisons;
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          HeroWidget(),
          Transform.translate(
            offset: Offset(0, -30),
            child: Container(
              height: 60.0,
              padding: EdgeInsets.symmetric(vertical: 5.0),
              margin: EdgeInsets.symmetric(horizontal: 16),
              decoration: BoxDecoration(
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                    color: Colors.grey[350],
                    blurRadius: 20.0,
                    offset: Offset(0, 10.0),
                  ),
                ],
              ),
              child: ListTile(
                leading: Icon(Icons.search),
                title: TextField(
                  decoration: InputDecoration(
                    border: InputBorder.none,
                    hintText: AppLocalizations.of(context)
                        .translate('home_search_input_placeholder'),
                  ),
                ),
                trailing: IconButton(
                  icon: Icon(
                    Icons.clear,
                  ),
                  onPressed: () 
                  ,
                ),
              ),
            ),
          ),
          MediaQuery.removePadding(
            context: context,
            removeTop: true,
            child: maisonListView(),
          ),
        ],
      ),
    );
  

  ListView maisonListView() 
    return ListView.builder(
      shrinkWrap: true,
      primary: false,
      itemCount: _currentList.length,
      itemBuilder: (BuildContext context, int index) 
        return GestureDetector(
          onTap: () => Navigator.of(context).pushNamed(
            MaisonPage.routeName,
            arguments: _currentList[index].id,
          ),
          child: Text(_currentList[index].name),
        );
      ,
    );
  

  onChange() 
    setState(() );
  

如果我运行代码,我可以看到该品牌的列表。问题出在一个maison上的水龙头上,我想打开一个新页面并显示所有内容。 所以我在 maison_repository 中添加了一个方法,如果你检查它,你可以看到一个find 方法。

在单个 maison 页面中,我尝试过初始化存储库并以这种方式使用 find 方法:

import 'dart:async';
import 'package:myapp/models/maison.dart';
import 'package:myapp/repository/maisons_repository.dart';
import 'package:flutter/material.dart';

class MaisonPage extends StatefulWidget 
  static const routeName = '/single-maison';

  @override
  _MaisonPageState createState() => _MaisonPageState();


class _MaisonPageState extends State<MaisonPage> 

  MaisonsRepository _maisonsRepository;

  @override
  Widget build(BuildContext context) 

    String maisonId = ModalRoute.of(context).settings.arguments as String;
    Maison maison = _maisonsRepository.find(maisonId);


    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            expandedHeight: MediaQuery.of(context).size.width * 0.70,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text(maison.name),
              background: MaisonHeroWidget(
                id: maison.id,
                imageUrl: maison.imageUrl,
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildListDelegate(
              [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: <Widget>[
                      Text(
                        maison.description,
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  

当我尝试访问此页面时,我得到:

The following NoSuchMethodError was thrown building MaisonPage(dirty, dependencies: [_ModalScopeStatus], state: _MaisonPageState#335d9):
The method 'find' was called on null.
Receiver: null
Tried calling: find("2389")

The relevant error-causing widget was: 
  MaisonPage file:///Users/christiangiupponi/Dev/FlutterApp/myapp/lib/main.dart:63:40
When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1      _MaisonPageState.build (package:myapp/pages/maison.dart:69:40)
#2      StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
#3      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4502:15)
#4      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4675:11)
...
════════════════════════════════════════════════════════════════════════════════════════════════════

我错过了什么? 这是获取数据的好方法吗?

【问题讨论】:

您收到异常是因为 _maisonsRepository 未在 _MaisonPageState 中初始化。 感谢您的评论,我现在已经初始化了存储库,但还有其他问题。当我调用 find 方法时,我的 items 列表似乎是空的,知道为什么吗? 您应该使用initState 创建存储库并调用getMaisons 方法。或者,当MaisonsRepository 被构造时,你可以调用那个方法。 【参考方案1】:

如 cmets 中所述,_maisonsRepository 尚未初始化。至于空的itemsList,可以在repository初始化后调用getMaisons

【讨论】:

以上是关于Flutter - Cubit 和一些需要澄清的主要内容,如果未能解决你的问题,请参考以下文章

Flutter BLoC/Cubit STATE 类最佳实践

Flutter - cubit - 改变cubit内的布尔值

Flutter Cubit 状态不离开起点(MyInitialState)

使用 Bloc/Cubit 进行 Flutter 状态管理

我需要处置cubit实例吗?

如何保持我的bottomNavigationBar Cubit BLoc Flutter的状态?