Как вы уже знаете о последних рекомендациях Flutter. Одним из них является Шаблон блока, который работает с Потоком событий и генерирует соответствующее состояние. Но эта статья будет посвящена не полному объяснению вышеописанного решения для управления состоянием, а небольшой его части. Давайте углубимся и узнаем больше…

Поправьте меня, если я ошибаюсь. В наших разработках мы часто используем следующие виджеты:

НижняяНавигацияБар

TabBar

TextFormFields на основе пароля

Что общего между ними? любые предположения…

Вот ответ: мы используем setState, чтобы получить преимущество. Если вы новичок, это нормально, но если вы достигли определенного уровня, вы можете этого не делать, так как это считается плохой практикой. Итак, мы рассмотрим логику замены setState по умолчанию во Flutter на StreamController, чтобы получить такое же преимущество и в профессиональном плане.

Примечание. Здесь я рассчитываю, что моя аудитория знает о Streams и немного знает о Bloc и его расширении Cubit.

Оглавление

Замена логики и реализации виджета

- Замена логики и реализации виджета

Мы собираемся использовать простой и немного хитрый способ (с Cubit Bloc) для их реализации.

Сначала для TextFields по-простому.

1- Поля TextFormField на основе пароля

Рассмотрим этот фрагмент

CustomFormField(
  obscureText: snapshot.data!,
  textEditingController: _password,
  hintText: 'Password',
  onChanged: bloc.passC,
  inputAction: TextInputAction.done,
  suffixIcon: IconButton(
    icon: const Icon(Icons.visibility),
    onPressed: () {},
  ),
)

Это настраиваемое поле формы, которое ведет себя так же, как TextFormField. относительно его свойства «suffixIcon», мы собираемся реализовать логику скрытия/отображения пароля через StreamBuilder.

Во-первых, создайте экземпляр StreamController с типом bool.

StreamController<bool> _passVisibility = StreamController<bool>();

Примечание. Чтобы использовать поток непрерывно, нам нужно немного расширить наш контроллер, иначе возникнет ошибка.

Так же, как это…

StreamController<bool> _passVisibility = StreamController<bool>.broadcast();

Кроме того, обертывание нашего FormField с помощью StreamBuilder и внесение некоторых изменений будет выглядеть примерно так…

StreamBuilder<bool>(
    stream: passwordVisibilityController.stream,
    initialData: true,
    builder: (context, snapshot) {
      return CustomFormField(
        obscureText: snapshot.data!,
        textEditingController: _password,
        hintText: 'Password',
        inputAction: TextInputAction.done,
        suffixIcon: IconButton(
          icon: const Icon(Icons.visibility),
          onPressed: () {},
        ),
      );
    }),

Теперь наша структура готова, нам нужно добавить тройку для переключения значков и текста ошибки. Вот последний фрагмент:

StreamBuilder<bool>(
    stream: passwordVisibilityController.stream,
    initialData: true,
    builder: (context, snapshot) {
      return CustomFormField(
        obscureText: snapshot.data!,
        textEditingController: _password,
        hintText: 'Password',
        onChanged: bloc.passC,
        errorText:
        s.hasError ? s.error.toString() : null,
        inputAction: TextInputAction.done,
        suffixIcon: !snapshot.data!
            ? IconButton(
          icon: const Icon(Icons.visibility),
          onPressed: () {
            passwordVisibilityController.sink
                .add(true);
          },
        )
            : IconButton(
          icon:
          const Icon(Icons.visibility_off),
          onPressed: () {
            passwordVisibilityController.sink
                .add(false);
          },
        ),
      );
    }),

Попробуйте этот способ добиться того же результата профессионально.

Давайте заглянем в раздел BottomNavigationBar.

2-нижняя панель навигации

Допустим, у нас есть 4 элемента бара, перечисленных как:

Главная

Мои заказы

Список пожеланий и

Профиль соответственно.

Рассмотрим этот фрагмент

List<Widget>? list;
int? index;
@override
void initState() {
  // TODO: implement initState
  super.initState();
  index = 0;
  list = [
    Home(),
     const MyOrders(),
     const Wishlist(),
     const Profile(),
  ];
}

@override
Widget build(BuildContext context) {
  double iconSize = 6.w;
      return Scaffold(
        body: list![index],
        bottomNavigationBar: BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              elevation: 8,
              enableFeedback: true,
              backgroundColor: AmsTheme.appWhite,
              currentIndex: state,
              showSelectedLabels: false,
              showUnselectedLabels: false,
              onTap: (i) {
             setState(() {
               index = i;
              });             
            },
              items: [
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/home.png",
                       height: iconSize,
                    ),
                    label: "Home"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/orders.png",
                       height: iconSize,
                    ),
                    label: "My Orders"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/wishlist.png",
                       height: iconSize,
                    ),
                    label: "Wishlist"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/profile.png",
                       height: iconSize,
                    ),
                    label: "Profile"),
              ],
            ),
          );
}

Без ошибок BottomNavBar. Давайте конвертируем это с помощью Cubit.

Здесь я беру Cubit (вместо блока), потому что теперь нам не нужно предоставлять ему какое-либо событие, а просто генерировать соответствующее состояние. Вот и все!

Создайте новый файл под названием «nav_bar_cubit.dart» и запишите этот фрагмент.

import 'package:flutter_bloc/flutter_bloc.dart';

class BottomNavBarCubit extends Cubit<int> {
  BottomNavBarCubit() : super(0);

  dynamic updateIndex(int index) => emit(index);
}

Так просто, как, что.

Затем нам нужно зарегистрировать этот Cubit перед фазой инициализации, т. е. MaterialApp().

BlocProvider(
  create: (context) => BottomNavBarCubit(),
),
child: MaterialApp().....

Теперь нам просто нужно реализовать это на нашем желаемом экране.

Чтобы использовать этот кубит, нам нужен BlocBuilder для получения текущего состояния (индекса) и функции.

Вот обновленный фрагмент

List<Widget>? list;
@override
void initState() {
  // TODO: implement initState
  super.initState();
  list = [
    Home(),
     const MyOrders(),
     const Wishlist(),
     const Profile(),
  ];
}
@override
Widget build(BuildContext context) {
  double iconSize = 6.w;
  return BlocBuilder<BottomNavBarCubit, int>(
    builder: (context, state) {
      return Scaffold(
        body: list![state],
        bottomNavigationBar: BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              elevation: 8,
              enableFeedback: true,
              backgroundColor: AmsTheme.appWhite,
              currentIndex: state,
              showSelectedLabels: false,
              showUnselectedLabels: false,
              onTap: (i) => 
              context.read<BottomNavBarCubit>().updateIndex(i),
              items: [
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/home.png",
                       height: iconSize,
                    ),
                    label: "Home"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/orders.png",
                       height: iconSize,
                    ),
                    label: "My Orders"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/wishlist.png",
                       height: iconSize,
                    ),
                    label: "Wishlist"),
                BottomNavigationBarItem(
                    icon: Image.asset(
                      "assets/icons/profile.png",
                       height: iconSize,
                    ),
                    label: "Profile"),
              ],
            ),
          ),
    }
  );
}

Всего несколько строк кода, но определенно хорошо написанный код 👍

Запустите приложение, и все готово.

P.S. Та же логика применима и к TabBar.

Пришло время подвести итоги. Надеюсь, вам понравилось читать.

👏 И не забывайте подписываться на меня.

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

Проверьте мои ручки в социальных сетях:

- Youtube (учебники по Flutter (советы, приемы, решение проблем и т. д.)



- LinkedIn



- Репозиторий GitHub



- - -Хорошего дня!!! — — —