Flutter - Cubit и некоторые пояснения необходимы

Я действительно новичок в флаттере и паттерне Cubit.
Насколько я знаю, Cubit - это что-то совершенно новое, и теперь это основа паттерна BloC.

Я попытался понять, как это работает, понял некоторые концепции и кое-что об управлении состоянием, и я попытался создать простое приложение.

Приложение связано с 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 и добавляет дома в список.

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 .

На странице single maison я попытался запустить репозиторий и использовать метод поиска следующим образом:

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)
...
════════════════════════════════════════════════════════════════════════════════════════════════════

Что мне не хватает?
Это хороший подход для получения данных?


person Christian Giupponi    schedule 14.09.2020    source источник
comment
Вы получаете исключение, потому что _maisonsRepository не инициализирован в _MaisonPageState.   -  person Derek K    schedule 14.09.2020
comment
Спасибо за ваш комментарий, я инициализировал репозиторий, но есть другие проблемы. Кажется, что мой список items пуст, когда я вызываю метод поиска, есть идеи, почему?   -  person Christian Giupponi    schedule 15.09.2020
comment
Вы должны использовать initState для создания репозитория и вызова метода getMaisons. Или, когда MaisonsRepository построен, вы можете вызвать этот метод.   -  person mddg    schedule 18.09.2020


Ответы (1)


Как упоминалось в комментариях, _maisonsRepository еще не инициализирован. Что касается пустого items List, вы можете вызвать getMaisons после инициализации репозитория.

person Omatt    schedule 20.04.2021