Search code examples
flutterdartbloc

Does Flutter BLoc with anonymous routes?


I am watching a tutorial and it is clear that BLoc does not work with anonymous routes as, apparently, they do not share a context. However, my simple app works with anonymous routes.

It is a simple counter app that has 2 screens and the user can manipulate the counter on both of them. In the tutorial when he runs this app and navigates to the second screen , the app crashes , but mine does not and works fine.

Here is the code :

main.dart:

import 'package:counter_bloc_test/logic/cubits/counter_cubit.dart';
import 'package:counter_bloc_test/frontend/screens/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterCubit>(
      create: (context) => CounterCubit(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: const HomeScreen(title: 'Flutter Demo Home Page', color: Colors.blue,),
      ),
    );
  }
}

homescreen.dart:

import 'package:flutter/material.dart';
import 'package:counter_bloc_test/logic/cubits/counter_cubit.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:counter_bloc_test/frontend/screens/second_screen.dart';




class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key, required this.title , required this.color});

  final String title;
  final Color color;

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            BlocConsumer<CounterCubit, CounterState>(
              listener: (context, state) {
                if(state.wasIncremented){
                  ScaffoldMessenger.of(context).removeCurrentSnackBar();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text("Incremented"),
                    )
                  );
                }else{
                  ScaffoldMessenger.of(context).removeCurrentSnackBar();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(                      
                      content: Text("Decremented"),
                    )
                  );
                }
              },
              builder: (context, state) {
                return Text(
                  state.counterValue.toString(),
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                FloatingActionButton(
                  heroTag: "btn1",
                  onPressed: () {
                    BlocProvider.of<CounterCubit>(context).decrement();
                  },
                  tooltip: 'Decrement',
                  child: const Icon(Icons.remove),
                ),
                FloatingActionButton(
                  heroTag: "btn2",
                  onPressed: () {
                    BlocProvider.of<CounterCubit>(context).increment();
                  },
                  tooltip: 'Increment',
                  child: const Icon(Icons.add),
                ),
              ],
            ),
            const SizedBox(height: 24,),
            MaterialButton(
              
              color: widget.color,
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => const SecondScreen(
                      title: "Second Screen",
                      color: Colors.redAccent,
                      )
                  )
                );
              },
              child: const Text('Go to Second Page'),
            )
          ],
        ),
      ),
    );
  }
}

second_screen.dart

import 'package:flutter/material.dart';
import 'package:counter_bloc_test/logic/cubits/counter_cubit.dart';
import 'package:flutter_bloc/flutter_bloc.dart';



class SecondScreen extends StatefulWidget {
  const SecondScreen({super.key, required this.title , required this.color});

  final String title;
  final Color color;

  @override
  State<SecondScreen> createState() => _SecondScreenState();
}

class _SecondScreenState extends State<SecondScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.redAccent,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            BlocConsumer<CounterCubit, CounterState>(
              
              listener: (context, state) {
                if(state.wasIncremented){
                  ScaffoldMessenger.of(context).removeCurrentMaterialBanner();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text("Incremented"),
                    )
                  );
                }else{
                  ScaffoldMessenger.of(context).removeCurrentSnackBar();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      
                      content: Text("Decremented"),
                    )
                  );
                }
              },
              builder: (context, state) {
                return Text(
                  state.counterValue.toString(),
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                FloatingActionButton(
                  heroTag: null,
                  onPressed: () {
                    BlocProvider.of<CounterCubit>(context).decrement();
                  },
                  tooltip: 'Decrement',
                  child: const Icon(Icons.remove),
                ),
                FloatingActionButton(
                  heroTag: null,
                  onPressed: () {
                    BlocProvider.of<CounterCubit>(context).increment();
                  },
                  tooltip: 'Increment',
                  child: const Icon(Icons.add),
                ),
              ],
            ),
            const SizedBox(height: 24,),
            MaterialButton(
              color: widget.color,
              onPressed: () {
                Navigator.of(context).pop();
                
              },
              child: const Text('Go to First Page'),
            )
          ],
        ),
      ),
    );
  }
}

I expected the app to throw an error, however the state is preserved and it works fine. I am using bloc 8.1 and Flutter 3.10


Solution

  • The difference is with the initial provider you are using. By wrapping your MaterialApp with a BlocProvider you are making it available globally throughout your application.

    You are correct by expecting an error in the case the provider is located in your HomeScreen.