BlocBuilder在点击浮动操作按钮时没有重新构建

4
使用Bloc模式来显示从REST API获取的数据列表。 现在我想在按钮点击时更新列表视图。 当我点击按钮时,它会获取数据,但不会更新列表视图。 以下是完整的代码。

main.dart

import 'package:bloc_demo/homepage.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
       
        primarySwatch: Colors.blue,
      ),
      home:  HomePage(),
    );
  }
}

HomePage.dart

class HomePage extends StatelessWidget {
  String pageNo = "1";
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<UserBloc>(
          create: (BuildContext context) => UserBloc(UserRepository(),pageNo),
        ),
      ],
      child: Scaffold(
        appBar: AppBar(title: const Text('The BloC App')),
        body: blocBody(),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            pageNo = "2";
            UserBloc(
              UserRepository(),pageNo,
            ).add(LoadUserEvent());
          },
          child: const Icon(Icons.navigation),
        ),
      ),
    );
  }

  Widget blocBody() {
    return BlocProvider(
      create: (context) => UserBloc(
        UserRepository(),pageNo,
      )..add(LoadUserEvent()),
      child: BlocBuilder<UserBloc, UserState>(
        builder: (context, state) {
          if (state is UserLoadingState) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          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()),
                            ))),
                  );
                });
          }
          if (state is UserErrorState) {
            return const Center(
              child: Text("Error"),
            );
          }

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

UserBloc

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _userRepository;
  final String pageNo;
  UserBloc(this._userRepository,this.pageNo) : super(UserLoadingState()) {
    on<LoadUserEvent>((event, emit) async {
      emit(UserLoadingState());
      try {
        final users = await _userRepository.getUsers(pageNo);
        emit(UserLoadedState(users));
      } catch (e) {
        emit(UserErrorState(e.toString()));
      }
    });
  }
}

UserEvent

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

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

UserState

@immutable
abstract class UserState extends Equatable {}

//data loading state
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];
}

UserModel

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'];
  }
}

UserRepository

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

  Future<List<UserModel>> getUsers(String pageNo) async {
    Response response = await get(Uri.parse(userUrl+pageNo));

    if (response.statusCode == 200) {
      final List result = jsonDecode(response.body)['data'];
      return result.map((e) => UserModel.fromJson(e)).toList();
    } else {
      throw Exception(response.reasonPhrase);
    }
  }
}

你有没有用调试器检查你的BLoC是否在这一行跳转:emit(UserLoadedState(users)); - undefined
是的,它确实发出信号,但它不调用 bloc builder 状态。 - undefined
4个回答

1
1. 将HomePage转换为StatefulWidget:这样可以正确地维护页面编号状态,并且可以始终与bloc实例进行交互。
2. 使用单个UserBloc实例:不要在每次按钮点击时创建一个新的UserBloc实例,而是使用通过BlocProvider提供给小部件树的相同实例。
如何重构您的代码:
更新的HomePage
class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String pageNo = "1";
  late UserBloc userBloc;

  @override
  void initState() {
    super.initState();
    userBloc = UserBloc(UserRepository(), pageNo)..add(LoadUserEvent());
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<UserBloc>(
      create: (BuildContext context) => userBloc,
      child: Scaffold(
        appBar: AppBar(title: const Text('The BloC App')),
        body: blocBody(),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              pageNo = "2"; // Update the page number
            });
            userBloc.add(LoadUserEvent()); // Use the existing bloc instance
          },
          child: const Icon(Icons.navigation),
        ),
      ),
    );
  }

  Widget blocBody() {
    return BlocBuilder<UserBloc, UserState>(
      builder: (context, state) {
        // Your existing BlocBuilder code
      },
    );
  }
}

0
我并没有经常使用bloc,但大多数状态管理在你修改一个列表时不会触发变量改变的监听事件,所以你应该替换现有的列表而不仅仅是修改它。
我的意思是,如果你有一个List A,不要使用A.add(),而是使用new newA.fromList(...A,[])。

0
第一个问题是它在这里使用新的UserRepository创建了一个新的bloc实例。如果你喜欢,你可以创建一个单例类。
要使用当前bloc实例,你可以使用BlocProvider.of<UserBloc>(context);
   final bloc = BlocProvider.of<UserBloc>(context);
   bloc.add(LoadUserEvent());

你不能在创建上下文的同时访问该上下文。你可以使用Builder或新的widget来分离上下文。
现在,当我们获取数据时,新数据会替换旧数据,而不是扩展列表。因此,我正在修改bloc的方式。
class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _userRepository;
  final String pageNo; //I prefer using event for this.
  UserBloc(this._userRepository, this.pageNo) : super(UserLoadingState()) {
    on<LoadUserEvent>((event, emit) async {
      // emit(UserLoadingState()); //not needed, else we need to lift up the old user list
      debugPrint("pageNo $pageNo");
      try {
        final users = await _userRepository.getUsers(pageNo);
        List<UserModel> oldUser = []; //get the old user list
        if (state is UserLoadedState) {
          oldUser = (state as UserLoadedState).users;
          debugPrint("oldUser.length ${oldUser.length}");
        }
        emit(UserLoadedState([...oldUser, ...users]));
      } catch (e) {
        emit(UserErrorState(e.toString()));
      }
    });
  }
}

完整片段
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:http/http.dart';
import 'dart:convert';
import 'package:equatable/equatable.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  String pageNo = "1";
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<UserBloc>(
          create: (BuildContext context) => UserBloc(UserRepository(), pageNo)
            ..add(LoadUserEvent()), //initially load the user
        ),
      ],
      child: Builder(
        builder: (context) {
          /// you cant directly access the bloc from here, so use builder or new widget
          return Scaffold(
            appBar: AppBar(title: const Text('The BloC App')),
            body: blocBody(),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                final bloc = BlocProvider.of<UserBloc>(context);
                var pageNo = "2";
                bloc.add(LoadUserEvent());
              },
              child: const Icon(Icons.navigation),
            ),
          );
        },
      ),
    );
  }

  Widget blocBody() {
    return BlocBuilder<UserBloc, UserState>(
      builder: (context, state) {
        if (state is UserLoadingState) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
        if (state is UserLoadedState) {
          List<UserModel> userList = state.users;
          debugPrint("userList.length ${userList.length}");
          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()),
                          ),
                    ),
                  ),
                );
              });
        }
        if (state is UserErrorState) {
          return const Center(
            child: Text("Error"),
          );
        }

        return Container();
      },
    );
  }
}

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _userRepository;
  final String pageNo;
  UserBloc(this._userRepository, this.pageNo) : super(UserLoadingState()) {
    on<LoadUserEvent>((event, emit) async {
      // emit(UserLoadingState()); //not needed, else we need to lift up the old user list
      debugPrint("pageNo $pageNo");
      try {
        final users = await _userRepository.getUsers(pageNo);
        List<UserModel> oldUser = []; //get the old user list
        if (state is UserLoadedState) {
          oldUser = (state as UserLoadedState).users;
          debugPrint("oldUser.length ${oldUser.length}");
        }
        emit(UserLoadedState([...oldUser, ...users]));
      } catch (e) {
        emit(UserErrorState(e.toString()));
      }
    });
  }
}

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

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

@immutable
abstract class UserState extends Equatable {}

//data loading state
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];
}

class UserModel { //also prefer extending Equatable
  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'];
  }

}

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

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

    return List.generate(
        3,
        (index) => UserModel(
            id: index,
            email: "email",
            firstName: "firstName",
            lastName: "lastName",
            avatar: "avatar"));
  }
}

emit(UserLoadedState([...oldUser, ...users])); - 这行代码在编译时抛出错误。 - undefined
你能分享一下错误信息吗?你可以复制并粘贴full snippet部分,然后执行flutter clean命令。 - undefined

0
问题可能与您更新页面编号和在的回调中触发数据获取的方式有关。您应该使用提供的现有实例,并从那里分派,而不是直接创建的新实例。
以下是您FloatingActionButton> onPressed回调的更新版本:
floatingActionButton: FloatingActionButton(
  onPressed: () {
    // Access the existing UserBloc instance using BlocProvider
    context.read<UserBloc>().add(LoadUserEvent(pageNo: "2"));
  },
  child: const Icon(Icons.navigation),
),

然后,您需要修改您的LoadUserEvent类,以包括页面编号:
class LoadUserEvent extends UserEvent {
  final String pageNo;

  LoadUserEvent({required this.pageNo});

  @override
  List<Object?> get props => [pageNo];
}

最后,在您的UserBloc中,更新LoadUserEvent处理程序以使用提供的页码:
on<LoadUserEvent>((event, emit) async {
  emit(UserLoadingState());
  try {
    final users = await _userRepository.getUsers(event.pageNo);
    emit(UserLoadedState(users));
  } catch (e) {
    emit(UserErrorState(e.toString()));
  }
});

这样做可以确保您使用现有的UserBloc实例,并在触发数据获取时传递正确的页码。这个改变应该会触发BlocBuilder的重建,并相应地更新ListView。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接