In the code shown below , the dispatch event is called from within the build method after getting the BuildContext object. What if I wish to do is to dispatch an event during processing at the start of the page within the initState method itself ?
If I use didChangeDependencies method , then I am getting this error :
BlocProvider.of() called with a context that does not contain a Bloc of type FileManagerBloc. how to fix this?
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<FileManagerBloc>(
builder: (context)=>FileManagerBloc(),
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(color: Colors.blueGrey, child: TopMenuBar()),
Expanded(
child: BlocBuilder<FileManagerBloc,FileManagerState>(
builder: (context , state){
return GridView.count(
scrollDirection: Axis.vertical,
physics: ScrollPhysics(),
crossAxisCount: 3,
crossAxisSpacing: 10,
children: getFilesListWidget(context , state),
);
},
),
)
],
),
),
),
));
}
#override
void dispose() {
super.dispose();
}
#override
void didChangeDependencies() {
logger.i('Did change dependency Called');
final FileManagerBloc bloc = BlocProvider.of<FileManagerBloc>(context) ;
Messenger.sendGetHomeDir()
.then((path) async {
final files = await Messenger.sendListDir(path);
bloc.dispatch(SetCurrentWorkingDir(path)) ;
bloc.dispatch(UpdateFileSystemCacheMapping(path , files)) ;
});
}
The problem is that you are initializing the instance of FileManagerBloc inside the BlocProvider which is, of course inaccessible to the parent widget. I know that helps with automatic cleanup of the Bloc but if you want to access it inside initState or didChangeDependencies then you have to initialize it at the parent level like so,
FileManagerBloc _fileManagerBloc;
#override
void initState() {
super.initState();
_fileManagerBloc= FileManagerBloc();
_fileManagerBloc.dispatch(LoadEducation());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<FileManagerBloc>(
builder: (context)=> _fileManagerBloc,
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(color: Colors.blueGrey, child: TopMenuBar()),
Expanded(
child: BlocBuilder<FileManagerBloc,FileManagerState>(
builder: (context , state){
return GridView.count(
scrollDirection: Axis.vertical,
physics: ScrollPhysics(),
crossAxisCount: 3,
crossAxisSpacing: 10,
children: getFilesListWidget(context , state),
);
},
),
)
],
),
),
),
));
}
#override
void dispose() {
_fileManagerBloc.dispose();
super.dispose();
}
#override
void didChangeDependencies() {
logger.i('Did change dependency Called');
Messenger.sendGetHomeDir()
.then((path) async {
final files = await Messenger.sendListDir(path);
_fileManagerBloc.dispatch(SetCurrentWorkingDir(path)) ;
_fileManagerBloc.dispatch(UpdateFileSystemCacheMapping(path , files)) ;
});
}
alternatively, if FileManagerBloc was provided/initialized at a grandparent Widget then it could easily be accessible at this level through BlocProvider.of<CounterBloc>(context);
you can use it in didChangeDependencies method rather than initState.
Example
#override
void didChangeDependencies() {
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
//do whatever you want with the bloc here.
super.didChangeDependencies();
}
Solution:
Step 1: need apply singleton pattern on Bloc class
class AuthBloc extends Bloc<AuthEvent, AuthState> {
static AuthBloc? _instance;
static AuthBloc get instance {
if (_instance == null) _instance = AuthBloc();
return _instance!;
}
....
....
Step 2: use AuthBloc.instance on main.dart for Provider
void main() async {
runApp(MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc.instance,
),
...
...
],
child: App(),
));
}
Now you can use Bloc without context
you can get state by AuthBloc.instance.state from initState or anywhere
you can add event from anywhere by AuthBloc.instance.add(..)
you also call BlocA from another BlocB very simple
Related
Firstly, I want to ask is from which method of Stateful widget should I call state management methods. I need to choice two place init() method or build(). I don't exactly know which method is the appropriate method to call state management methods. Let me try to explain with examples to understand of my question.
I use rxdart for dependency injection, used Stream and build with bloc pattern. Then used global scope instead of single scope. So I build another class that extend InheritedWidget and predefine each of the of(context) from the ancestor widget. Then called bloc methods (state management) from each build() method of their needed UI class (Stateful or stateless).
In here, my problem is when every time I called from build method, some of the function build again and again, unless I need to do. So I fix with another way, that is declare, initialized the bloc class and call respective bloc class function with object from init method of stateful class. That is work really. But some of the article said, don't should call from init method and only should call from build method. I am anxiety with that principles and calling from init method not work as global scope (Ex: that init stream not work for another widgets). How should I do with that? please tell or guide me with something.
Here is my code flow to better understanding. I show with count down example bloc that work like When count down become 0, the button will appear and press again that button count down again and work same process again.
That is state management bloc
class OtpLoginModuleBloc {
bool isHideResendButton = true;
final _buttonTimerController = PublishSubject<bool>();
final _textTimerController = PublishSubject<int>();
Stream<bool> get buttonTimerStream => _buttonTimerController.stream;
Stream<int> get textTimerStream => _textTimerController.stream;
void toAppearResendButtonCountown({required int second}) {
var duration = const Duration(seconds: 1);
Timer.periodic(
duration,
(Timer timer) {
if (second == 0) {
isHideResendButton = !isHideResendButton;
_buttonTimerController.sink.add(isHideResendButton);
timer.cancel();
} else {
second--;
}
_textTimerController.sink.add(second);
},
);
}
}
Here is ancestor class
class _PreModuleState extends State<PreModule> {
#override
Widget build(BuildContext context) {
return LoginModuleProvider(
child: OtpLoginModuleProivder(
child: MaterialApp(
theme: themeData(),
home: const Scaffold(
body: SplashScreen(),
),
onGenerateRoute: RouteGenerator.route,
),
),
);
}
}
Here is provider class for global scope
class OtpLoginModuleProivder extends InheritedWidget {
final OtpLoginModuleBloc loginOtpModuleBloc;
OtpLoginModuleProivder({Key? key, required Widget child})
: loginOtpModuleBloc = OtpLoginModuleBloc(),
super(key: key, child: child);
#override
bool updateShouldNotify(OtpLoginModuleProivder oldWidget) => true;
static OtpLoginModuleBloc of(context) {
return (context.dependOnInheritedWidgetOfExactType<OtpLoginModuleProivder>()
as OtpLoginModuleProivder)
.loginOtpModuleBloc;
}
}
And my problem is here ...
Should I call like that
final OtpLoginModuleBloc otpLoginModuleBloc = OtpLoginModuleBloc();
#override
void initState() {
otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime);
super.initState();
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
Column(
children: [
const OtpLoginHeader(),
const SizedBox(
height: margin50,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: margin100),
child: buildPinCodeTextField(context, otpLoginModuleBloc),
),
const SizedBox(
height: margin30,
),
StreamBuilder(
initialData: otpCountingTime,
stream: otpLoginModuleBloc.textTimerStream,
builder: (context, AsyncSnapshot snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: margin80),
child: snapshot.data == 0
? sendOtpButton(otpLoginModuleBloc, otpCountingTime)
: Text('Request new OTP in ${snapshot.data}'),
);
},
),
],
),
],
);
}
OR
Widget build(BuildContext context) {
final otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime);
return Stack(
children: [
Column(
children: [
const OtpLoginHeader(),
const SizedBox(
height: margin50,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: margin100),
child: buildPinCodeTextField(context, otpLoginModuleBloc),
),
const SizedBox(
height: margin30,
),
StreamBuilder(
initialData: otpCountingTime,
stream: otpLoginModuleBloc.textTimerStream,
builder: (context, AsyncSnapshot snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: margin80),
child: snapshot.data == 0
? sendOtpButton(otpLoginModuleBloc, otpCountingTime)
: Text('Request new OTP in ${snapshot.data}'),
);
},
),
],
),
],
);
}
That is my all of my question...
Please help me and guide me ...
Ofc you should go for the latter.
use like this. This way, you can use _otpLoginModuleBloc within the class.
class _ExampleWidgetState extends State<ExampleWidget> {
late OtpLoginModuleBloc _otpLoginModuleBloc
#override
Widget build(BuildContext context) {
_otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
return Container();
}
}
In addition, if you need to run the otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime); only ONCE, then use below.
class ExampleWidget extends StatefulWidget {
const ExampleWidget({super.key});
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
late OtpLoginModuleBloc _otpLoginModuleBloc
final _otpCountingTime = 1;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_otpLoginModuleBloc.toAppearResendButtonCountown(second: _otpCountingTime);
});
super.initState();
}
#override
Widget build(BuildContext context) {
_otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
return Container();
}
}
This should answer your questions. Thank you.
Oh and don't forget to add dispose method inside the OtpLoginModuleBloc that cancels Timer and closes both _buttonTimerController and _textTimerController in case you don't need the bloc anymore.
I have this example ChangeNotifier:
class ExmapleNotifier extends ChangeNotifier {
bool isDark = false;
toggleTheme() {
isDark = !isDark;
notifyListeners();
}
}
when I try to use it with a ValueListenableBuilder, it throws an error that it's not a ValueListenable, is there any way to combine them both?
For this, I may go with ChangeNotifierProvider. You can also use AnimatedBuilder widget.
class CHTest extends StatelessWidget {
const CHTest({super.key});
#override
Widget build(BuildContext context) {
final exampleNotifier = ExmapleNotifier();
return AnimatedBuilder(
animation: exampleNotifier,
builder: (context, child) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("is Dark ${exampleNotifier.isDark}"),
ElevatedButton(
onPressed: exampleNotifier.toggleTheme,
child: Text("toggle"),
),
],
),
);
}
}
I have this code for listening to bloc on my screen.
late MyBloc myBloc;
#override
void initState() {
print('inside init state');
super.initState();
myBloc = BlocProvider.of<MyBloc>(context);
myBloc.stream.listen((state) {
if (state is MyAddCompletedState) {
print('listening to bloc');
}
}
}
If I add an event, it will print listening to bloc once. If I go to another screen and return to this same screen, it will print listening to bloc twice. It seems like the first listen I did was still active. Next, I tried to close the bloc on my dispose thinking that it would stop the first listen. So that when I come back to the screen it will have a fresh listen but it will have an error: Bad state: Cannot add new events after calling close. I tried to research about this and some mention to dispose the bloc but it doesn't have that syntax anymore. Please help on how to properly close or stop it from listening once I have change screen. Thanks!
//this is found on my screen
late MyBloc myBloc;
#override
void initState() {
print('inside init state');
super.initState();
myBloc = BlocProvider.of<MyBloc>(context);
myBloc.stream.listen((state) {
if (state is MyAddCompletedState) {
print('listening to bloc');
}
}
}
#override
void dispose() {
myBloc.close();
// myBloc.dispose(); --> I saw some tutorial to use this but it doesn't work
super.dispose();
}
This is on my main.dart:
return FutureBuilder(
future: InitFirst.instance.initialize(),
builder: (context, AsyncSnapshot snapshot) {
return MultiBlocProvider(
providers: [
BlocProvider<MyBloc>(
create: (context) => MyBloc(
authenticationRepository: authenticationRepository,
userDataRepository: userDataRepository,
),
),
...
This is the part where I trigger the event. After this event run, the stream.listen will get triggered. But it will be triggered multiple times every time I visit the my screen.
myBloc.add(MyAddEvent(
selectedName,
selectedCount);
Additional note: this event is triggering an update in Firebase which I need to check if it got completed that is why I do the stream.listen.
If the Stream used in Bloc keeps getting called when not in use, you may want to consider terminating the Stream with cancel() on your dispose() override. Try this
late MyBloc myBloc;
late StreamSubscription mSub;
#override
void initState() {
print('inside init state');
super.initState();
myBloc = BlocProvider.of<MyBloc>(context);
mSub = myBloc.stream.listen((state) {
if (state is MyAddCompletedState) {
print('listening to bloc');
}
}
}
#override
void dispose() {
mSub.cancel();
super.dispose();
}
There are several ways to solve this issue depending on the context. I try to avoid using BLoC instantiation with StatefulWidgets. And, I like to use Cubits in connection with Observers, depending on the event entering my stream. I have added most of them in the following code, which isn't all used but for you to look at as a reference. My code example eliminates the issues that you describe. I would be happy to help further if you could provide a minimum viable code.
The following code is an example that I have put together to demonstrate a possible strategy. The BLoC package website heavily inspires the code. It has the standard counter app that we are all familiar with and navigation functionality.
Please see the following code to see if it helps at all:
Please be sure to add
flutter_bloc: ^8.0.1 to your pubspec.yaml file.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
BlocOverrides.runZoned(
() => runApp(const MaterialApp(home: CounterPage())),
blocObserver: CounterObserver(),
);
}
class CounterObserver extends BlocObserver {
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('onTransition -- bloc: ${bloc.runtimeType}, transition: $transition');
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- bloc: ${bloc.runtimeType}, error: $error');
super.onError(bloc, error, stackTrace);
}
#override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- bloc: ${bloc.runtimeType}');
}
}
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
class ScoreCubit extends Cubit<int> {
ScoreCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
class CounterPage extends StatelessWidget {
const CounterPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CounterCubit>(create: (context) => CounterCubit()),
BlocProvider<ScoreCubit>(create: (context) => ScoreCubit()),
],
child: const CounterView(),
);
}
}
class CounterView extends StatelessWidget {
const CounterView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Column(
children: [
Center(
child: BlocBuilder<ScoreCubit, int>(
builder: (context, score) => Text('Score: $score'),
),
),
Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('$state',
style: Theme.of(context).textTheme.headline2);
},
),
),
],
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
heroTag: 'FAB1',
child: const Icon(Icons.add),
onPressed: () {
// format from
context.read<CounterCubit>().increment();
context.read<ScoreCubit>().increment();
}),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: 'FAB2',
child: const Icon(Icons.remove),
onPressed: () {
context.read<CounterCubit>().decrement();
},
),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: 'FAB3',
child: const Icon(Icons.arrow_forward),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: const Text('Next Page')),
body: const Center(
child: Text('This is the next page'),
),
),
),
);
},
),
],
),
);
}
}
Like all things in Flutter, this, of course, is only one strategy of many.
I am trying to implement bloc pattern in which I am using a repository class which consist all the methods which makes the calls with the API. On the other side I am implementing BlocBuilder to render the view based on bloc state however i am getting this error BlocBuilder<VehiclesBloc, VehiclesState>(dirty, dependencies: [_LocalizationsScope-[GlobalKey#df8d0]], state: _BlocBuilderBaseState<VehiclesBloc, VehiclesState>#dba40):
type 'Future' is not a subtype of type 'Widget?'
I am really not sure where the issues comes from. here are some snippets of the code.
this is the bloc class which causes the error
class VehiclesBloc extends Bloc<VehiclesEvent,VehiclesState>{
VehiclesBloc(VehiclesState initialState) : super(initialState);
#override
Stream<VehiclesState> mapEventToState(VehiclesEvent event) async* {
// TODO: implement mapEventToState
if(event is LoadVehiclesList){
yield* mapLoadEventToState(event);
}
}
Stream<VehiclesState> mapLoadEventToState(LoadVehiclesList event) async* {
if(event is LoadVehiclesList){
var response = await VehiclesService().getAll();
if(response.IsSuccess){
yield VehiclesLoaded(response.Data);
}else{
yield VehiclesLoadingFailed(response.ErrorList.toString());
}
}else{
yield VehiclesLoading();
}
}
}
here is the Statefull widget which implements the Bloc Builder
class VehicleList extends StatefulWidget {
const VehicleList({Key key}) : super(key: key);
static const String routeName = "/VehicleList";
//final ScrollController scrollController;
#override
_VehicleListState createState() => _VehicleListState();
}
class _VehicleListState extends State<VehicleList> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
VehiclesBloc vehiclesBloc =
VehiclesBloc(VehiclesLoading())..add(LoadVehiclesList());
#override
void initState() {
// TODO: implement initState
super.initState();
//VehiclesService().getAll();
}
#override
void dispose() {
// TODO: implement dispose
vehiclesBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
final isRtl = context.locale.languageCode == "ar";
return Scaffold(
key: _scaffoldKey,
backgroundColor: kBackgroundColor,
drawer: SideNavigationDrawer(),
body: Container(
child: Column(
children: [
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
onPressed: () {
_scaffoldKey.currentState.openDrawer();
},
icon: Icon(
Icons.menu,
size: 35,
color: Colors.black,
),
)
],
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
BlocBuilder<VehiclesBloc,VehiclesState>(
builder: (context, state) {
if (state is VehiclesLoaded) {
// return BuildListVehicle(state.lsVehicle);
return Center();
} else if (state is VehiclesLoadingFailed) {
return Center(
child: CustomErrorWidget(),
);
} else {
return Center(
child: LoadingDialog.showLoadingDialog(context,
text: ""),
);
}
},
cubit: vehiclesBloc,
),
],
),
),
)
],
),
));
}
I think this code part causes the problem:
return Center(
child: LoadingDialog.showLoadingDialog(context,text: ""),
);
Possibly, LoadingDialog.showLoadingDialog does not return a Widget but is just a function that returns Future.
For side effects (e.g. you want to show the dialog), you should use listeners instead of executing such code inside the build method. Instead of BlocBuilder, just use BlocConsumer and add the listener:
BlocConsumer<VehiclesBloc,VehiclesState>(
listener: (context, state) {
if (state is {your loading state}) {
LoadingDialog.showLoadingDialog(context, text: "");
}
},
builder: ...,
),
Some more insights about your code:
Instead of creating BLoC as a variable in your stateful widget, use BlocProvider that would handle create/dispose part of your BLoC.
Yield the VehiclesLoading state before loading the data and not just as an "else" case. This way you could handle the loading behaviour easily in your UI.
To fix the above issues, just follow the documentation: https://bloclibrary.dev/
I have a flutter widget that attempts to solve soduku grids. I have class called SodukuSolver which does all the calculations and provides a List<String> of the current results. I call setState to refresh the list, but it does not update the screen.
Below, I'll try to include as much of the relevant code as I can. Full source is at https://github.com/mankowitz/soduku
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: "Soduku solver", home: Soduku());
}
}
class SodukuState extends State<Soduku> {
SodukuSolver ss;
List<String> _list;
int _changes = 0;
int _remaining = 81;
#override
Widget build(BuildContext context) {
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
return Scaffold(
appBar: AppBar(title: Text('Soduku solver'), actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.directions_walk),
onPressed: () {
_iterate();
},
),
]),
body: _buildGrid(),
);
}
Widget _buildGrid() {
return Column(children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 9,
),
itemBuilder: _buildGridItems,
itemCount: 81,
),
),
),
]);
}
Widget _buildGridItems(BuildContext context, int index) {
return GestureDetector(
child: GridTile(
child: Container(
child: Center(
child: Text(_list[index]),
),
),
),
);
}
void _iterate() {
setState(() {
_changes = ss.iterateSoduku();
_remaining = ss.empties();
_list = ss.getList();
});
}
}
class Soduku extends StatefulWidget {
#override
SodukuState createState() => SodukuState();
}
So the problem is that _iterate() is being called, and I can use the debugger to see that the internal state of SodukuSolver is being updated and it is even passing _list correctly, but the grid on screen doesn't update, even though _changes and _remaining do update.
You are creating new SodukuSolver with same _starting every time the widget builds and then obtaining _list from it. So you are overriding changes from previous iteration.
Looks like SodukuSolver creation should be performed once. You can override initState in SodukuState and initialise SodukuSolver there or initialise it in the same place where it is declared
Just add your code in the initState() method as following
#override
void initState() {
super.initState();
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
}
In your case, your list is not getting updated as setState() method will call your SodukuSolver() and ss.getList(); methods every time. because, setSate() ultimately calls build method to render every time.
So adding it inside your initState method will solve your issue. As it is getting called only once when the screen/route initialises.