slovnicki / beamer

A routing package built on top of Router and Navigator's pages API, supporting arbitrary nested navigation, guards and more.
MIT License
589 stars 130 forks source link

BeamerDelegate fails with a runtime error in StreamBuilder<>... #615

Open yardwerkz opened 1 year ago

yardwerkz commented 1 year ago

Describe the bug A clear and concise description of what the bug is.

I have code which wraps a call to context.beamToNamed with a StreamBuilder<>:

import 'package:beamer/beamer.dart';
import 'package:flutter/material.dart';
import 'package:yardwerkz_flutter/blocs/bloc_provider.dart';
import 'package:yardwerkz_flutter/customer/blocs/address_suggestions_bloc.dart';
import 'package:yardwerkz_flutter/customer/blocs/yard_bloc.dart';
import 'package:yardwerkz_flutter/customer/models/yard.dart';
import 'package:yardwerkz_flutter/customer/screens/yard_screen.dart';

class AddressScreen extends StatelessWidget {
  const AddressScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Address')),
      body: const Padding(
        padding: EdgeInsets.all(16.0),
        child: MyAutocomplete(),
      ),
    );
  }
}

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

  @override
  State<MyAutocomplete> createState() => _MyAutocompleteState();
}

class _MyAutocompleteState extends State<MyAutocomplete> {
  @override
  Widget build(BuildContext context) {
    final addressSuggestionsBloc =
        BlocProvider.of<AddressSuggestionsBloc>(context);
    final yardBloc = BlocProvider.of<YardBloc>(context);

    return StreamBuilder<List<String>>(
      stream: addressSuggestionsBloc!.suggestions,
      builder: (context, addressSnapshot) => StreamBuilder<Yard>(
          stream: yardBloc!.outYard,
          builder: (context, yardSnapshot) {
            if (!yardSnapshot.hasData) {
              return Autocomplete<String>(
                optionsBuilder: (textEditingValue) {
                  if (textEditingValue.text.isEmpty) {
                    return const <String>[];
                  } else {
                    addressSuggestionsBloc.input.add(textEditingValue.text);
                  }
                  if (addressSnapshot.hasData) {
                    return addressSnapshot.data!;
                  } else {
                    return const <String>[];
                  }
                },
                onSelected: (selected) {
                  yardBloc.inAddressString.add(selected);
                },
              );
            } else {
              final yard = yardSnapshot.data!;
              final lat = yard.location?.latitude ?? 0.0;
              final lng = yard.location?.longitude ?? 0.0;
              debugPrint('hasData: $lat, $lng');
              // FIXME: BeamerDelegate calls notifyListeners() which causes a bug.
              // context.beamToNamed('/customer/diagram?lat=$lat&lng=$lng');
              // FIXME: Use Navigator 1.0 to get around this
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => YardScreen(
                          initialLatitude: lat, initialLongitude: lng)));
              return const Center(
                child: CircularProgressIndicator(),
              );
            }
          }),
    );
  }
}

I am getting the following error: The following assertion was thrown while dispatching notifications for BeamerDelegate: setState() or markNeedsBuild() called during build.

This Router widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: Router dependencies: [UnmanagedRestorationScope] state: _RouterState#644ac The widget which was currently being built when the offending call was made was: StreamBuilder dependencies: [_RouterScope] state: _StreamBuilderBaseState<Yard, AsyncSnapshot>#13556 When the exception was thrown, this was the stack:

0 Element.markNeedsBuild. (package:flutter/src/widgets/framework.dart:4651:9)

1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4663:6)

2 State.setState (package:flutter/src/widgets/framework.dart:1159:15)

3 _RouterState._handleRouterDelegateNotification (package:flutter/src/widgets/router.dart:791:5)

4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:403:24)

5 BeamerDelegate.update (package:beamer/src/beamer_delegate.dart:452:7)

Beamer version: (e.g. v0.14.1, master, ...) 1.5.2 To Reproduce Steps to reproduce the behavior: See code above. Expected behavior A clear and concise description of what you expected to happen. To navigate to the named page. Screenshots If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

All iPhones and Androids

Additional context Add any other context about the problem here. The code referred to in the stack trace is a call to notifyListeners().