I am using sqflite database to save user list. I have user list screen, which shows list of user and it has a fab button, on click of fab button, user is redirected to next screen where he can add new user to database. The new user is properly inserted to the database but when user presses back button and go backs to user list screen, the newly added user is not visible on the screen. I have to close the app and reopen it,then the newly added user is visible on the screen.

I am using bloc pattern and following is my code to show user list

class _UserListState extends State<UserList> {
  UserBloc userBloc;

  @override
  void initState() {
    super.initState();
    userBloc = BlocProvider.of<UserBloc>(context);
    userBloc.fetchUser();
  }

  @override
  void dispose() {
    userBloc?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(

      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).pushNamed("/detail");
        },
        child: Icon(Icons.add),
      ),
      body: StreamBuilder(
        stream: userBloc.users,
        builder: (context, AsyncSnapshot<List<User>> snapshot) {
          if (!snapshot.hasData) {
            return Center(
              child: CircularProgressIndicator(),
            );
          }

          if (snapshot.data != null) {
            return ListView.builder(
              itemBuilder: (context, index) {
                return Dismissible(
                  key: Key(snapshot.data[index].id.toString()),
                  direction: DismissDirection.endToStart,
                  onDismissed: (direction) {
                    userBloc.deleteParticularUser(snapshot.data[index]);
                  },
                  child: ListTile(
                    onTap: () {
                      Navigator.of(context).push(MaterialPageRoute(
                          builder: (context) => UserDetail(
                               user: snapshot.data[index],
                              )));
                    },
                    title: Text(snapshot.data[index].name),
                    subtitle:
                        Text("Mobile Number ${snapshot.data[index].userId}"),
                    trailing:
                        Text("User Id ${snapshot.data[index].mobileNumber}"),
                  ),
                );
              },
              itemCount: snapshot.data.length,
            );
          }
        },
      ),
    );
  }
}

Following is my bloc code

    class UserBloc implements BlocBase {
  final _users = BehaviorSubject<List<User>>();

  Observable<List<User>> get users => _users.stream;

  fetchUser() async {
    await userRepository.initializeDatabase();

    final users = await userRepository.getUserList();
    _users.sink.add(users);
  }

  insertUser(String name,int id,int phoneNumber) async {
    userRepository.insertUser(User(id, name, phoneNumber));
    fetchUser();
  }

  updateUser(User user) async {
    userRepository.updateUser(user);
  }

  deleteParticularUser(User user) async {
    userRepository.deleteParticularUser(user);
  }

  deleteAllUser() {
    return userRepository.deleteAllUsers();
  }

  @override
  void dispose() {
    _users.close();
  }
}

As Remi posted answer saying i should try BehaviorSubject and ReplaySubject which i tried but it does not help. I have also called fetchUser(); inside insertUser() as pointed in comments

Following is the link of the full example

https://github.com/pritsawa/sqflite_example


Solution 1: Rémi Rousselet

The issue is that you're using a PublishSubject.

When a new listener subscribes to a PublishSubject, it does not receive the previously sent value and will only receive the next events.

The easiest solution is to use a BehaviorSubject or a ReplaySubject instead. These two will directly call their listener with the latest values.


Solution 2: user

Follow up from the comments, it seems you don't have a single instance of your UsersBloc in those two pages. Both the HomePage and UserDetails return a BlocProvider which instantiate a UsersBloc instance. Because you have two blocs instances(which you shouldn't have) you don't update the streams properly.

The solution is to remove the BlocProvider from those two pages(HomePage and UserDetail) and wrap the MaterialApp widget in it to make the same instance available to both pages.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      bloc: UserBloc(),
      child:MaterialApp(...

The HomePage will be:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return UserList(),
    );
  }
}

Remove the BlocProvider from UserDetail as well. In the UsersBloc then call fetchUser() inside the insertUser() method after the user insertion, to requery the database and update the users stream.

Also as Rémi Rousselet said, use one of the subjects that return previous values.


Solution 3: Jai Sachdeva

@override
void initState() {
super.initState();
userBloc = BlocProvider.of<UserBloc>(context);
userBloc.fetchUser();
}

The problem is that you have called the userBloc.fetchUser() function in the initState of the page.

Bloc stream emits whenever a new data is added to it and the userBloc.fetchUser() function does exactly that, it adds the userList that you fetch from the Sqflite database.

Whenever you come back to the userlist screen from add user screen, init function is NOT called. It is only called when the userlist screen is created, that is, whenever you push it to the navigation stack.

The workaround is to call userBloc.fetchUser() whenever your StreamBuilder's snapshot data is null.

...
 if (!snapshot.hasData) {
      userBloc.fetchUser();
        return Center(
          child: CircularProgressIndicator(),
        );
      }
...