Search code examples
flutterdartflutter-blocflutter-widget

Flutter Bloc Form State & WillPopScope


I start with block. I'm trying to create a form with a read mode and an edit/create. I arrive with the WillPopScope widget to manage the rollback depending on the mode. Only my form doesn't find its initial state: it remains to modify whereas I do not save the modification. And on the back event to the list this one is not up to date.

Thanks,

Edit : I think that problem with state because when I pass in edition or back from edition to read : nothing state is happens why ? How back from edition to read and get initial value ?

class FamilyDetailsPage extends StatelessWidget {
  const FamilyDetailsPage({Key? key, required this.family}) : super(key: key);
  final Family family;
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => FamilyBloc(repository: FamilyRepository()),
      child: FamilyDetailsView(family: family),
    );
  }
}

class FamilyDetailsView extends StatefulWidget {
  const FamilyDetailsView({Key? key, required this.family}) : super(key: key);
  final Family family;
  @override
  State<FamilyDetailsView> createState() => FamilyDetailsViewState();
}

class FamilyDetailsViewState extends State<FamilyDetailsView>
    with SingleTickerProviderStateMixin {
  late FamilyBloc _familyBloc;
  late bool isUpdated;
  late Family _currentFamily;

  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  static final GlobalKey<FormState> _formKey =
      GlobalKey<FormState>(debugLabel: '_appFamilyFormState');

  @override
  void initState() {
    super.initState();
    _currentFamily = widget.family;
    isUpdated = (_currentFamily.id != 0) ? false : true;
    _familyBloc = BlocProvider.of<FamilyBloc>(context);
    _familyBloc.add(FetchFamilyEvent(item: _currentFamily));
  }

  @override
  void dispose() {
    _familyBloc.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        if (!isUpdated || _currentFamily.id == 0) {
          return true;
        }
        setState(() => isUpdated = false);
        return false;
      },
      child: Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          title: const Text('Family'),
          actions: <Widget>[
            !isUpdated
                ? IconButton(
                    onPressed: () => setState(() => isUpdated = true),
                    icon: const Icon(Icons.edit))
                : IconButton(
                    onPressed: () {
                      _saveFamily();
                      setState(() => isUpdated = false);
                    },
                    icon: const Icon(Icons.save),
                  ),
          ],
        ),
        body: Form(
            key: _formKey,
            child: Padding(
                padding: const EdgeInsets.all(16.0), child: _buildForm())),
      ),
    );
  }

  void _saveFamily() {
    if (_formKey.currentState != null) {
      if (_formKey.currentState!.validate()) {
        _familyBloc.add(_currentFamily.id == 0
            ? CreateFamilyEvent(item: _currentFamily)
            : UpdateFamilyEvent(item: _currentFamily));
      }
    }
  }

  Widget _buildForm() {
    return BlocConsumer<FamilyBloc, FamilyState>(
        listener: (context, state) {},
        builder: (context, state) {
          if (state is ItemLoadedState || state is IsSavedState) {
            return Column(
              children: <Widget>[
                TextFormField(
                    readOnly: !isUpdated,
                    decoration: const InputDecoration(
                        labelText: 'Name', border: OutlineInputBorder()),
                    //controller: _cName,
                    initialValue: _currentFamily.name,
                    onChanged: (value) => _currentFamily.name = value,
                    validator: (value) {
                      if (value!.trim().isEmpty) {
                        return 'Name cannot be empty';
                      }
                      return null;
                    }),
              ],
            );
          }
          if (state is FailState) {
            print(state.message);
          }
          if (state is LoadingState) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          return const Text('No data');
        });
  }
}

Bloc file :

class FamilyBloc extends Bloc<FamilyEvent, FamilyState> {
  FamilyBloc({required this.repository}) : super(LoadingState()) {
    on<BackEvent>(_onBackEvent);
    on<FetchFamiliesEvent>(_onFetchFamilies);
    on<FetchFamilyEvent>(_onFetchFamily);
    on<UpdateFamilyEvent>(_onUpdateFamily);
    on<CreateFamilyEvent>(_onCreateFamily);
  }
  FamilyRepository repository;

  FutureOr<void> _onBackEvent(BackEvent event, Emitter<FamilyState> emit) {
    emit(InitialState());
  }
}

Event file :

abstract class FamilyEvent extends Equatable {
  final Family? item;

  const FamilyEvent({this.item});
  @override
  List<Object> get props => [];
}

class BackEvent extends FamilyEvent {}

State File :

abstract class FamilyState extends Equatable {
  final Family? item;
  final List<Family>? list;
  const FamilyState({this.item, this.list});
  @override
  List<Object> get props => [];
}

class InitialState extends FamilyState {}

class FailState extends FamilyState {
  const FailState({required this.message});
  final String message;
}

class LoadingState extends FamilyState {}

class ListLoadedState extends FamilyState {
  const ListLoadedState({required this.listFamily}) : super(list: listFamily);
  final List<Family> listFamily;
}

class ItemLoadedState extends FamilyState {
  const ItemLoadedState({required this.family}) : super(item: family);
  final Family family;
}

class IsSavedState extends FamilyState {
  const IsSavedState({required this.family});
  final Family family;
}

Solution

  • I found the solution. In my _buildForm don't check the state InitialState for get initial value.

    if (state is InitialState) {
      SchedulerBinding.instance.addPostFrameCallback((_) => _initForm());
      return _buildForm();
    }