Evento di blocco attivato solo una volta

Aug 22 2020

Ho seguito questo approccio per gestire i blocchi e salvare qualche riga di codice.

Voglio recuperare i dati dalla mia API. Vorrei farlo tramite initStatemetodo e chiamare myBloc.add(MyEvent()). Ma il problema è che è stato chiamato solo una volta.

Ho cercato su Google e provato qualche soluzione su diversi blog e si tratta di problemi di repository Github ufficiali, ma continua a non funzionare. Ho trovato una domanda simile ma dal momento che non sto usando alcuna iniezione di dipendenza o singleton, non sono riuscito a trovare esattamente cosa e dove si trova il problema e il mio problema non è ancora stato risolto.

Ecco cosa ho provato finora e ancora non ho risolto il problema:

  1. Rimozione di Equatable dal blocco
  2. Ricaricare a fondo il dispositivo
  3. Esegui flutter cleancomando
  4. Eseguire nuovamente l'app

Per chiarire, dai un'occhiata a questa registrazione.

E infine, ecco come appare il mio script:

leave_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/data/repositories/leave_repository.dart';
import 'package:meta/meta.dart';

part 'leave_event.dart';
part 'leave_state.dart';

class LeaveBloc extends Bloc<LeaveEvent, LeaveState> {
  LeaveBloc() : super(LeaveInitial());

  final LeaveRepository repository = LeaveRepository();

  @override
  Stream<LeaveState> mapEventToState(
    LeaveEvent event,
  ) async* {
    print('TRIGGERED EVENT IS: $event');
    if (event is LeaveScreenInitialized) {
      yield LeaveLoading();

      try {
        final response = await repository.fetch();
        if (response is List<Leave> && response.isNotEmpty) {
          yield LeaveLoaded(data: response);
        } else {
          yield LeaveEmpty();
        }
      } catch (e) {
        yield LeaveFailure(error: e.toString());
      }
    }

    if (event is LeaveAdded) {
      yield LeaveCreated(data: event.data);
    }
  }
}

leave_event.dart

part of 'leave_bloc.dart';

abstract class LeaveEvent {
  const LeaveEvent();

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

class LeaveScreenInitialized extends LeaveEvent {}

class LeaveAdded extends LeaveEvent {
  final Leave data;
  const LeaveAdded({@required this.data}) : assert(data != null);

  // @override
  // List<Object> get props => [data];

  @override
  String toString() => 'LeaveAdded { data: $data }';
}

leave_state.dart

part of 'leave_bloc.dart';

abstract class LeaveState {
  const LeaveState();

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

class LeaveInitial extends LeaveState {}

class LeaveLoading extends LeaveState {}

class LeaveEmpty extends LeaveState {}

class LeaveLoaded extends LeaveState {
  final List<Leave> data;
  const LeaveLoaded({@required this.data}) : assert(data != null);

  // @override
  // List<Object> get props => [data];

  @override
  String toString() => 'LeaveLoaded { data: $data }';
}

class LeaveFailure extends LeaveState {
  final String error;
  const LeaveFailure({@required this.error}) : assert(error != null);

  // @override
  // List<Object> get props => [error];

  @override
  String toString() => 'LeaveFailure { error: $error }';
}

class LeaveCreated extends LeaveState {
  final Leave data;
  const LeaveCreated({@required this.data}) : assert(data != null);

  // @override
  // List<Object> get props => [data];

  @override
  String toString() => 'LeaveCreated { data: $data }';
}

leave_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave/leave_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave_update/leave_update_bloc.dart';
import 'package:flutter_prismahr/app/components/empty.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/routes/routes.dart';

import 'components/leave_list.dart';
import 'components/leave_list_loading.dart';

class LeaveScreen extends StatefulWidget {
  LeaveScreen({Key key}) : super(key: key);

  @override
  _LeaveScreenState createState() => _LeaveScreenState();
}

class _LeaveScreenState extends State<LeaveScreen> {
  LeaveBloc _leaveBloc;
  LeaveUpdateBloc _leaveUpdateBloc;
  List<Leave> _leaves;

  @override
  void initState() {
    print('INIT STATE CALLED');
    _leaves = <Leave>[];
    _leaveBloc = BlocProvider.of<LeaveBloc>(context);
    _leaveUpdateBloc = BlocProvider.of<LeaveUpdateBloc>(context);
    _leaveBloc.add(LeaveScreenInitialized());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            pinned: true,
            floating: true,
            title: Text(
              'Leave Requests',
              style: Theme.of(context)
                  .textTheme
                  .headline6
                  .copyWith(fontWeight: FontWeight.w900),
            ),
          ),
          SliverToBoxAdapter(
            child: MultiBlocListener(
              listeners: [
                BlocListener<LeaveBloc, LeaveState>(
                  listener: (context, state) {
                    if (state is LeaveLoaded) {
                      setState(() {
                        _leaves = state.data;
                      });
                    }

                    if (state is LeaveCreated) {
                      setState(() {
                        _leaves.add(state.data);
                      });
                    }
                  },
                ),
                BlocListener<LeaveUpdateBloc, LeaveUpdateState>(
                  listener: (context, state) {
                    if (state is LeaveUpdateSuccess) {
                      int index = _leaves.indexWhere((leave) {
                        return leave.id == state.data.id;
                      });

                      setState(() {
                        _leaves[index] = state.data;
                        _leaveUpdateBloc.add(ResetState());
                      });
                    }
                  },
                ),
              ],
              child: BlocBuilder<LeaveBloc, LeaveState>(
                builder: (context, state) {
                  if (state is LeaveLoading) {
                    return Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 20,
                        vertical: 30,
                      ),
                      child: LeaveListLoading(),
                    );
                  }

                  if (state is LeaveEmpty) {
                    return Padding(
                      padding: const EdgeInsets.only(top: 100),
                      child: Empty(),
                    );
                  }

                  return LeaveList(
                    data: _leaves,
                    bloc: _leaveUpdateBloc,
                  );
                },
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: BlocBuilder<LeaveBloc, LeaveState>(
        builder: (context, state) {
          if (state is! LeaveLoading) {
            return FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () async {
                final data = await Navigator.of(context).pushNamed(
                  Routes.LEAVE_CREATE,
                );

                if (data != null) {
                  _leaveBloc.add(LeaveAdded(data: data));
                }
              },
            );
          }
          return SizedBox();
        },
      ),
    );
  }
}

app_router.dart

...
...

import 'package:flutter_prismahr/app/views/leave/leave_screen.dart';

...
...

class Router {
  // Provide a function to handle named routes. Use this function to
  // identify the named route being pushed, and create the correct
  // screen.
  final LeaveBloc _leaveBloc = LeaveBloc();
  final LeaveUpdateBloc _leaveUpdateBloc = LeaveUpdateBloc();
  final LeaveCreateBloc _leaveCreateBloc = LeaveCreateBloc();

  Route<dynamic> generateRoute(RouteSettings settings) {
    final RouteArguments args = settings.arguments;

    switch (settings.name) {
      ...
      ...

      case Routes.LEAVE:
        return MaterialPageRoute(
          builder: (_) => MultiBlocProvider(
            providers: [
              BlocProvider(create: (context) => _leaveBloc),
              BlocProvider(create: (context) => _leaveUpdateBloc),
              BlocProvider(create: (context) => _leaveCreateBloc),
            ],
            child: LeaveScreen(),
          ),
        );

      ...
      ...

      default:
        return MaterialPageRoute(
          builder: (_) => Scaffold(
            body: Center(
              child: Text('No route defined for ${settings.name}'),
            ),
          ),
        );
    }
  }

  void dispose() {
    _leaveBloc.close();
    _leaveUpdateBloc.close();
    _leaveCreateBloc.close();
  }
}

Qualche indizio??

Risposte

4 Fukka Sep 23 2020 at 14:01

TL; DR

  1. Non utilizzare BlocProvider(create: (context) => _yourBloc)per l'accesso a blocchi a livello di percorso.
  2. Usa BlocProvider.value(value: _leaveBloc, child: LeaveScreen())invece.
  3. Non chiudere il blocco all'interno del disposemetodo dell'interfaccia utente.

Con l'aiuto di @Rolly, siamo riusciti a risolvere la causa principale di questo problema. È successo perché in questo caso sto utilizzando il provider di accesso al blocco a livello di percorso e chiudendo il blocco dal disposemetodo sull'interfaccia utente. Dato che era un problema di molto tempo fa, non sono sicuro di ricordare tutto. Ma cercherò di spiegare come ha senso e come ricordo come posso.

CAUSA PRINCIPALE

Pensa al blocco come a una porta. Dico alla mia app di entrare quando leaveBlocl' LeaveScreenutente accede, fare un pasticcio e dare loro ciò di cui hanno bisogno. La prima volta che si accede, funziona perché era aperta, l'app sa cosa fare fino a quando l'utente non ha premuto il pulsante Indietro e il mio script lo chiude.

Quando l'utente sta tornando su quella pagina, il blocco era chiuso, l'app prova a bussare alla porta ma non si apre, quindi non sa cosa fare se non restare lì ad aspettare che la porta venga aperta.

SOLUZIONE

Poiché stiamo usando l'accesso al blocco a livello di route, l'app non deve chiudere nessuno dei blocchi all'interno del livello dell'interfaccia utente. Invece, crea una funzione dispose sul file di percorso e attivala vicino alla parte superiore dell'albero del widget. Così:

router.dart

class Router {
  final LeaveBloc _leaveBloc = LeaveBloc(
    ...
  );

  Route<dynamic> generateRoute(RouteSettings settings) {
    case Routes.LEAVE:
        return MaterialPageRoute(
            builder: (_) =>
                BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
  }

  void dispose() {
    _leaveBloc.close();
    // another bloc
    // another bloc
  }
}

main.dart

void main() async {
  ...

  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final Router _router = Router();
  ...


  @override
  void dispose() {
    _router.dispose(); // <-- trigger dispose when the user closes the app
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
    );
  }
}

NOTA AGGIUNTIVA

Si noti che sto cambiando lo routescript da questo:

    case Routes.LEAVE:
        return MaterialPageRoute(
          builder: (_) => MultiBlocProvider(
            providers: [
              BlocProvider(create: (context) => _leaveBloc),
              BlocProvider(create: (context) => _leaveUpdateBloc),
              BlocProvider(create: (context) => _leaveCreateBloc),
            ],
            child: LeaveScreen(),
          ),
        );

A questo:

    case Routes.LEAVE:
        return MaterialPageRoute(
            builder: (_) =>
                BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));

Era necessario perché dobbiamo dire all'app che non vogliamo chiudere i blocchi dal livello dell'interfaccia utente e lasciare che il metodo di smaltimento del router faccia il lavoro. È stato spiegato ufficialmente nella documentazione della libreria dei blocchi in questa riga.

Utilizziamo BlocProvider.value quando forniamo l'istanza CounterBloc alle rotte perché non vogliamo che BlocProvider gestisca l'eliminazione del blocco (poiché _AppState ne è responsabile).

Riferimento: documentazione ufficiale della libreria dei blocchi (Bloc Access - Named Route Access)

Rolly Aug 22 2020 at 19:02

È normale essere chiamati solo una volta nel file initState. L'hook del ciclo di vita viene eseguito solo una volta, quando viene creato il widget.

Se vuoi che venga eseguito ogni volta che accedi alla schermata dei dettagli, passa a una nuova schermata dei dettagli, poiché ciò ricreerà ogni volta il widget dei dettagli e aggiungerà il tuo evento.