How to initialize stateProvider from constructor in RiverPod - flutter

Basically, I want to set the initial constructor value to StateProvider so I can use it in its child widgets.
final stateProvider = StateProvider((ref) => "default");
class ExampleWidget extends ConsumerWidget {
ExampleWidget({required String text}){
// how to override default hear as
// stateProvider = text
}
#override
Widget build(BuildContext context, ScopedReader watch) {
return Column(
children: [
Text(watch(stateProvider).state),
Container(
child: ChildWidget(),
),
],
);
}
}
class ChildWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
return Container(
child: Text(watch(stateProvider).state),
);
}
}
So how do I implement this?
I have tried this but the state value is not updating.
Edit ----------------
With riverpod_hook & hooks_flutter
final stateProvider = StateProvider((ref) => "default");
class ExampleWidget extends HookWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
useEffect((){
context.read(stateProvider).state = "new value";
},[]);
});
final state = useProvider(stateProvider).state;
return Column(
children: [
Text(state),
],
);
}
}
above code throwing this error
Hooks can only be called from the build method of a widget that mix-in Hooks``
So I tried to initialize under the build method like this
useEffect((){
context.read(stateProvider).state = "new value";
},[]);
but above code throws the following error:
The following Error was thrown building ExampleWidget(dirty, dependencies: [UncontrolledProviderScope]):
Instance of 'Error'
I just want to initialize the value once that's why I want to use useEffect hook.

You can't access providers in the constructor normally. You also shouldn't update the state directly in the build method, as this can cause build errors (the widget hasn't finished building before you set a state which would cause another rebuild).
You should be able to use addPostFrameCallback to change the state after the widget has built.
For example:
class ExampleWidget extends ConsumerWidget {
const ExampleWidget({required String text});
final String text;
#override
Widget build(BuildContext context, ScopedReader watch) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
context.read(stateProvider).state = text;
});
return Column(
children: [
Text(watch(stateProvider).state),
Container(
child: ChildWidget(),
),
],
);
}
}
Regarding your edits:
Hooks can only be called from the build method of a widget that mix-in Hooks
This is because the callback function is not really in the build method, it's executed externally.
The following Error was thrown building ExampleWidget(dirty, dependencies: [UncontrolledProviderScope]):
Instance of 'Error'
This is an example of the build errors I mentioned earlier in this post.
I would say you're better off refactoring to just set the StateProvider to the text in a prior widget rather than accepting text as a parameter and trying to set it, but I think the following would work with your current approach, though again I'd recommend approaching your problem differently.
class ExampleWidget extends HookWidget {
const ExampleWidget({required String text});
final String text;
#override
Widget build(BuildContext context) {
final future = useMemoized(() => Future<void>.delayed(const Duration(milliseconds: 1)));
final snapshot = useFuture<void>(future, initialData: null);
if (snapshot.connectionState == ConnectionState.done) {
context.read(stateProvider).state = text;
}
return Column(
children: [
Text(useProvider(stateProvider).state),
Container(
child: ChildWidget(),
),
],
);
}
}
By delaying the state change until after the initial build we avoid build errors.

Related

In which method, should call state management methods of the class in Flutter bloc with rxdart?

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.

Will stateless widget be rebuilt again?

I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?

how to Flutter Getx binds obs to Widget?

when I use Getx to update my Widget?
I do not know Rx() how to contact to the thing I put in.
code is _obx=Rx().
but I send data is "".obs. that is not Rx() but this is RxString().
when I use "".obs.value="newString". why Rx() can know that who updates data.
just like :
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetIncrementPage extends StatefulWidget {
GetIncrementPage({Key key}) : super(key: key);
#override
_GetIncrementPageState createState() => _GetIncrementPageState();
}
class _GetIncrementPageState extends State<GetIncrementPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('get'),
),
body: Container(
alignment: Alignment.center,
child: _body(),
),
);
}
Widget _body() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlineButton(
child: Text('get 数字加减'),
onPressed: c.increment,
),
OutlineButton(
child: Text('get log 变化'),
onPressed: c.change,
),
Obx(() {
printInfo(info: '刷新了页面 get_example');
return Text(c.count.toString());
}),
ObxValue((v) {
printInfo(info: '刷新了页面 get_ObxValue_log1 ');
return Text('logValue:' + v.toString());
}, ObjectKey('key').obs),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log1');
return Text('logObx:' + c.log.toString());
}),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log2');
return Text(c.log2.toString());
}),
// ObxValue((var value) => Text('${value.toString()}'), c),
],
);
}
#override
void dispose() {
Get.delete<Controller2>();
super.dispose();
}
final Controller2 c = Get.put(Controller2());
}
///
/// Created by fgyong on 2020/10/22.
///
class Controller2 extends GetxController {
var count = 0.obs;
var count2 = 0.obs;
final log = ''.obs;
final log2 = ''.obs;
increment() => count++;
#override
void onClose() {
printInfo(info: 'Controller close');
super.onClose();
}
void change() {
log.value += ' ${log.value.length}';
}
}
when i change log.value to new String,why log2 do not fresh.
class Obx extends StatefulWidget {
final WidgetCallback builder;
const Obx(this.builder);
_ObxState createState() => _ObxState();
}
class _ObxState extends State<Obx> {
RxInterface _observer;
StreamSubscription subs;
_ObxState() {
_observer = Rx();
}
#override
void initState() {
subs = _observer.subject.stream.listen((data) => setState(() {}));
super.initState();
}
#override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
#override
Widget build(BuildContext context) => notifyChilds;
}
Why can rx() establish contact with the log, please help me. When I update
How can Rx() know when logging?
just help me.
You can use Obx or GetX widgets from Get to "listen" to changes to observable variables you declare in a GetxController.
I think you are also confusing Rx as an ObserVER vs. ObservABLE. Rx is an observable, i.e. you watch it for changes using Obx or GetX widgets, (I guess you can call these two widgets "Observers".)
Basic Example
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
// ↑ declare controller inside build method
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('${c.log2.value}')
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
)
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
You likely don't need a StatefulWidget when using GetX. A GetxController lives outside the lifecycle of widgets. State is stored in a GetX Controller (instead of in a StatefulWidget).
GetX takes care of streams & subscriptions through variables you declare as obs, like count.obs and log2.obs. When you want to "listen" or "observe", use Obx or GetX widgets. These automatically listen to obs changes of its child and rebuild when it changes.
Obx vs. GetBuilder vs. GetX
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('Obx: ${c.log2.value}')
),
// ↓ requires manual controller.update() call
GetBuilder<Controller>(
builder: (_c) => Text('GetBuilder: ${_c.log2.value}'),
),
// ↓ controller instantiated by Get widget
GetX<Controller>(
init: Controller(),
builder: (_c) => Text('GetX: ${_c.log2.value}'),
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
),
RaisedButton(
child: Text('Update GetBuilder'),
onPressed: c.update, // rebuild GetBuilder widget
),
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
Obx
Listens to observable (obs) changes. Controller needs to already be declared/initialized elsewhere to use.
GetX
Listens to observable (obs) changes. Can initialize controller itself using init: constructor argument, if not done elsewhere. Optional argument. Safe to use init: if Controller already instantiated. Will connect to existing instance.
GetBuilder
Does not listen to obs changes. Must be rebuilt manually by you, calling controller.update(). Similar to a setState() call. Can initialize controller itself using init: argument, if not done elsewhere. Optional.
First:
when I "".obx.value="newString".why Rx() can know.
This is wrong, the .obx doesn't exist, I guess you mean .obs;
When you create a OBS variable like: final a = ''.obs, the type of this var will be a RxString(), so you can use to observer this var whatever you want to.
I know two widgets can you use to observer in your screen:
GetX(), Obx()
see link https://github.com/jonataslaw/getx/issues/937,
when Obx() build,we named it ObxA, named "ABC".obs abcobs,
in Obx
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
when build,RxString() will execute get value,and addListen():
code is
set value(T val) {
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
subject.add(_value);
}
/// Returns the current [value]
T get value {
if (getObs != null) {
getObs.addListener(subject.stream);
}
return _value;
}
void addListener(Stream<T> rxGetx) {
if (_subscriptions.containsKey(rxGetx)) {
return;
}
_subscriptions[rxGetx] = rxGetx.listen((data) {
subject.add(data);
});
}
so They made a connection

how to separate setState on main class

How to separate setState in Abc class?
I have lots of this kind of methods in one class. Now, my class is too long. Is there any way to separate this class to subclass or another way?
class _AbcState extends State<Abc> {
String name = 'name';
double number = 15.5;
int value = 10;
//REMOVE FROM ABC class
void changeName() {
setState(() {
name = 'new Name';
});
}
void changeNumber() {
setState(() {
number = 10.0;
});
}
void changeValue() {
setState(() {
value = 5;
});
}
//
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
There are 2 ways to do this:
First:
You can separate the individual input fields into their stateful widgets if necessary and let them handle their individual states internally, only giving you the changed data in respective onChange callbacks.
This is the simplest and quickest solution.
A better approach is to:
Second:
Use a library/dependency/package for the state management in your application:
This is an example of the Provider flutter package with ChangeNotifier:
You can check the official docs here for detailed explanations and examples:
Provider
ChangeNotifier
Simple example:
Create a class extending the ChangeNotifier:
Whenever the some data changes in the ChangeNotifer you can call the notifyListeners() method.
class UserProvider extends ChangeNotifier {
String username;
String password;
void updateUsername(String username) {
this.username = username;
notifyListeners();
}
void updatePassword(String password) {
this.password = password;
notifyListeners();
}
}
Wrap your widget tree in a ChangeNotifierProvider widget:
This widget will let you access the instance of the ChangeNotifier class anywhere in down the widget tree like InheritedWidget.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserProvider>(
lazy: false,
create: (context) => UserProvider(),
child: Home(),
);
}
}
Access the instance of ChangeNotifier from anywhere down the widget tree with and call any methods or data variables:
/// Get an instance of the UserProvider in the ancestors of the current widget tree like this.
UserProvider userProvider = Provider.of<UserProvider>(context);
/// Call any method inside the UserProvider class like this
userProvider.updateUsername("John Doe");
/// access any data variables inside the UserProvider class like this.
User userInfo = userProvider.username;
Changing UI based on data/state changes in ChangeNotifier:
You can use the Consumer and Selector widgets in the provider package, which provide an efficient ways to redraw the UI based on certain parameters of the ChangeNotifier class, when the notifyListeners() method is called from the ChangeNotifier class.
Consumer:
This widget will simply rebuild the entire widget tree wrapped inside it, whenever ANY data changes inside the ChangeNotifier with notifyListeners().
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, _) {
return Scaffold(
appBar: AppBar(
title: const Text(userProvider.username),
),
body: Center(
child: Text(
"Your password is: ${userProvider.password}!",
),
),
);
},
);
}
}
Selector:
This widget will allow for more finer control over your widget rebuild. It will rebuild only part of the widget based on specific data variable changes inside the ChangeNotifier.
The below example will only rebuild the Text widget if the password data changes inside our UserProvider ChangeNotifier class.
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, _) {
return Scaffold(
appBar: AppBar(
title: const Text(userProvider.username),
),
body: Center(
child: Selector<UserProvider, String>(
selector: (context, userProvider) => userProvider.password,
builder: (context, state, child) {
return Text(
"Your password is: ${userProvider.password}!",
);
},
),
),
);
},
);
}
}

How to share the bloc between contexts

I'm trying to access the bloc instance created near the root of my application after navigating to a new context with showDialog(). However, if I try getting the bloc like I usually do, by getting it from the context like _thisBlocInstance = BlocProvider.of<ThisBlocType>(context), I get an error that indicates there is no bloc provided in this context.
I assume this is because the showDialog() builder method assigns a new context to the widgets in the dialog that don't know about the Bloc I am trying to find, which was instantiated as soon as the user logs in:
#override
Widget build(BuildContext context) {
_authBloc = BlocProvider.of<AuthBloc>(context);
_accountBloc = AccountBloc(authBloc: _authBloc);
return BlocProvider(
bloc: _accountBloc,
....
There is a button in the corner that displays a dialog:
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: FloatingActionButton(
onPressed: () => showDialog(
context: context,
builder: (newContext) => EventDialog(),
).then(
(val) => print(val)
),
child: Icon(Icons.add),
),
),
);
}
And in the EventDialog, I try to find the bloc with the context again:
#override
void build(BuildContext context) {
_accountBloc = BlocProvider.of<AccountBloc>(context);
_userMenuItems = _accountBloc.usersInAccount
.map((user) => DropdownMenuItem(
child: Text(user.userName),
value: user.userId,
))
.toList();
}
And this fails, with an error 'the getter bloc was called on null', or, there is no bloc of that type attached to this context.
Is there some way to access the bloc just from the context after using showDialog(), or otherwise navigating to a new context?
This is the bloc provider class:
import 'package:flutter/material.dart';
//This class is a generic bloc provider from https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/
//it allows easy access to the blocs by ancestor widgets and handles calling their dispose method
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
abstract class BlocBase {
void dispose();
}
The best way I found to access the original bloc in a new context is by passing a reference to it to a new bloc that manages the logic of the new context. In order to keep the code modular, each bloc shouldn't control more than one page worth of logic, or one thing (e.g. log-in state of the user). So, when I create a new screen/context with showDialog(), I should also have a new bloc that deals with the logic in that screen. If I need a reference to the original bloc, I can pass it to the constructor of the new bloc via the dialog widget's constructor, so any information in the original bloc can still be accessed by the new bloc/context:
child: FloatingActionButton(
onPressed: () => showDialog(
context: context,
builder: (newContext) => NewEventDialog(
accountBloc: BlocProvider.of<AccountBloc>(context),
),
).then((event) => eventsBloc.addEvent(event)),
...
class NewEventDialog extends StatelessWidget {
final AccountBloc accountBloc;
NewEventBloc _newEventBloc;
NewEventDialog({this.accountBloc}) : assert(accountBloc != null);
#override
Widget build(BuildContext context) {
_newEventBloc = NewEventBloc(accountBloc: accountBloc);
return BlocProvider(
bloc: _newEventBloc,
...
The last answer is okay but it can be simplified, that is just transfering Bloc to its child widget.
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: FloatingActionButton(
onPressed: () => showDialog(
context: context,
builder: (newContext) => EventDialog((
accountBloc: BlocProvider.of<AccountBloc>(context),
),
).then(
(val) => print(val)
),
child: Icon(Icons.add),
),
),
);
}
class NewEventDialog extends StatelessWidget {
final AccountBloc accountBloc;
NewEventDialog({this.accountBloc}) : assert(accountBloc != null);
#override
void build(BuildContext context) {
_accountBloc = accountBloc;
_userMenuItems = _accountBloc.usersInAccount
.map((user) => DropdownMenuItem(
child: Text(user.userName),
value: user.userId,
))
.toList();
}
So far I find this problem occurs when going to widget via page routing. We can transfer the Bloc widget to widget to avoid this problem.