Where should I initialize the blocs in flutter_bloc - flutter

I am using Flutter_bloc package to work with bloc pattern in flutter, but i am wondering if it is a good practice to use a MultiBlocProvider inside main function and add all of my blocs in there like this:
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(Mafqood());
}
class Mafqood extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers : [
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(AuthInitialState(), AuthRepository()),
),
BlocProvider<LoginBloc>(
create: (context) => LoginBloc(LoginInitialState(), AuthRepository()),
),
BlocProvider<ProfileBloc>(
create: (context) => ProfileBloc(ProfileInitialState(), AuthRepository()),
),
],
child: MaterialApp(
or it is better to add the bloc just where I need it? and why?
Thanks in advance.

You should use MultiBlocProvider inside the main function as you did. This is the best practice. And this is the goal of the providers.
Edit:
Now I realized that there is another answer here.

The main usage of MultiBlocProvider is using the bloc object in different places inside your application before you had to define which bloc depends on another bloc.
If you have an app that each screen use its own bloc, then you don't have a need for MultiBlocProvideras you can creat the bloc in the build function of the screen
class ParentScreen extends StatelessWidget {
const ParentScreen ({Key? key, this.data}) : super(key: key);
final data;
#override
Widget build(BuildContext context) {
return BlocProvider<MyBloc>(
create: (_) => MyBloc()),
child: MyScreenBody());
}
}
Class MyScreenBody extends StatefulWidget {
const MyScreenBody({Key? key}) : super(key: key);
#override
_MyScreenBodyState createState() => _MyScreenBodyState();
}
class _MyScreenBodyState extends State<MyScreenBody> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<MyBloc, MyState>(
builder: (context, state) {
return //... your code
}
)
);
}

Related

Consumer and context.watch in MultiProvider

I am trying Flutter for the first time, and I am a little confused by the MultiProvider class.
The question is straightforward, but I didn't find an explanation:
when should one use Consumer and when context.watch?
For instance, taking one of the examples apps I have found, I tried using two providers for two global states, the theme and the status of the app:
runApp(
MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => AppTheme()),
ChangeNotifierProvider(create: (context) => AppStatus()),
],
child: const MyApp()
));
Then the app widget accesses the theme with Consumer:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AppTheme>(
builder: (context, appTheme, child) {
// ...
As far as I understand, now all children widgets will inherit the provider. Is it right?
My home page, then, called by the MyApp class does not use Consumer, but context.watch:
#override
Widget build(BuildContext context) {
final appTheme = context.watch<AppTheme>();
final appStatus = context.watch<AppStatus>();
return NavigationView(
// ...
It works, don't get me wrong, but I just copied the row above my appStatus, so I don't really fully understand it. This is also due to another screen that I've concocted to access the AppStatus global state, but I use Consumer, as suggested by the Flutter documentation:
class _ViewerState extends State<Viewer> {
#override
Widget build(BuildContext context) {
return Consumer<AppStatus>(
builder: (context, appStatus, child) {
return ScaffoldPage.scrollable(
header: const PageHeader(title: Text('Test')),
children: [
FilledButton(child: Text("Try ${appStatus.count}"), onPressed: (){ appStatus.increment(); debugPrint('pressed ${appStatus.count}'); }),
FilledButton(child: Text("Reset"), onPressed: (){ appStatus.reset(); }),
]);
},
);
}
}
I have the feeling that I am misusing something here, and I do not really understand what's going on under the hood...
context.watch<T>() and Consumer<T> does the same thing. Most of the time context.watch<T>() is just more convenient. In some cases where context is not available Consumer<T> is useful.

Flutter - Cant find bloc in the parent tree when using showDialog

Im using Flutter and flutter_bloc for a small app, and i used MultiBlocProvider to use multiple BlocProviders that i need in the main home page, and under the home page, there is a MainWidget, which can access the given Bloc easily by: BlocProvider.of<OldBloc>(context)
The MainWidget calls NewWidget as a dialog by: showDialog(context: context, builder: (context) => NewWidget())
The problem is, i cannot access OldBloc from NewWidget(), so i assumed that MainWidget isnt passing its context to NewWidget when using showDialog ?
HomeScreen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => OldBloc()..add(Initialize()),
),
BlocProvider(
create: (context) => OtherBloc()..add(Initialize()),
),
],
child: Stack(
children: [
MainWidget(),
MenuWidget(),
],
),
));
}
}
MainWidget.dart
import 'package:flutter/material.dart';
class MainWidget extends StatelessWidget {
const MainWidget({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextField(onTap: () => showDialog(context: context, builder: (context) => NewWidget()));
}
}
NewWidget.dart
import 'package:flutter/material.dart';
class NewWidget extends StatelessWidget {
const NewWidget({Key key}) : super(key: key);
#override
Widget build(context) {
return Text(BlocProvider.of<OldBloc>(context).name); // <------- THIS GIVES AN ERROR THAT IT CANT FIND A BLOC OF THE TYPE OldBloc
}
}
You can simply use this (suggested by Flex Angelov):
showDialog(
context: superContext,
builder: (_) {
return BlocProvider.value(
value: superContext.read<MyBloc>(),
child: const MyDialogWidget(),
);
},
);
and make sure superContext has access to your BLoC.
You have no access to BuildContext in your showDialog method, documentation:
The widget returned by the builder does not share a context with the location that showDialog is originally called from.
The context argument is used to look up the Navigator and Theme for the dialog. It is only used when the method is called.
Its also recommended to use a StatefulBuilder or a custom StatefulWidget, here is an example.
I solved this problem.
I use cubit instead of bloc but it doesn't matter.
ShowDialog doesn't pass parent context. So you should create a new cubit and pass your parent cubit and state in arguments for a new cubit. I called main cubit FooCubit, new cubit FooCubitWrapper and DialogWidget is child widget that needs some BLOC logic in showDialog.
var fooCubit = context.read<FooCubit>(); // your parrent cubit and state
return BlocBuilder<FooCubit , FooState>(
builder: (_, state) {
.
.///some logic///
.
showDialog(
context: context,
builder: (_) => // here you have to create new cubit
BlocProvider( // because there is no access to parent cubit
create: (_) => FooCubitWrapper(cubit, state),
child: DialogWidget(),
),
);
This is FooCubitWrapper. For example, we need the boo method from the parent cubit. So we need to create here boo method and inside we need to reference to parent boo method and emit parent state.
class FooCubitWrapper extends Cubit<FooState> {
FooCubit fooCubit;
FooCubitWrapper(this.fooCubit, FooState initialState) : super(initialState);
boo() {
fooCubit.boo();
emit(fooCubit.state);
}
}
And finally, in your DialogWidget, you do all like usual
var cubit = context.read<TagsCubitWrapper>();
return BlocBuilder<TagsCubitWrapper, TagsState>(
builder: (context, state) {
// work with methods and fields as usual
cubit.boo();
if !(state.someField) {}
});

Flutter Widget Testing for BlocBuilder Widgets

I have a Widget which uses bloc builder to map the different state of widget.
class BodyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<NewsBloc, NewsState>(builder: (context, state) {
return state.map(
.....
);
});
}
....
}
The BodyWidget is created in a Widget with BlocProvider.
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
getIt<NewsBloc>()..add(const NewsEvent.fetchNewsData()),
child: BodyWidget(),
);
}
....
}
And the NewsBloc is defined as
#injectable
class NewsBloc extends Bloc<NewsEvent, NewsState> {
final GetNews getNews;
NewsBloc({
#required this.getNews,
}) : super(const _Initial());
#override
Stream<NewsState> mapEventToState(
NewsEvent event,
) async* { ... }
}
I am using get_it and injectable for Dependency Injection.
Now I am trying to write a simple widget test for BodyWidget and I am not so sure how to inject all these dependency in test.
class MockBuildContext extends Mock implements BuildContext {}
class MockNewsBloc extends Mock implements NewsBloc {}
void main() {
ForYouNewsTab _widget;
MockBuildContext _context;
NewsBloc _newsBloc;
Widget makeTestableWidgets({Widget child}) {
return MaterialApp(
home: Scaffold(
// body: BlocProvider(
// create: (_context) => getIt<NewsBloc>(),
// child: child,
// ),
body: child,
),
);
}
setUp(() {
_context = MockBuildContext();
_widget = ForYouNewsTab();
});
test('ForYouNewsTab is sub class of StatelessWidget', () {
expect(_widget, isA<StatelessWidget>());
});
testWidgets('should return sized box for initial state',
(WidgetTester tester) async {
await tester.pumpWidget(makeTestableWidgets(child: _widget));
});
}
I did search in stackoverflow, but could not found a solution that works form me.
I solved my issue by following very basic steps. Not so sure if its the right way. Anyway if anyone ever comes to the same problem, it might help them.
class MainPage extends StatelessWidget {
//added line
final NewsBloc newsBloc;
const MainPage({
Key key,
#required this. newsBloc,
})
#override
Widget build(BuildContext context) {
return BlocProvider(
// changed line
// create: (context) => getIt<NewsBloc>()..add(const NewsEvent.fetchNewsData()),
create: (context) => newsBloc..add(const NewsEvent.fetchNewsData()),
child: BodyWidget(),
);
}
....
}
Now in my test case I can create MockNewsBloc and inject it easily to the MainPage when it is under testing.

ProviderNotFoundException (Error: Could not find the correct Provider<LayoutData> above this SchedulingPage Widget using layoutBuilder

I'm getting a providerNotFoundException, and I suspect that there is a context mismatch in the below code, but I'm having a hard time seeing it. As I understand, problems with BuilderContext arise when an .of method is performed within the same build method, but I don't see this happening in this case. Some of the Provider.of methods work fine as annotated in the below code, but as soon as SchedulingPage is called, the Provider.of methods no longer work.
What is the problem here?
Edit: I updated to use my full code below:
Here's the full error: ProviderNotFoundException (Error: Could not find the correct Provider above this LoginForm Widget
void main() {
runApp(
Home(),
);
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// ChangeNotifierProvider(create: (context) => CalendarData()),
ChangeNotifierProvider(create: (context) => LayoutData()),
],
child: MyApp(),
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints: $constraints");
Size mediaSize = MediaQuery.of(context).size;
double safeAreaSize = mediaSize.height - constraints.maxHeight;
Provider.of<LayoutData>(context).safeAreaDiff = safeAreaSize;
Provider.of<LayoutData>(context).safeArea = constraints;
Provider.of<LayoutData>(context).mediaArea = mediaSize;
var test = Provider.of<LayoutData>(context).mediaArea.width;
print(test); // this works
return Scaffold(body: LoginScreen());
}),
),
);
}
}
class LoginScreen extends StatelessWidget {
const LoginScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
double test = Provider.of<LayoutData>(context).mediaArea.width;
print("test: $test"); // this works
return LoginForm();
}
}
class LoginForm extends StatefulWidget {
LoginForm({Key key}) : super(key: key);
#override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
#override
Widget build(BuildContext context) {
double width = Provider.of<LayoutData>(context).mediaArea.width; // The code fails here
print('width: $width');
return Text("this is where it fails ^^^^^^^^");
}
}
class LayoutData with ChangeNotifier {
double safeAreaDiff = 0.0;
BoxConstraints safeArea;
Size mediaArea;
LayoutData() {
initializeApp();
}
void initializeApp() {
print("layout initialized");
}
}
You can not access provider in same class in which you create. That must be parent widget.
void main() {
runApp(Home());
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CalendarData()),
ChangeNotifierProvider(create: (context) => LayoutData()),
],
child: MyApp(),
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints: $constraints");
Size mediaSize = MediaQuery.of(context).size;
double safeAreaSize =
mediaSize.height - constraints.maxHeight; // works
Provider.of<LayoutData>(context).safeAreaDiff =
safeAreaSize; // works
Provider.of<LayoutData>(context).safeArea = constraints; // works
Provider.of<LayoutData>(context).mediaArea = mediaSize; // works
Provider.of<CalendarData>(context).working = "beer"; // works
print(Provider.of<CalendarData>(context).working); // works
return Scaffold(body: SchedulingPage());
}),
),
);
}
}
Output:
Performing hot restart...
Restarted application in 1,181ms.
I/flutter (25187): constraints: BoxConstraints(w=411.4, h=659.4)
I/flutter (25187): layout initialized
I/flutter (25187): 411.42857142857144
I/flutter (25187): test: 411.42857142857144
I/flutter (25187): width: 411.42857142857144
I'm posting this on the off chance that someone has the same problem that I did. It turned out the issue was not fixed by the chosen solution. The code, as written should have worked. However, in this case, the solution was difficult to find. It turned out that the import itself was incorrect. There were two imports of the Provider Data Class, like this:
import 'package:myProject/providers/CalendarData.dart';
and
import 'package:gcfdlayout2/Providers/CalendarData.dart';
This ambiguity confused the IDE, I believe, and, although it didn't give me any errors at build time, it did at run time, but the "could not find Provider" made me assume that it couldn't find the Provider in the Tree, not in the code itself.
The way I finally found this was to use other methods of running the code. Initially, I was only using Visual Studio Code, but I never got any errors that suggested it was an import problem. I changed to Android Studio, and it informed me that there were two imports for the CalendarData class.
Have you tried placing your MultiProvider() above your MaterialApp()?

The callback which is called everytime Navigator.push to next page

I have two Pages moving each others like this.
MyHomePage -> ResPage -> MyHomePage -> ResPage
I want to exec the function when every time the ResPage appears.
code is this
Navigator.push(context,MaterialPageRoute(
builder: (context) => ResPage()));
Navigator.push(context,MaterialPageRoute(
builder: (context) => MyHomePage())
resPage is Stateful Widget.
class ResPage extends StatefulWidget {
ResPage({Key key}) : super(key: key);
#override
_ResPageState createState() => _ResPageState();
}
class _ResPageState extends State<ResPage> {
void initState(){ // it called just once.
}
#override
Widget build(BuildContext context) {
return Stack( // it called many times.
children: <Widget>[
background,
Scaffold(
backgroundColor: Colors.transparent,
body: resStack
),
],
);
}
initState() is called one time, and build is called many times.
Is there any call back when the page appears??
Yes it is!
You can add a post frame callback(a call after the widget was built)
like this:
Widget build(BuildContext context) {
WidgetsBinding.addPostFrameCallback(functionToCall);
}