Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter snackbar display based on stream

Tags:

flutter

rxdart

I currently have an interesting issue related to displaying of a snackbar based on a user action.

Above sounds very trivial, but lets elaborate:

I have 2 screens:

  1. list of employees
  2. Add employee

The application uses the bloc pattern (with streams/rxdart).

Here is what I want:

  • User clicks on add employee FAB button in list of employees screen and is navigated to the Add Employee screen (works perfectly fine)
  • User fills in employee details and clicks save
  • Upon save, employee is added to the stream, and updated the list of employees screen (works fine)
  • User is navigated BACK to list of employees (works fine)
  • Snackbar is displayed stating that the employee has successfully been added (This is the problem)

I tried several ways of implementing this:

Add a new stream (employeeAdded), and when adding an employee to the employees stream, additionally push a boolean to employee added.

In the list of employees, add a new stream builder and in the builder logic add the snackbar.

This gives all sorts of problems, for example trying to display the snackbar before the page has been (re)build, and so on.

The question is twofold: What is good UX practice for this scenario, and what would be a good solution for this problem?

(will post code on request)

Thanks for the help!

like image 367
Joey Roosing Avatar asked Jan 03 '19 13:01

Joey Roosing


People also ask

How do you show SnackBar in flutter without scaffolding?

you probably have a parent widget that contains Scaffold as well. Create a scaffoldKey for that and pass it to your child widget that have to show the Snakcbar . Note that you can't use Sanckbar without Scaffold widget.

What can I use instead of Shownackbar flutter?

But it fails with The method showSnackBar was called on null . What's wrong with this code? Scaffold. of() is deprecated for showing SnackBars, use ScaffoldMessenger instead.

How do you create a SnackBar class in flutter?

Creating SnackBar In Flutter We can create a snackbar in flutter by calling its constructor. There is only one required property to create a snackbar which is content. Usually we will use a Text widget for content since we need to show some message to the user. We can use other widgets instead if we want.


4 Answers

There is ready recipe for showing Snackbar in Bloc. Basically BlocListener has been created for that purpose. Please look on https://felangel.github.io/bloc/#/recipesfluttershowsnackbar

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final dataBloc = BlocProvider.of<DataBloc>(context);
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: BlocListener(
        bloc: dataBloc,
        listener: (BuildContext context, DataState state) {
          if (state is Success) {
            Scaffold.of(context).showSnackBar(
              SnackBar(
                backgroundColor: Colors.green,
                content: Text('Success'),
              ),
            );
          }
        },
        child: YourChild()
      ),
   );
  }
}
like image 50
Tomasz Białecki Avatar answered Oct 15 '22 16:10

Tomasz Białecki


Wrap your body (the child of your Scaffold, in your case returned by _buildBody) with a Builder so you get to write some "free" code with the context.

Now you can listen to the stream with a handler you provide (like you already did), but this time you register the listener only once per Scaffold build (and not registering a listener at each event occurred in employeesAdded stream).

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Builder(
        builder: (BuildContext context) {
          bloc.employeesAdded.listen(
            (_) {
              Scaffold.of(context).showSnackBar(
                SnackBar(
                  content: Text('Employee added'),
                ), // SnackBar
              );
            },
          );

          return _buildBody(bloc);
        },
      ), // Builder
    ); // Scaffold
  }
like image 29
idow09 Avatar answered Oct 15 '22 15:10

idow09


@joey I have found a solution but I'm not sure that it's the best. I'm changing my app to bloc pattern now, the same problem as yours occurred so I did this:

In my bloc file I put a controller for the scaffoldState:

ScaffoldState scaffold;

StreamController<ScaffoldState> _scaffoldController = StreamController<ScaffoldState>();
StreamSink<ScaffoldState> get setScaffold => _scaffoldController.sink;

In the constructor of the bloc file I listen to that stream:

LoginBloc(){
    _scaffoldController.stream.listen((sc){ scaffold = sc;});
}

In the widget I sink the scaffoldState whenever I want to use it:

onPressed:() {
   bloc.setScaffold.add(Scaffold.of(context));
}

so then, I can show snackbars as usual from the bloc:

scaffold.showSnackBar(SnackBar(content: Text(hi world'),),);

UPDATE

I think the best way would for separating the ui and the bloc would be to listen for the stream on the ui (either on initState or didChangeDependencies if you are using a blocprovider).

like image 45
Jose Jet Avatar answered Oct 15 '22 16:10

Jose Jet


Although its not my favourite answer, I am also not sure if there is a better alternative; this is what I did as a temporary fix for future watchers:

Create a new stream (in my case employeeAdded), upon adding employees, also create an entry into the stream:

  final _employees = BehaviorSubject<List<Employee>>(seedValue: List<Employee>());
  final _employeeAdded = BehaviorSubject();

  // streams (out)
  Observable<List<Employee>> get employees => _employees.stream;
  Observable<dynamic> get employeesAdded => _employeeAdded.stream;

  addEmployee(Employee employee) async {
    final newList = <Employee>[]..addAll(_employees.value)..add(employee);
    await _employeeRepo.upsertEmployee(employee);
    _employees.add(newList);

    _employeeAdded.add(true);
    //FIXME: Save to db
  }

Note there is no seedValue in employeeAdded, this is to prevent the snackbar from showing on initial load.

In my screen / page I have a scaffold; it's body calls another method which should explain the rest of the code:

 Widget _buildBody(EmployeeBloc bloc) {
    return StreamBuilder(
      stream: bloc.employees,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          bloc.employeesAdded.listen(
            (_) => Scaffold.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Employee added'),
                  ),
                ),
          );
          bloc.seedEmployees();
          return Center(
            child: Text("No employees"),
          );
        }
        return _buildList(bloc, snapshot.data);
      },
    );
  }

Note the listen on the bloc with the hasData if.

This works for now, but would like to know if there is a more neat example.

like image 32
Joey Roosing Avatar answered Oct 15 '22 15:10

Joey Roosing