Use StreamBuilder as root of navigation stack with GoRouter - flutter

Currently I have a StreamBuilder switching between a HomePage and LandingPage depending on the current auth state. The issue I have encountered is that I cannot pop the stack to the original /landing directory on a state change. This means that when a user logs in, the AuthPage remains on the top of the stack and the StreamBuilder builds the HomePage underneath.
AuthGate
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
return snapshot.hasData ? const HomePage() : const LandingPage();
},
);
}
}
LandingPage
This pushes the AuthPage to the stack.
class LandingPage extends StatelessWidget {
const LandingPage({super.key});
...
Row(
children: <Widget>[
FloatingActionButton.extended(
heroTag: UniqueKey(),
onPressed: () {
context.push('/auth');
},
label: const Text('Get started'),
),
FloatingActionButton.extended(
heroTag: UniqueKey(),
onPressed: () {
context.push('/auth');
},
label: const Text('Log in'),
),
],
)
...
}
}
Stack before auth change
Stack after auth change
Note how the AuthPage remains on the top of the stack but the Widget under StreamBuilder changes to the HomePage
(This is my first Stack question so please feel free to ask me to amend any information etc.)

If you are using GoRouter, then what you want to achieve should be done through GoRouter similarly to this:
GoRouter(
refreshListenable:
GoRouterRefreshListenable(FirebaseAuth.instance.authStateChanges()),
initialLocation: '/auth',
routes: <GoRoute>[
GoRoute(
path: '/landing',
name: 'landing',
builder: (context, state) {
return LandingPage()
},
routes: [
GoRoute(
path: 'auth',
name: 'auth',
builder: (context, state) => const AuthPage(),
),
],
),
GoRoute(
path: '/home',
name: 'home',
builder: (context, state) => const HomePage(),
),
],
errorBuilder: (context, state) {
return const Scaffold(
body: Text('404'),
);
},
redirect: (context, state) async {
final userRepo = injector.get<UserRepository>();
final user = FirebaseAuth.instance;
const authPaths = [
'/landing',
'/landing/auth',
];
bool isAuthPage = authPaths.contains(state.subloc);
if(user != null) {
if (isAuthPage) {
return '/home';
}
}
if(!isAuthPage) {
return '/auth';
}
return null;
},
);
class GoRouterRefreshListenable extends ChangeNotifier {
GoRouterRefreshListenable(Stream stream) {
notifyListeners();
_subscription = stream.asBroadcastStream().listen(
(_) {
notifyListeners();
},
);
}
late final StreamSubscription _subscription;
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
Please also read documentation on of GoRouter.

Related

Widget is not rebuilding when notifyListeners is called

i am trying to create a login page so that when i am logged-in, the Navigationrail Widget lets me access all its destinations. When logged off i can only access two pages.
I am using Provider in login.dart to triger a widget rebuild in main.dart .
here is the code.
login.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:httptest/depand/XmlLogin.dart';
import 'package:httptest/main.dart';
void login(String userName, String passWord) async {
Response response;
Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
var token = getToken.Transcribe(response.data);
LoginProvider obj = LoginProvider();
obj.providestate(true);
print(token);
print("logged in");
handler.next(response);
},
));
try {
//Http Post method
} catch (e) {
print(e);
}
}
class LoginProvider extends ChangeNotifier {
bool loginstate = false;
void providestate(bool val) {
loginstate = val;
print("loginstate changed to $loginstate");
notifyListeners();
}
}
main.dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
List<Widget> pages = [Page0(), Page1(), Page2(), Placeholder()];
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: ChangeNotifierProvider<LoginProvider>(
create: (context) => LoginProvider(),
child: Builder(
builder: (context) {
return Consumer<LoginProvider>(
builder: (context, provider, child) {
return NavigationRail(
extended: constraints.maxWidth >= 600,
minExtendedWidth: 200,
destinations: [
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination()
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
if (provider.loginstate) {
setState(() {
selectedIndex = value;
});
print("On");
} else {
if (value == 0 || value == 3) {
setState(() {
selectedIndex = value;
});
print("OFF");
}
}
},
);
});
},
),
)),
Expanded(
child: Scaffold(
body: IndexedStack(
index: selectedIndex,
children: pages,
),
),
),
],
),
);
});
}
}
the login goes through but i Still cant access pages 1 and 2.
it prints out 'loginstate changed to True' from login.dart.
To ensure access place a provider in a widget tree something like this:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginProvider()),
...
],
child: const MyApp(),
),
);
}
Then in child widgets access it using either Consumer to listen changes or Provider.of when just calling a method.
Consumer<LoginProvider>(
builder: (context, provider, child) {
...
},
)
Provider.of<LoginProvider>(context, listen: false).login(userName, passWord);
See Simple app state management for the full example.

Can not navigate with Go Router when implement Navigation Widget in navigatorBuilder function

Description
I am trying to Navigate between screens with a Navigation Menu using go_router plugin. When I click the item in the menu, nothing happens but if I change the URL the screen does change.
Video shows the problem
Expect
Every time I navigate back and forth, both URL and screen change
My Code
app_router.dart
class AppRouter {
AppRouter(this._appBloc);
final AppBloc _appBloc;
GoRouter get router => GoRouter(
routes: pages.values.toList(growable: false),
errorBuilder: (context, state) => ErrorPage(
key: state.pageKey,
),
refreshListenable: GoRouterRefreshStream(_appBloc.stream),
navigatorBuilder: _buildRouterView,
redirect: _redirect,
);
String? _redirect(GoRouterState state) {
final loggedIn = _appBloc.state.status == AppStatus.authenticated;
final name = state.subloc;
final loggingIn = name == '/login' || name == '/';
if (!loggedIn) return loggingIn ? null : '/login';
if (loggingIn) return '/app';
return null;
}
static Map<String, GoRoute> pages = {
route_names.onboard: GoRoute(
name: route_names.onboard,
path: routes[route_names.onboard]!,
pageBuilder: (context, state) => OnboardPage.page(key: state.pageKey),
routes: [
GoRoute(
path: route_names.login.subRoutePath,
name: route_names.login,
pageBuilder: (context, state) => LoginPage.page(key: state.pageKey),
),
GoRoute(
path: route_names.signUp.subRoutePath,
name: route_names.signUp,
pageBuilder: (context, state) => LoginPage.page(key: state.pageKey),
),
],
),
'app': GoRoute(
path: '/app',
// All /app pages get the main scaffold
builder: (context, state) {
return Text("App Main");
},
routes: [
ExplorePage.route,
PlanPage.route,
AccountPage.route,
]),
};
Widget _buildRouterView(BuildContext context, GoRouterState state, Widget child) {
return Builder(
builder: (context) => BlocBuilder<AppBloc, AppState>(builder: (context, appState) {
if (appState.status == AppStatus.unauthenticated) {
return child;
}
return HomePageSkeleton(
child: child,
);
}),
);
}
}
app.dart
class AppView extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
AppView({super.key, required AppBloc appBloc}) {
_appBloc = appBloc;
_appRouter = AppRouter(_appBloc);
}
late final AppBloc _appBloc;
late final AppRouter _appRouter;
#override
Widget build(BuildContext context) {
return BlocListener<AppBloc, AppState>(
listener: (context, state) {
if (state == const AppState.unauthenticated()) {
_appRouter.router.goNamed(route_names.login);
}
},
child: MaterialApp.router(
supportedLocales: AppLocalizations.supportedLocales,
routeInformationParser: _appRouter.router.routeInformationParser,
routeInformationProvider: _appRouter.router.routeInformationProvider,
routerDelegate: _appRouter.router.routerDelegate,
),
);
}
}
HomePageSkeleton.class
// inside build method
class HomePageSkeleton extends StatelessWidget with NavigationMixin {
const HomePageSkeleton({Key? key,required this.child}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Responsive.isMobile(context) ? AppBottomNavigation(index: 0) : const SizedBox(),
body: SafeArea(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Responsive.isTablet(context) || Responsive.isLaptop(context))
// It takes 1/6 part of the screen
Expanded(
child: SideMenu(index: 0, onSelected: (index) => onTabSelected(index, context)),
),
Expanded(
// It takes 5/6 part of the screen
flex: 5,
child: child),
],
),
),
);
}
}
//onTapSelected method
void onTabSelected(int index, BuildContext context) {
switch (index) {
case 0:
// context.goNamed(route_names.explore);
context.go('/app/explore');
break;
case 1:
// context.goNamed(route_names.plan);
context.go('/app/plan');
break;
case 2:
// context.goNamed(route_names.account);
context.go('/app/account');
break;
default:
throw Exception('Unknown view');
}
}
I change my class AppRouter into:
class AppRouter {
AppRouter(AppBloc appBloc)
: _router = GoRouter(
routes: getPages().values.toList(growable: false),
errorBuilder: (context, state) => ErrorPage(
key: state.pageKey,
),
refreshListenable: GoRouterRefreshStream(appBloc.stream),
navigatorBuilder: _buildRouterView,
redirect: (GoRouterState state) {
_redirect(state, appBloc);
},
),
_appBloc = appBloc;
final AppBloc _appBloc;
final GoRouter _router;
GoRouter get router {
return _router;
}
... and it worked

Problem with Future<dynamic> is not a subtype of type List<Routes> in Flutter

I have problem with async-await. (I am not very good at programming, but learning by creating random apps...)
Problem: Using dio to get data from Node.js json-server, but I cant transform data from
Future to List. Error: type 'Future' is not a subtype of type 'List' at line 13. List<Routes> routes = _getData();
I have read a lot of discussions here on stackoverflow and many other websites, but I just can't make it work. :( So here I am asking with specific code.
Needed code:
Code where error appears (route_list_screen.dart)
import 'package:app/api/api.dart';
import 'package:flutter/material.dart';
import 'package:app/models/routes.dart';
class RouteList extends StatefulWidget {
const RouteList({Key? key}) : super(key: key);
#override
State<RouteList> createState() => _RouteListState();
}
List<Routes> routes = _getData();
class _RouteListState extends State<RouteList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Text'),
automaticallyImplyLeading: true,
centerTitle: true,
),
body: ListView.separated(
itemCount: routes.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes[index].number),
subtitle: Text(routes[index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes[index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
_getData() async {
Future<dynamic> futureOfRoutes = getRouteList(856);
List<dynamic> routes = await futureOfRoutes;
return routes;
}
Connecting to server (api.dart)
import 'package:app/models/routes.dart';
const _url = 'http://10.0.2.2:3000/routes';
getRouteList(int driverId) async {
Response response;
var dio = Dio(BaseOptions(
responseType: ResponseType.plain,
));
response = await dio.get(_url, queryParameters: {"driver_id": driverId});
final data = routesFromJson(response.data);
return data;
}
List with param Routes = Routes is model from app.quicktype.io
_getData() returns a future, you can't direct assign it on List<Routes> where it is Future<dynamic>.
You can use initState
class _RouteListState extends State<RouteList> {
List<Routes>? routes;
_loadData() async {
routes = await _getData();
setState(() {});
}
#override
void initState() {
super.initState();
_loadData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: routes == null
? Text("On Future ....")
: ListView.separated(
itemCount: routes?.length??0,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes![index].number),
subtitle: Text(routes![index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes![index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
Also check FutureBuilder

How to work with NavigationBar in go_router? | Flutter

I am currently struggling refactoring my routing code with go_router.
I already got some simple routes like /signin & /signup, but the problem comes in when I try to make the routing work with a BottomNavigationBar that has multiple screens. I would like to have a separate route for each of them like /home, /events & /profile.
I figured out that I somehow have to return the same widget with a different parameter to prevent the whole screen to change whenever a BottomNavigationBarItem is pressed and instead only update the part above the BottomNavigationBar which would be the screen itself.
I came up with a pretty tricky solution:
GoRoute(
path: '/:path',
builder: (BuildContext context, GoRouterState state) {
final String path = state.params['path']!;
if (path == 'signin') {
return const SignInScreen();
}
if (path == 'signup') {
return const SignUpScreen();
}
if (path == 'forgot-password') {
return const ForgotPasswordScreen();
}
// Otherwise it has to be the ScreenWithBottomBar
final int index = getIndexFromPath(path);
if (index != -1) {
return MainScreen(selectedIndex: index);
}
return const ErrorScreen();
}
)
This does not look very good and it makes it impossible to add subroutes like /profile/settings/appearance or /events/:id.
I would like to have something easy understandable like this:
GoRoute(
path: '/signin',
builder: (BuildContext context, GoRouterState state) {
return const SignInScreen();
}
),
GoRoute(
path: '/signup',
builder: (BuildContext context, GoRouterState state) {
return const SignUpScreen();
}
),
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return const ScreenWithNavBar(selectedScreen: 1);
}
),
GoRoute(
path: '/events',
builder: (BuildContext context, GoRouterState state) {
return const ScreenWithNavBar(selectedScreen: 2);
},
routes: <GoRoute>[
GoRoute(
path: ':id',
builder: (BuildContext context, GoRouterState state) {
return const EventScreen();
}
)
]
)
Is there any way to achieve the behavior?
This is an outstanding feature request for go_router that I hope to resolve in the coming weeks. Stay tuned.
Now you can use ShellRouter with GoRouter to create Navigation Bar
Explaination:
Things to keep in mind while using context.go() from ShellRoute to GoRoute
Specify parentNavigatorKey prop in each GoRoute
Use context.go() to replace page , context.push() to push page to stack
Code Structure to follow:
final _parentKey = GlobalKey<NavigatorState>();
final _shellKey = GlobalKey<NavigatorState>();
|_ GoRoute
|_ parentNavigatorKey = _parentKey 👈 Specify key here
|_ ShellRoute
|_ GoRoute // Needs Bottom Navigation
|_ parentNavigatorKey = _shellKey
|_ GoRoute // Needs Bottom Navigation
|_ parentNavigatorKey = _shellKey
|_ GoRoute // Full Screen which doesn't need Bottom Navigation
|_parentNavigatorKey = _parentKey
Code:
Router
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
final router = GoRouter(
initialLocation: '/',
navigatorKey: _rootNavigatorKey,
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
pageBuilder: (context, state, child) {
print(state.location);
return NoTransitionPage(
child: ScaffoldWithNavBar(
location: state.location,
child: child,
));
},
routes: [
GoRoute(
path: '/',
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) {
return const NoTransitionPage(
child: Scaffold(
body: Center(child: Text("Home")),
),
);
},
),
GoRoute(
path: '/discover',
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) {
return const NoTransitionPage(
child: Scaffold(
body: Center(child: Text("Discover")),
),
);
},
),
GoRoute(
parentNavigatorKey: _shellNavigatorKey,
path: '/shop',
pageBuilder: (context, state) {
return const NoTransitionPage(
child: Scaffold(
body: Center(child: Text("Shop")),
),
);
}),
],
),
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: '/login',
pageBuilder: (context, state) {
return NoTransitionPage(
key: UniqueKey(),
child: Scaffold(
appBar: AppBar(),
body: const Center(
child: Text("Login"),
),
),
);
},
),
],
);
BottomNavigationBar
class ScaffoldWithNavBar extends StatefulWidget {
String location;
ScaffoldWithNavBar({super.key, required this.child, required this.location});
final Widget child;
#override
State<ScaffoldWithNavBar> createState() => _ScaffoldWithNavBarState();
}
class _ScaffoldWithNavBarState extends State<ScaffoldWithNavBar> {
int _currentIndex = 0;
static const List<MyCustomBottomNavBarItem> tabs = [
MyCustomBottomNavBarItem(
icon: Icon(Icons.home),
activeIcon: Icon(Icons.home),
label: 'HOME',
initialLocation: '/',
),
MyCustomBottomNavBarItem(
icon: Icon(Icons.explore_outlined),
activeIcon: Icon(Icons.explore),
label: 'DISCOVER',
initialLocation: '/discover',
),
MyCustomBottomNavBarItem(
icon: Icon(Icons.storefront_outlined),
activeIcon: Icon(Icons.storefront),
label: 'SHOP',
initialLocation: '/shop',
),
MyCustomBottomNavBarItem(
icon: Icon(Icons.account_circle_outlined),
activeIcon: Icon(Icons.account_circle),
label: 'MY',
initialLocation: '/login',
),
];
#override
Widget build(BuildContext context) {
const labelStyle = TextStyle(fontFamily: 'Roboto');
return Scaffold(
body: SafeArea(child: widget.child),
bottomNavigationBar: BottomNavigationBar(
selectedLabelStyle: labelStyle,
unselectedLabelStyle: labelStyle,
selectedItemColor: const Color(0xFF434343),
selectedFontSize: 12,
unselectedItemColor: const Color(0xFF838383),
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
_goOtherTab(context, index);
},
currentIndex: widget.location == '/'
? 0
: widget.location == '/discover'
? 1
: widget.location == '/shop'
? 2
: 3,
items: tabs,
),
);
}
void _goOtherTab(BuildContext context, int index) {
if (index == _currentIndex) return;
GoRouter router = GoRouter.of(context);
String location = tabs[index].initialLocation;
setState(() {
_currentIndex = index;
});
if (index == 3) {
context.push('/login');
} else {
router.go(location);
}
}
}
class MyCustomBottomNavBarItem extends BottomNavigationBarItem {
final String initialLocation;
const MyCustomBottomNavBarItem(
{required this.initialLocation,
required Widget icon,
String? label,
Widget? activeIcon})
: super(icon: icon, label: label, activeIcon: activeIcon ?? icon);
}
Output:
The solution that worked for me in the end was the following:
I awas able to create a route that specifies which values are allowed:
GoRoute(
path: '/:screen(home|discover|notifications|profile)',
builder: (BuildContext context, GoRouterState state) {
final String screen = state.params['screen']!;
return TabScreen(screen: screen);
}
)
With that done, I pass whatever value the route contains (e.g. '/home' or '/discover') to the TabScreen which then displays the exact same Widget, but without reloading also the TabBar over and over again:
class TabScreen extends StatelessWidget {
const TabScreen({
super.key,
required this.screen
});
final String screen;
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(child: screen == 'home' ? const HomeScreen() : screen == 'discover' ? const DiscoverScreen() : screen == 'notifications' ? const NotificationsScreen() : ProfileScreen(),
CustomBottomNavigationBar()
]
);
}
}

How to call dispose when using BLoC pattern and StatelessWidget

I am trying to understand BLoC pattern but I cannot figure out where or when to call dispose() in my example.
I am trying to understand various state management techniques in Flutter.
I came up with an example I managed to build with the use of StatefulWidget, scoped_model and streams.
I believe I finally figured out how to make my example work with the use of "BloC" pattern but I have a problem with calling the dispose() method as I use the StatelessWidgets only.
I tried converting PageOne and PageTwo to StatefulWidget and calling dispose() but ended up with closing the streams prematurely when moving between pages.
Is it possible I should not worry at all about closing the streams manually in my example?
import 'package:flutter/material.dart';
import 'dart:async';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<ThemeData>(
initialData: bloc.themeProvider.getThemeData,
stream: bloc.streamThemeDataValue,
builder: (BuildContext context, AsyncSnapshot<ThemeData> snapshot) {
return MaterialApp(
title: 'bloc pattern example',
theme: snapshot.data,
home: BlocPatternPageOne(),
);
},
);
}
}
// -- page_one.dart
class BlocPatternPageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(block pattern) page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go to page two'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return BlocPatternPageTwo();
},
),
);
},
);
}
}
// -- page_two.dart
class BlocPatternPageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(bloc pattern) page two'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go back to page one'),
onPressed: () {
Navigator.of(context).pop();
},
);
}
}
// -- bloc.dart
class SwitchProvider {
bool _switchValue = false;
bool get getSwitchValue => _switchValue;
void updateSwitchValue(bool value) {
_switchValue = value;
}
}
class ThemeProvider {
ThemeData _themeData = ThemeData.light();
ThemeData get getThemeData => _themeData;
void updateThemeData(bool value) {
if (value) {
_themeData = ThemeData.dark();
} else {
_themeData = ThemeData.light();
}
}
}
class Bloc {
final StreamController<bool> switchStreamController =
StreamController.broadcast();
final SwitchProvider switchProvider = SwitchProvider();
final StreamController<ThemeData> themeDataStreamController =
StreamController();
final ThemeProvider themeProvider = ThemeProvider();
Stream get streamSwitchValue => switchStreamController.stream;
Stream get streamThemeDataValue => themeDataStreamController.stream;
void sinkSwitchValue(bool value) {
switchProvider.updateSwitchValue(value);
themeProvider.updateThemeData(value);
switchStreamController.sink.add(switchProvider.getSwitchValue);
themeDataStreamController.sink.add(themeProvider.getThemeData);
}
void dispose() {
switchStreamController.close();
themeDataStreamController.close();
}
}
final bloc = Bloc();
At the moment everything works, however, I wonder if I should worry about closing the streams manually or let Flutter handle it automatically.
If I should close them manually, when would you call dispose() in my example?
You can use provider package for flutter. It has callback for dispose where you can dispose of your blocs. Providers are inherited widgets and provides a clean way to manage the blocs. BTW I use stateless widgets only with provider and streams.
In stateless widget, there is not dispose method so you need not to worry about where to call it.
It's as simple as that