skip to Main Content

Using Bloc pattern to show a list of data fetched from rest api.
Now I want to update the list on listview on button click.
When I click on button, it fetches the data but it does not update the listview
Here is the complete code.

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);
    }
  }
}

2

Answers


  1. The first issue is here it is creating new bloc instance with new UserRepository repository. You can create singleton class if you like.

    To use access the current bloc instance you can use BlocProvider.of<UserBloc>(context);.

       final bloc = BlocProvider.of<UserBloc>(context);
       bloc.add(LoadUserEvent());
    

    You can not access the bloc the same context you create it. You can use Builder or new widget for separate context.

    Now while we fetch data, the old data is replaced by new data instead of extending the list. Therefor I am modifying the bloc like

    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()));
          }
        });
      }
    }
    

    Full snippet

    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"));
      }
    }
    
    Login or Signup to reply.
    1. Convert HomePage to a StatefulWidget: This allows you to maintain the page number state properly. Also, it lets you interact with the bloc instance consistently.
    2. Use a Single Instance of UserBloc: Instead of creating a new instance of UserBloc on every button click, use the same instance that is provided to your widget tree via BlocProvider.

    how you can refactor your code:

    Updated 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
          },
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search