В этой статье мы увидим, как использовать библиотеку блоков для выполнения HTTP-запросов и управления состоянием нашего приложения. Окончательное приложение будет выглядеть следующим образом:

Как видите, при первом запуске приложения мы увидим индикатор загрузки, когда данные извлекаются с сервера. И когда они загружаются, мы показываем их в списке.

Для этого мы будем использовать несколько пакетов:

dependencies:
///other dependencies

  flutter_bloc: ^8.1.1
  http: ^0.13.5
  equatable: ^2.0.5

Теперь мы продолжим и создадим механизм для получения данных с сервера с помощью API. Чтобы иметь возможность выполнять операцию http, теперь мы создаем папку внутри lib и называем ее repo. Внутри этого репозитория создайте новый файл с именем repositories.dart.

import 'dart:convert';

import 'package:bloc_example/models/user_model.dart';
import 'package:http/http.dart';

class UserRepository {
  String userUrl = 'https://reqres.in/api/users?page=2';

  Future<List<UserModel>> getUsers() async {
    Response response = await get(Uri.parse(userUrl));
   
    if (response.statusCode == 200) {
      final List result = jsonDecode(response.body)['data'];
      return result.map((e) => UserModel.fromJson(e)).toList();
    } else {
      throw Exception(response.reasonPhrase);
    }
  }
}

Чтобы сделать http-запрос, мы будем использовать http package. Давайте теперь посмотрим на наш json. Мы используем API из этой ссылки.

Как видите, json из приведенной выше ссылки дает нам представление о том, как должна выглядеть наша модель. Из этого json мы получим свойство тега данных. Здесь мы сосредоточимся на имени, фамилии, адресе электронной почты и аватаре.

Давайте построим наш модельный класс.

class UserModel {
  int? id;
  String? email;
  String? firstName;
  String? lastName;
  String? avatar;

  UserModel({this.id, this.email, this.firstName, this.lastName, this.avatar});

  UserModel.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    email = json['email'];
    firstName = json['first_name'];
    lastName = json['last_name'];
    avatar = json['avatar'];
  }
}

Двигаемся дальше, давайте обсудим, как работает наш блок

Как вы можете видеть на картинке выше.

  • Сначала у нас есть UI, а из UI мы делаем запрос к блоку.
  • Блок будет иметь две вещи, событие и состояние. Сначала, когда пользовательский интерфейс подключается к блоку, он создает и запускает событие.
  • Событие в конечном итоге вызовет репозитории на сервер через конечную точку.
  • Теперь мы получаем данные с сервера и передаем их обратно в блок. Когда у нас есть данные, мы запускаем состояние.
  • Поскольку у нас есть изменение состояния, пользовательский интерфейс знает из шаблона Bloc и обновляет пользовательский интерфейс.

Мы создаем новую папку blocks и создаем новый файл с именем app_states.dart.

import 'package:bloc_example/models/user_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

@immutable
abstract class UserState extends Equatable {}

class UserLoadingState extends UserState {
  @override
  List<Object?> get props => [];
}

class UserLoadedState extends UserState {
  final List<UserModel> users;
  UserLoadedState(this.users);
  @override
  List<Object?> get props => [users];
}

class UserErrorState extends UserState {
  final String error;
  UserErrorState(this.error);
  @override
  List<Object?> get props => [error];
}

Equatable используется для сравнения значений, одинаковых они или нет. Если две переменные имеют одинаковые значения, мы не собираемся изменять или обновлять пользовательский интерфейс.

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

Кроме Getx, где мы можем сделать переменную реактивной с помощью x.obs. Но в Блоке это так не работает. И при использовании блока вы должны помнить, что каждое состояние является классом.

В этом проекте у нас есть три состояния.

  • Один, когда данные загружаются.
  • Во-вторых, когда данные загружены.
  • В-третьих, когда у нас возникает ошибка при получении данных.

И внутри Equatable у него есть свойство с именем get props. Вы должны переопределить его. Поскольку наш класс UserState расширил Equatable, класс состояния, то есть UserLoadingState, UserLoadedState и UserErrorState, будет иметь доступ к свойству get props. Чтобы получить это, нам нужно переопределить его и установить для него наши значения.

После состояния в блоке Flutter с каждым состоянием у него есть событие. Теперь мы создаем файл с именем app_events.dart. Как и состояние, нам нужно создать класс для событий.

import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

@immutable
abstract class UserEvent extends Equatable {
  const UserEvent();
}

class LoadUserEvent extends UserEvent {
  @override
  List<Object?> get props => [];
}

Как и государство, мы должны распространять справедливость на событие. Нам нужно создать выделенный класс для каждого события. Класс события должен расширять базовый класс. Здесь нашим базовым классом является UserEvent. И событием является LoadUserEvent. И переопределяет реквизиты get.

@immutable ssdasdasd здесь означает, что мы не хотим изменять свойства этого класса.

После создания состояния и события нам нужно найти способ их связать.

Для этого нам нужно создать новый класс, а затем внедрить только что созданное состояние и событие. Теперь давайте продолжим и соединим их с помощью библиотеки блоков.

Внутри папки blocks мы создаем новый файл с именем app_blocks.

import 'package:bloc_example/blocs/app_events.dart';
import 'package:bloc_example/blocs/app_states.dart';
import 'package:bloc_example/repos/repositories.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _userRepository;

  UserBloc(this._userRepository) : super(UserLoadingState()) {
    on<LoadUserEvent>((event, emit) async {
      emit(UserLoadingState());
      try {
        final users = await _userRepository.getUsers();
        emit(UserLoadedState(users));
      } catch (e) {
        emit(UserErrorState(e.toString()));
      }
    });
  }
}

Здесь у нас есть класс с именем UserBloc, который расширяет класс Bloc. Этот Блок должен взять и событие и государство. В нашем случае это UserEvent и UserState. На самом деле блок здесь взят из библиотеки блоков.

Этот UserBloc имеет конструктор, в который мы передаем репозиторий. Потому что мы будем вызывать это из пользовательского интерфейса. Когда мы вызываем этот класс, мы передаем ему UserRepository. Поскольку мы использовали блок, нам нужно передать начальное состояние суперконструктору. После вызова начального значения мы вызываем метод on.

Теперь метод on принимает тип события, поэтому мы передаем здесь LoadUserEvent. Вы можете использовать это событие, чтобы что-то сделать. Когда вызывается это событие, мы говорим: "Давай, давай", и выдаем какое-то состояние. В этом случае UserLoadingState, чтобы увидеть изменения в пользовательском интерфейсе.

После подключения состояния, события из нашего блока, для запуска состояния и работы с ним, мы создаем новый файл с именем homepage.dart в нашей папке lib.

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<UserBloc>(
          create: (BuildContext context) => UserBloc(UserRepository()),
        ),
      ],
      child: Scaffold(
          appBar: AppBar(title: const Text('The BloC App')),
          body: blocBody()),
    );
  }

Widget blocBody() {
    return BlocProvider(
      create: (context) => UserBloc(
        UserRepository(),
      )..add(LoadUserEvent()),
      child: BlocBuilder<UserBloc, UserState>(
        builder: (context, state) {
          if (state is UserLoadingState) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
           if (state is UserErrorState) {
            return const Center(child:  Text("Error"));
          }
          if (state is UserLoadedState) {
            List<UserModel> userList = state.users;
            return ListView.builder(
                itemCount: userList.length,
                itemBuilder: (_, index) {
                  return Padding(
                    padding:
                        const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
                    child: Card(
                        color: Theme.of(context).primaryColor,
                        child: ListTile(
                            title: Text(
                              '${userList[index].firstName}  ${userList[index].lastName}',
                              style: const TextStyle(color: Colors.white),
                            ),

                            subtitle: Text(
                              '${userList[index].email}',
                              style: const TextStyle(color: Colors.white),
                            ),
                            
                            leading: CircleAvatar(
                              backgroundImage: NetworkImage(
                                  userList[index].avatar.toString()),
                            ))),
                  );
                });
          }

          return Container();
        },
      ),
    );
  }
}

Как видите, у нас есть MultiBlocProvider из нашей библиотеки блоков. Поскольку мы хотим загружать данные с сервера и хотим загружать их только один раз, поэтому в нашем методе сборки мы внедряем UserRepository. С помощью блока формы MultiBlocProvider вы можете добавить несколько блоков в свой класс, чтобы использовать их повсюду. Также там, где вы можете разместить этот репозиторий, вам нужно иметь класс. В нашем случае это Scaffold.

Итак, что делает MultiBlocProvider, так это то, что он перемещает данные с сервера и делает данные и состояния доступными для всего дочернего элемента.

Внутри блока вы можете увидеть BlocProvider. Если вы знакомы с Getx, это похоже на Get.put. Внутри BlocProvider мы внедряем наш блок, т. е. UserBloc, с репозиторием. Поскольку мы уже внедрили наш блок вверху, мы можем получить доступ к событию внутри блока blocBody.

Сначала, после внедрения блока с репозиторием, мы затем добавляем событие, то есть LoadUserEvent, с каскадным оператором, который запускает UserLoadingState из UserBloc.

Теперь мы можем проверить состояние внутри билдера. Поскольку у нас есть три состояния, мы проверяем их одно за другим, чтобы выполнить разные действия и вернуть другой пользовательский интерфейс.

Как вы видете

  • Во-первых, когда UserLoadingState мы показываем CircularProgressIndicator, это означает, что мы загружаем данные.
  • Во-вторых, когда UserLoadedState мы возвращаем список данных.
  • В-третьих, когда UserErrorState мы возвращаем текст ошибки.

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

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

Заключение

В этой статье я объяснил Блок и механизм работы Блока. Вы можете поиграть с кодом в соответствии с вашими потребностями и выбором. Попробуйте создать новую страницу. Блочная архитектура может быть очень полезна для больших проектов.

Я надеюсь, что эта запись в блоге предоставит вам достаточно важной информации о Flutter Bloc, чтобы вы могли использовать библиотеку и архитектуру блоков в своем предстоящем проекте.

❤ ❤ Спасибо, что прочитали эту статью ❤❤

Если вам понравилась статья Хлопайте 👏 .

Кроме того, подпишитесь, чтобы быть в курсе интересных статей и проектов.

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

Давайте подключимся

Мы можем быть друзьями. Найдите на Facebook, Linkedin, Github, Youtube, BuyMeACoffee и Instagram.

Посещение: Flatter Junction

Добавить:КупиКофе

Полный код: